嵌入式学习基础笔记(51)

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操作流程

  1. 初始化:复位脉冲 + 存在脉冲检测

  2. 发送ROM命令:跳过ROM(0xCC)或匹配ROM

  3. 发送功能命令:启动温度转换(0x44)

  4. 等待转换完成:延时750ms(12位分辨率)

  5. 重新初始化

  6. 发送ROM命令:跳过ROM(0xCC)

  7. 发送功能命令:读取暂存器(0xBE)

  8. 读取数据:读取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 综合应用建议

  1. 串口调试:先实现基本的串口收发,再增加协议

  2. 温度采集:确保DS18B20时序正确,可先用逻辑分析仪验证

  3. 协议实现:从简单协议开始,逐步增加功能

  4. 错误处理:添加超时、校验、重试机制


相关推荐
星期五不见面1 小时前
机器人学习!(二)ROS-基于v4l2loopback虚拟摄像头项目(4)2026/01/14
学习·机器人
Gary Studio1 小时前
轮[特殊字符]机器人学习笔记
学习
yi.Ist2 小时前
博弈论 Nim游戏
c++·学习·算法·游戏·博弈论
电子小白1232 小时前
第13期PCB layout工程师初级培训-5-生产制造
笔记·嵌入式硬件·学习·制造·pcb·layout
楼田莉子2 小时前
C++高级数据结构——LRU Cache
数据结构·c++·后端·学习
副露のmagic2 小时前
更弱智的算法学习day 38
python·学习
秦奈2 小时前
Unity复习学习笔记(八):动画、模型与寻路
笔记·学习·unity
brave and determined2 小时前
工程设计类学习(DAY6):PCB可制造性设计规范全解析
网络·嵌入式硬件·学习·pcb设计·设计规范·嵌入式设计·设计工艺
姚瑞南2 小时前
【AI 风向标】强化学习(RL):智能体自我优化的学习范式
人工智能·经验分享·python·gpt·深度学习·学习·机器学习