找回密码
 立即注册
查看: 58|回复: 1

掉电唤醒使用高波特率(115200bps)唤醒,第2个字节正确接受内容

[复制链接]
  • 打卡等级:以坛为家III
  • 打卡总天数:747
  • 最近打卡:2026-07-22 07:47:21
已绑定手机
已实名认证

139

主题

3695

回帖

9515

积分

超级版主

积分
9515
发表于 7 天前 | 显示全部楼层 |阅读模式

串口唤醒省电模式——高波特率下可靠接收数据说明

STC32G 系列单片机支持通过 普通 I/O 口中断 从掉电模式(STOP)唤醒 MCU。将 RXD(P3.0)配置为下降沿中断 + 唤醒使能后,串口数据到来时 MCU 即可被唤醒并接收数据。但这个流程在较高波特率时会出问题:唤醒后直接开串口接收,前几个 bit 的采样点会偏移到相邻 bit 上,导致数据错位。

为什么高速率下会错位

UART 接收器内部靠一个 16 倍波特率的过采样时钟来定位每个 bit 的中心采样点。这个采样时钟由波特率定时器的溢出脉冲分频产生。正常工作时,起始位的下降沿会使接收器内部的计数器复位,保证 8 个数据 bit 的采样点依次对准各自中心。

问题出在省电模式唤醒这个场景。MCU 进入掉电模式后主时钟停振,用于产生波特率的定时器也随之停止。唤醒时 IRCDB 寄存器会控制等待一段时间,让内部高速 IRC 频率恢复标称值后再让 CPU 开始运行。此时频率已经正确,但波特率定时器重新启动后的第一个溢出脉冲和输入数据的 bit 边界之间的相位是随机的——定时器停在了什么位置、重新启动后第一个溢出何时到来,与 RXD 线上此刻在传哪个 bit 没有任何同步关系。

这个随机相位偏移一旦产生,会固定地影响后续所有采样点。偏移量本身是一个固定的绝对时间(取决于定时器启动瞬间的计数值),这个绝对时间在不同波特率下占一个 bit 的比例不同:

波特率 每个 bit 内定时器溢出次数 相位偏移占 bit 宽度的比例 实际效果
2400 5000 次 极小 采样点仍在 bit 中心附近,数据正确
9600 1250 次 较小 部分 bit 的采样点可能落入边界判决区
115200 104 次 显著 采样点可能越过 bit 边界读到相邻 bit
230400 52 次 显著 以 0xFF 为唤醒字节时可正常工作
460800 26 次 严重 唤醒后首有效字节丢失

实测结果与此一致:2400bps 下首字节完全正常,9600bps 开始出现错位。

实测数据(STC32G144K246 @48MHz,下降沿 I/O 中断唤醒):

波特率 发送数据 接收数据 首字节正确?
2400 00 50 A5 23 32 00 50 A5 23 32 ✓ 是
9600 00 50 A5 23 32 前 2 字节内容错误 ✗ 否
115200 00 50 A5 23 32 7D E9 23 32 ✗ 否
230400 00 50 A5 23 32 0xFF唤醒正常 ✗ 否(0xFF唤醒可)
460800 00 50 A5 23 32 7D E9 23 32 ✗ 否

:9600bps 下一个 bit 约 104μs,每个 bit 内定时器溢出 1250 次。若相位偏差几十个计数周期,占比不到 5%,但已足以让某些 bit 的采样点落入边界附近,导致误判。

处理方法:放弃第一个字节,用它换同步时间

解决问题的思路很直接——既然唤醒后定时器重启的相位不可控,那就不给它机会出错:让出第一个字节的时间不做接收,等这个字节完整传完、RXD 回到空闲高电平后,再启动波特率时钟和接收器。此时接收器从空闲状态开始监视 RXD,下一个字节的起始位下降沿能触发完整且正确的位同步。

具体流程:

  1. 进入休眠前:关掉波特率时钟(T2R = 0)。注意仅关 REN 是不够的——REN=0 只是不让收到的数据写入 SBUF、不产生 RI 中断,UART 内部该检测起始位还是检测、该移位接收还是移位,状态机一直在跑。必须停掉波特率时钟才能让接收器彻底停下来。
  2. 下降沿唤醒:RXD 上第一个字节的起始位(下降沿)触发 P3 中断,MCU 从 STOP 模式唤醒。
  3. 等待停止位:ISR 中执行 while(P30==0),持续检测 RXD 电平。这个循环会一直等到 P3.0 变高——也就是第一个字节的停止位到达。无论唤醒字节是 0x00(连续 9 bit 低电平)还是 0xFF(仅起始位为低),等待时间都足够让 HIRC 和定时器充分稳定。
  4. 停止位到达:P3.0 恢复高电平,RXD 处于空闲状态。此时 ISR 退出等待。
  5. 开启接收:依次写入定时器重载值、启动波特率时钟、加若干 NOP 让时钟跑几个周期、清除 RI、置位 REN。由于 RXD 当前为空闲高电平,UART 接收器能正确地检测到下一个字节的起始位,完成位同步。
  6. 唤醒字节被丢弃:第一个字节已完整经过,不进入接收缓冲区。

