51单片机-03-串行通信

一、串行通信基础概念

1.1 通信方向

|-----------|-----------------------------------------------|
| 通信类型 | 特点与举例 |
| 单工 | 方向固定,只有发送方→接收方。如遥控器发指令给电视 |
| 半双工 | 双方都可收发,但同一时刻只能一个方向传输,共用一根线。如 I²C(SCL+SDA)、对讲机 |
| 全双工 ★ | 双方同时收发,使用两根线(TXD + RXD)。UART 就是全双工 |

1.2 串行 vs 并行

|----------|--------------------------------------|
| 传输方式 | 特点 |
| 串行 | 1根信号线,逐bit传输。速度慢、成本低、距离远(RS485 可达千米) |
| 并行 | 多根信号线同时传多个bit。速度快、成本高、距离近(≤30米,易受干扰) |

1.3 同步 vs 异步

|----------|------------------------------------------------------|
| 方式 | 说明 |
| 同步 | 有专用时钟线(CLK),双方靠时钟线对齐。如 SPI(4线:MOSI/MISO/CS/SCLK)、I²C |
| 异步 ★ | 没有时钟线,双方约定相同波特率来对齐。UART 属于异步通信 |

1.4 常见串行协议对比

|--------|-------------------------|-------------------|
| 协议 | 信号线 | 说明 |
| UART | TXD / RXD | 全双工异步,点对点,最简单最常用 |
| I²C | SCL / SDA | 半双工同步,一主多从,2线 |
| SPI | MOSI / MISO / CS / SCLK | 全双工同步,一主多从,4线,速度快 |
| RS485 | A / B(差分) | 半双工,差分信号,抗干扰,距离远 |

二、UART 通信参数详解

2.1 电平标准

|-----------|------------------------------------------------------|
| 标准 | 电平定义 |
| TTL(单片机) | 高电平 = 5V,低电平 = 0V |
| RS232(PC) | 负逻辑:高电平 = -3V ~ -15V,低电平 = +3V ~ +15V(需CH340等转换芯片) |
| RS485 | 差分信号:A-B > 2V = 高电平;A-B < -2V = 低电平,抗干扰极强 |

|-----------|-------------------------------------------------------------------|
| CH340 | 51单片机(TTL电平)与电脑(RS232)通过 USB 连接时,需要 CH340 芯片做电平转换,同时将 USB 转成串口信号。 |

2.2 串口通信参数格式:波特率 数据位 校验位 停止位

|--------------|--------------------------------|
| 参数格式示例 | 含义 |
| 9600 8 N 1 | 波特率9600bps,8位数据,无校验,1位停止位(最常用) |
| 115200 8 E 1 | 波特率115200bps,8位数据,偶校验,1位停止位 |
| 2400 8 O 1 | 波特率2400bps,8位数据,奇校验,1位停止位 |

2.3 校验方式

|-----------|-----------------------------|
| 校验类型 | 规则 |
| N --- 无校验 | 不附加任何校验位,最简单 |
| 奇校验 O | 数据位中1的个数 + 校验位,总计1的个数保持为奇数 |
| 偶校验 E | 数据位中1的个数 + 校验位,总计1的个数保持为偶数 |
| 累加和校验 | 所有数据字节累加取低8位作为校验值(Modbus常用) |
| CRC校验 | 循环冗余校验,检错能力最强 |

|----------|-----------------------------------|
| 校验局限 | 奇偶校验无法检测出「偶数个bit同时出错」的情况(错误互相抵消)。 |

2.4 LSB 低位先行

UART 发送数据时,低位(bit0)先发,高位(bit7)后发。

例:发送 0xAB = 1010 1011,实际在线上先出现 bit0=1,然后 bit1=1,...,最后 bit7=1。

三、51单片机 UART 波特率计算

51单片机的 UART 使用 Timer1 工作在「模式2(8位自动重装)」来产生波特率。

3.1 波特率计算公式(SMOD=1,倍速模式)

