CH582、CH592、CH584沁恒MCU UART实例讲解

发布于:2025-09-08 ⋅ 阅读:(18) ⋅ 点赞:(0)

UART接口是一个通用的异步串口,应该是最“古老”的通讯协议之一,几乎每片MCU都有至少一个UART接口,介绍UART的书籍也比较多,所以这篇博文其实是没有必要的。只是最近有个项目刚好有用到,所以习惯性的作个梳理。

一、UART通信协议解析:从原理到实践

一)、UART基础概述

UART(通用异步收发器)作为最基础的串行通信协议,凭借其硬件简单、成本低廉的特性,成为嵌入式系统设备间通信的首选方案。其核心特点包括:

  • 异步通信:无需共享时钟信号,通过预定义波特率同步

  • 全双工传输:独立TX/RX线路实现双向数据交互

  • 点对点连接:仅支持两个设备直接通信

二)、硬件架构与接口设计

2.1 物理层实现

UART硬件接口通常包含以下关键组件:

  1. 电平转换电路:支持TTL(0-3.3V/5V)与RS-232(±3-15V)等标准

  2. 收发器芯片:如MAX232(RS-232转换)、MAX485(差分信号)

  3. 终端电阻:RS-485总线需配置120Ω电阻抑制信号反射

2.2 典型连接方案

设备类型

连接方式

适用场景

微控制器间通信

TXA-RXB/RXA-TXB + GND

短距离(<1m)

工业设备

RS-485总线+终端电阻

长距离(<1200m)

计算机外设

USB转UART模块

标准串口扩展

三)、协议帧结构与时序分析

3.1 标准数据帧组成

UART数据帧采用起始位+数据位+校验位+停止位结构:

  • 起始位:1位低电平(逻辑0)标志传输开始

  • 数据位:5-9位有效数据(通常8位对应1字节)

  • 校验位:奇校验/偶校验/无校验(可选)

  • 停止位:1/1.5/2位高电平(逻辑1)标志帧结束

3.2 时序示例(8N1配置)

Bit 0

Bit 1

...

Bit 7

Bit 8

Bit 9

Bit 10

0

1

...

1

1

1

1

起始位

D0

...

D7

停止位

空闲位

四)、关键设计要点与优化策略

4.1 波特率配置

  • 常见速率:9600bps(工业控制)、115200bps(调试接口)

  • 误差控制:双方波特率偏差需<2%

4.2 抗干扰设计

  1. 电气隔离:使用光耦隔离芯片(如HCPL-0720)

  2. 差分信号:RS-485总线抗共模干扰能力达±12V

  3. 软件滤波:中值滤波算法消除偶发噪声

4.3 可靠性增强

  • 错误检测:奇偶校验+CRC循环冗余校验

  • 流量控制:RTS/CTS硬件流控避免数据溢出

  • 超时重传:软件实现超时检测与自动重发

五)、典型应用案例

5.1 智能家居控制系统

  • 架构:STM32主控(UART1)→ 蓝牙模块(透传模式)

  • 参数:115200bps, 8N1, 硬件流控

  • 优化:采用DMA传输降低CPU负载

5.2 工业传感器网络

  • 拓扑:RS-485总线多节点通信

  • 协议:Modbus RTU(基于UART)

  • 挑战:解决信号反射与终端匹配问题

六)、常见问题排查

  1. 数据乱码:检查波特率/校验位/停止位配置

  2. 通信中断:验证GND共地及线路阻抗

  3. 速率受限:提升波特率需缩短走线长度

UART作为跨越数十年的通信标准,其简单性与灵活性使其持续活跃在物联网、工业控制等领域。理解其硬件原理与协议细节,是构建可靠串口通信系统的基石。随着FPGA实现方案的普及,UART在现代系统中仍焕发新的生命力。 

二、沁恒蓝牙芯片的UART接口

1、UART简介

CH585和CH584芯片提供了4组全双工的异步串口UART0/1/2/3。支持全双工和半双工串口通讯, 其中UART0提供发送状态引脚用于切换RS485,并且支持MODEM调制解调器信号CTS、DSR、RI、DCD、 DTR、RTS。

