Arduino温控PC风扇以及信息显示思路总结

温控风扇的一个总所周知的例子就是CPU上的散热风扇,风扇速度会随着CPU负载上升的同时增加,达到及时散热的目的,然而这次要捣鼓的东西也跟这个差不多但是是在放在大机柜里,四路温控风扇,并带有温度风扇速度显示,以及通过触摸显示屏设置上下温度阀值,我毫不犹豫选择了Arduino,这在油管上也有很多类似的项目,不过也杂而不全,翻来覆去,测试了各个部分然后确定了自己想要的方案。

1. 温度传感器选择

温度传感器有很多,我选择了其中的热敏电阻LM35进行了测试以及对比,有如下几点想说的:

  • 最开始我用的100k的热敏电阻和51k的电阻在3.3V下分压计算得到的温度和LM35基本差不多,我很满意,区别只是热敏电阻相对LM35来说浮动小很多
  • 之后我热敏电阻在5V情况下分压计算温度发现跟之前有一两度的差距,我纠结了很久,反复查看分压得到的值,热敏电阻计算到的值跟之前就差了一点,但是代入公式温度有了一两度的差距,验证后的确如此,不纠结
  • LM35在跑了几天后不知道是线路问题还是啥直飙40+摄氏度,感觉不太可靠

再加上项目要求,我选择了稳定性较高的热敏电阻,但是想要得到很高的测量精度,需要做很多优化工作,难度较大,好在对精度要求不高,机柜温度,八九不离十就行。热敏电阻的具体操作可以参考这里,而LM35的在这边,附带一个要用到的Arduino函数知识点What is analogReference() function in Arduino?

2. PC风扇速度控制

我一开始拿到个3 pin的PC风扇(12伏特)来测试,但还有2 pin4 pin,前期找到了一些资料来了解它们

具体操作我在油管上找到了篇绝佳的教程Controlling fan speed with mosfet and Arduino ,我是完全照着这个来,完美,但是还是有几点要强调的:

  • 风扇速度可以通过PWM电压(无论几pin风扇)来控制也可以选择4 pin风扇的pwm线来控制,我选择了前者
  • 视频中MOS管两端插了个电阻,当时有疑惑,后来得到了解决

3. 风扇速度测量

我一开始简单地想在施加PWM电压控制风速的同时直接把占空比当做速度的百分比,这明显是合理的,且网上大多都是通过百分比来表示,相对于一串数字的RPM来说更加直观点
但是后来摸索来摸索去又进入了测量RPM的怪圈,因为我测试的是3 pin的风扇,有一根测速度的线在上面,这就很尴尬不测测不行了。
我同样在油管上找到了合适的教程How to measure Fan RPM with Arduino using hall effect sensor以及一些霍尔效应的原理
风扇的RPM速度是拿到了,但是在整合程序的时候出现了两个问题:

  • 测量风扇速度必须在程序中腾出一秒来感应霍尔效应,那么在这一秒内Arduino等于阻塞了,但是又不好搞多线程导致程序有延迟
  • 在使用PWM控制风扇的时候测量出来的霍尔效应是垃圾数值,不能用作于风扇速度,具体看这里有这么段话**:One thing you need to be aware of is that when you are controlling a three pin fan via a MOSFET then you need to crank the PWM signal to 100% for a few milliseconds while reading the speed or else you will get garbage readings.**

所以兜兜转转又回到了用百分比描述风扇速度

4. 显示屏选择

