基于STC8G1K08-20单片机,使用IO口P55模拟单总线串口,在22.1184MHz系统时钟下实现19200波特率,采用定时器和中断进行优化可以显著提高时序精度、降低CPU占用并增强通信可靠性。以下是详细的实现思路和优化后的代码。
一、核心优化思路
- 定时器作为精准时基:利用硬件定时器产生精确的位周期中断(52.0833µs),取代软件延时,确保每位采样/发送时刻的准确性,这是提升软件UART稳定性的关键。
- 中断驱动状态机:构建发送和接收状态机,在定时器中断服务程序中推进状态,实现非阻塞式操作,解放主循环。
- 低功耗与可靠性:在空闲时关闭定时器或进入低功耗模式,仅在通信时激活;同时增加帧错误检测和超时机制。
二、定时器配置与计算(22.1184MHz @ 19200bps)
STC8G1K08-20拥有多个定时器(Timer0/1/2)。以16位自动重载模式下的Timer0为例进行计算:
- 系统时钟 :
Fosc = 22.1184 MHz - 定时器时钟 :1T模式,
Ftimer = Fosc = 22.1184 MHz - 期望中断周期 (1位时间):
T_bit = 1 / 19200 ≈ 52.0833 µs - 定时器计数周期数 :
N = T_bit * Ftimer = 52.0833e-6 * 22.1184e6 ≈ 1152 - 定时器初值计算 (16位向上计数,自动重载):
初值 = 65536 - N = 65536 - 1152 = 64384 (0xFB80)
关键点 :22.1184MHz是19200波特率的整数倍,因此N=1152是精确整数,无累积误差,这是实现高精度软件UART的理想条件。
三、优化后完整代码实现
c
/**
* @file soft_uart_timer_isr.c
* @brief STC8G1K08-20 基于定时器中断的软件单总线UART (22.1184MHz @ 19200bps)
* @ref 参考了定时器中断优化、状态机设计和低功耗实践
*/
#include "stc8g.h"
#include "intrins.h"
// 硬件引脚定义
#define UART_PIN P55 // 单总线通信引脚 P5.5
#define UART_PORT P5
#define UART_MASK 0x20 // P5.5 位掩码
// 全局状态与缓冲区
unsigned char xdata uart_tx_buffer[16]; // 发送缓冲区
unsigned char xdata uart_rx_buffer[16]; // 接收缓冲区
volatile unsigned char uart_tx_write_idx = 0; // 发送缓冲区写索引
volatile unsigned char uart_tx_read_idx = 0; // 发送缓冲区读索引
volatile unsigned char uart_rx_write_idx = 0; // 接收缓冲区写索引
volatile unsigned char uart_rx_read_idx = 0; // 接收缓冲区读索引
volatile bit uart_tx_busy = 0; // 发送状态标志
volatile bit uart_rx_ready = 0; // 接收数据就绪标志
volatile unsigned char uart_rx_byte = 0; // 当前接收字节
volatile unsigned char uart_tx_byte = 0; // 当前发送字节
volatile unsigned char uart_bit_count = 0; // 位计数器 (0-10: 起始位+8数据位+停止位)
volatile bit uart_current_mode = 0; // 0:接收模式, 1:发送模式
// --- 系统时钟初始化 (22.1184MHz) ---
void sysclk_init(void) {
// STC8G内部IRC校准到22.1184MHz
P_SW2 |= 0x80; // 使能访问扩展寄存器
IRC24MCR = 0x80; // 使能内部高速IRC
while (!(IRC24MCR & 0x01)); // 等待时钟稳定
CLKDIV = 0x00; // 时钟不分频
P_SW2 &= ~0x80;
}
// --- GPIO初始化 ---
void gpio_init(void) {
// P5.5 初始化为高阻输入(接收模式)
P5M0 &= ~UART_MASK;
P5M1 |= UART_MASK;
UART_PIN = 1; // 内部上拉,确保空闲高电平
}
// --- 定时器0初始化 (16位自动重载,精确位定时) ---
void timer0_init(void) {
AUXR |= 0x80; // Timer0设为1T模式 (22.1184MHz)
TMOD &= 0xF0; // 清除Timer0模式位
TMOD |= 0x00; // 模式0,16位自动重载
// 计算初值:1152个周期对应52.0833µs @ 22.1184MHz
TL0 = 0x80; // 低字节 0xFB80
TH0 = 0xFB; // 高字节
TF0 = 0; // 清除溢出标志
TR0 = 0; // 初始停止定时器
ET0 = 1; // 使能Timer0中断
}
// --- 外部中断0初始化 (用于检测起始位) ---
void exint0_init(void) {
IT0 = 1; // 下降沿触发
EX0 = 1; // 使能INT0中断
// 将INT0映射到P5.5 (需查STC8G手册确认)
// P_SW1 |= 0x40; // 示例值,具体取决于型号
}
// --- 发送一个字节(非阻塞,放入缓冲区)---
bit uart_send_byte(unsigned char dat) {
unsigned char next_idx;
EA = 0; // 关中断保护缓冲区操作
next_idx = (uart_tx_write_idx + 1) % 16;
if (next_idx == uart_tx_read_idx) {
EA = 1; // 缓冲区满
return 0; // 返回失败
}
uart_tx_buffer[uart_tx_write_idx] = dat;
uart_tx_write_idx = next_idx;
// 如果当前不在发送状态,则启动发送
if (!uart_tx_busy) {
uart_start_tx();
}
EA = 1;
return 1; // 成功加入队列
}
// --- 启动发送过程 ---
void uart_start_tx(void) {
if (uart_tx_read_idx != uart_tx_write_idx) {
uart_tx_busy = 1;
uart_current_mode = 1; // 切换到发送模式
// 从缓冲区取出一个字节
uart_tx_byte = uart_tx_buffer[uart_tx_read_idx];
uart_tx_read_idx = (uart_tx_read_idx + 1) % 16;
// 配置引脚为推挽输出
P5M0 |= UART_MASK;
P5M1 &= ~UART_MASK;
// 发送起始位
UART_PIN = 0;
uart_bit_count = 0; // 从起始位开始计数
// 启动定时器
TL0 = 0x80; // 重装初值
TH0 = 0xFB;
TR0 = 1; // 启动定时器
EX0 = 0; // 发送期间禁用接收中断
} else {
uart_tx_busy = 0;
// 发送完成,切换回接收模式
uart_switch_to_rx_mode();
}
}
// --- 切换到接收模式 ---
void uart_switch_to_rx_mode(void) {
uart_current_mode = 0;
gpio_init(); // 重新配置为高阻输入
EX0 = 1; // 使能起始位检测中断
TR0 = 0; // 停止定时器,等待起始位
}
// --- 外部中断0服务程序 (检测起始位) ---
void exint0_isr(void) interrupt 0 {
// 验证起始位,防止噪声误触发
_nop_(); _nop_(); _nop_(); _nop_();
if (UART_PIN != 0) { // 防抖动检查
return;
}
EX0 = 0; // 关闭外部中断,防止重复触发
uart_current_mode = 0; // 进入接收模式
uart_bit_count = 0; // 位计数器清零
uart_rx_byte = 0; // 接收数据清零
// 配置定时器,在半位周期后采样第一位数据位中心
TL0 = 0x80 + (0xFB80 - 0xFB80/2); // 调整为半位周期
TH0 = 0xFB;
TR0 = 1; // 启动定时器进行位采样
}
// --- 定时器0中断服务程序 (核心状态机) ---
void timer0_isr(void) interrupt 1 {
static unsigned char temp_bit;
// 自动重载初值,确保下个周期精确
TL0 = 0x80;
TH0 = 0xFB;
uart_bit_count++;
if (uart_current_mode) { // 发送模式
switch (uart_bit_count) {
case 1: // 起始位结束,发送LSB
UART_PIN = uart_tx_byte & 0x01;
uart_tx_byte >>= 1;
break;
case 2: case 3: case 4: case 5: // 数据位1-4
case 6: case 7: case 8: case 9:
UART_PIN = uart_tx_byte & 0x01;
uart_tx_byte >>= 1;
break;
case 10: // 发送停止位
UART_PIN = 1;
break;
case 11: // 停止位结束,发送完成
TR0 = 0; // 停止定时器
// 检查是否还有数据要发送
if (uart_tx_read_idx != uart_tx_write_idx) {
uart_start_tx(); // 发送下一个字节
} else {
uart_tx_busy = 0;
uart_switch_to_rx_mode(); // 切换回接收
}
break;
}
} else { // 接收模式
switch (uart_bit_count) {
case 1: // 半位周期后,在起始位中心,验证起始位
if (UART_PIN != 0) { // 起始位无效
TR0 = 0;
EX0 = 1; // 重新使能起始位检测
return;
}
break;
case 2: case 3: case 4: case 5: // 数据位1-4采样点
case 6: case 7: case 8: case 9:
temp_bit = UART_PIN;
uart_rx_byte >>= 1;
if (temp_bit) {
uart_rx_byte |= 0x80; // LSB first
}
break;
case 10: // 停止位采样点
if (UART_PIN == 1) { // 有效的停止位
// 数据存入缓冲区
unsigned char next_idx = (uart_rx_write_idx + 1) % 16;
if (next_idx != uart_rx_read_idx) { // 缓冲区未满
uart_rx_buffer[uart_rx_write_idx] = uart_rx_byte;
uart_rx_write_idx = next_idx;
uart_rx_ready = 1; // 设置数据就绪标志
}
} else {
// 帧错误处理,可增加错误计数器
}
TR0 = 0; // 停止定时器
EX0 = 1; // 重新使能起始位检测
break;
default:
// 错误状态,重置
TR0 = 0;
EX0 = 1;
break;
}
}
}
// --- 从接收缓冲区读取一个字节(非阻塞)---
bit uart_receive_byte(unsigned char *pdat) {
bit result = 0;
EA = 0; // 关中断保护
if (uart_rx_read_idx != uart_rx_write_idx) {
*pdat = uart_rx_buffer[uart_rx_read_idx];
uart_rx_read_idx = (uart_rx_read_idx + 1) % 16;
result = 1;
if (uart_rx_read_idx == uart_rx_write_idx) {
uart_rx_ready = 0; // 缓冲区空
}
}
EA = 1;
return result;
}
// --- 主函数 ---
void main(void) {
unsigned char received_data;
sysclk_init(); // 系统时钟初始化
gpio_init(); // GPIO初始化
timer0_init(); // 定时器初始化
exint0_init(); // 外部中断初始化
EA = 1; // 开总中断
// 发送测试字符串
uart_send_byte('H');
uart_send_byte('e');
uart_send_byte('l');
uart_send_byte('l');
uart_send_byte('o');
uart_send_byte('\r');
uart_send_byte('
');
while (1) {
// 非阻塞接收处理
if (uart_receive_byte(&received_data)) {
// 简单的回显功能
uart_send_byte(received_data);
// 应用逻辑处理
if (received_data == 'A') {
// 执行操作A
uart_send_byte('O');
uart_send_byte('K');
uart_send_byte('\r');
uart_send_byte('
');
}
}
// 低功耗优化:当无通信任务时,可进入空闲模式
if (!uart_tx_busy && !uart_rx_ready) {
// PCON |= 0x01; // 进入IDLE模式,由中断唤醒
// _nop_();
}
// 其他后台任务...
}
}
四、优化特性与性能分析
1. 时序精度显著提升
通过硬件定时器产生中断,消除了软件延时循环的不确定性。在22.1184MHz下,1152个时钟周期的定时器中断提供了理论误差为0%的位定时,这是实现稳定19200bps通信的基础。
2. CPU占用率大幅降低
采用中断驱动和非阻塞缓冲区后,主循环仅在收发数据时被短暂中断,大部分时间可执行其他任务或进入低功耗模式。下表对比了优化前后的CPU占用情况:
| 操作模式 | 优化前(轮询) | 优化后(中断+缓冲区) |
|---|---|---|
| 发送1字节 | ~95% CPU(阻塞) | < 5% CPU(仅中断服务) |
| 接收1字节 | 100%轮询检测 | < 5% CPU(中断驱动) |
| 空闲状态 | 持续轮询检测 | 可进入IDLE模式 |
3. 通信可靠性增强
- 起始位验证:在外部中断和定时器中断中双重验证起始位,有效抑制噪声干扰。
- 停止位检查:验证停止位电平,提供简单的帧错误检测。
- 缓冲区机制:环形缓冲区防止数据丢失,支持突发数据传输。
4. 灵活性与可扩展性
- 非阻塞API :
uart_send_byte()和uart_receive_byte()提供友好的应用接口。 - 动态模式切换:自动在发送和接收模式间切换,支持半双工通信。
- 易于调整波特率:只需修改定时器重载值,即可支持其他标准波特率(如9600、38400等)。
五、调试与验证建议
-
示波器验证波形:
c// 发送0x55(01010101)测试波形 void send_test_pattern(void) { while(1) { uart_send_byte(0x55); // 延时足够时间以便观察 for(int i=0; i<30000; i++); } }用示波器测量应得到周期为52.08µs的方波,占空比接近50%。
-
压力测试与错误率统计:
cvoid stress_test(void) { unsigned long tx_count = 0, error_count = 0; unsigned char test_data = 0, received; while(1) { if(uart_send_byte(test_data)) { tx_count++; // 等待回环(硬件连接TX到RX) while(!uart_receive_byte(&received)) { // 超时检测 }