开发环境:
IDE:MounRiver Studio
MCU:CH585
1 串口简介
USART(Universal Synchronous Asynchronous Receiver and Transmitter,通用同步-异步接收发射器)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。USART利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,也支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。使用多缓冲器配置的DMA方式,可以实现高速数据通信。
虽然USART既可以同步又可以异步,但是常见的最常用的就是使用功能的异步功能,如果作为异步通信就是UART(Universal Asynchronous Receiver and Transmitter),可以说,UART是USART的子集,但是同步通信相比异步通信多了一根时钟同步信号线。
下面简单介绍下同步和异步。
在同步通讯中,收发设备双方会使用一根信号线表示时钟信号,在时钟信号的驱动下双方进行协调,同步数据,见下图。通讯中通常双方会统一规定在时钟信号的上升沿或下降沿对数据线进行采样。

Figure ‑ 同步通讯
在异步通讯中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用的信号位,或者把主体数据进行打包,以数据帧的格式传输数据,见下图,某些通讯中还需要双方约定数据的传输速率,以便更好地同步。

Figure ‑ 异步通讯
在同步通讯中,数据信号所传输的内容绝大部分就是有效数据,而异步通讯中会包含有帧的各种标识符,所以同步通讯的效率更高,但是同步通讯双方的时钟允许误差较小,而异步通讯双方的时钟允许误差较大。
从上面的介绍可以看出,USART以同步方式通信需要时钟同步信号,但不需要额外的起始、停止位,可以实现更快的传输速度。但USART控制起来更复杂,CH585只有异步通信。
异步串行通信以字符为单位,即一个字符一个字符地传送 。

Figure ‑ 异步串口通信协议
串口外设的架构图看起来十分复杂,实际上对于软件开发人员来说,我们只需要大概了解串口发送的过程即可。从下至上,我们看到串口外设主要由三个部分组成,分别是波特率控制、收发控制和数据存储转移。
波特率,即每秒传输的二进制位数,用 b/s (bps)表示,通过对时钟的控制可以改变波特率。
1) 计算串口内部基准时钟 Fuart,设置 R8_UARTx_DIV 寄存器,最大值 127,通常写入 1。
2) 计算波特率,设置 R16_UARTx_DL 寄存器。
波特率公式 =Fsys * 2 / R8_UARTx_DIV / 16 / R16_UARTx_DL。
【注】UART和USART的区别
USART(universal synchronous asynchronous receiver and transmitte): 通用同步异步收发器,USART是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。
UART(universal asynchronous receiver and transmitter): 通用异步收发器,异步串行通信口(UART)就是我们在嵌入式中常说的串口,它还是一种通用的数据通信议。从名字上可以看出,USART在UART基础上增加了同步功能,即USART是UART的增强型。
当我们使用USART在异步通信的时候,它与UART没有什么区别,但是用在同步通信的时候,区别就很明显了:大家都知道同步通信需要时钟来触发数据传输,也就是说USART相对UART的区别之一就是能提供主动时钟。如CH32的USART可以提供时钟支持ISO7816的智能卡接口。
USART是指单片机的一个端口模块,可以根据需要配置成同步模式(SPI,I2C),也可以将其配置为异步模式,后者就是UART。所以说UART姑且可以称之为一个与SPI,I2C对等的“协议”,而USART则不是一个协议,而是更应该理解为一个实体。相比于同步通讯,UART不需要统一的时钟线,接线更加方便。但是,为了正常的对信号进行解码,使用UART通讯的双方必须事先约定好波特率,即每个码元的长度。
2 串口硬件
串口的接口通过三个引脚与其他设备连接在一起。任何USART双向通信至少需要两个脚:接收数据输入(RX)和发送数据输出(TX)。
- RX:接收数据串行输入。通过采样技术来区别数据和噪音,从而恢复数据。
- TX :发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O 口被同时用于数据的发送和接收。

