51单片机温控风扇:霍尔测速+DS18B20温度检测+PWM调速实现
2026/9/16 3:17:43 网站建设 项目流程

简介:一份基于STC89C52单片机的霍尔转速测量、DS18B20温度检测与PWM电机调速完整方案,面向电子、自动化类学生的课程设计与毕业设计。系统支持按键启停、正反转、加减速,通过LCD1602实时显示温度、PWM档位和速度,当温度超过50℃时蜂鸣器自动报警。压缩包共141个文件,大小61.42MB,包含程序源码、电路图、任务书、开题报告、参考论文、答辩技巧、系统框图、程序流程图、芯片资料、器件清单、焊接说明及疑难问题说明;文件类型以jpg图片、doc/docx文档、pdf资料、mp4视频及C源码为主,并附带hex烧录文件、工程文件和原理图,可按模块直接编译与烧录。配套操作演示视频和焊接说明显著降低调试门槛,配合实物图可快速定位排错,适合从原理理解到实物验收的完整学习链路。目前已有259人学习,整体资料结构化强,可高效支撑课程设计、毕设答辩或电子竞赛备赛。

1. 为什么是霍尔 + 温度 + PWM:这个 51 单片机项目真正要解决的事

大多数温控风扇只做“温度高了直接全速”这种两级跳变,听起来省事,但实际散热对象温度会来回波动,噪音也刺耳。霍尔传感器是少数能让单片机知道风扇真实转速的低成本器件,用一个磁体和一颗霍尔芯片就能把转动变成脉冲序列。这个设计用 STC89C52 把霍尔测速、DS18B20 温度采集和 L298N 的 PWM 调速放在同一个系统里,按键可以切换正反转和加减速,温度超过 50 度会报警,同时 LCD1602 上能看到当前温度、PWM 档位和实测速度。对正在做 51 单片机课程设计或想把手头直流电机风机组装成可调速系统的人,这套资料里的源码、电路图、器件清单、答辩说明把完整的信号链路拆得很清楚,比单独看一个传感器例程更有参考价值。

2. 信号链路与选型:霍尔传感器、DS18B20、L298N 在 STC89C52 上的接法

在做软件调参之前,先把信号通路的每个环节摸清楚。这个设计里的信号链是:霍尔传感器的转速脉冲进外部中断,DS18B20 的数字温度值走单总线,按键的状态决定 PWM 的档位方向,L298N 根据 IN1~IN4 和 ENA 引脚输出驱动波形。三路信号各有各的电气要求,放在一起容易出现共地、上拉、时序互相干扰的问题。

2.1 霍尔传感器测速原理与脉冲输入测试

霍尔效应的核心是:磁场变化会让半导体薄片的横向产生电位差。用在测速场景,通常装一个开关型霍尔传感器,比如 A3144 这类常见的单极霍尔,磁铁随电机轴转动,霍尔靠近和离开时输出高低电平,每转一圈产生几个脉冲。转速测量就变成计算单位时间内脉冲个数,而不是采集模拟电压,这对 51 单片机的外部中断来说非常友好。

接法上,霍尔模块的输出引脚一般接到 STC89C52 的外部中断引脚,常用 INT0 的 P3.2。如果传感器是开漏结构,必须在信号线上加一个 4.7k 到 10k 的上拉电阻,否则低电平会被瞬间拉起来,脉冲边沿乱掉。机械按键和电机火花会把毛刺混进信号,最直接的办法是先跑一个读取测试,把外部中断配成下降沿触发,中断服务函数里翻转一个 LED。

// 霍尔脉冲读取测试:P3.2 接传感器输出,LED 接 P2.0 sbit test_led = P2^0; void ext0_init(void) { IT0 = 1; // 下降沿触发:霍尔靠近磁铁,输出从高变低时进中断 EX0 = 1; // 打开外部中断 0 EA = 1; // 打开总中断 } void ext0_isr(void) interrupt 0 { test_led = ~test_led; // 翻转 LED,代替示波器做脉冲指示 }

