定时器+中断优化单总线通信

基于STC8G1K08-20单片机,使用IO口P55模拟单总线串口,在22.1184MHz系统时钟下实现19200波特率,采用定时器和中断进行优化可以显著提高时序精度、降低CPU占用并增强通信可靠性。以下是详细的实现思路和优化后的代码。

一、核心优化思路

  1. 定时器作为精准时基:利用硬件定时器产生精确的位周期中断(52.0833µs),取代软件延时,确保每位采样/发送时刻的准确性,这是提升软件UART稳定性的关键。
  2. 中断驱动状态机:构建发送和接收状态机,在定时器中断服务程序中推进状态,实现非阻塞式操作,解放主循环。
  3. 低功耗与可靠性:在空闲时关闭定时器或进入低功耗模式,仅在通信时激活;同时增加帧错误检测和超时机制。

二、定时器配置与计算(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. 灵活性与可扩展性
  • 非阻塞APIuart_send_byte()uart_receive_byte()提供友好的应用接口。
  • 动态模式切换:自动在发送和接收模式间切换,支持半双工通信。
  • 易于调整波特率:只需修改定时器重载值,即可支持其他标准波特率(如9600、38400等)。

五、调试与验证建议

  1. 示波器验证波形

    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%。

  2. 压力测试与错误率统计

    c 复制代码
    void 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)) {
                    // 超时检测
                }

参考来源

相关推荐
cici158741 小时前
STM32 + VS1003/VS1053 MP3播放器SD卡读取程序
stm32·单片机·嵌入式硬件
念一不念二2 小时前
[SSD]SSD主控
嵌入式硬件
xiangw@GZ2 小时前
DDR3 颗粒信号定义解析
单片机·嵌入式硬件
小+不通文墨2 小时前
在树莓派中部署emqx
经验分享·笔记·单片机·学习
Deitymoon2 小时前
STM32——oled显示字符串和数字
stm32·单片机·嵌入式硬件
深圳市晨芯阳科技有限公司3 小时前
带延时功能的电压检测系列晨芯阳HC809
单片机·嵌入式硬件·电源芯片·深圳市晨芯阳科技有限公司
xiangw@GZ3 小时前
DDR2 / DDR3 / DDR4 颗粒信号差异对照表
单片机·嵌入式硬件
科芯创展3 小时前
1A,60VIN,1MHz,XZ4116,降压恒流LED驱动芯片 输入电压:5V-60V
stm32·单片机·嵌入式硬件
振浩微433射频芯片4 小时前
工业环境下的“硬核”选择:如何科学评估国产433芯片的可靠性?
网络·人工智能·科技·单片机·物联网·学习