51单片机-串口程序代码

张开发
2026/4/11 12:32:30 15 分钟阅读

分享文章

51单片机-串口程序代码
一、先上代码usart.c/* 89C51 串口通信功能函数库 适合初学者阅读 —— 每一句都有注释 作者示例教学代码 日期2024 【用法示例 - 请先看这里】 #include reg51.h void main(void) { // 第一步初始化串口设置波特率为 9600 UART_Init(); // 第二步发送一个字节例如发送字符 AASCII 0x41 UART_SendByte(A); // 第三步发送一个字符串 UART_SendString(Hello, 89C51!\r\n); // 第四步开启总中断开始接收数据初始化时已开启这里提示一下 // 当串口收到数据时会自动调用中断服务函数 UART_ISR() // 接收到的数据存放在 rx_buffer[] 缓冲区中 // 可以用 rx_count 知道当前缓冲区里有几个字节 // 第五步主循环中检查缓冲区是否有数据 while (1) { if (rx_count 0) // 如果缓冲区里有数据 { unsigned char ch; EA 0; // 关总中断防止读取时被打断 ch rx_buffer[0]; // 取出第一个字节 // 把缓冲区往前移一位简单处理方式 unsigned char i; for (i 0; i rx_count - 1; i) rx_buffer[i] rx_buffer[i 1]; rx_count--; // 计数减一 EA 1; // 重新开总中断 UART_SendByte(ch); // 把收到的字节原样发回去回显 } } } */ #include usart.h /* 包含89C51寄存器定义头文件里面定义了TMOD、SCON等寄存器 */ /* ------------------------------------------------------- 宏定义波特率相关常数 ------------------------------------------------------- 晶振频率89C51 最常见的是 11.0592 MHz 为什么选 11.0592因为用它可以精确产生标准波特率误差为 0% 如果用 12 MHz波特率会有误差通信容易出错。 ------------------------------------------------------- */ #define FOSC 11059200UL /* 晶振频率单位 HzUL 表示无符号长整型 */ #define BAUD_RATE 9600 /* 目标波特率常用值9600、19200、38400 bps */ /* 波特率计算方法重点 ------------------------------------------------ 89C51 串口工作在方式1时使用定时器1产生波特率。 定时器1 工作在方式28位自动重装溢出时产生时钟。 公式 TH1 256 - (FOSC / 12 / 32 / BAUD_RATE) 拆解说明 - FOSC / 12定时器的时钟频率 晶振 ÷ 12每个机器周期12个振荡周期 例11059200 / 12 921600 Hz - / 32 串口方式1每位需要采样32次 例921600 / 32 28800 Hz - / BAUD_RATE计算每个波特需要溢出几次 例28800 / 9600 3 - 256 - 结果得到定时器初值方式2下TH1存的是重装值 例256 - 3 253 0xFD 所以 TH1 0xFD 时波特率恰好是 9600 bps晶振11.0592MHz 如果 SMOD 1电源控制寄存器PCON的最高位波特率加倍 TH1 256 - (FOSC / 12 / 16 / BAUD_RATE) ------------------------------------------------ */ #define TH1_VALUE (256 - (FOSC / 12 / 32 / BAUD_RATE)) /* 上面这行用宏自动计算 TH1 的值这样修改 BAUD_RATE 就能自动更新 */ /* ------------------------------------------------------- 接收缓冲区定义 ------------------------------------------------------- */ #define RX_BUF_SIZE 10 /* 缓冲区大小最多存32个字节可根据需要修改 */ unsigned char rx_buffer[RX_BUF_SIZE]; /* 接收数据缓冲区数组 */ unsigned char rx_count 0; /* 当前缓冲区中已存数据的字节数初始为0 */ /* 函数UART_Init 功能初始化89C51串口 参数无 返回无 */ void UART_Init(void) { /* --- 第1步配置定时器1用于产生波特率 --- */ SCON 0x50; TMOD 0x0F; /* 先清除 TMOD 高4位定时器1的配置位保留低4位定时器0 0x0F 0000 1111与运算后 TMOD 高4位变0 */ TMOD | 0x20; /* 设置定时器1 为 方式28位自动重装模式 0x20 0010 0000即 M11, M00表示方式2 方式2优点溢出后自动从TH1重装到TL1不需要软件手动重装 */ TH1 TH1_VALUE; /* 设置重装值溢出后自动装入TL1的值 根据波特率公式计算9600bps时 0xFD 253 */ TL1 TH1_VALUE; /* 设置初始值让定时器从这个值开始计数 第一次溢出时间和以后一样保证第一个波特率周期正确 */ TR1 1; /* 启动定时器1开始计数产生波特率 TR1 是 TCON 寄存器中的位1 表示运行 */ /* --- 第2步配置串口控制寄存器 SCON --- */ // SCON 0x50; /* 0x50 0101 0000逐位说明 位7 SM0 0 \ 位6 SM1 1 / SM00,SM11串口工作方式110位异步 方式11位起始位 8位数据 1位停止位 位5 SM2 0 多机通信位单机通信置0 位4 REN 1 允许接收必须置1才能接收数据 位3 TB8 0 方式1不用置0 位2 RB8 0 方式1不用置0 位1 TI 0 发送中断标志初始清0 位0 RI 0 接收中断标志初始清0 */ /* --- 第3步开启中断 --- */ ES 1; /* 开串口中断Enable Serial port interrupt ES 是 IE 寄存器中的位1 允许串口产生中断 */ EA 1; /* 开总中断Enable All interrupts EA 必须1各个中断才能真正起作用 如果 EA0即使 ES1 也不会触发中断 */ } /* 函数UART_SendByte 功能通过串口发送一个字节的数据 参数dat - 要发送的字节数据0x00 ~ 0xFF 返回无 发送流程把数据写入SBUF硬件自动发送等待TI1表示发完 */ void UART_SendByte(unsigned char dat) { unsigned char t 0; // TI 0; /* 清除发送中断标志 // TI1 表示上一次发送完成了这里先清0准备新的发送 */ SBUF dat; /* 把数据写入串口发送缓冲寄存器 SBUF 写入后硬件自动将数据转换成串行信号发送出去 89C51 中 SBUF 发送和接收共用地址但物理上是两个不同的寄存器 */ while (!TI) { t; if(t200) break; } /* 等待发送完成 发送过程中 TI0发完一个字节后硬件自动将 TI 置1 while(!TI) 就是只要 TI0还没发完就一直等待 注意这是查询等待方式CPU会在这里空转直到发完 */ TI 0; /* 发送完成后手动清除 TI 标志 因为这里没有用发送中断所以要手动清TI 如果不清下次调用此函数时可能出错 */ } /* 函数UART_SendString 功能通过串口发送一个字符串以\0结尾的C字符串 参数str - 指向字符串的指针字符串必须以\0结尾 返回无 示例UART_SendString(Hello!\r\n); \r\n 是回车换行串口调试助手中会换行显示 */ void UART_SendString(unsigned char *str) { while (*str ! \0) /* 循环条件当前字符不是字符串结束符\0 *str 表示取指针str指向的字符 字符串Hello在内存中是 H,e,l,l,o,\0 */ { UART_SendByte(*str); /* 发送当前字符调用上面的发送字节函数 */ str; /* 指针移向下一个字符 str 使 str 指向字符串的下一个位置 */ } /* 退出循环时*str \0字符串发送完毕 */ } /* 中断服务函数UART_ISR串口中断服务程序 功能当串口收到数据时自动执行将数据存入缓冲区 触发条件RI1收到数据或 TI1发送完成都会触发串口中断 中断号 4 串口中断89C51固定分配 中断优先级可在 IP 寄存器中设置这里使用默认低优先级 注意中断函数不能有参数不能有返回值 */ void UART_ISR(void) interrupt 4 { /* --- 处理接收中断 --- */ if (RI 1) /* 判断是否是接收完成中断 RI1 表示串口接收到了一个完整字节 如果是TI1则跳过我们不处理发送中断 */ { RI 0; /* 立即清除接收中断标志 必须用软件手动清零否则会不断触发中断 这是初学者最容易忘记的一步 */ if (rx_count RX_BUF_SIZE) /* 检查缓冲区是否已满 如果满了就丢弃新数据防止数组越界 */ { rx_buffer[rx_count] SBUF; /* 从接收缓冲寄存器SBUF读出数据 存入接收缓冲区的下一个空位 rx_buffer[0]是第1个收到的字节 rx_buffer[1]是第2个以此类推 */ rx_count; /* 缓冲区数据计数加1 */ } UART_SendByte(SBUF); //发送收到的数据 /* 如果 rx_count RX_BUF_SIZE说明缓冲区满了新数据被丢弃 实际项目中可以设置一个溢出标志位来通知主程序 */ } /* --- 处理发送中断这里不需要处理忽略即可--- */ if (TI 1) /* 如果是发送完成中断 */ { TI 0; /* 清除发送中断标志防止反复进入中断 */ /* 在本例中发送使用查询方式在SendByte中等待TI 所以发送中断不需要额外处理直接清标志退出即可 */ } } void rx_buffer_clear(void) { rx_count 0; rx_buffer[0] \0; } /* 【补充说明 - 常见问题】 Q1: 为什么串口通信会乱码 A: 1. 检查两端波特率是否一致 2. 检查晶振是否是 11.0592 MHz其他晶振会有误差 3. 检查 TH1 值是否正确 Q2: 发送函数卡住不动死在while(!TI) A: 检查 TR1 是否启动检查 TI 是否被意外清零 Q3: 接收不到数据 A: 1. 检查 REN1允许接收位 2. 检查 EA1 且 ES1总中断和串口中断都开了 3. 检查 RI 是否在中断里清零了 Q4: 缓冲区总是满 A: 主程序处理数据的速度太慢增大 RX_BUF_SIZE 或加快处理 【波特率与 TH1 对照表晶振 11.0592 MHzSMOD0】 波特率 TH1值十进制 TH1值十六进制 1200 bps 230 0xE6 2400 bps 244 0xF4 4800 bps 250 0xFA 9600 bps 253 0xFD 19200 bps 253 (SMOD1) 0xFD需要设置PCON | 0x80 38400 bps 255 (SMOD1) 0xFF误差较大不推荐 */usart.h#ifndef __USART_H__ #define __USART_H__ #include reg52.h void UART_Init(void); void UART_SendByte(unsigned char dat); void UART_SendString(char *str); void rx_buffer_clear(void); #endif二、硬件连接说明TX RX三、代码用法UART_Init(); UART_SendByte(K); UART_SendString(while\r\n);

更多文章