2、 主要特性

 兼容16C550异步串口并且有所增强

 支持5、6、7或者8个数据位以及1或者2个停止位

 支持奇、偶、无校验、空白0、标志1等校验方式

 可编程通讯波特率,最高达9Mbps波特率

 内置8个字节的FIFO先进先出缓冲器,支持4个FIFO触发级

 UART0支持MODEM调制解调器信号CTS、DSR、RI、DCD、DTR、RTS

 UART0支持硬件流控制信号CTS和RTS自动握手和自动传输速率控制,兼容TL16C550C

 支持串口帧错误检测、支持Break线路间隔检测

 支持全双工和半双工串口通讯,UART0提供发送状态引脚用于切换RS485

三、实例应用

/********************************** (C) COPYRIGHT *******************************
 * File Name          : Main.c
 * Author             : WCH
 * Version            : V1.0
 * Date               : 2020/08/06
 * Description        : 串口1收发演示
 *********************************************************************************
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * Attention: This software (modified or not) and binary are used for 
 * microcontroller manufactured by Nanjing Qinheng Microelectronics.
 *******************************************************************************/

#include "CH58x_common.h"

uint8_t TxBuff[] = "This is a tx exam\r\n";
uint8_t RxBuff[100];
uint8_t trigB;

/*********************************************************************
 * @fn      main
 *
 * @brief   主函数
 *
 * @return  none
 */
int main()
{
    uint8_t len;

    HSECFG_Capacitance(HSECap_18p);
    SetSysClock(CLK_SOURCE_HSE_PLL_62_4MHz);

    /* 配置串口1:先配置IO口模式,再配置串口 */
    GPIOB_SetBits(GPIO_Pin_7);
    GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU);      // RXD-配置上拉输入
    GPIOB_ModeCfg(GPIO_Pin_7, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出,注意先让IO口输出高电平
    UART0_DefInit();

#if 1 // 测试串口发送字符串
    UART0_SendString(TxBuff, sizeof(TxBuff));

#endif

#if 1 // 查询方式:接收数据后发送出去
    while(1)
    {
        len = UART0_RecvString(RxBuff);
        if(len)
        {
            UART0_SendString(RxBuff, len);
        }
    }

#endif

#if 1 // 中断方式:接收数据后发送出去
    UART0_ByteTrigCfg(UART_7BYTE_TRIG);
    trigB = 7;
    UART0_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT);
    PFIC_EnableIRQ(UART0_IRQn);
#endif

    while(1);
}

/*********************************************************************
 * @fn      UART1_IRQHandler
 *
 * @brief   UART1中断函数
 *
 * @return  none
 */
__INTERRUPT
__HIGH_CODE
void UART0_IRQHandler(void)
{
    volatile uint8_t i;

    switch(UART0_GetITFlag())
    {
        case UART_II_LINE_STAT: // 线路状态错误
        {
            UART0_GetLinSTA();
            break;
        }

        case UART_II_RECV_RDY: // 数据达到设置触发点
            for(i = 0; i != trigB; i++)
            {
                RxBuff[i] = UART0_RecvByte();
                UART0_SendByte(RxBuff[i]);
            }
            break;

        case UART_II_RECV_TOUT: // 接收超时,暂时一帧数据接收完成
            i = UART0_RecvString(RxBuff);
            UART0_SendString(RxBuff, i);
            break;

        case UART_II_THR_EMPTY: // 发送缓存区空,可继续发送
            break;

        case UART_II_MODEM_CHG: // 只支持串口0
            break;

        default:
            break;
    }
}
/********************************** (C) COPYRIGHT *******************************
 * File Name          : CH58x_uart0.c
 * Author             : WCH
 * Version            : V1.2
 * Date               : 2021/11/17
 * Description        : source file(ch585/ch584)
 *********************************************************************************
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * Attention: This software (modified or not) and binary are used for 
 * microcontroller manufactured by Nanjing Qinheng Microelectronics.
 *******************************************************************************/

#include "CH58x_common.h"

/*********************************************************************
 * @fn      UART0_DefInit
 *
 * @brief   串口默认初始化配置
 *
 * @param   none
 *
 * @return  none
 */
void UART0_DefInit(void)
{
    UART0_BaudRateCfg(115200);
    R8_UART0_FCR = (2 << 6) | RB_FCR_TX_FIFO_CLR | RB_FCR_RX_FIFO_CLR | RB_FCR_FIFO_EN; // FIFO打开,触发点4字节
    R8_UART0_LCR = RB_LCR_WORD_SZ;
    R8_UART0_IER = RB_IER_TXD_EN;
    R8_UART0_DIV = 1;
}