Arduino显示屏我主要参考Top 5 Arduino Displays,主流的显示屏都有讲到。但是我这边的要求是尺寸大一点带触摸功能最终我选择了4寸TFT彩屏 480X320超高清液晶屏LCD触摸屏
,连接以及代码操作可以参考Arduino TFT LCD Touch Screen Tutorial Arduino Tutorial: 3.5” Color TFT display ILI9481 on Arduino Uno and Mega from Banggood.com 。其实中间过程比较坎坷,挑了好几次试验了蛮久才最终定下来,源于我对这方面只是的匮乏,不太懂,这里也强调几个点:

  • 选择shield显示屏,直白点说也就是可以直插到Arduino板子上,这样就免了接线烦恼直接用
  • 选择MCUFRIEND_kbv,里面有大量演示的例子以及代码让你校验这块屏幕,一些压感参数,这是我到最后才发现的。其中的代码优雅性也对自己有很大启发
  • MCUFRIEND_kbv/examples/TouchScreen_Calibr_native是校验触摸屏,可以得到校准参数然后用到examples/Touch_shield_new/中,一路没问题那就没问题了,之后就开始自己的设计
  • 一些相关函数可以参考Adafruit GFX Library ,说到底MCUFRIEND_kbv是对Adafruit GFX的强大包装,自动识别驱动和尺寸,让小白一路畅通

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#include<math.h>
#include <Adafruit_GFX.h>
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft; // hard-wired for UNO shields anyway.
#include <TouchScreen.h>

// Assign human-readable names to some common 16-bit color values:
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF

const int XP = 8, XM = A2, YP = A3, YM = 9; //ID=0x7796
const int TS_LEFT = 922, TS_RT = 122, TS_TOP = 53, TS_BOT = 933;

#define MINPRESSURE 200
#define MAXPRESSURE 1000

// 触摸屏实例化
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
TSPoint tp;
// 触摸不同区域的不同标志
byte touch;

// 第一行的初始坐标以及行间距
const uint16_t line_x = 8, line_y = 20, line_distance = 40;
// + - 按钮的位置大下参数,一共四个按钮小方块,以左上角第一个作为起点,side_正方形按钮边长
// distance分别为了左右上下两个方块的间隔,symbol为符号到正方向边上的距离,intital_y表达式中的常量3代表与第一行之间的偏移量
const uint16_t initial_x = 300 , initial_y = line_y - 3 , side = 30, x_distance = 50, y_distance = 40 , symbol_x = 7, symbol_y = 5;

int16_t BOXSIZE;
int16_t PENRADIUS = 1;
uint16_t ID, oldcolor, currentcolor;
const byte Orientation = 1; //PORTRAIT

//MOSFET Gate 控制电源PWM,选择PWM pin 44~46 for mega
#define Gate 44

//MF52-100K 常温25摄氏度(298.15K)下阻值为100k欧
const float voltagepower = 5.0;
const float R = 51; //采样电阻为51千欧
const int B = 3950;
const double T1 = 273.15 + 25; //常温
const double R1 = 100; //常温对应的阻值,注意单位是千欧
const byte analogid = 8; //A8处读取电压值

// 风扇速度调节的温度范围
byte MinTemp = 25;
byte MaxTemp = 32;
// 风扇电压模拟值变量
byte analogvalue;


void show_threshold(void)
{
// 显示设置值
tft.setCursor(line_x, line_y);
tft.print("MinTemp:"); // 第一行
tft.print(MinTemp);
tft.println(" \367C");
tft.setCursor(line_x, line_y + line_distance * 1); // 第二行
tft.print("MaxTemp:");
tft.print(MaxTemp);
tft.println(" \367C");
}


void showTempAndControlFan(void)
{
//获得A1处的电压值
double digitalValue = analogRead(analogid);
double voltageValue = (digitalValue / 1023) * 5;
//通过分压比获得热敏电阻的阻值
double Rt = ((voltagepower - voltageValue) * R) / voltageValue;
//换算得到温度值
double temp = ((T1 * B) / (B + T1 * log(Rt / R1))) - 273.15;
// 显示温度
tft.setCursor(line_x, line_y + line_distance * 2); // 第三行
tft.print("Current Temp: ");
tft.print((int)temp);
tft.print(" \367C");

// 温度控制风扇
if (temp >= MaxTemp) {
analogvalue = 255;
//digitalWrite(buzzer, HIGH);
}
else if (temp >= MinTemp) {
//根据温度MinTemp~MaxTemp调节风扇速度对应analog值128~255
analogvalue = map(temp, MinTemp, MaxTemp, 128, 255);
//digitalWrite(buzzer, LOW);
}
else {
analogvalue = 0;
//digitalWrite(buzzer, LOW);
}
analogWrite(Gate, analogvalue);
}