|---------------|--------------------------------------------------|
| 条件 | 数值 |
| 晶振频率 | 11.0592 MHz |
| Timer1 工作频率 | 11.0592 MHz ÷ 12 = 0.9216 MHz |
| SMOD=1 时波特率公式 | 波特率 = (2^SMOD × 0.9216MHz) ÷ (32 × (256 - TH1)) |
| 2400 bps 初值 | TH1 = TL1 = 232(0xE8) |
| 9600 bps 初值 | TH1 = TL1 = 253(0xFD) |

|-----------------|------------------------------------------------------------------|
| 为何用11.0592? | 11.0592 MHz 是特意选择的晶振频率,能让常用波特率(2400/9600/115200)的计算结果恰好是整数,避免误差。 |

四、SCON 串口控制寄存器

地址:98H 可位寻址

|---------|---------|---------|---------|---------|---------|--------|--------|
| B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
| SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |

|------------|----------------------------------------------------------|
| 位 / 名称 | 功能说明 |
| B7 SM0 | 工作模式高位(与SM1组合) |
| B6 SM1 | 工作模式低位:SM0=0, SM1=1 → 模式1,8位UART,最常用! |
| B5 SM2 | 多机通信控制位,一般清0 |
| B4 REN | 允许接收位。REN=1 → 允许UART接收数据,必须置1才能收到数据 |
| B3 TB8 | 发送第9位数据(模式2/3),模式1不用 |
| B2 RB8 | 接收到的第9位数据(模式2/3),模式1不用 |
| B1 TI | 发送中断标志位。发送完一帧后硬件置1,需软件手动清0(发送完后 SCON &= ~(1<<1)) |
| B0 RI | 接收中断标志位。接收完一帧后硬件置1,进入中断服务函数后需软件手动清0(SCON &= ~(1<<0)) |

SCON 初始化关键操作

SCON &= ~(3 << 6); // 清除 SM0/SM1

SCON |= (1 << 6); // SM1=1, SM0=0 → 模式1(8位UART)

SCON |= (1 << 4); // REN=1 允许接收

// TI/RI 由硬件置1,软件在用完后手动清0

五、PCON 电源控制寄存器(波特率加倍)

|---------|-----------------------------------------|
| | 作用 |
| B7 SMOD | 串口波特率倍速位。SMOD=1 → 波特率 × 2;SMOD=0 → 正常速率 |
| 其余位 | 电源管理相关,串口初始化中不使用 |

PCON &= ~(3 << 6); // 清 B7/B6

PCON |= (1 << 7); // SMOD=1 波特率加倍(2400bps 时需要)
六、UART 完整初始化流程(uart.c 逐步详解)

目标:配置为 2400bps,8位数据,无校验,1停止位,启用接收中断

cs 复制代码
void uart_init(void)

{

    // ① 配置SCON:模式1(8位UART),允许接收

    SCON &= ~(3 << 6);   // 清SM0/SM1

    SCON |=  (1 << 6);   // SM1=1

    SCON &= ~(1 << 7);   // SM0=0 → 模式1

    SCON |=  (1 << 4);   // REN=1 允许接收

    // ② 配置PCON:SMOD=1 波特率加倍

    PCON &= ~(3 << 6);

    PCON |=  (1 << 7);   // SMOD=1

    // ③ 配置Timer1:模式2(8位自动重装),用于产生波特率

    TMOD &= ~(0x0F << 4);  // 清高4位(Timer1部分)

    TMOD |=  (1 << 5);     // Timer1 M1=1,M0=0 → 模式2

    // ④ 装入初值(2400bps)

    TL1 = 232;             // 初值

    TH1 = 232;             // 自动重装值

    // ⑤ 启动Timer1

    TCON |= (1 << 6);     // TR1=1

    // ⑥ 打开中断

    IE |= (1 << 7);        // EA=1  总开关

    IE |= (1 << 4);        // ES=1  串口中断子开关

}

发送函数

cs 复制代码
// 发送单个字符

void uart_sendchar(char ch)

{

    SBUF = ch;                     // 写入发送缓冲器,自动开始发送

    while ((SCON & (1<<1)) == 0);  // 等待TI=1(发送完成)

    SCON &= ~(1 << 1);             // 清TI

}



// 发送字符串(遇\0结束)

void uart_sendstr(const char *p)

{

    while (*p) uart_sendchar(*p++);

}



// 发送定长缓冲区

void uart_sendbuff(const char *p, int len)

