51单片机UART串口通信与DS18B20温度传感器
一、UART串口通信
1.1 UART基本概念
-
UART:Universal Asynchronous Receiver Transmitter,通用异步收发器
-
特点:异步、全双工、串行通信协议
-
功能:单片机与外部设备进行数据通信的基本接口
1.2 UART通信参数配置
| 参数 | 常见值 | 说明 |
|---|---|---|
| 波特率 | 9600, 115200等 | 每秒传输的比特数 |
| 数据位 | 8位 | 数据长度 |
| 校验位 | N(无), E(偶), O(奇) | 数据校验方式 |
| 停止位 | 1位 | 数据帧结束标志 |
常见配置示例:9600 8N1 (波特率9600,8位数据,无校验,1位停止位)
1.3 通信方式对比
| 通信方式 | 数据线 | 传输方向 | 特点 |
|---|---|---|---|
| 单工 | 1根 | 单向固定 | 一方固定发送,一方固定接收 |
| 半双工 | 1根 | 双向分时 | 同一时间只能单向传输 |
| 全双工 | 2根 | 双向同时 | 收发可同时进行,UART属于此类 |
1.4 UART数据帧格式
起始位(1位低电平) + 数据位(5-8位) + 校验位(可选) + 停止位(1-2位高电平)
1.5 51单片机UART相关寄存器
1.5.1 SCON寄存器(串行控制寄存器,地址98H)
| 位 | 符号 | 功能 | 说明 |
|---|---|---|---|
| 7 | SM0/FE | 工作方式选择0/帧错误标志 | 与SM1共同决定工作方式 |
| 6 | SM1 | 工作方式选择1 | 00:方式0, 01:方式1, 10:方式2, 11:方式3 |
| 5 | SM2 | 多机通信控制位 | 通常设为0 |
| 4 | REN | 接收允许控制位 | 1:允许接收, 0:禁止接收 |
| 3 | TB8 | 发送数据第9位 | 方式2、3时使用 |
| 2 | RB8 | 接收数据第9位 | 方式2、3时使用 |
| 1 | TI | 发送中断标志 | 发送完成置1,需软件清0 |
| 0 | RI | 接收中断标志 | 接收完成置1,需软件清0 |
工作方式选择表:
| SM0 SM1 | 工作方式 | 功能说明 | 波特率 |
|---|---|---|---|
| 0 0 | 方式0 | 同步移位串行方式 | SYSclk/12 |
| 0 1 | 方式1 | 8位UART,波特率可变 | (2^SMOD/32)×定时器1溢出率 |
| 1 0 | 方式2 | 9位UART,波特率固定 | (2^SMOD/64)×SYSclk |
| 1 1 | 方式3 | 9位UART,波特率可变 | 同方式1 |
1.5.2 PCON寄存器(电源控制寄存器,地址87H)
| 位 | 符号 | 功能 | 说明 |
|---|---|---|---|
| 7 | SMOD | 波特率倍增位 | 1:波特率加倍, 0:波特率正常 |
| 1 | PD | 掉电模式 | 1:进入掉电模式 |
| 0 | IDL | 空闲模式 | 1:进入空闲模式 |
1.6 UART示例代码
/**
* UART串口初始化函数
* 配置为9600 8N1,使用定时器1模式2产生波特率
* 晶振频率:11.0592MHz
*/
#include <reg52.h>
void uart_init() {
// 设置定时器1为模式2(8位自动重装)
TMOD &= 0x0F; // 清除T1模式位
TMOD |= 0x20; // 设置T1为模式2
// 设置波特率为9600(SMOD=0)
TH1 = 0xFD; // 波特率9600对应的重装值
TL1 = 0xFD;
// 启动定时器1
TR1 = 1;
// 配置串口为模式1(8位UART,波特率可变)
SCON = 0x50; // 0101 0000:方式1,允许接收
// 清空中断标志
TI = 0;
RI = 0;
// 开启串口中断(可选)
ES = 1; // 允许串口中断
EA = 1; // 开启总中断
}
/**
* 发送一个字节数据(查询方式)
* @param dat 要发送的数据
*/
void uart_send_byte(unsigned char dat) {
SBUF = dat; // 将数据写入发送缓冲区
while(!TI); // 等待发送完成
TI = 0; // 清发送中断标志
}
/**
* 发送字符串
* @param str 要发送的字符串(以\0结尾)
*/
void uart_send_string(unsigned char *str) {
while(*str != '\0') {
uart_send_byte(*str);
str++;
}
}
/**
* 接收一个字节数据(查询方式)
* @return 接收到的数据
*/
unsigned char uart_receive_byte() {
while(!RI); // 等待接收完成
RI = 0; // 清接收中断标志
return SBUF; // 返回接收到的数据
}
/**
* 串口中断服务函数
* 接收数据并回传(echo)
*/
void uart_isr() interrupt 4 {
unsigned char recv_data;
if(RI) { // 接收中断
RI = 0; // 清接收标志
recv_data = SBUF; // 读取接收到的数据
// 将接收到的数据发送回去
SBUF = recv_data;
while(!TI); // 等待发送完成
TI = 0; // 清发送标志
}
}
/**
* 主函数:UART测试程序
* 功能:接收PC发送的数据并回传
*/
void main() {
unsigned char recv_data;
// 初始化串口
uart_init();
// 发送欢迎信息
uart_send_string("UART Test Start!\r\n");
uart_send_string("Please input data:\r\n");
while(1) {
// 查询方式接收数据
recv_data = uart_receive_byte();
// 将接收到的数据发送回去
uart_send_byte(recv_data);
// 如果是回车,则添加换行
if(recv_data == '\r') {
uart_send_byte('\n');
}
}
}
1.7 Modbus协议简介
1.7.1 Modbus数据帧格式
从机地址(1字节) + 功能码(1字节) + 数据(N字节) + CRC校验(2字节)
1.7.2 常用功能码
| 功能码 | 功能说明 | 示例应用 |
|---|---|---|
| 0x01 | 读取线圈状态 | 读取LED状态 |
| 0x02 | 读取输入状态 | 读取按键状态 |
| 0x03 | 读取保持寄存器 | 读取温度值 |
| 0x05 | 写单个线圈 | 控制单个LED |
| 0x06 | 写单个寄存器 | 设置数码管显示值 |
/**
* Modbus协议解析示例
* 功能码:0x01-LED控制,0x02-数码管控制,0x03-蜂鸣器控制
*/
#include <reg52.h>
// 外设控制函数声明
void led_control(unsigned char state);
void seg_display(unsigned char pos, unsigned char value);
void beep_control(unsigned int freq);
/**
* Modbus协议处理函数
* @param data 接收到的数据缓冲区
* @param len 数据长度
*/
void modbus_process(unsigned char *data, unsigned char len) {
unsigned char addr = data[0]; // 从机地址
unsigned char func = data[1]; // 功能码
unsigned char crc_l = data[len-2]; // CRC低字节
unsigned char crc_h = data[len-1]; // CRC高字节
// 校验CRC(此处为示例,实际应实现CRC校验)
// 根据功能码执行相应操作
switch(func) {
case 0x01: // LED控制
led_control(data[2]); // 参数:LED状态
break;
case 0x02: // 数码管控制
seg_display(data[2], data[3]); // 参数:位置和数值
break;
case 0x03: // 蜂鸣器控制
beep_control(data[2] * 100); // 参数:频率(*100)
break;
default:
// 错误功能码
break;
}
}
/**
* Modbus数据接收处理示例
*/
void modbus_receive_example() {
unsigned char modbus_buf[10];
unsigned char buf_index = 0;
while(1) {
if(RI) { // 接收到数据
RI = 0;
modbus_buf[buf_index++] = SBUF;
// 简单的帧结束判断(以回车符结束)
if(SBUF == '\r') {
// 处理Modbus指令
modbus_process(modbus_buf, buf_index);
buf_index = 0;
}
// 防止缓冲区溢出
if(buf_index >= 10) {
buf_index = 0;
}
}
}
}
二、DS18B20温度传感器
2.1 DS18B20基本参数
| 参数 | 规格 | 说明 |
|---|---|---|
| 测量范围 | -55°C ~ +125°C | 宽温度测量范围 |
| 精度 | ±0.5°C | 测量精度 |
| 分辨率 | 9-12位可调 | 默认12位,0.0625°C/位 |
| 工作电压 | 3.0V ~ 5.5V | 宽电压工作 |
| 接口 | 单总线(1-Wire) | 只需一根数据线 |
2.2 DS18B20引脚说明
| 引脚 | 功能 | 说明 |
|---|---|---|
| GND | 地 | 电源负极 |
| DQ | 数据输入/输出 | 单总线数据线 |
| VDD | 电源正极 | 可选,寄生供电时可悬空 |
2.3 DS18B20操作时序
2.3.1 复位时序
主机拉低总线 >480us → 主机释放总线 → DS18B20等待15-60us后拉低60-240us → DS18B20释放总线
2.3.2 写时序
-
写0:拉低总线 >60us,然后释放
-
写1:拉低总线 1-15us,然后释放,等待45us以上
2.3.3 读时序
主机拉低总线1-15us → 主机释放总线 → 主机在15us内采样总线电平 → 读取数据位
2.4 DS18B20操作流程
-
初始化:复位脉冲 + 存在脉冲检测
-
发送ROM命令:跳过ROM(0xCC)或匹配ROM
-
发送功能命令:启动温度转换(0x44)
-
等待转换完成:延时750ms(12位分辨率)
-
重新初始化
-
发送ROM命令:跳过ROM(0xCC)
-
发送功能命令:读取暂存器(0xBE)
-
读取数据:读取9字节(温度值在前2字节)
2.5 DS18B20温度值计算
-
12位分辨率:温度值 = 读取值 × 0.0625
-
正负温度判断:高字节的最高位为符号位(0正,1负)
2.6 DS18B20示例代码
/**
* DS18B20温度传感器驱动
* 使用单总线协议,数据线连接至P3.7
*/
#include <reg52.h>
#include <intrins.h>
sbit DQ = P3^7; // DS18B20数据线
// 延时函数(微秒级)
void delay_us(unsigned int t) {
while(t--);
}
// 延时函数(毫秒级)
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 120; j++);
}
/**
* DS18B20初始化
* @return 初始化结果:0-成功,1-失败
*/
bit ds18b20_init() {
bit ack;
DQ = 1; // 拉高总线
delay_us(5);
DQ = 0; // 主机拉低总线
delay_us(500); // 保持480us以上(复位脉冲)
DQ = 1; // 释放总线
delay_us(60); // 等待15-60us
ack = DQ; // 读取存在脉冲(0:存在,1:不存在)
delay_us(240); // 等待存在脉冲结束
return ack; // 返回初始化结果
}
/**
* 向DS18B20写入一个字节
* @param dat 要写入的数据
*/
void ds18b20_write_byte(unsigned char dat) {
unsigned char i;
for(i = 0; i < 8; i++) {
DQ = 0; // 拉低总线开始写时序
_nop_(); // 延时约1us
DQ = dat & 0x01; // 写入数据位(最低位)
delay_us(60); // 保持60us
DQ = 1; // 释放总线
dat >>= 1; // 准备下一位
delay_us(5); // 延时
}
}
/**
* 从DS18B20读取一个字节
* @return 读取的数据
*/
unsigned char ds18b20_read_byte() {
unsigned char i, dat = 0;
for(i = 0; i < 8; i++) {
DQ = 0; // 拉低总线开始读时序
_nop_(); // 延时约1us
DQ = 1; // 释放总线,让DS18B20控制
_nop_(); // 延时约1us
dat >>= 1; // 右移,为接收新位做准备
if(DQ) { // 读取数据位
dat |= 0x80; // 最高位置1
}
delay_us(60); // 等待时序结束
}
return dat;
}
/**
* 启动温度转换
*/
void ds18b20_start_convert() {
ds18b20_init(); // 初始化
ds18b20_write_byte(0xCC); // 跳过ROM命令
ds18b20_write_byte(0x44); // 启动温度转换
}
/**
* 读取温度值(返回整型,放大100倍)
* @return 温度值(如25.12°C返回2512)
*/
int ds18b20_read_temp() {
unsigned char temp_l, temp_h;
int temp;
// 重新初始化
ds18b20_init();
// 跳过ROM命令
ds18b20_write_byte(0xCC);
// 发送读取暂存器命令
ds18b20_write_byte(0xBE);
// 读取温度值(前两个字节)
temp_l = ds18b20_read_byte();
temp_h = ds18b20_read_byte();
// 组合温度值(16位有符号整数)
temp = temp_h;
temp <<= 8;
temp |= temp_l;
// 温度值计算(放大100倍)
// 12位分辨率:LSB = 0.0625°C
// 放大100倍:0.0625 * 100 = 6.25
temp = temp * 625 / 100; // 避免浮点运算
return temp;
}
/**
* 主函数:DS18B20温度采集示例
* 功能:每隔1秒采集一次温度并通过串口发送
*/
void main() {
int temperature;
unsigned char temp_str[10];
// 初始化串口(参考前面的uart_init函数)
uart_init();
uart_send_string("DS18B20 Temperature Test\r\n");
while(1) {
// 启动温度转换
ds18b20_start_convert();
// 等待转换完成(750ms for 12-bit)
delay_ms(750);
// 读取温度值
temperature = ds18b20_read_temp();
// 通过串口发送温度值
uart_send_string("Temperature: ");
// 判断正负温度
if(temperature < 0) {
uart_send_byte('-');
temperature = -temperature;
}
// 发送整数部分
temp_str[0] = temperature / 1000 + '0';
temp_str[1] = (temperature % 1000) / 100 + '0';
temp_str[2] = (temperature % 100) / 10 + '0';
temp_str[3] = '.';
temp_str[4] = temperature % 10 + '0';
temp_str[5] = 'C';
temp_str[6] = '\r';
temp_str[7] = '\n';
temp_str[8] = '\0';
uart_send_string(temp_str);
// 延时1秒
delay_ms(1000);
}
}
2.7 综合示例:Modbus从机带温度采集
/**
* 综合示例:Modbus从机设备
* 功能码扩展:0x04-温度采集
*/
#include <reg52.h>
// 全局变量
unsigned char modbus_addr = 0x01; // 从机地址
/**
* 处理Modbus功能码0x04(读取温度寄存器)
* @param data_buf 数据缓冲区
*/
void modbus_func_04(unsigned char *data_buf) {
int temperature;
// 读取温度值
temperature = ds18b20_read_temp();
// 构建响应数据
data_buf[0] = modbus_addr; // 从机地址
data_buf[1] = 0x04; // 功能码
data_buf[2] = 0x02; // 数据字节数
data_buf[3] = temperature >> 8; // 温度高字节
data_buf[4] = temperature & 0xFF; // 温度低字节
// 计算CRC(此处简化为固定值,实际应计算)
data_buf[5] = 0x00; // CRC低字节
data_buf[6] = 0x00; // CRC高字节
// 发送响应
for(int i = 0; i < 7; i++) {
uart_send_byte(data_buf[i]);
}
}
/**
* Modbus主处理函数
*/
void modbus_main_process() {
unsigned char recv_buf[20];
unsigned char recv_index = 0;
unsigned char func_code;
while(1) {
if(RI) { // 接收到数据
RI = 0;
recv_buf[recv_index++] = SBUF;
// 检查帧完整性(以至少6字节和地址匹配为条件)
if(recv_index >= 6 && recv_buf[0] == modbus_addr) {
func_code = recv_buf[1];
switch(func_code) {
case 0x01: // LED控制
led_control(recv_buf[2]);
break;
case 0x02: // 数码管控制
seg_display(recv_buf[2], recv_buf[3]);
break;
case 0x03: // 蜂鸣器控制
beep_control(recv_buf[2] * 100);
break;
case 0x04: // 温度采集
modbus_func_04(recv_buf);
break;
default:
// 发送错误响应
break;
}
recv_index = 0; // 清空缓冲区
}
// 防止缓冲区溢出
if(recv_index >= 20) {
recv_index = 0;
}
}
}
}
三、重点内容总结
3.1 UART串口通信要点
| 知识点 | 关键内容 | 备注 |
|---|---|---|
| 接线方式 | TXD-RXD交叉连接 | 单片机与PC通信必须交叉 |
| 波特率设置 | 使用定时器1模式2生成 | 11.0592MHz晶振计算方便 |
| 工作方式 | 方式1最常用 | 8位UART,波特率可变 |
| 中断处理 | TI和RI标志必须软件清零 | 防止重复中断 |
| 数据帧 | 起始位+数据位+校验位+停止位 | 完整的数据格式 |
3.2 DS18B20温度传感器要点
| 知识点 | 关键内容 | 备注 |
|---|---|---|
| 单总线协议 | 严格的时序要求 | 必须精确控制延时 |
| 温度读取 | 先启动转换再读取 | 转换需要时间(750ms) |
| 温度计算 | 12位分辨率,0.0625°C/位 | 注意正负温度判断 |
| 寄生供电 | VDD可悬空 | 简化接线 |
3.3 Modbus协议要点
| 知识点 | 关键内容 | 备注 |
|---|---|---|
| 数据格式 | 地址+功能码+数据+CRC | 标准Modbus RTU格式 |
| 主从结构 | 主机发起,从机响应 | 从机不能主动发送 |
| 功能码定义 | 根据应用自定义 | 0x01-0x04为常用功能码 |
| CRC校验 | 16位CRC校验 | 保证数据传输可靠性 |
3.4 综合应用建议
-
串口调试:先实现基本的串口收发,再增加协议
-
温度采集:确保DS18B20时序正确,可先用逻辑分析仪验证
-
协议实现:从简单协议开始,逐步增加功能
-
错误处理:添加超时、校验、重试机制