void show_fan(void)
{
for (int i = 1; i <= 4; i++) {
tft.setCursor(line_x, line_y + line_distance * (2 + 1 * i));
tft.print("Fan");
tft.print(i);
tft.print(" Speed: ");
if (analogvalue == 255) {
tft.print("100%");
}
else if (analogvalue == 0) {
tft.print(" 0%");
}
else {
tft.print(" ");
tft.print(analogvalue * 100 / 255);
tft.print("%");
}
}
}


void setup() {
Serial.begin(9600);
Serial.println("start...");

tft.reset();
ID = tft.readID();
tft.begin(ID);
tft.setRotation(Orientation);
tft.fillScreen(BLACK);
tft.setTextColor(WHITE);
tft.setTextSize(2);

//https://forum.arduino.cc/index.php?topic=364055.0
// 设置加减符号边框
tft.fillRect(initial_x, initial_y, side, side, MAGENTA);
tft.fillRect(initial_x + x_distance, initial_y, side, side, MAGENTA);
tft.fillRect(initial_x, initial_y + y_distance , side, side, MAGENTA);
tft.fillRect(initial_x + x_distance, initial_y + y_distance, side, side, MAGENTA);
// 显示加减符号
tft.setCursor(initial_x + symbol_x, initial_y + symbol_y);
tft.print("+");
tft.setCursor(initial_x + symbol_x + x_distance, initial_y + symbol_y);
tft.print("-");
tft.setCursor(initial_x + symbol_x, initial_y + symbol_y + y_distance);
tft.print("+");
tft.setCursor(initial_x + symbol_x + x_distance, initial_y + symbol_y + y_distance);
tft.print("-");
tft.setTextColor(WHITE, BLACK);
Serial.println("Screen is " + String(tft.width()) + "x" + String(tft.height()));
}

void loop() {
show_threshold();
showTempAndControlFan();
show_fan();

uint16_t xpos, ypos; //screen coordinates
tp = ts.getPoint(); //p.x, p.y are ADC values

// if sharing pins, you'll need to fix the directions of the touchscreen pins
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
// we have some minimum pressure we consider 'valid'
// pressure of 0 means no pressing!

if (tp.z > MINPRESSURE && tp.z < MAXPRESSURE) {
xpos = map(tp.y, TS_TOP, TS_BOT, 0, tft.width());
ypos = map(tp.x, TS_RT, TS_LEFT, 0, tft.height());
Serial.println(String(xpos) + "," + String(ypos));
if (xpos > initial_x && xpos < initial_x + side) {
if (ypos > initial_y && ypos < initial_y + side) {
touch = 1;
}
}
if (xpos > initial_x + x_distance && xpos < initial_x + x_distance + side) {
if (ypos > initial_y && ypos < initial_y + side) {
touch = 2;
}
}
if (xpos > initial_x && xpos < initial_x + side) {
if (ypos > initial_y + y_distance && ypos < initial_y + y_distance + side) {
touch = 3;
}
}
if (xpos > initial_x + x_distance && xpos < initial_x + x_distance + side) {
if (ypos > initial_y + y_distance && ypos < initial_y + y_distance + side) {
touch = 4;
}
}
}

if (touch == 1) {
MinTemp++;
touch = 0;
delay(200);
}

if (touch == 2) {
MinTemp--;
touch = 0;
delay(200);
}

if (touch == 3) {
MaxTemp++;
touch = 0;
delay(200);
}

if (touch == 4) {
MaxTemp--;
touch = 0;
delay(200);
}

}

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!