{

    while (len--) uart_sendchar(*p++);

}

接收中断服务函数

cs 复制代码
xdata char recv_buffer[32];  // xdata = 外部RAM,空间更大

unsigned int pos = 0;



void uart_handler(void) interrupt 4  // 串口中断号固定为4

{

    if ((SCON & (1 << 0)) == 1)      // RI=1,收到数据

    {

        if (pos < 32)

        {

            recv_buffer[pos++] = SBUF; // 读SBUF存入缓冲区

            recv_buffer[pos] = 0;      // 末尾填0

        }

    }

    SCON &= ~(1 << 0);               // 手动清RI

}

|----------|--------------------------------------------------------------------|
| SBUF | SBUF 是发送和接收共用的名字,但物理上是两个独立寄存器。写SBUF = 数据 → 触发发送;读 SBUF → 取出接收到的数据。 |

七、Modbus 自定义协议

设计了一套仿 Modbus 的主从通信协议,格式如下:

7.1 数据帧格式(7字节固定长度)

|--------------|--------------|--------------|--------------|--------------|--------------|--------------|
| 主机 → 从机(下行帧) |||||||
| 起始位 0xAA | 地址码 0x01 | 功能码 0x01 | 数据1 0x42 | 数据2 0x00 | 校验码 0xEE | 结束位 0xBB |

|--------------|--------------|--------------|--------------|--------------|--------------|--------------|
| 从机 → 主机(上行应答帧) |||||||
| 起始位 0xAA | 地址码 0x01 | 功能码 0x81 | 数据1 0x42 | 数据2 0x00 | 校验码 0x6E | 结束位 0xBB |

7.2 各字段含义

|-----------|--------------------------------------------|
| 字段 | 说明 |
| 起始位 0xAA | 帧头标志,固定为 0xAA,用于判断帧是否开始 |
| 地址码 | 从机设备地址,本例中 DEV_ADDRESS = 0x01,广播为 0xFF |
| 功能码 | 0x01=控制LED,0x02=控制数码管,0x03=控制蜂鸣器,0x04=温度采集 |
| 数据1 / 数据2 | 功能码的参数,如LED编号、音调编号等 |
| 校验码 | 累加和校验 = (起始位+地址码+功能码+数据1+数据2) 的低8位之和 |
| 结束位 0xBB | 帧尾标志,固定为 0xBB |
| 应答功能码 | 从机应答时,功能码最高位置1(如 0x01 → 0x81),表示数据流向是从机→主 |

7.3 校验码计算示例

下行帧:0xAA + 0x01 + 0x01 + 0x42 + 0x00 = 0xEE ← 这就是校验码

上行帧:0xAA + 0x01 + 0x81 + 0x42 + 0x00 = 0x16E,取低8位 = 0x6E ← 应答校验码
八、main.c 代码逻辑详解

8.1 整体流程

|-----------------|----------------------------------------|
| 步骤 | 说明 |
| uart_init() | 初始化串口,等待主机发来数据 |
| 检测 pos > 0 | 接收中断把数据存入 recv_buffer,pos > 0 说明有数据来了 |
| delay(0x5FFF) | 等待一小段时间,确保一帧数据全部接收完毕 |
| parse() | 解析帧:检查帧头0xAA、帧尾0xBB、地址码、校验码,返回功能码 |
| do_handler(ret) | 根据功能码执行对应操作(点灯/数码管/蜂鸣器) |
| callback() | 构造应答帧,通过 uart_sendbuff 回复主机 |
| pos = 0 | 清空接收缓冲区,准备下一帧 |

8.2 parse() --- 帧解析函数

cs 复制代码
int parse(void)

{

    int sum = 0;

    // 第一步:检查帧头和帧尾

    if (recv_buffer[0]==0xAA && recv_buffer[6]==0xBB)

    {

        // 第二步:检查设备地址

        if (recv_buffer[1] == DEV_ADDRESS)

        {

            // 第三步:计算累加和校验(前5个字节)

            for (i=0; i<5; i++) sum += (unsigned char)recv_buffer[i];

            // 第四步:比较校验码

            if (sum == (unsigned char)recv_buffer[5])

                return (unsigned char)recv_buffer[2]; // 返回功能码

        }

    }

    return 0; // 0 = 解析失败 / 不是本机的帧

}

