一、项目概述
本项目基于 STC89C52 单片机,实现了一个温度采集与监控系统。上位机通过串口与单片机通信,可以实现以下功能:
- 读取 DS18B20 采集的温度
- 控制 LED 灯和蜂鸣器
- 设置温度上下限阈值
- 切换手动/自动工作模式
- 自动模式下超限告警
这个项目适合 51 单片机入门进阶,涉及单总线通信、串口协议、定时器中断、数码管动态扫描等知识点。
二、硬件清单
| 器件 | 型号/参数 | 数量 |
|---|---|---|
| 单片机 | STC89C52RC | 1 |
| 温度传感器 | DS18B20 | 1 |
| 数码管 | 4位共阴 | 1 |
| LED | 普通发光二极管 | 2 |
| 蜂鸣器 | 无源蜂鸣器 | 1 |
| 晶振 | 11.0592MHz | 1 |
| USB转TTL | CH340 | 1 |
| 电阻 | 4.7kΩ(DS18B20上拉) | 1 |
三、硬件连接
1. DS18B20
| DS18B20 | 单片机 |
|---|---|
| GND | GND |
| DQ | P3.7(并接4.7kΩ上拉到VCC) |
| VCC | VCC |
注意:DQ 必须接 4.7kΩ 上拉电阻,否则读不到数据。
2. 数码管(普中51-MS开发板)
| 数码管 | 单片机 |
|---|---|
| 位选 | P1.0 ~ P1.3 |
| 段选 | P0 |
3. LED 与蜂鸣器
| 外设 | 引脚 | 有效电平 |
|---|---|---|
| LED1 | P2.0 | 低电平亮 |
| 蜂鸣器 | P2.1 | 方波驱动(无源) |
4. 串口
| 单片机 | USB转TTL |
|---|---|
| P3.1 (TXD) | RXD |
| P3.0 (RXD) | TXD |
| GND | GND |
波特率 2400,8 数据位,1 停止位,无校验。
四、通信协议设计
1. 上位机 → 单片机(7字节定长帧)
text
AA 01 CMD P1 P2 SUM BB
| 字节 | 含义 |
|---|---|
| AA | 帧头 |
| 01 | 地址 |
| CMD | 命令码 |
| P1 | 参数1 |
| P2 | 参数2 |
| SUM | 校验和(前5字节之和) |
| BB | 帧尾 |
命令码定义:
| CMD | 功能 | P1 | P2 |
|---|---|---|---|
| 0x01 | 读温度 | - | - |
| 0x02 | 控制外设 | 设备号 | 状态 |
| 0x03 | 设阈值 | 上限 | 下限 |
| 0x04 | 设为自动 | - | - |
| 0x05 | 设为手动 | - | - |
设备号:
| 设备号 | 设备 |
|---|---|
| 0x01 | LED |
| 0x02 | 蜂鸣器 |
状态:0x00=关,0x01=开
2. 单片机 → 上位机(7字节应答帧)
text
AA 01 CMD DATA_H DATA_L SUM BB
五、软件架构
工程共 3 个文件:
text
项目/
├── ds18b20.c (主文件,含 main 函数 + DS18B20 驱动 + 串口)
├── timer.c (数码管扫描 + 蜂鸣器方波 + 1s 计时)
└── timer.h (头文件,声明共用变量和函数)
模块划分:
| 模块 | 功能 |
|---|---|
| 串口收发 | 收 7 字节命令,回 7 字节应答 |
| DS18B20 驱动 | 复位、读写字节、读温度 |
| 数码管 | 定时器中断动态扫描 |
| 蜂鸣器 | 定时器中断输出方波 |
| 命令解析 | 根据命令码执行对应动作 |
| 自动控制 | 1s 定时读温度,超限告警 |
六、关键代码解析
1. 串口接收中断
c
void UartHandler(void) interrupt 4
{
if (RI)
{
RI = 0;
if (RecvLen < sizeof(RecvBuff))
{
RecvBuff[RecvLen] = SBUF;
RecvLen++;
if (7 == RecvLen)
{
IsCommandLine = 1;
}
}
else
{
RecvLen = 0;
}
}
}
要点:
- 用 RI 判断接收中断
- 收满 7 字节置标志,主循环处理
- 加越界保护
2. DS18B20 读温度
c
int ReadTemp(void)
{
unsigned char LSBVal = 0;
unsigned char MSBVal = 0;
int Val = 0;
DQ = 1;
Delay480us();
Delay480us();
EA = 0; // ★ 关中断,保护时序
DQReset();
ReadDQRespone();
SendByte(0xCC);
SendByte(0x44);
EA = 1;
Delay800ms();
EA = 0;
DQReset();
ReadDQRespone();
SendByte(0xCC);
SendByte(0xBE);
LSBVal = RecvByte();
MSBVal = RecvByte();
EA = 1;
Val = (MSBVal << 8) | LSBVal;
Val >>= 4; // 取整数摄氏度
return Val;
}
要点:
- 读写时关中断 EA = 0,防止定时器打断时序
- 读回后右移 4 位,得到整数°C
- 加超时防止死循环
3. 定时器中断(数码管 + 蜂鸣器 + 1s计时)
c
void Timer0Handler(void) interrupt 1
{
static unsigned char scan = 0;
static unsigned char buzzCnt = 0;
TH0 = 0xFF;
TL0 = 0x48; // 200us
if (BuzzerOn)
BEEP = ~BEEP; // 蜂鸣器方波
else
BEEP = 0;
buzzCnt++;
if (buzzCnt >= 4) // 800us 扫一位
{
buzzCnt = 0;
DisplayNum(scan);
scan++;
if (4 == scan) scan = 0;
}
Timer1msCnt++;
if (Timer1msCnt >= 5000) // 1s
{
Timer1msCnt = 0;
AutoFlag = 1;
}
}
要点:
- 200us 中断一次
- 蜂鸣器每次中断翻转 → 2.5kHz 方波
- 每 4 次中断扫一位数码管
- 5000 次中断 = 1s
4. 命令解析
c
if (RecvBuff[2] == 0x01) // 读温度
{
CurTemp = ReadTemp();
SetDisplayNum(CurTemp);
SendResponse(0x01, CurTemp);
}
else if (RecvBuff[2] == 0x02) // 控制外设
{
if (RecvBuff[3] == 0x01) SetLed(RecvBuff[4]);
else if (RecvBuff[3] == 0x02) SetBuzzer(RecvBuff[4]);
SendResponse(0x02, 0x0000);
}
else if (RecvBuff[2] == 0x03) // 设阈值
{
TempHighLimit = RecvBuff[3];
TempLowLimit = RecvBuff[4];
SendResponse(0x03, 0x0000);
}
else if (RecvBuff[2] == 0x04) // 自动模式
{
WorkMode = 1;
SendResponse(0x04, 0x0000);
}
else if (RecvBuff[2] == 0x05) // 手动模式
{
WorkMode = 0;
SetLed(0);
SetBuzzer(0);
SendResponse(0x05, 0x0000);
}
5. 自动控制逻辑
c
if (WorkMode == 1 && AutoFlag)
{
AutoFlag = 0;
CurTemp = ReadTemp();
SetDisplayNum(CurTemp);
if (CurTemp > TempHighLimit) // 超上限
{
SetLed(1);
SetBuzzer(0);
}
else if (CurTemp < TempLowLimit) // 低于下限
{
SetLed(1);
SetBuzzer(1);
}
else // 范围内
{
SetLed(0);
SetBuzzer(0);
}
}
七、完整工作流程
flowchart TD
A[上电] --> B[UartInit + Timer0Init
数码管显示 0]
B --> C{while 1}
C --> D{收到命令?}
C --> E{自动模式+1s到?}
D -- 是 --> F[解析命令并执行]
E -- 是 --> G[读温度并超限告警]
F --> C
G --> C