我是潘,曾经是个工程师。这是 “Arduino 公开课” 系列的入门教程。第5.1课介绍了温控风扇,这次我们要通过中断函数并使用霍尔传感器测量风扇速转。有任何疑问请在评论区提出,我会逐一回答。
霍尔传感器是一种磁感应元件,只要有磁场(磁铁、金属物体、电感)接近,就会产生相应的电平信号。霍尔传感器应用很广,电机/车轮测速、电流测量、运动与接近检测等。汽车、航天、工业制造等领域都是不可或缺的元件。
常见的霍尔传感器模块分为两种,一种是线性模块,根据磁感应强度输出相应的模拟量(电压),另一种是开关型模块,磁感应达到一定强度,输出一个脉冲信号。另外,线性模块也可以采用数字通信,只不过中间加入了 ADC 模块,主要用在需要高精度、高稳定性的测量场合。
这是最常见的霍尔传感器模块,采用A3144E霍尔传感器原件,同时提供了模拟和数字信号输出。测速原理是,在转盘上设置磁感应点,每次磁场穿过传感器时,输出一个脉冲信号:
图片来源:wikipedia
传感器的接线很简单,除了两根电源线,将 Vin 接入 Arduino 的 D3(3号数字端口)即可,如果出现传输信号不稳定的情况,可以接一只 5K 上拉电阻。
在风扇转盘的边缘贴上1颗小磁铁,然后将传感器固定在上方,记录每秒圈数乘以60,即可换算出 rpm (每分钟转速)。
速度是距离/次数与时间的比值,因此程序要设计两个变量,一个计时,另一个计数。
计时用到 millis(),该函数的作用是记录系统累计运行时间,返回毫秒(值类型为 unsigned long),最长纪录为50天,超时后返回0秒。如果要更精确,可以用micro()返回微秒。
millis() 返回的是累计时间,但我们要测量输出的是每一秒内的旋转次数,怎么处理呢?其实,只要设置一个临时变量,标记下一秒的时间点,当 millis() 到达这个时间点后,输出 milli() 至 millis() + 1000ms 之间的触发次数即可。
同时,我们采用外部中断函数计数。当传感器被磁铁触发,程序中断,执行计数函数,记录一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/*
作者:Ardui.Co
效果:霍尔传感器测速
版本:1.0
更新时间:2017年2月14日
*/
constbyteinterruptPin=3;
constlongtaketime=1000; // 每次测量的时间
unsignedlongtime;//设置变量 time,计时
floatVal=0;//设置变量 Val,计数
voidsetup(){
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(interruptPin),count,FALLING);//触发信号必须是变化的,上升或下降皆可
time=millis();//开始计时,time获得当前系统时间
}
voidloop(){
if(millis()>=time)
{
Serial.println(Val*60);//转换成rpm,单磁铁触发分辨率为60rpm,2个磁铁为30rpm
time=millis()+taketime;//标记未来的时间点,1000ms后执行if判断,输出结果。另,降低刷新频率,可以提高分辨率
Val=0;//输出速度结果后清零,记录下一秒的触发次数
}
}
voidcount(){
Val+=1;
}
|
使用 digitalPinToInterrupt(interruptPin) 原因是提高程序的兼容性,只要修改interruptPin 即可在不同 Arduino 版本上映射对应的中断端口号。
特别注意的是,触发外部中断函数的模式,必须是 RISING 或者 FALLING。如果是CHANGE,传感器每一次触发都会产生“高 – 低”、“高 – 低”两个信号,也就是说函数会被触发两次;如果是LOW,磁铁穿过传感器的过程中,外部中断函数会被连续触发,这个时间内 count() 会多次计数;
上面程序使用未来的时间点 millis() + 1000ms 来控制中断,但还有一种算法,就是当前时间去比较过去的时间,将时间范围控制在1000ms以内。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/*
作者:Ardui.Co
效果:霍尔传感器测试方法2
版本:1.0
更新时间:2017年2月19日
*/
constbyteinterruptPin=3;
constlongtaketime=1000; // 每次测量的时间
unsignedlongtime; //设置变量time,计时
floatVal=0;//设置变量Val,计数
voidsetup(){
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(interruptPin),count,FALLING);//触发信号必须是变化的,上升或下降皆可
time=millis();//开始计时,time获得当前系统时间
}
voidloop(){
if(millis()-taketime<time)return;//当前时间减测量时间,获得1000ms前的时间点,时间未到则跳出
Serial.println(Val *60);
time=millis();//输出结果后,获取当前时间
Val=0;
}
voidcount(){
Val+=1;
}
|
两个程序效果是完全一样的,但从理解上,一个是用未来时间点来规定测量时间,一个是用过去时间点。
大部分 PC 散热风扇内置了霍尔传感器,我们用示波器测量第3个引脚:
每 10~12 ms 出现一个波谷,转速约为 900~1000rpm,与刚才测量的结果有一些出入。(请思考为什么?答案会在评论区介绍)
降低刷新速度或者增加触发点,都能提高测量的分辨率。比如,对于转速特别低的设备,每秒不到1圈,我们将刷新率降到5秒,分辨率就能提高至 60rpm / 5 = 12rpm。也可以增加 n 个磁感应点,每次触发低电平信号,则记录 1/n 圈,分辨率也相应提高到1/n 。
现在,我们通过外部传感器产生中断请求,然后来计数。那么,是否可以由内部计时,定时来触发中断?答案是肯定的,上面两个程序就使用了内部中断的一种——程序定时器,它们以 millis() 计时,然后在规定的时间点上输出结果。
但是,millis() 计时是十分不精准的,因为,虽然程序中断也是内部中断的一种,但是在各种中断的优先级别中,跟普通程序没有任何区别,外部中断会打断 millis() 的计时。尽管外部中断的时间非常短,但其执行时间不会被记入millis()。
后面将深入介绍 Arduino 的内部中断和硬件定时器的使用。