/*********************************************************************
 * @fn      UART0_BaudRateCfg
 *
 * @brief   串口波特率配置
 *
 * @param   baudrate    - 波特率
 *
 * @return  none
 */
void UART0_BaudRateCfg(uint32_t baudrate)
{
    uint32_t x;

    x = 10 * GetSysClock() / 8 / baudrate;
    x = (x + 5) / 10;
    R16_UART0_DL = (uint16_t)x;
}

/*********************************************************************
 * @fn      UART0_ByteTrigCfg
 *
 * @brief   串口字节触发中断配置
 *
 * @param   b       - 触发字节数 refer to UARTByteTRIGTypeDef
 *
 * @return  none
 */
void UART0_ByteTrigCfg(UARTByteTRIGTypeDef b)
{
    R8_UART0_FCR = (R8_UART0_FCR & ~RB_FCR_FIFO_TRIG) | (b << 6);
}

/*********************************************************************
 * @fn      UART0_INTCfg
 *
 * @brief   串口中断配置
 *
 * @param   s       - 中断控制状态,是否使能相应中断
 * @param   i       - 中断类型
 *                    RB_IER_MODEM_CHG  - 调制解调器输入状态变化中断使能位(仅 UART0 支持)
 *                    RB_IER_LINE_STAT  - 接收线路状态中断
 *                    RB_IER_THR_EMPTY  - 发送保持寄存器空中断
 *                    RB_IER_RECV_RDY   - 接收数据中断
 *
 * @return  none
 */
void UART0_INTCfg(FunctionalState s, uint8_t i)
{
    if(s)
    {
        R8_UART0_IER |= i;
        R8_UART0_MCR |= RB_MCR_INT_OE;
    }
    else
    {
        R8_UART0_IER &= ~i;
    }
}

/*********************************************************************
 * @fn      UART0_Reset
 *
 * @brief   串口软件复位
 *
 * @param   none
 *
 * @return  none
 */
void UART0_Reset(void)
{
    R8_UART0_IER = RB_IER_RESET;
}

/*********************************************************************
 * @fn      UART0_SendString
 *
 * @brief   串口多字节发送
 *
 * @param   buf     - 待发送的数据内容首地址
 * @param   l       - 待发送的数据长度
 *
 * @return  none
 */
void UART0_SendString(uint8_t *buf, uint16_t l)
{
    uint16_t len = l;

    while(len)
    {
        if(R8_UART0_TFC != UART_FIFO_SIZE)
        {
            R8_UART0_THR = *buf++;
            len--;
        }
    }
}

/*********************************************************************
 * @fn      UART0_RecvString
 *
 * @brief   串口读取多字节
 *
 * @param   buf     - 读取数据存放缓存区首地址
 *
 * @return  读取数据长度
 */
uint16_t UART0_RecvString(uint8_t *buf)
{
    uint16_t len = 0;

    while(R8_UART0_RFC)
    {
        *buf++ = R8_UART0_RBR;
        len++;
    }

    return (len);
}

以上代码,基于官方提供的demo例程修改得到,可通过转TTL正常与PC机通讯。

沁恒的UART接口有个特点,使用的时候需要根据实际情况灵活配置:

UART接收中断可以配置为1字节、2字节、4字节及7字节触发。

/**
 * @brief  Configuration UART TrigByte num
 */
typedef enum
{
    UART_1BYTE_TRIG = 0, // 1字节触发
    UART_2BYTE_TRIG,     // 2字节触发
    UART_4BYTE_TRIG,     // 4字节触发
    UART_7BYTE_TRIG,     // 7字节触发

} UARTByteTRIGTypeDef;



/*********************************************************************
 * @fn      UART0_ByteTrigCfg
 *
 * @brief   串口字节触发中断配置
 *
 * @param   b       - 触发字节数 refer to UARTByteTRIGTypeDef
 *
 * @return  none
 */
void UART0_ByteTrigCfg(UARTByteTRIGTypeDef b)
{
    R8_UART0_FCR = (R8_UART0_FCR & ~RB_FCR_FIFO_TRIG) | (b << 6);
}


#if 1 // 中断方式:接收数据后发送出去
    UART0_ByteTrigCfg(UART_7BYTE_TRIG);
    trigB = 7;
    UART0_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT);
    PFIC_EnableIRQ(UART0_IRQn);
#endif


网站公告

今日签到

点亮在社区的每一天
去签到