esp8266 4线风扇调速测速

1.主板4针风扇接口

先从一张年代久远的图说起,听说是intel的规范,2003年的,

给出了了主板4针风扇接口的外围电路设计,咱就参考这个测试:

英特尔主板风扇-4pin设计规范

目前4pin接口主流的线序

😥和上图有点不一样,看仔细了

引脚编号 导线颜色 引线定义
1 GND
2 +12V
3 测速线
4 调速线

2.电路分析

1,2号引脚

负责风扇供电,风扇是外转子无刷电机,没有机械式换向器(使用半导体换向器),因此不会产生碳刷交替时的电压,所以Intel只并一个铝电解电容对电源进行低频滤波,不知道这样理解对不对?

3号引脚

输出风扇转速,用4.7k上拉到12V电源,再接27.4k和10k两个电阻分压,FAN TACH测速线从10k分压测量脉冲信号,粗略计算一下,FAN TACH的高电平为2.85V,匹配3.3V系统的逻辑电平,所以esp8266的GPIO引脚可以测量

😳为什么肯定3号线输出的是电平信号而不是模拟电压?

题主没有示波器,调节4线风扇的转速时,先没有用3号引脚的外围电路,直接用万用表测量3号引脚,其电压范围差不多0.02V~0.18V,推测这个引脚不能输出高电平但有内部上拉电阻,接着搭建了外围电路,在FAN TACH位置进行测量,发现无论怎样调速,电压保持1.51V左右不变,假设为电平信号计算的高电平为2.85V,那么可以肯定该引脚输出一个占空比约为50%,频率随转速变化的TTL信号

4号引脚

调节风扇的转速,使用NPN晶体管,型号MMBT3904,40V 100mA,集电极和发射极串接两个4.7k限流

3.测试准备

原理图

开发板为NodeMCU 1.0 ESP-12E

R7为10k电位器,接入A0读取模拟量

T1为三极管,用2N222A代替,参数40V 600mA,集电极限流电阻建议1-4.7k

C1为滤波电容,推荐钽电容或铝电解

英特尔的图27.4k用R4 20k和R5 6.8k代替,合计26.8k,FAN_SPEED高电平增加到2.89V

12V电源和开发板必须共地,否则三极管不能正常开关,无法调速

NodeMCU 1.0 可产生PWM信号的引脚

上图中,标有“正弦波”的引脚可产生PWM信号,实际上D0也可以

面包板搭建电路

4.安利vMicro

visual studo的arduino ide马甲程序,除了支持原版的所有功能,外加vs的标配功能,例如实时代码纠错,参数列表,自动补全,自动加载函数,等等,实在审美不了原版万年的蓝绿色,另外用vs写程序更专业😢

如何安装

在vs拓展管理中搜索arduino安装即可,使用教程网上很多,这里不做复述

5.示例代码

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/*
Name: Blink_and_Pwm.ino
Created: 2020/2/18 12:29:16
Author: OldGerman
*/

#include "Ticker.h"
Ticker timer1;

// pins
const int analogInPin = A0; // A0 读取电位器电压
const int fan_speed = 12; // D6 测速
const int pwm_out = 16; // D0 调速

// other message
unsigned long duration = 0; // 加权时间
const int read_times = 2; // 测速信号采样周期(多取几次可更精确)
const int fan_min_Hz = 40; // 测速线每秒最低脉冲次数,不同的风扇不一样,需要根据转速范围计算,或者实测也行

class Pwm
{
public:
Pwm(int pin)
: _pin(pin)
{ pinMode(_pin, OUTPUT); }

void pwmWrite()
{ analogWrite(_pin, outputValue()); }

static int sensorValue;

private:
static int outputValue()
{
sensorValue = analogRead(analogInPin); // esp8266 ADC为10位,analogRead()范围0~1023
return sensorValue;
}
int _pin;
};

int Pwm::sensorValue = 0; // 外部初始化静态类成员
Pwm pwm(pwm_out);

void class_pwm()
{
pwm.pwmWrite();

Serial.print("ADC: ");
Serial.print(Pwm::sensorValue);
Serial.print(' ');
}