Figure ‑ UART接口
PA9和PA8分别是TXD和RXD。
3 串口发送
3.1 串口发送实现
下面笔者就用标准库来操作串口1。
1.串口配置
这个比较简单,前面的章节已经讲过了,只需要注意的是,这里的GPIO不再是普通GPIO,要配置成复用功能,因此TX和RX分别配置成GPIO_ModeOut_PP_5mA和GPIO_ModeIN_PU。
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
串口初始化是通过 UART1_DefInit ()函数实现的,
2.数据发送与接收
CH585 的发送通过数据寄存器R8_UARTx_THR实现的,接收是通过数据寄存器R8_UARTx_RBR实现的。
CH585库函数操作 R8_UARTx_THR寄存器发送数据的函数是:
void UARTx_SendString(uint8_t *buf, uint16_t l)
通过该函数向串口寄存器 R8_UARTx_THR写入一个数据。
CH585库函数操作 R8_UARTx_RBR寄存器读取串口接收到的数据的函数是:
uint16_t UARTx_RecvString(uint8_t *buf)
通过该函数可以读取串口接受到的数据。
另外,笔者在此给出输出格式的说明,请读者朋友参考。
Table ‑ 输出格式说明
| 格式 |
说明 |
|---|
| %d |
按照十进制整型数打印 |
| %6d |
按照十进制整型数打印,至少6个字符宽 |
| %f |
按照浮点数打印 |
| %6f |
按照浮点数打印,至少6个字符宽 |
| %.2f |
按照浮点数打印,小数点后有2位小数 |
| %6.2f |
按照浮点数打印,至少6个字符宽,小数点后有2位小数 |
| %x |
按照十六进制打印 |
| %c |
打印字符 |
| %s |
打印字符串 |
主函数代码如下。
#include "CH58x_common.h"
uint8_t TxBuff[] = "This is a tx exam\r\n";
uint8_t RxBuff[100];
uint8_t trigB;
/*********************************************************************
* @fn main
*
* [url=home.php?mod=space&uid=2666770]@Brief[/url] 主函数
*
* [url=home.php?mod=space&uid=1141835]@Return[/url] none
*/
int main()
{
uint8_t len;
HSECFG_Capacitance(HSECap_18p);
SetSysClock(CLK_SOURCE_HSE_PLL_62_4MHz);
/* 配置串口1:先配置IO口模式,再配置串口 */
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU); // RXD-配置上拉输入
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出,注意先让IO口输出高电平
UART1_DefInit();
// 测试串口发送字符串
UART1_SendString(TxBuff, sizeof(TxBuff));
while(1)
{
len = UART1_RecvString(RxBuff);
if(len)
{
UART1_SendString(RxBuff, len);
}
}
while(1);
}
我们来总结下串口发送的流程:
1.初始化硬件,时钟;
2.UART 的GPIO初始化,UART参数初始化;
3.打印输出
##3.2 实验现象
将程序编译好下载到板子中,打开串口助手,按下图设置相应参数,按下板子的复位按键,在接收区可以看到如下信息。

Figure ‑ 串口发送实验结果
4 串口接收数据(中断方式)
4.1 串口接收实现
中断方式相对于与普通方式,还需要开启中断并且初始化以及中断服务函数。
UART1_ByteTrigCfg(UART_7BYTE_TRIG);
CH585 的 UART 包含 16 字节的硬件 FIFO,该函函数用于设置 UART1 接收 FIFO 的中断触发阈值,UART_7BYTE_TRIG 表示触发条件为接收 FIFO 达到 7 字节。
UART1_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT);
配置 UART1 中断使能,RB_IER_RECV_RDY:接收数据就绪中断,RB_IER_LINE_STAT:线路状态错误中断。
PFIC_EnableIRQ(UART1_IRQn);
在中断控制器中使能 UART1 中断通道。CH585 使用 RISC-V 内核,其中断控制器为 PFIC,此函数激活 UART1 的全局中断通道。
trigB = 7;
用户自定义变量记录触发阈值,当中断触发时,最多可读取 trigB 字节(7 字节)。
__INTERRUPT
__HIGH_CODE
void UART1_IRQHandler(void)
{
volatile uint8_t i;
switch(UART1_GetITFlag())
{
case UART_II_LINE_STAT:
{
UART1_GetLinSTA();
break;
}
case UART_II_RECV_RDY:
for(i = 0; i != trigB; i++)
{
RxBuff[i] = UART1_RecvByte();
UART1_SendByte(RxBuff[i]);
}
break;
case UART_II_RECV_TOUT:
i = UART1_RecvString(RxBuff);
UART1_SendString(RxBuff, i);
break;
case UART_II_THR_EMPTY:
break;
case UART_II_MODEM_CHG:
break;
default:
break;
}
}
在中断服务程序中,接收到数据后立即输出。
主函数代码如下:
#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()
{
HSECFG_Capacitance(HSECap_18p);
SetSysClock(CLK_SOURCE_HSE_PLL_62_4MHz);
/* 配置串口1:先配置IO口模式,再配置串口 */
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU); // RXD-配置上拉输入
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出,注意先让IO口输出高电平
UART1_DefInit();
UART1_SendString(TxBuff, sizeof(TxBuff));
// 中断方式:接收数据后发送出去
UART1_ByteTrigCfg(UART_7BYTE_TRIG);
trigB = 7;
UART1_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT);
PFIC_EnableIRQ(UART1_IRQn);
while(1);
}
将程序编译好下载到板子中,打开串口助手,按下图设置相应参数,按下板子的复位按键,在接收区可以看到如下信息。

Figure ‑ 串口接收实验结果