UART自动波特率识别程序设计方案

串口(uart)是嵌入式里最基础最常用也最简单的一种通讯(数据传输)方式,可以说是工程师入门通讯领域的启蒙老师,同时串口打印也是嵌入式项目里非常经典的调试与交互方式。
最精简的串口仅使用两根单向信号线:txd、rxd,这两根信号线是独立工作的,因此数据收发既可分开也可同时进行,这就是所谓的全双工。串口没有主从机概念,并且没有专门的时钟信号 sck,所以串口通信也属于异步传输。
说到异步传输,这就不得不提波特率(每秒钟传输bit数)的问题了,通信双方必须使用一致的波特率才能完成正确的数据传输。正常情况下,我们都是为两个串口设备事先约定好波特率,比如 mcu 与上位机通信,在 mcu 程序里按 115200 的波特率去初始化 uart 外设,然后上位机串口调试助手也设置 115200 波特率,双方再联合工作。
有时候,我们也希望能有一种灵活的波特率约定方式,比如建立通信前,在上位机串口调试助手里随意设置一种波特率,然后按这个波特率发送数据,mcu 端能自动识别出这个波特率,并用识别出来的波特率去初始化 uart 外设,然后再进行后续数据传输,这种方式就叫自动波特率识别。痞子衡今天要分享的就是在 mcu 里实现自动波特率识别的程序设计:
程序主页:https://github.com/jayheng/cortex-m-apps/tree/master/components/autobaud
一、串口(uart)自动波特率识别程序设计
1.1 函数接口定义
首先是设计自动波特率识别程序头文件:autobaud.h ,这个头文件里直接定义如下 3 个接口函数原型。涵盖必备的初始化流程 init()、deinit(),以及最核心的波特率识别功能 get_rate()。
//! @brief 初始化波特率识别
void
autobaud_init
(
void
);
//! @brief 检测波特率识别是否已完成,并获取波特率值
bool
autobaud_get_rate
(
uint32_t
*rate);
//! @brief 关闭波特率识别
void
autobaud_deinit
(
void
);
1.2 识别设计思想
关于识别,因为上位机数据是从 rxd 引脚过来的,所以在 mcu 里需要先将 rxd 引脚配置成普通数字输入 gpio(这个引脚需要上拉,默认保持高电平),然后检测这个 gpio 的电平跳变(一般用下降沿)并计时。
下图是典型的 uart 单字节传输时序,i/o 空闲状态是高电平,传输时总是由 1bit 低电平起始位开启,然后是从 lsb 到 msb 的 8bit 数据位,校验位是可选项(我们暂不开启),最后由 1bit 高电平停止位结束,i/o 回归高电平空闲状态。
note 1:检测下降沿跳变,是因为 i/o 空闲为高,起始位的存在保证了每 byte 传输周期总是从下降沿开始。
note 2:起始位和停止位两个 bit 的存在还兼有波特率容错的功能,通信双方波特率在 3% 的误差内数据传输均可以正常进行。
虽然我们不需要约定上位机波特率,但是要想实现波特率自动识别,上位机初始传输的数据却必须要事先约定好(可理解为接头暗号),这涉及到 mcu 里检测电平跳变次数与相应计时计算。mcu识别完成后将暗号发回给上位机确认。
痞子衡设计的接头暗号是 0x5a, 0xa6 两个字节,两字节暗号相比单字节暗号容错性更好一些(以防 i/o 上有干扰,导致误识别),根据指定的暗号和 uart 传输时序图,我们很容易得到如下常量定义:
enum
_autobaud_counts
{
//! 0x5a 字节对应的下降沿个数
kfirstbyterequiredfallingedges = 
4
,
//! 0xa6 字节对应的下降沿个数
ksecondbyterequiredfallingedges = 
3
,
//! 0x5a 字节(从起始位到停止位)第一个下降沿到最后一个下降沿之间的实际bit数
knumberofbitsforfirstbytemeasured = 
8
,
//! 0xa6 字节(从起始位到停止位)第一个下降沿到最后一个下降沿之间的实际bit数
knumberofbitsforsecondbytemeasured = 
7
,
//! 两个下降沿之间允许的最大超时(us)
kmaximumtimebetweenfallingedges = 
80000
,
//! 对实际检测出的波特率值做对齐处理,以便于更好地配置uart模块
kautobaudstepsize = 
1200
};
上述常量定义里,kmaximumtimebetweenfallingedges 指定了两个下降沿之间允许的最大时间间隔,超过这个时间,自动波特率程序将丢掉前面统计的下降沿个数,重头开始识别,这个设计也是为了防止 i/o 上有电平干扰,导致误识别。
kautobaudstepsize 常量是为了对检测出的波特率值做对齐处理,公式是 rounded = stepsize * (value/stepsize + 0.5),其中 value 是实际检测出的波特率值,rounded 是对齐后的波特率值,用对齐后的波特率值能更好地配置uart外设(这跟uart模块里波特率发生器sbr设计有关)。
最后就是 i/o 电平下降沿检测方法设计,这里既可以用软件查询(就是循环读取 i/o 输入电平,比较当前值与上一次值的差异),也可以使用gpio模块自带的边沿中断功能。推荐使用后者,一方面计时更精确,另外也不用阻塞系统。检测到下降沿发生就调用一次如下 pin_transition_callback() 函数,在这个函数里统计跳变次数以及计时。
//! @brief 管脚下降沿跳变回调函数
static
void
pin_transition_callback
(
void
);
1.3 主代码实现
根据上一小节描述的设计思想,我们很容易写出下面的主代码(autobaud_irq.c),代码里痞子衡都做了详细注释。有一点要提的是关于其中系统计时。
//! @brief 使能gpio管脚中断
extern
void
enable_autobaud_pin_irq
(
pin_irq_callback_t
func);
//! @brief 关闭gpio管脚中断
extern
void
disable_autobaud_pin_irq
(
void
);
//!< 已检测到的下降沿个数
static
uint32_t
s_transitioncount;
//!< 0x5a 字节检测期间内对应计数值
static
uint64_t
s_firstbytetotalticks;
//!< 0xa6 字节检测期间内对应计数值
static
uint64_t
s_secondbytetotalticks;
//!< 上一次下降沿发生时系统计数值
static
uint64_t
s_lasttoggleticks;
//! s_ticksbetweenfailure)
{
s_transitioncount = 
1
;
}
switch
(s_transitioncount)
{
case
1
:
// 0x5a 字节检测时间起点
s_firstbytetotalticks = ticks;
break
;
case
kfirstbyterequiredfallingedges:
// 得到 0x5a 字节检测期间内对应计数值
s_firstbytetotalticks = ticks - s_firstbytetotalticks;
break
;
case
(kfirstbyterequiredfallingedges + 
1
):
// 0xa6 字节检测时间起点
s_secondbytetotalticks = ticks;
break
;
case
(kfirstbyterequiredfallingedges + ksecondbyterequiredfallingedges):
// 得到 0xa6 字节检测期间内对应计数值
s_secondbytetotalticks = ticks - s_secondbytetotalticks;
// 关闭gpio管脚中断
disable_autobaud_pin_irq();
break
;
}
// 记录本次下降沿发生时系统计数值
s_lasttoggleticks = ticks;
}
二、串口(uart)自动波特率识别程序实现
前面讲的都是硬件无关设计,但最终还是要落实到具体 mcu 平台上的,其中 gpio 中断部分是跟 mcu 紧相关的。我们以恩智浦 i.mxrt1011 为例来介绍硬件实现。
2.1 管脚中断方式实现(基于i.mxrt1011)
恩智浦 mimxrt1010-evk 有板载调试器 daplink,这个 daplink 中也集成了 usb 转串口的功能,对应的 uart 引脚是 iomuxc_gpio_09_lpuart1_rxd 和 iomuxc_gpio_10_lpuart1_txd,我们就选用这个管脚 gpio1[9] 做自动波特率检测,实现代码如下:
bsp程序:https://github.com/jayheng/cortex-m-apps/tree/master/apps/autobaud_imxrt1011/bsp/src/pinmux_utility.c
typedef
void
(*
pin_irq_callback_t
)(
void
);
static
pin_irq_callback_t
s_pin_irq_func;
//! @brief uart引脚功能切换函数
void
uart_pinmux_config
(
bool
setgpio)
{
if
(setgpio)
{
iomuxc_setuartautobaudpinmode(iomuxc_gpio_09_gpiomux_io09, gpio1, 
9
);
}
else
{
iomuxc_setuartpinmode(iomuxc_gpio_09_lpuart1_rxd);
iomuxc_setuartpinmode(iomuxc_gpio_10_lpuart1_txd);
}
}
//! @brief 使能gpio管脚中断
void
enable_autobaud_pin_irq
(
pin_irq_callback_t
func)
{
s_pin_irq_func = func;
// 开启gpio1_9下降沿中断
gpio_setpininterruptconfig(gpio1, 
9
, kgpio_intfallingedge);
gpio1->imr |= (
1u
<< 
9
);
nvic_setpriority(gpio1_combined_0_15_irqn, 
1
);
nvic_enableirq(gpio1_combined_0_15_irqn);
}
//! @brief gpio中断处理函数
void
gpio1_combined_0_15_irqhandler
(
void
)
{
uint32_t
interrupt_flag = (
1u
<< 
9
);
// 仅当gpio1_9中断发生时
if
((gpio_getpinsinterruptflags(gpio1) & interrupt_flag) && s_pin_irq_func)
{
//执行一次回调函数
s_pin_irq_func();
gpio_clearpinsinterruptflags(gpio1, interrupt_flag);
}
}
2.2 在mimxrt1010-evk上实测
一切就绪,我们现在来实测一下,主函数流程很简单,测试结果也表明达到了预期效果,每次将 mcu 程序复位运行后,串口调试助手里可任意设置波特率。
int
main
(
void
)
{
// 略去系统时钟配置...
// 初始化定时器
microseconds_init();
// 将gpio1_9先配成输入gpio
bool
setgpio = 
true
;
uart_pinmux_config(setgpio);
// 初始化波特率识别
autobaud_init();
// 检测波特率识别是否已完成,并获取波特率值
uint32_t
baudrate;
while
(!autobaud_get_rate(&baudrate));
// 关闭波特率识别
autobaud_deinit();
// 配置uart1引脚
setgpio = 
false
;
uart_pinmux_config(setgpio);
// 初始化uart1外设
uint32_t
uartclksrcfreq = board_debugconsolesrcfreq();
dbgconsole_init(
1
, baudrate, kserialport_uart, uartclksrcfreq);
printf(
autobaud test success\r\n
);
printf(
detected baudrate is %d\r\n
, baudrate);
while
(
1
);
}
至此,嵌入式里串口(uart)自动波特率识别程序设计与实现痞子衡便介绍完毕了,

研究人员设计了一款能够实时测量血小板强度的微流控装置
北京宝沃复活失败,申请破产清算
基于MSP430F413单片机和MFRC522芯片实现低功耗预付费水表的设计
腔镜手术机器人研发商术锐完成近3亿元B轮融资
IC设计:软硬件交互-polling方式
UART自动波特率识别程序设计方案
调节阀和控制阀的区分
iPod如何当做移动硬盘来用?
SCHURTER硕特高功率应用的安全网设计
IM公司启动Origami生态系统,并首次推出Origami B20开发模块
串口转WIFI模块
机器人和人工智能科技成为常态,会造成现有劳动力的失业吗?
消息 传滴滴亏损109亿 苹果春季发布会时间3月25日
Iphone5S/5C拆机秘密武器大曝光!
一款轻巧多模的无线充电鼠标雷柏M300S
德国对特斯拉触摸屏失灵问题进行调查
是德科技出席首届“全球5G 大会”并展示最新5G创新与合作成果
什么是Apache日志?Apache日志分析工具介绍
锤子M1L评测 好不好用
如何在Ubuntu 22.04上搭建ftp服务器