8.3 do_handler() --- 执行控制

cs 复制代码
void do_handler(unsigned char n)

{

    switch (n)

    {

        case 1:  // 控制LED:recv_buffer[3] 是LED编号

            led_on(recv_buffer[3]);

            break;

        case 2:  // 控制数码管:recv_buffer[3] 是显示数字

            num_to_buff(recv_buffer[3]);

            digiter_show();

            break;

        case 3:  // 控制蜂鸣器音调:1=200Hz 2=400Hz 3=600Hz 4=800Hz

            timer0_init();

            if      (recv_buffer[3]==1) g_i = HZ_200;

            else if (recv_buffer[3]==2) g_i = HZ_400;

            else if (recv_buffer[3]==3) g_i = HZ_600;

            else if (recv_buffer[3]==4) g_i = HZ_800;

            break;

    }

}

8.4 callback() --- 构造应答帧

cs 复制代码
void callback(void)

{

    xdata char send_buffer[10];

    int sum = 0;

    memcpy(send_buffer, recv_buffer, 7);      // 复制接收帧

    send_buffer[2] |= (1 << 7);              // 功能码最高位置1

                                              // 0x01 → 0x81(表示应答)

    // 重新计算校验码(内容变了)

    for (i=0; i<5; i++) sum += send_buffer[i];

    send_buffer[5] = sum;                    // 写入新校验码

    uart_sendbuff(send_buffer, 7);           // 发送应答帧

}

九、关键寄存器速查 --- SCON + PCON

|----------------|------------------------------------|-----------------|
| 寄存器·位 | 代码操作 | 作用 |
| SCON SM0/SM1 | SCON|=(1<<6); SCON&=~(1<<7) | 模式1:8位UART |
| SCON REN (B4) | SCON |= (1<<4) | 允许接收 |
| SCON TI (B1) | while(!(SCON&2)); SCON&=~2 | 等待发送完成后清TI |
| SCON RI (B0) | SCON &= ~(1<<0) | 接收完成,中断中清RI |
| PCON SMOD (B7) | PCON |= (1<<7) | 波特率加倍 |
| TMOD 高4位 | TMOD&=~0xF0; TMOD|=(1<<5) | Timer1模式2(自动重装) |
| TH1 / TL1 | TH1=TL1=232 | 2400bps波特率初值 |
| TCON TR1 (B6) | TCON |= (1<<6) | 启动Timer1 |
| IE EA (B7) | IE |= (1<<7) | 总中断开关 |
| IE ES (B4) | IE |= (1<<4) | 串口中断子开关 |

相关推荐
国科安芯2 小时前
星载电源遥测模块抗辐照RISC-V MCU的性能适配与应用
单片机·嵌入式硬件·无人机·cocos2d·risc-v
F133168929572 小时前
33V降5V,25V降12V,12A,WD5030A
人工智能·单片机·嵌入式硬件·汽车·智能家居
CWM-183125336392 小时前
士模微CM4101:低噪声精密运算放大器完美替代OP27
嵌入式硬件·音视频
三佛科技-134163842122 小时前
HN4004_40V10A 贴片SOP8 N沟道MOSFET场效应管详细分析
嵌入式硬件·物联网·智能家居·pcb工艺
羽获飞2 小时前
从零开始学嵌入式之STM32——26.STM32的通用定时器-生成PWM方波
stm32·单片机·嵌入式硬件
12.=0.3 小时前
【stm32_1】集成开发环境的搭建 + KEIL5使用STM32标准固件库源码建立M4工程模板
stm32·单片机·嵌入式硬件
CodeCraft Studio3 小时前
构高可靠嵌入式软件开发环境:Green Hills嵌入式IDE、编译器与JTAG调试工具全面解析
ide·嵌入式硬件·嵌入式开发·c++编译器·嵌入式开发工具·green hills·jtag调试
我不是程序猿儿3 小时前
【嵌入式】中断(Interrupt)和外部中断(External Interrupt)
单片机·嵌入式硬件
姓刘的哦3 小时前
RK3568之pinctrl子系统和GPIO子系统
单片机·嵌入式硬件