这段代码里有三个关键点。IT0 置 1 是让 51 单片机用边沿触发而不是电平触发,电平触发在主循环忙于刷新 LCD 时会重复进入中断,导致 LED 看起来常亮;中断服务函数只做最少的翻转操作,不要把速度计算直接放进来,否则脉冲密集时函数退不出去;测试时用手转动磁铁应该看到 LED 随转动频率闪烁,如果不能稳定闪烁,先检查传感器电源和磁铁极性,而不是急着改程序。

霍尔传感器的供电可以取 5V,注意与单片机共地。市面上不少霍尔模块是“传感器加比较器输出”的结构,输出的信号幅度和单片机电平不匹配,最好先用万用表量高电平和低电平,低于 0.7 倍 VCC 时考虑加 74HC14 施密特整形。这部分在实测里比理论更容易出问题,很多板子上霍尔信号线上叠加了电源纹波,导致中断频繁触发,速度值紊乱。

2.2 DS18B20 单总线通信:读取温度与默认参数

DS18B20 只有一根数据线 DQ,所有读写都靠单总线时序。它在上电后默认使用内部 12 位分辨率,转换精度 0.0625 度,对室内温度监控完全够用。DQ 引脚必须接一个 4.7k 上拉电阻到 VCC,总线上只有一个传感器时接线很简单,但要记住总线空闲是被上拉到高电平,单片机输出低电平属于开口逻辑,不能直接把 IO 配置成推挽输出,否则会拉伤总线。

温度读取的流程是:单片机先拉低总线 480us 以上产生复位脉冲,等待传感器回复存在脉冲;然后写跳过 ROM 指令 0xCC,再发启动转换指令 0x44,等 750ms 转换完成;之后再来一次复位、跳过 ROM 的 0xCC、读暂存器的指令 0xBE,从 DQ 上连续读回两个字节。下面是读一个字节并用软件延时实现时序的代码摘录,实际项目里的 18b20.c 就是按这个节奏写的。

// 读取一个字节,数据低位先出 unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; EA = 0; // 数据传输期间关中断,防止定时器打断 DQ = 0; _nop_(); DQ = 1; // 释放总线,进入读采样窗口 _nop_(); if (DQ) dat |= 0x80; // 在 60us 内采样当前位 EA = 1; delay_us(60); } return dat; }

为什么这里要关中断?因为 DS18B20 的读写时序是以 us 为单位的,STC89C52 内部定时器中断和外部中断随时可能插进来。如果执行到 DQ 为高那个采样点恰好被中断打断,读到的位就错了,紧接着温度值会乱或一直是 85.25 度。后面在第 4 章会继续讲中断与单总线并存的处理办法。

温度值读回来后做符号扩展,高字节低 4 位是符号位,低字节寄存的是温度小数部分。常用写法是把两个字节拼成一个 int,然后除以 16 得到带小数的温度值。在 18b20.c 里一般会提供 delay 相关函数,注意不要用毫秒级延时代替 60us 级时序,否则高温环境下读出的数值会周期性偏差。

2.3 L298N 双 H 桥驱动与 PWM 档位接口

L298N 的输入侧有四路控制信号 IN1~IN4,用来决定电机两端的高低电平组合。使能端 ENA 接高电平时通道 A 有效,接 PWM 时就能调速。正转时 IN1=1、IN2=0,反转时反过来;电机停止时 IN1=IN2=1 或都为 0,否则会出现刹停,电流反灌进驱动管。

L298N 的电机电源和逻辑电源可以分开,电机启动瞬间电流很大,电源线要粗,最好在靠近 L298N 的电源输入脚并联一个 100uF 电解电容和一个 0.1uF 瓷片电容。单片机一侧只给 L298N 信号,信号参考地必须与 L298N 电源地接在一起,不然 PWM 波形无法形成电流回路,电机只能永远全速跑。

档位与占空比的对应关系建议做成一张参数表,一方面便于在程序里查表,另一方面给答辩或技术文档留个清晰的说明。PWM 频率不要太高,51 单片机软件生成的 PWM 适合用 1kHz 以下频率,100Hz 到 500Hz 都常用。下表是这套设计里常用的 PWM 档位定义,加速减速按键改的就是 index。