时序关系描述:

唤醒字节在 RXD 线上依次呈现为:起始位(低)→ 8 个数据位(LSB 在前)→ 停止位(高)。起始位的下降沿触发 P3 中断,MCU 开始唤醒。ISR 中的 while(P30==0) 循环持续检测 RXD,直到停止位的高电平出现——此时第一个字节已完整结束,RXD 回到空闲高电平。ISR 退出等待,装入定时器重载值、启动波特率时钟、加 NOP 延时、清 RI、置 REN。UART 接收器从空闲高电平开始监视 RXD,下一个字节的起始位下降沿将触发完整的位同步过程,该字节及其后的所有字节均能正确接收。

波特率选型与功耗考虑

本方案需要额外丢弃一个唤醒字节,这引发了一个直观的疑问:用低速波特率(如 9600)直接接收不就不用丢字节了吗?

实际情况是:高波特率下虽然多丢一个字节,但整体通讯时间大幅缩短,MCU 能更快返回休眠,平均功耗反而更低。

以接收 32 字节有效数据为例:

方案 需传输总量 总耗时 相对耗时
9600 直收(不丢字节) 32 字节 32×10×104μs =33.3ms 100%(基准)
115200(丢 1 唤醒字节) 33 字节 33×10×8.68μs =2.87ms 8.6%
230400(丢 1 唤醒字节) 33 字节 33×10×4.34μs =1.43ms 4.3%

结论:115200 方案的总耗时仅为 9600 的 8.6%。MCU 在唤醒状态下的工作电流(mA 级)持续时间越短,平均电流就越低。在间歇工作场景下,高波特率+丢弃唤醒字节方案的综合功耗显著优于低速直收方案。

定长接收说明

STC32G12K128 系列没有串口超时中断功能,因此需要兼容该型号时必须使用定长接收(收满指定字节数后设置完成标志),不能依赖超时中断判断帧结束。

参考代码(定长接收·115200bps)

以下代码基于 STC32G 数据手册 §9.7.7(使用 RxD 管脚中断唤醒省电模式)的官方例程框架,添加了 I/O 下降沿唤醒 + 主动丢弃唤醒字节的处理。已验证通过 STC32G144K246 @48MHz。

/*******************************
 * STC32G 省电模式串口唤醒参考程序
 * IO 中断唤醒 + 丢弃首字节
 * 115200bps, 4字节定长接收, 纯寄存器操作
 *******************************/
#include "stc32g144k246.h"
#include "intrins.h"

#undef main

#define FOSC 48000000UL    /* 主频 48MHz */
#define BRT  115200UL      /* 波特率 */
#define T2RL (unsigned int)(65536UL - (FOSC / BRT + 2UL) / 4UL)

volatile bit fRdy;               /* 收到完整帧标志 */
volatile unsigned char cnt;      /* 接收字节计数器 */
unsigned char xdata buf[4];      /* 接收缓冲区 */
volatile bit fBusy;              /* 发送忙标志 */

/* 发送一个字节 */
void SendByte(unsigned char d)
{
    while (fBusy);
    fBusy = 1; SBUF = d;
}

/* 发送字符串 */
void SendStr(char *s)
{
    while (*s) SendByte((unsigned char)(*s++));
}

/* 简单延时 */
void Delay(unsigned int ms)
{
    unsigned int i;
    do { i = FOSC / 6000; while (--i); } while (--ms);
}