void read_speed()
{
duration = 0;
if (Pwm::sensorValue < 720) { // 当风扇开始转动时,计算转速
for (int i = 0; i < read_times; i++) { // 对高低电平的变化时间加权计算,得出总时间,单位: ms
duration += pulseIn(fan_speed, HIGH); // pulseIn()默认等待超时时间1000000UL,等待过长会使硬件看门狗复位
duration += pulseIn(fan_speed, LOW);
}
}
else {
duration = 4294967295; // sensorValue = 1023 风扇会停止输出脉冲信号,pulseIn()会一直等待电平变化,导致复位
// 设置为unsigned long类型的最大值,让计算后风扇转速接近为0
}
duration = duration / read_times; // 高低电平变化1次用时,单位: ms
duration = 1000000 / duration; // 1秒内产生的脉冲次数(风扇转速,单位: Hz)

if(duration < fan_min_Hz) // 计算值小于最低每秒脉冲数,直接取最小值
duration = fan_min_Hz;

Serial.print(duration);
Serial.print(" HZ");
duration = duration * 60; // 1分钟内可变化几次(风扇转速: r/min)
Serial.print(" v is ");
Serial.print(duration);
Serial.println(" r/min");
}

void all_loop()
{
class_pwm();
read_speed();
}

//-------------------------------------MAIN FUNCTION-------------------------------------
void setup()
{
Serial.begin(115200);

/*定时器不能设置两个相同的定时任务,会导致硬件看门狗复位*/
//timer2.attach_ms(100, class_pwm);
//timer2.attach_ms(100, read_speed);

/*将上述两函数打包进一个函数内先后调用*/
timer1.attach_ms(200, all_loop);
}

void loop(){}

6.Demo

打开串口监视器,调节点位器控制风扇转速,输出如下:

7.有关代码的疑问

第35行,analogRead(),返回值范围

esp8266内置10位ADC,所以该函数返回范围0-1023,对于Arduino开发板例如Uno,该函数返回0-255

第56行,sensorValue < 720,为什么不是1023?

一般来说,12V风扇的最低转速不为0,例如猫头鹰的NF-12,其转速范围300-1500rpm,题主测试用的4010风扇,转速范围2400-128400左右,实测从A0读取的值大于720时,继续旋转电位器增大读数,风扇测速线的脉冲频率在40Hz左右不变,所以,可以认为在720时,风扇已经降至最低转速,继续减小esp8266输出的pwm占空比,并不会使风扇继续降速,如图:

若继续增大到1023,会导致看门狗复位

众所周知,esp8266不能像Arduino那样长时间执行循环,实测当跳到1023左右时,风扇的测速线不再输出脉冲信号,因为我们在for循环中调用了多次pulseIn(),该函数默认等待超时时间为1秒,测速线停止输出脉冲,pulseIn()每次都等待1秒,超时触发看门狗,复位瞬间:

第17行,read_times取多少合适?

😳首先,题主esp8266运行主频80Mhz,在160Mhz时pulseIn()的处理可能更快

read_times示例程序中取2,使第57行for迭代2次,每一次迭代调用了2次pulseIn(),那么循环一共调用了4次pulseIn,在setup()中,我们使用Ticker对象调用attach_ms()每200ms调用read_speed(),因此,多次调用pulseIn()总共等待的时间最多为200ms,题主使用的4010风扇最低脉冲频率为40Hz,即每1000ms产生40个脉冲周期,每个脉冲周期25ms,所以理想情况下,每25ms正好触发2次pulseIn()

然而,程序执行函数时处理也需要时间,实际上每25ms执行次数小于1:

当我把 read_times取5时,40Hz时极易发生看门狗复位,此时每200ms for执行10次pulseIn(),也就是说10次耗费时间超过了200ms致使esp8266没有喘气的时间,这时定时器任务可以认为是一个死循环,说明每次执行pulseIn()的时间大于20ms,所以25ms只能触发约一次该函数的调用😮

另外,还要考虑 class_pwm()的处理耗时,题主认为read_times取3,即每200ms调用6次最坏情况总计150ms已经很极限,so,我取了2,每200ms调用4次最坏情况总计100ms

总之,这个值得根据风扇计算,对于猫头鹰NF-12,最低每秒5Hz,即每脉冲周期长达200ms,那么相应地延长Ticker的定时周期,以降低read_speed()的调用频率,也是不错的选择😃

8.参考文献

Arduino环境esp8266 pwm函数使用

pulseIn() 函数的使用方法-超声波测距

esp8266 的看门狗有什么作用?

特别感谢Arduino论坛:skyhome 提供的思路