当前位置:首页 > 公众号精选 > 嵌入式大杂烩
[导读]大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是嵌入式里串口(UART)自动波特率识别程序设计与实现。串口(UART)是嵌入式里最基础最常用也最简单的一种通讯(数据传输)方式,可以说是工程师入门通讯领域的启蒙老师,同时串口打印也是嵌入式项目里非常经典的调试与交互方...

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是嵌入式里串口(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),代码里痞子衡都做了详细注释。有一点要提的是关于其中系统计时,可参考痞子衡旧文 《嵌入式里通用微秒(microseconds)计时函数框架设计与实现》 。

//! @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;
//!< 下降沿之间最大超时对应计数值
static uint64_t s_ticksBetweenFailure;

void autobaud_init(void)
{
    s_transitionCount = 0;
    s_firstByteTotalTicks = 0;
    s_secondByteTotalTicks = 0;
    s_lastToggleTicks = 0;
    // 计算出下降沿之间最大超时对应计数值
    s_ticksBetweenFailure = microseconds_convert_to_ticks(kMaximumTimeBetweenFallingEdges);
    // 使能GPIO管脚中断,并注册中断处理回调函数
    enable_autobaud_pin_irq(pin_transition_callback);
}

void autobaud_deinit(void)
{
    // 关闭GPIO管脚中断
    disable_autobaud_pin_irq();
}

bool autobaud_get_rate(uint32_t *rate)
{
    if (s_transitionCount == (kFirstByteRequiredFallingEdges   kSecondByteRequiredFallingEdges))
    {
        // 计算出实际检测到的波特率值
        uint32_t calculatedBaud =
            (microseconds_get_clock() * (kNumberOfBitsForFirstByteMeasured   kNumberOfBitsForSecondByteMeasured)) /
            (uint32_t)(s_firstByteTotalTicks   s_secondByteTotalTicks);

        // 对实际检测出的波特率值做对齐处理
        // 公式:rounded = stepSize * (value/stepSize .5)
        *rate = ((((calculatedBaud * 10) / kAutobaudStepSize)   5) / 10) * kAutobaudStepSize;

        return true;
    }
    else
    {
        return false;
    }
}

void pin_transition_callback(void)
{
    // 获取当前系统计数值
    uint64_t ticks = microseconds_get_ticks();
    // 计数这次检测到的下降沿
    s_transitionCount ;

    // 如果本次下降沿与上次下降沿之间间隔过长,则从头开始检测
    uint64_t delta = ticks - s_lastToggleTicks;
    if (delta > 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) 
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

串行通信需要传输的数据通过调制器(Modulator)将数据转换为模拟信号,经过信号调制(Modulation)后在传输线上传输,接收端通过解调器(Demodulator)将信号解码还原成原始数据。

关键字: 串口 串行通信 并行通信

51 单片机内部有一个全双工串行接口。什么叫全双工串口呢?一般来说,只能接受或只能发送的称为单工串行;既可接收又可发送,但不能同时进行的称为半双工;能同时接收和发送的串行口称为全双工串行口。串行通信是指数据一位一位地按顺...

关键字: 单片机 全双工 串口

串口是“串行接口”的简称,即采用串行通信方式的接口。串行通信将数据字节分成一位一位的形式在一条数据线上逐个传送,其特点是通信线路简单,但传输速度较慢。因此串口广泛应用于嵌入式、工业控制等领域中对数据传输速度要求不高的场合...

关键字: 串口 RS232 同步传输

串口作为单片机开发的一个常用的外设,应用范围非常广。大部分时候,串口需要接收处理的数据长度是不定的。那么怎么才能判断一帧数据是否结束呢,今天就以STM32单片机为例,介绍几种接收不定长数据的方法。

关键字: 单片机 串口 STM32

这是FPGA之旅设计的第十例啦,在上一例中,已经成功驱动了OLED屏幕,本例将结合上一例,以及第四例多bytes串口通信做一个有趣的例程。

关键字: FPGA OLED屏 串口

接下来测试烧写功能,本次采用串口和USB烧写方式。使用ISP串口烧写这是51单片机常用的方案,本次测试比较顺利,没有遇到什么问题。但是USB烧写没有测试成功,USB烧写方式不需要任何的驱动和硬件支持,直接将USB线和ST...

关键字: PCB控制板 USB 串口

摘要:多功能电能表在配电系统中应用广泛,其计量的准确度对企业管理和考核至关重要,因此在设计多功能电能表时需要对其进行校准,满足一定应用等级。常规的多功能电能表校准方法是以电能脉冲校准为主,现提出一种基于C#和功率校表法的...

关键字: 多功能电能表 串口 波特率

摘 要:为了能通过串口采集电能参数,完成一种基于串口的三相电能采集设备的研制,设计了电能采集设备的硬件和软件部分。其中硬件采用MCU+专用电能计量芯片的结构,结构简单;软件则用于实现输入、输出、三相电能参数的采集和串行通...

关键字: 电能采集 ATT7022B MSP430 串口

如何确定时基假如要测量的波特率为9600,则每一比特位的时间为:1/9600≈104μs,一般示波器横向上每个大格子里5个小格子,要想看清一比特位一般需要一个小格子就够了,则时基为:104μs*5=520μs,也就是说时...

关键字: 串口

VivadoML最新版2021下载方法:《安装Vivado2021.1ML版,编译时间真的会减少吗?》今天我们通过zedboard串口使用的实例来简单介绍vivado和vitis的使用步骤。1,首先打开软件,新建一个空白...

关键字: 串口 zedboard vi
关闭
关闭