void main(void)
{
    unsigned char i;

    /* === 系统初始化 === */
    P_SW2 |= 0x80;          /* 允许访问 XFR 寄存器 */
    CKCON = 0; WTST = 0;    /* 最高速度模式: 程序存储器无等待 */

    /* 所有 IO 口设为准双向模式 */
    P0M0 = 0; P0M1 = 0; P1M0 = 0; P1M1 = 0;
    P2M0 = 0; P2M1 = 0; P3M0 = 0; P3M1 = 0;

    IRCDB = 0x10;           /* 内部 IRC 稳定等待时间 */

    /* P3.0 配置为下降沿中断模式(暂不使能中断与唤醒,避免上电误触发)*/
    P3NCS &= ~0xFF;         /* 施密特触发输入 */
    P3IM0 = 0;              /* 下降沿中断模式 */

    /* === UART1 初始化:定时器2做波特率发生器 === */
    SCON = 0x50;            /* 模式1: 8位UART, 允许接收 */
    AUXR = (AUXR & ~0x0F) | 0x05;  /* T2x12=1(1T模式), S1BRT=1(选T2做波特率) */
    TM2PS = 0;              /* 预分频=0 */
    T2H = (unsigned char)(T2RL >> 8);   /* 装载重载值高8位 */
    T2L = (unsigned char)(T2RL);        /* 装载重载值低8位 */
    T2R = 1;                /* 启动定时器2,产生波特率时钟 */
    REN = 1;                /* 允许接收 */
    ES = 1;                 /* 使能串口中断 */
    TI = 0; RI = 0;         /* 清除中断标志 */
    fBusy = 0; fRdy = 0; cnt = 0;

    EA = 1;                 /* 开总中断 */
    Delay(10);              /* 等待串口稳定 */
    SendStr("STC32G LP Wakeup OK\r\n");

    while (1)
    {
        if (fRdy)
        {
            fRdy = 0;
            if (buf[0] == 0x55)
            {
                /* 收到休眠指令 0x55:进入掉电模式 */
                SendStr(">sleep\r\n");
                Delay(5);

                /* 关闭串口,准备休眠 */
                T2R = 0; REN = 0;

                /* 使能 P3.0 下降沿中断 + 掉电唤醒功能 */
                P3INTF = 0;         /* 清除残留中断标志 */
                P3WKUE = 0x01;      /* 使能 P3.0 掉电唤醒 */
                P3INTE = 0x01;      /* 使能 P3.0 中断 */
                EA = 1;

                /* 进入掉电模式 STOP */
                PCON |= 0x02;
                NOP(4);

                /* 从掉电模式醒来后,P3 中断已被 ISR 清除使能 */
                fRdy = 0; cnt = 0; ES = 1;
            }
            else
            {
                /* 普通数据:回显收到的4个字节 */
                for (i = 0; i < 4; i++) SendByte(buf[i]);
            }
        }
    }
}

/* 串口1中断服务函数 (中断号 4) */
void UART1_ISR(void) interrupt 4
{
    if (TI) { TI = 0; fBusy = 0; }
    if (RI)
    {
        RI = 0;
        buf[cnt++] = SBUF;
        if (cnt >= 4) { cnt = 0; fRdy = 1; }
    }
}

/* P3 口中断服务函数 (中断号 40) */
void PORT3_ISR(void) interrupt 40
{
    unsigned char f = P3INTF;
    P3INTF = 0;
    if (f & 0x01)
    {
        /* 等待唤醒字节的停止位结束 */
        while (P30 == 0);

        /* 关闭 P3 中断,重新启动串口 */
        P3INTE &= ~0x01;
        P3INTF = 0;
        T2H = (unsigned char)(T2RL >> 8);
        T2L = (unsigned char)(T2RL);
        T2R = 1; RI = 0; REN = 1;
    }
}

使用说明:

  • 休眠指令:发送 55 xx xx xx(4 字节,首字节 0x55),MCU 收到后进入掉电模式
  • 唤醒方式:发送任意 5 字节(FF + 4字节数据),首字节 0xFF 用于唤醒并自动丢弃,后续 4 字节被正常接收并回显
  • 波特率切换:修改 BRT 宏定义和对应的 T2RL 值即可

实测结果:

波特率 发送 接收 结论
115200 FF A5 5A 00 FF A5 5A 00 FF ✅ 唤醒+回显正确
230400 FF A5 5A 00 FF A5 5A 00 FF ✅ 唤醒+回显正确

回复

使用道具 举报 送花

  • 打卡等级:以坛为家III
  • 打卡总天数:747
  • 最近打卡:2026-07-22 07:47:21
已绑定手机
已实名认证

139

主题

3695

回帖

9515

积分

超级版主

积分
9515
发表于 7 天前 | 显示全部楼层
对应的测试通过程序:
STC32G_LP_Wakeup_115200.zip (35.53 KB, 下载次数: 0) STC32G144K246@48Mhz下测试通过
截图202607151755593579.jpg

回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-7-22 07:52 , Processed in 0.071319 second(s), 51 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表