摩托车信号灯






4.98/5 (23投票s)
使用 Arduino Nano 控制一辆旧的 1978 年 Suzuki GS500 上的所有工作灯
引言
几年前,我开始在业余时间定制摩托车,作为工作后放松的方式。定制摩托车的特点是它们通常在外观上是独一无二的。前两辆摩托车确实有一个Arduino来控制转向灯并与刹车灯配合工作,但这辆我想有所不同。如果你好奇,可以在这里查看构建过程。
我希望所有的照明都由LED驱动,但网上的选择非常差,所以我不得不自己制作所有的示廓灯,以适应摩托车的外观。
尽管“完成”的产品仍在修改中,但它已经足够稳定,可以作为我的夏季日常驾驶工具。
背景
Arduino代码相当简单;它尚未优化,除了用于看门狗定时器的<avr/wdt.h>
之外,没有使用任何外部库。
电路稍微复杂一些,但接线并不太难。之所以需要添加所有额外的零件,而不是直接使用Arduino来驱动灯光,是因为每个引脚只能处理大约20ma @ 5vdc的电流。我从Digi-key采购的超亮白色LED每个消耗大约20ma。即使串联连接以减少电流负载,每个LED仍然会降压约2vdc,将每个输出引脚限制为2个LED,这不足以满足我的需求。因此,添加了一些PNP达林顿晶体管,这样我就可以在需要时每个输出泵出总共1.0A的电流。
这是所构建内容的粗略草图。
上面的图纸最终看起来像下面的样子
这是完整电气图的片段(zip文件中有一个PDF和PNG格式的完整版本)
Using the Code
首先是按钮去抖动!实际上完成了两个版本的按钮去抖动:一个用于刹车开关,另一个用于转向灯和远/近光选择器。所有输入都设置为HIGH,通过将此引脚接地(通过车架底盘),这会告诉代码刹车已按下。
但是,当开关即将激活或摩托车经过颠簸时,您可能会收到很多误报。因此,需要有一个延迟时间,我们需要在此时间内看到输入处于恒定状态;我发现大约100
毫秒的效果很好。
下面的代码将在每次输入切换状态时设置一个时间戳,如果时间戳超过100毫秒的阈值,我们知道这很可能是一个稳定状态。如果当前状态也与_on
(LOW)匹配,那么刹车灯应该亮起。
bool isBrake() {
static long lasttm;
static int last;
int state = digitalRead(switchB);
bool ret = false;
if (last != state) {
lasttm = millis();
last = state;
}
if ((millis() - lasttm) > 100) {
ret = last == _on;
}
return ret;
}
松开刹车开关将翻转状态并返回false
,没有时间延迟。
转向灯按钮略有不同。代码类似于刹车逻辑,但现在我只希望按钮最初按下时触发其他代码;这由triggered
布尔值处理,代码每次按下只会返回一次true
,有点像按下电脑的电源按钮。
bool isLeftTurn() {
static long lasttm;
static int last;
static bool triggered;
int state = digitalRead(switchL);
bool ret = false;
if (last != state) {
lasttm = millis();
last = state;
triggered = true;
}
if ((millis() - lasttm) > 100 && triggered) {
ret = last == _on;
triggered = false;
}
return ret;
}
setHiLowBeam
的工作方式大致相同,只不过它也处理输出端。
void setHiLowBeam() {
static long lasttm;
static int last;
static bool triggered;
int state = digitalRead(switchH);
if (last != state ) {
lasttm = millis();
last = state;
triggered = true;
}
if ((millis() - lasttm) > 100 && triggered && last==_on) {
digitalWrite(HILOW, digitalRead(HILOW) == LOW ? HIGH : LOW);
triggered = false;
}
}
转向灯计时器
从主循环中,它将调用一个简单的函数,获取当前的毫秒时间戳并来回切换ison
变量,调用代码只是想知道转向灯应该处于什么状态,即使转向灯当前没有运行。
int isBlinkTime(unsigned long curr){
static unsigned long fut_time;
static int ison;
if (curr > fut_time) {
ison = !ison;
fut_time = curr+_blinktime;
}
return(ison ? _off : _on);
}
如果你曾经在路上骑过摩托车或跟在后面,你就会知道有时我们会忘记关闭那些烦人的转向灯,有时会持续数英里,所以下一个函数将检查3分钟的超时,以及是否开始转向灯序列。
L
和R
(左和右)是通过从主循环中调用瞬时isLeftTurn
和isRightTurn
函数获得的。如果转向灯超过fut_time
时间戳,它将把currTS
设置为none
并取消信号。
如果左侧或右侧被按下,并且我们不在序列中,则开始一个新的带有下一个超时的序列。在序列中按下任一按钮也会取消序列。
void CheckTurnSignals(unsigned long curr, bool L, bool R){
static unsigned long fut_time;
if (curr >= fut_time) {
currTS = none;
}
if (L || R) {
if (currTS == none) {
//the turn signal needs to start
//and run for a max of 3 minutes
fut_time = curr + _timeout;
currTS = L ? left : right;
} else {
//the turn signal is being cancelled, because of a
//secondary push to either button
currTS = none;
}
}
}
引起后面司机的注意
至少闪烁刹车10次,以提醒他们你正在停车,然后保持刹车灯常亮。这是另一个计时器/计数器函数,用于确定刹车灯的状态。该函数获取当前的毫秒时间戳,如果刹车被按下,并且有一个用于闪烁的开/关值的指针,并返回我们是否仍处于闪烁状态。
bool isBrakeFlashTime(unsigned long curr, bool B,int *state){
static unsigned long fut_time;
static int flshcntr;
static int flserstate;
if (curr >=fut_time){
flserstate = !flserstate;
if(B && flserstate)flshcntr++;
fut_time=curr+_flshSleapTime;
}
*state = flserstate;
if (!B)
flshcntr = 0; //no brake, reset the counter
else if(flshcntr <= _flshCntrMax)
return true; //has brake and still has flashes
return false; //fall though, not in flash
}
多功能刹车灯
刹车灯分为4个部分:外环、内环、左半部分、右半部分(后左转向灯、后左刹车、后右刹车和后右转向灯),因此通过这4个部分,我可以实现
- 全亮刹车(所有灯都亮)
- 示廓灯(外环亮)
- 脉冲刹车(内环和外环来回闪烁)
- 动画(稍后详细介绍)
- 左转(RLTS闪烁,RRTS常亮)
- 左转带刹车(RLTS闪烁,RRB和RRTS常亮)
- 右转(RRTS闪烁,RLTS常亮)
- 右转带刹车(RRTS闪烁,RLB和RLTS常亮)
动画转向灯“走动”灯光在所有4组之间向左或向右移动(除非刹车被按下),这有点炫耀,但它确实能引起注意。
byte updateRearTurnBits(unsigned long curr){
static unsigned long fut_time;
static byte scrcnt;
static byte leftscroll;
static byte rightscroll;
if(curr>=fut_time){
fut_time = curr+_scrollTime;
scrcnt =++scrcnt % 4;
leftscroll = (leftscroll<<1) | (!scrcnt & 1);
rightscroll = (rightscroll >>1) | (!scrcnt<<7);
}
if(currTS == left)
return leftscroll;//scroll from right to left
else
return rightscroll; //scroll from left to right
}
代码更新两个字节,一个向左移动,一个向右移动。每次fut_time
超过时,它会更新行
scrcnt =++scrcnt % 4;
这基本上在第四次计数时将计数重置为0。我将其用作标记来翻转位!scrcnt
,以及我正在寻找的位(左侧为0x1或0x80并将其推入每个序列以使其字节滚动)。然后根据当前的转向灯状态返回左侧滚动或右侧滚动。
循环
主循环可能是所有代码中最简单的
void loop() {
//====reset the watchdog on each loop===
wdt_reset();
//=====Get the statuses=====
bool B = isBrake();
bool L = isLeftTurn();
bool R = isRightTurn();
setHiLowBeam();
//====update the timers====
unsigned long curr = millis();
int blnk = isBlinkTime(curr);
int rearflashstate;
bool brkflsh = isBrakeFlashTime(curr,B,&rearflashstate);
byte rtb = updateRearTurnBits(curr);
CheckTurnSignals(curr,L,R);
//update all lights depending on the current state.
}
顶部块将重置看门狗定时器并获取输入的状态。第二部分将获取当前的毫秒时间并计算出其余的当前状态。我不会费心展示数字输出触发,因为它非常基础,但如果你想查看其余部分,请下载代码并尝试一下。
关注点
我想分享这个的主要原因之一是,当将迷你电脑添加到一辆老旧的、电子噪音很大的摩托车上时,会发生一些隐藏的陷阱,从线圈到触点都会产生大量的电磁干扰
- 如果输入和输出离开项目盒的保护范围,请对它们进行光电隔离
- 项目盒应该是金属的,并接地到底盘。
- 在实际世界中,输入去抖动至关重要,在路上行驶时会发生许多误报。
- 不要单独依赖Arduino稳压器,它们很好,但不足以与大量外部电路配合使用。放置一个前置稳压器7805,它能真正处理摩托车充电系统疯狂的调制波动。
- 始终使用看门狗定时器。你可能会认为你的代码是完美的,但外部因素,如不良的电磁干扰,可能会使小型计算机锁定。看门狗至少会重启计算机并使其恢复运行,以便行车灯仍然可以工作。
我稍微修改的看门狗定时器代码来自https://forum.arduino.cc/index.php?topic=63651.0,作者为za_nic。
如果我对这个版本的硬件满意,它将再次返工,以排除Arduino板并使用单个ATmega 168A-PU芯片作为下一个挑战,因为我刚买了一堆,现在它们需要好好利用。
如果有人感兴趣,我也可以添加从Digi-key和Amazon购买的零件清单。现在zip文件中包含一个文本文件,其中列出了从Digi-Key和Amazon购买的所有零件(尽管其中一些是我的工作室库存)。 我将尽快抽出时间发布详细的电气图的PDF版本。请告诉我你的想法现在zip文件中包含完整的电气图,为PDF和PNG文件。
历史
- 1.0:首次发布
- 1.1:修复了拼写错误,并将零件文本文件添加到Bike_lights_v3b.zip文件
- 1.2:将最终电气图添加到zip文件,并在文本中包含其片段