PWM 档位 index占空比电机状态
00%停止
120%最低转速,避开启动死区
240%低速
360%中速
480%高速
5100%全速

上述档位表只是默认值,实际使用时把最小档位从 0 直接跳到 20%,是为了避开电机启动死区。占空比低于 15% 时,直流电机往往只哼不转,反而持续发热;PWM 档位步进可以改成 5% 细调,但按键响应会变得迟钝,需要多次按键才能跨过启动区。

2.4 整机连接与共地检查清单

连接顺序可以这样走:先把 STC89C52 的最小系统板放稳,插上 LCD1602 数据口,然后接 DS18B20 和霍尔传感器,最后才是 L298N 和电机。大多数信号噪声都是电机回路带来的,最后接电机也是对前面调试验证的保护。

用万用表量几个关键电压:霍尔 VCC 对 GND 是 5V,DS18B20 DQ 对 GND 静态应接近 5V,L298N 逻辑电源输入端对地是 5V。任何一个不是这个基准,都先别写程序,错误的是接法和上拉不足会让程序怎么调都白搭。

  1. 单片机、霍尔模块、DS18B20、L298N 的逻辑地全部接同一个 GND 点。
  2. 霍尔信号线到单片机引脚的走线尽量短,不与电机线平行。
  3. L298N 的 ENA 不能悬空,悬空时内部电气状态不确定,电机会出现乱转。
  4. 电源部分用 9V 或 12V 给电机,STC89C52 再经过 7805 降压,不要让电机和单片机共用一条电源线。

3. 固件实现:定时器 PWM、霍尔脉冲计数和按键状态机

硬件链路确定后,第 3 章要解决的是 main.c 和三个底层文件的配合方式。常见误区是把所有功能写进 while(1),导致 LCD 刷新时按键没响应、温度转换时脉冲计数丢。这个项目的源码里用 STARTUP.A51 和 delay.c 做启动与延时,1602.c 负责液晶输出,18b20.c 负责单总线时序,主程序只搭状态框架。

3.1 main.c 的主循环:轮询按键、刷新显示、保持 PWM 周期

主循环里必须做的是连续检查按键状态,同时更新液晶,但不要把 DS18B20 的 750ms 转换等待直接写进主循环。原因是等待期间霍尔脉冲还在来,外部中断可以维持计数,但按键和显示全部停摆,操作手感会很差。更合理的做法是设置一个温度采集完成标记,主循环每轮先处理按键,再在标记置位后读取温度并更新显示。

