好的,手册数据已获取。下面我为您完整解答。
一、4800bps @ 12MHz 的波特率误差计算
根据 STC89C52RC 手册"串行通信中波特率的设置"章节,STC89C52RC 默认工作在 12T模式(传统8051模式),定时器1方式2(8位自动重装)作为波特率发生器时:
公式:
方式1、3波特率 = 2SMOD / 32 × (定时器/计数器1的溢出率)
12T模式下,定时器1溢出率 = SYSclk / [12 × (256 − N)]
您的配置: SMOD=1(PCON|=0x80),N=0xF3=243,SYSclk=12MHz
计算过程:
溢出率 = 12,000,000 / [12 × (256 − 243)]
= 12,000,000 / (12 × 13)
= 12,000,000 / 156
≈ 76,923.08 Hz
实际波特率 = 2^1 / 32 × 76,923.08
= 2 / 32 × 76,923.08
= 1/16 × 76,923.08
≈ 4807.69 bps
理论目标波特率 = 4800 bps
误差 = (4807.69 − 4800) / 4800 × 100%
≈ +0.16%
结论:误差约为 +0.16%。
这个误差值在 理论上是可接受的(串口异步通信通常要求误差 < 2%),但实际使用中仍可能出问题,原因在于:
- 12MHz 无法整数分频出 4800,0.16% 是理论计算值,实际晶振本身还有制造误差
- PC 端 USB 转串口芯片(如 CH340、PL2303)的波特率也是近似值,双方误差叠加后可能超过容限
- 手册中 4800bps 的标准推荐配置是 11.059MHz + SMOD=0 + 重装值 0xFA,12MHz 不在推荐列表中
建议: 如果坚持用 12MHz,改用 2400bps(SMOD=1,重装值 0xE6)误差更小,或直接换 11.0592MHz 晶振。
二、串口回显程序(收到1个数+1后发回)
以下是完整的单文件 C 程序,实现 串口中断接收,收到一个字节后加1再发送回去:
/*****************************************************************************
* 文件名: main.c
* 描 述: STC89C52RC 串口通信——收到1个数+1后发回
* 硬件连接: P3.0(RxD) — USB转串口模块的TxD
* P3.1(TxD) — USB转串口模块的RxD
* 晶 振: 12.000MHz
* 波特率: 4800bps (SMOD=1, 定时器1方式2, 重装值0xF3)
* 编译环境: Keil C51
*
* 操作说明:
* 1. 用 STC-ISP 软件下载程序到单片机
* 2. 打开串口助手,波特率设为4800
* 3. 发送任意1个字节,单片机收到后+1再发回
* 4. 例如:发送 0x01 → 收到 0x02;发送 0x66 → 收到 0x67
*****************************************************************************/
#include <REGX52.h>
/* 接收缓冲区 */
volatile unsigned char rx_data; // 存放接收到的数据
volatile unsigned char rx_flag; // 接收完成标志,1=已收到新数据
/*****************************************************************************
* 函数名: UART_Init
* 功 能: 串口初始化,4800bps@12.000MHz
* 参 数: 无
* 返回值: 无
*****************************************************************************/
void UART_Init(void)
{
SCON = 0x50; // 方式1(8位UART),REN=1允许接收
PCON |= 0x80; // SMOD=1,波特率加倍
TMOD &= 0x0F; // 清除定时器1的模式位
TMOD |= 0x20; // 定时器1,方式2(8位自动重装)
TH1 = 0xF3; // 4800bps 重装值 (12MHz, SMOD=1)
TL1 = 0xF3;
ET1 = 0; // 禁止定时器1中断(波特率发生器不需要)
ES = 1; // 使能串口中断
EA = 1; // 开启总中断
TR1 = 1; // 启动定时器1
}
/*****************************************************************************
* 函数名: UART_SendByte
* 功 能: 串口发送1个字节(查询方式)
* 参 数: dat — 要发送的数据
* 返回值: 无
*****************************************************************************/
void UART_SendByte(unsigned char dat)
{
SBUF = dat; // 将数据写入发送缓冲器,启动发送
while (TI == 0); // 等待发送完成(TI被硬件置1)
TI = 0; // 软件清除TI标志
}
/*****************************************************************************
* 函数名: UART_ISR
* 功 能: 串口中断服务函数
* 说 明: 收到数据后存入rx_data,置位rx_flag
* 中断服务程序尽量简洁,数据处理放在主循环中
*****************************************************************************/
void UART_ISR(void) interrupt 4
{
if (RI == 1) // 接收中断
{
RI = 0; // 清除接收中断标志
rx_data = SBUF; // 读取接收到的数据
rx_flag = 1; // 置位接收标志
}
/* TI发送中断未使用,因为发送采用查询方式 */
}
/*****************************************************************************
* 函数名: main
* 功 能: 主函数,初始化串口后循环处理接收数据
* 参 数: 无
* 返回值: 无
*****************************************************************************/
void main(void)
{
unsigned char temp;
rx_flag = 0; // 初始化接收标志
UART_Init(); // 初始化串口
while (1)
{
if (rx_flag == 1) // 检查是否收到新数据
{
rx_flag = 0; // 清除标志
temp = rx_data; // 读取收到的数据
temp = temp + 1;// 数据+1
UART_SendByte(temp); // 发送回去
}
}
}
程序说明
| 项目 |
说明 |
| 波特率 |
4800bps @ 12.000MHz,SMOD=1,重装值 0xF3 |
| 接收方式 |
中断接收,不占用CPU轮询 |
| 发送方式 |
查询发送,简单可靠 |
| 回显逻辑 |
收到1字节 → 该字节+1 → 发回 |
| 中断服务 |
仅做最简操作(读SBUF、清标志),数据处理在主循环中完成 |
测试方法
- 用 STC-ISP 下载软件 将程序烧录到 STC89C52RC
- 打开串口助手,设置:波特率 4800,数据位 8,停止位 1,无校验
- 发送 0x01 → 应收到 0x02
- 发送 0x66 → 应收到 0x67
- 发送 0xFF → 应收到 0x00(0xFF+1=0x100,溢出后为0x00)
如果仍然只能收到 0x00
请检查以下两点:
- 晶振频率:确认板子上是 12.000MHz 还是 11.0592MHz?如果是 11.0592MHz,请将重装值改为 0xFA,并将
PCON |= 0x80; 改为 PCON &= 0x7F;(SMOD=0)
- 串口助手波特率:确认串口助手的波特率设置与单片机一致(4800)
- 接线:确认 P3.0(RxD) 接 USB转串口的 TxD,P3.1(TxD) 接 RxD,交叉连接
🔍 搜索 "STC89C52RC 12MHz 时钟 串口通信"
⏱️ 查找用时 3.1s | 总回答用时 21.3s