void main(void) { sys_init(); // 时钟、定时器、外部中断、LCD 初始化 while (1) { key_scan(); // 非阻塞扫描,只在有按键变化时更新目标参数 if (temp_ready) { // 温度转换完成标记,由定时器置位 temperature = ds18b20_get_temp(); temp_ready = 0; } refresh_lcd(); // 刷新温度、PWM 档位、速度 check_alarm(); // 判断超温报警,控制蜂鸣器 } }

主循环看起来简单,重点在于 temp_ready 不能靠 delay 去轮询,而应该用定时器中断置位。比如 DS18B20 启动转换后,用定时器 1 做 1s 的软件周期,到时间置位 temp_ready,这样 1s 恰好也是转速测量的窗口,一举两得。这种结构能让按键扫描保持毫秒级响应,不会因为温度转换而卡顿。

3.2 定时器 0 产生 PWM 波:占空比由档位表查出来

STC89C52 的两个定时器没有硬件 PWM 输出引脚,这个设计更常用的是定时器 0 定时中断,在中断里比较当前计数值和预设占空比,决定输出高电平还是低电平。另一种用法是定时器溢出直接翻转引脚,但占空比只能 50%,不适合无级调速。

#define PWM_PERIOD 100 // 一个周期 100 个时间片,500Hz 对应 2ms unsigned char pwm_duty = 0; // 0~100 对应占空比 0%~100% unsigned char pwm_cnt = 0; void timer0_isr(void) interrupt 1 { TH0 = 0xFC; // 每 20us 进一次中断,见下方计算 TL0 = 0x66; pwm_cnt++; if (pwm_cnt >= PWM_PERIOD) pwm_cnt = 0; if (pwm_cnt < pwm_duty) ENA = 1; else ENA = 0; }

为什么 TH0/TL0 是这个值?设晶振 12MHz,每个机器周期 1us,定时器每 20us 中断一次,也就是计数到 20 就溢出。20us 乘 100 个时间片等于 2ms,对应 PWM 频率 500Hz。这个频率既能避开音频噪声区间,又能让电机不过分发热。如果晶振改成 11.0592MHz,那延时函数和定时器初值都要重新算,不能直接套 0xFC66,串口和温度时序都会偏。

3.3 霍尔脉冲计数:用外部中断计数,用定时器窗口算速度

霍尔脉冲使用外部中断 0 累加,中断服务函数只做一句话:pulse_count++。转速计算放到定时器 1 的 1s 中断里,每秒读取 pulse_count,然后清零,速度值就是每秒脉冲数。加入每转磁极数参数后,转速公式为:rpm = (每秒脉冲数 / 磁极数)* 60。

volatile unsigned int pulse_count = 0; volatile unsigned int rpm = 0; void ext0_isr(void) interrupt 0 { pulse_count++; // 只计数,不放其他逻辑 } void timer1_isr(void) interrupt 3 { TR0 = 0; // 读计数时暂停外部中断计数,保证一致性 rpm = pulse_count / PULSE_PER_REV * 60; pulse_count = 0; TR0 = 1; }

这里有个细节:PULSE_PER_REV 这个宏需要自己标定。常见电机轴上贴一块磁铁,那么每转一个脉冲,PULSE_PER_REV=1;如果转盘上有 4 个磁极,那就是 4。霍尔器件装得离轴太远或磁极太弱,会发生漏脉冲导致 rpm 起伏,后续在第 4 章讲如何用实测校准。PWM 档位和实际转速并不完全相等,电机负载变化时同一占空比下实际转速会差不少,显示速度用霍尔测出来的值,能直接看出后续闭环控制的误差。

3.4 按键扫描状态机:消抖与档位修改

按键部分常见做法是检测到低电平就立刻执行动作,但机械按键在按下和松开时会产生 5ms 到 20ms 的抖动,直接执行会导致一次按键变两次。单片机课程设计里最稳的做法是 10ms 消抖后改变一次状态。这个设计把按键分成四个独立键,分别是启动/停止、正转/反转、加速、减速,加速减速每次改变一个 PWM 档位 index。

void key_scan(void) { static unsigned char key_filter = 0; static unsigned int key_count = 0; unsigned char key_now = read_key(); // 读回低电平有效按键值 if (key_now != key_filter) { key_count++; if (key_count >= 2) { key_filter = key_now; key_count = 0; if (key_filter == KEY_ACC) { pwm_index++; if (pwm_index > 5) pwm_index = 5; set_pwm_duty(duty_table[pwm_index]); } // 其他按键类似:停止、反转、减速 } } else { key_count = 0; } }

这个状态机的意图是:两次连续扫描都读到同一个键值时才确认按下,10ms 的扫描周期乘 2 次正好消抖。加速键和减速键修改共享的 pwm_index,然后查表拿出新的占空比;启动/停止键则把 pwm_index 清零或恢复上一次保存的值。如果所有按键都放在一个 while(1) 里轮询,不要设置长按自动加速,否则按键扫描本身会变成阻塞源。

3.5 温度、档位和速度的 LCD 联动显示

LCD1602 是 16 字符 x 2 行,内容分配需要规划:第一行显示温度和状态,第二行显示档位和速度。1602.c 里一般提供 LCD_Init、LCD_WriteCommand、LCD_WriteData 和 LCD_SetCursor 四个底层函数,上层组合字符串即可。

显示更新要把整数和小数分开处理,把速度值直接转成字符串拼进数组会简单很多。要注意霍尔速度是 unsigned int,温度可能带负数,精度到 0.1 度就够了。对于 51 单片机,不建议用完整版 stdio 的 sprintf,会大幅拉高 Flash 占用,用整型除法和手动拼字符串更划算。

按键改动的档位应当尽快反映在显示上,不能等温度转换完成。我一般把 refresh_lcd 里的温度读取和键值变化拆开,键值变化在第一行右侧闪动提示,温度只在 temp_ready 置位后重写一次。这样每次刷新耗时短,也不容易出现液晶闪烁。

中断/资源负责模块注意点
外部中断 0霍尔计数服务函数只做累加
定时器 0生成 PWM20us 中断一次,占空比调节
定时器 11s 测速窗口读取计数前关门
单总线时序DS18B20传输期间必须关中断
主循环按键与显示非阻塞刷新

这个表能帮代码阅读者快速定位每个中断的责任。调试时按这张表去查,不会在中断函数里翻半天。

4. 调参与排错:霍尔读数抖动、DS18B20 数值 -55 度、电机驱动异常

硬件和软件拼起来之后,大部分时间会花在调试上。以下三类问题是我实际跑这套源码时反复遇到的,也是答辩时最容易被问到的点。

4.1 霍尔读数抖动和转速值跳变怎么办

霍尔速度最典型的问题是转速显示值大幅跳变,有时到 0,有时翻倍。先检查磁铁的磁场方向,单极霍尔对磁极方向敏感,N 极和 S 极接近时的输出完全不同,磁铁安装方向错误时,每转一圈会多出或漏掉脉冲。其次是上拉电阻的阻值,开漏输出配 10k 在较长线上会让边沿变缓,可以换成 4.7k。

还有一个常见坑:PWM 调速时电机电流突变会让电源电压抖动,霍尔传感器和单片机共用同一个 5V,一旦电压跌落,霍尔输出会出现无效脉冲。解决办法是在霍尔供电脚附近加一个 100nF 电容吸收高频噪声,或者在主程序里对连续两次脉冲间隔做限定,间隔小于 1ms 的脉冲直接丢弃。速度计算端加滤波更容易实现:

// 对测速值做一阶低通滤波 rpm_filtered = (rpm_filtered * 7 + rpm_raw * 3) / 10;

这里系数 7 和 3 代表新值占比 30%,旧值占比 70%。系数偏大时响应快但跳动大,系数偏小时平滑但反映慢。可以先用这个默认值观察 10 秒内的显示稳定性,再把 3 改成 1 来加强滤波。但过犹不及,滤波太重时,电机实际堵转后转速显示还在缓慢下降,影响后续超速报警判断。

4.2 DS18B20 读出 -55 度:信号与时序的排查路径

-55 度是 DS18B20 的标志性错误返回值,看到这个数基本表示单总线初始化失败或传感器没被识别。第一步用万用表量 DQ 与 VCC 之间的电压,正常状态不通信时应接近 5V,如果只有 0.5V,说明上拉电阻没焊好或传感器损坏。第二步检查程序复位时序的长度,DS18B20 需要 480us 以上的低电平复位,常见的 20us 定时器中断不停插入会让复位根本不成立。

这个问题和中断密切相关。DS18B20 读写时序以微秒计,禁止一切中断插入。最稳妥的办法是给温度转换单独开一个长时间关中断区间,或者把定时器中断优先级调低,在 18b20 子程序里不执行 EA=1。不过这样会让霍尔计数暂停几百微秒,影响测速精度。实际工程里常用软件互斥:DS18B20 启动转换后,把读取命令打包放到一个专用函数里,主循环再去取结果。下面给出一段异常重试的思路:

// 如果读回来是 0xFF 或 0x00,重新初始化并读一次 unsigned char try_read_temp(void) { unsigned char retries = 3; while (retries--) { if (ds18b20_reset() == 0) { // 0 表示存在脉冲正常 ds18b20_write(0xCC); ds18b20_write(0xBE); return ds18b20_read_byte(); } } return 0x7F; // 统一错误码,显示 --.- °C }

这段代码的意思是:复位失败就重试 3 次,连续失败不要死循环卡死主程序。显示 --.- 比一直显示 -55 直观,也方便判断是传感器没插好而不是数据异常。很多 51 单片机程序死在 while 等待应答上,就是没做重试机制。

4.3 L298N 发热和 PWM 频率的关系

L298N 发热主要在两个场景:一是电机堵转,电流全灌在对管上;二是 PWM 频率过高,开关损耗占大头。51 单片机生成的 PWM 一般不超过 1kHz,500Hz 左右是安全和效率的平衡点。档位表里的最小占空比设为 20%,目的就是让电机在启动瞬间获得足够的启动电压,同时避免占空比太低导致长时间不转而发热。

一个很实用的验证手段是用示波器量 L298N 的输出对地。没有示波器时,可以用一个 1k 电阻和发光二极管串联在电机两端,占空比越高灯越亮,频率过高时灯会明显闪烁。这个方法能迅速判断 PWM 波形有没有输出,但不能精确量化占空比,适合现场粗调。

下面这张排错表可以直接对应到实际现象:

症状可能原因排查动作
转速一直为 0霍尔电源没接 / 磁铁极性反万用表量 VCC,手转磁铁观察 LED
转速偏大每转脉冲数设置少了实测 10 圈脉冲总数,修正 PULSE_PER_REV
温度恒定 -55DQ 未上拉 / 时序被中断打断查 4.7k 上拉,调整中断优先级
电机只能全速ENA 没接 PWM 或引脚虚焊发光二极管测 ENA 波形
按键按一下跳两档缺少消抖或检测到两次下降沿检查 key_scan 里的计数消抖

这个排错表也是答辩时展示设计完整度的好素材,说明不只会贴代码,还能定位硬件和软件边界。

5. 把档位调速升级为闭环:用温度目标值直接推算 PWM 占空比

5.1 从“超过 50 度报警”到“温度越高转速越快”的换算

温度报警只是开环,真正让这个设计有持续价值的是用 DS18B20 测到的温度直接改变 PWM 档位,做一个自动调速的温控风扇系统。50 度报警上限可以作为高速启动点。设定一个目标温度区间,低于下限按最低档运行,高于上限全速,中间用线性插值计算占空比。

unsigned char calc_duty_from_temp(float temp) { if (temp <= 30.0) return 20; // 最低 20% 占空比 if (temp >= 50.0) { buzzer = 1; return 100; } // 30 到 50 度线性映射到 20%~100% return (unsigned char)((temp - 30.0) * 4.0 + 20.0); }

占空比公式里的 4.0 是 (100-20)/(50-30) 得到的,温度每上升 1 度占空比涨 4%。这样写有一个问题:电机在 20% 占空比下可能不转,所以函数里把起始占空比提高到 20%。还可以用霍尔测速值判断堵转,当实测 rpm 低于阈值时,直接把占空比升到 60% 并触发蜂鸣器。

5.2 霍尔实测速度作为保护反馈,以及现场标定方法

霍尔速度值除了显示,还能用来做堵转保护和闭环补偿。当温度和 PWM 要求 50% 以上输出,但实测 rpm 低于 300 转且持续 3 秒,就可以认为电机被卡住或负载异常,此时停止 PWM 并报警。检测逻辑放在定时器 1 的 1s 中断里,用一个软计数器累计连续低速秒数,比每次刷新显示时判断更及时。

现场标定速度值的方法是:电机轴上贴一块磁铁,用手匀速转 10 圈,数霍尔脉冲总数,除以 10 得到单圈脉冲数,把宏 PULSE_PER_REV 改成这个数。标定后把空载和负载各测 10 次,取平均值作为后续闭环控制的转速基准。

最后一步是把手动档位与自动占空比合并,做法是按键按下时不直接改占空比,而是修改“自动调速启用”标志。当 auto_mode 为 1 时,主循环用 calc_duty_from_temp 的返回值去设置 pwm_duty;为 0 时才使用按键调整的档位表。这样整套 51 单片机温控风扇设计就能从课设直接改造成一个可用的散热系统,霍尔传感器和温度传感器各司其职,速度与温度都落到 PWM 占空比这一个最终变量上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询