0%

USART入门

Main Takeaway

Following 哈工深上传到B站的电控组培训来入门robomaster电控组,我购买了普中科技玄武套餐开发板作为硬件。

本篇介绍我学习USART的见闻——实验仍有一些问题未解决

USART

简介

  • USART(Universal Synchronous/Asynchronous Receiver/Transmitter),通用同步(CLK同步接收和发送,数据量大选择)/异步串行接收/发送器

    Tips:UART仅有异步通信

  • USART是异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输,它能将要传输的资料在串行通信与并行通信之间加以转换,能够灵活地与外部设备进行全双工数据交换(并行转串行)

  • USART有两个引脚用于传输数据,RX和TX,接线的时候应使用交叉线,此外如果是不同设备之间通信还必须有一个GND线,VCC线为可选项。

    TX(transmitter):发送数据

    RX(receiver):接收数据

  • 全双工:两条线互不干扰,可同时进行发送和接收 半双工:一次只能进行发送/接收

  • 异步:只需频率一样,相位可以不一样(常用) 同步:还需时钟线CLK(数据量很大时)

  • 支持最基本的

    • 通用串口同步、异步通讯
    • LIN总线功能(局域互联网)
    • IRDA功能(红外通讯)
    • SmartCard功能
  • UART:只有异步通讯

USART的通信协议

一帧一帧地发送

USART时序图
  • 起始位:当未有数据发送时,数据线处于逻辑“1”状态;先发出一个逻辑“0”信号,表示开始传输字符。

  • 数据位:紧接着起始位之后。数据位的个数可以是4、5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。

    eg:00010001,先发送1

  • 奇偶校验位:数据为加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验资料传送的正确性。(弱校验)

  • 停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。

  • 空闲位或起始位:处于逻辑“1”状态,表示当前线路上没有资料传送,进入空闲状态。处于逻辑“0”状态,表示开始传送下一数据段(起始位)。

  • 波特率:即每秒传输的二进制位数, 用 b/s (bps)表示,通过对时钟的控制可以改变波特率。USART具有独立的高精度波特率发生器,不占用定时/计数器。常用的波特率有:9600、115200……

    Notes:奇偶校验是弱校验,115200最常用,发送/接收的FIFO(CPU太快了 )

USART工作原理

image-20230608115210441

FIFO 操作

  • INTRO:FIFO 是“First-In First-Out”的缩写,意为“先进先出”,是一种常见的队列操作。Stellaris 系列ARM 的UART 模块包含有2 个16 字节的FIFO:一个用于发送,另一个用于接收。可以将两个FIFO 分别配置为以不同深度触发中断。可供选择的配置包括:1/8、 1/4、1/2、3/4 和7/8 深度。

    Tips:例如,如果接收FIFO选择1/4,则在UART 接收到4 个数据时产生接收中断

  • 目的:收发FIFO 主要是为了解决UART 收发中断过于频繁而导致CPU 效率不高的问题而引入的。

    在进行UART通信时,中断方式比轮询方式(polling)要简便且效率高。但是,如果没有收发 FIFO,则每收发一个数据都要中断处理一次,效率仍然不够高。如果有了收发FIFO,则可以在连续收发若干个数据(可多至14 个)后才产生一次中断然后一并处理,这就大大提高了收发效率。

  • 发送FIFO的基本工作过程:

    • 只要有数据填充到发送FIFO 里,就会立即启动发送过程。

    • 由于发送本身是个相对缓慢的过程,因此在发送的同时其它需要发送的数据还可以继续填充到发送 FIFO 里。

      Tips:当发送 FIFO 被填满时就不能再继续填充了,否则会造成数据丢失,此时只能等待。

    • 发送 FIFO 会按照填入数据的先后顺序把数据一个个发送出去,直到发送 FIFO 全空时为止。已发送完毕的数据会被自动清除,在发送FIFO 里同时会多出一个空位。

  • 接收FIFO的基本工作过程:

    • 当硬件逻辑接收到数据时,就会往接收FIFO 里填充接收到的数据。

    • 程序应当及时取走这些数据,数据被取走也是在接收FIFO 里被自动删除的过程,因此在接收 FIFO 里同时会多出一个空位。

      Tips:如果在接收 FIFO 里的数据未被及时取走而造成接收FIFO 已满,则以后再接收到数据时因无空位可以填充而造成数据丢失。

  • Notes:完全不必要担心FIFO 机制可能带来的数据丢失或得不到及时处理的问题,因为它已经帮你想到了收发过程中存在的任何问题,只要在初始化配置UART 后,就可以放心收发了, FIFO 和中断例程会自动搞定一切。

发送接收

  • 发送逻辑:从发送FIFO 读取的数据执行“并→串”转换。控制逻辑输出起始位在先的串行位流,并且根据控制寄存器中已编程的配置,后面紧跟着数据位(注意:最低位 LSB 先输出)、奇偶校验位和停止位。
  • 接收逻辑:在检测到一个有效的起始脉冲后,对接收到的位流执行“串→并”转换。此外还会对溢出错误、奇偶校验错误、帧错误和线中止(line-break)错误进行检测,并将检测到的状态附加到被写入接收FIFO 的数据中。

数据收发过程

  • 发送时,数据被写入发送FIFO。如果USART 被使能,则会按照预先设置好的参数(波特率、数据位、停止位、校验位等)开始发送数据,一直到发送FIFO 中没有数据。一旦向发送FIFO 写数据(如果FIFO 未空),UART 的忙标志位BUSY 就有效,并且在发送数据期间一直保持有效。BUSY 位仅在发送FIFO 为空,且已从移位寄存器发送最后一个字符,包括停止位时才变无效。即 USART 不再使能,它也可以指示忙状态。
  • 在USART 接收器空闲时,如果数据输入变成“低电平”,即接收到了起始位,则接收计数器开始运行,若起始位持续时间达到阈值,则起始位有效,否则会被认为是错误的起始位并将其忽略。
  • 如果起始位有效,则根据数据字符被编程的长度,对连续的数据位进行采样。如果奇偶校验模式使能,则还会检测奇偶校验位。
  • 最后,如果Rx 为高电平,则有效的停止位被确认,否则发生帧错误。当接收到一个完整的字符时,将数据存放在接收FIFO 中。

波特率

  • 波特率除数(baud-rate divisor)是一个22 位数,它由16 位整数和6 位小数组成,是一个用来设置USART的通信速度的参数。波特率发生器使用这两个值组成的数字来决定位周期,决定发送、接收的波特率。

    通过带有小数波特率的除法器,在足够高的系统时钟速率下,UART 可以产生所有标准的波特率,而误差很小。

Tips:根据距离的远近设置波特率的大小

中断控制

出现以下情况时,可使UART产生中断:

image-20230608120340707

Tips:接收数据就绪可读

由于所有中断事件在发送到中断控制器之前会一起进行“或运算”操作,所以任意时刻 USART 只能向中断产生一个中断请求。通过查询中断状态函数,软件可以在同一个中断服务函数里处理多个中断事件(多个并列的if 语句)

使用DMA收发

  • DMA(Direct Memory Access,直接内存访问)是一种用于数据传输的技术,它可以绕过CPU直接在外设和内存之间进行数据传输。在DMA操作中,有两个重要的地址:外设基地址和内存基地址。

    • 外设基地址是指用于DMA传输的外设设备的起始地址。
    • 内存基地址是指用于DMA传输的内存区域的起始地址。

    DMA的优点是可以减少CPU的负担,提高系统的效率和性能。

    DMA的缺点是需要额外的硬件支持,如DMA控制器和总线仲裁器,以及额外的软件配置,如设置DMA通道、传输方向、数据量、优先级等参数。

Tips:数据传输想象成快递,本来店家需要和快递员一个一个联系,DMA就是快递柜分好类,店家就不用一个一个联系了

USART重要寄存器

状态寄存器(USART_SR)

通过查询状态寄存器可查询状态

image-20230608120652738
  • TC:发送完成 (Transmission complete)

    当包含有数据的一帧发送完成后,并且TXE=1时,由硬件将该位置’1’。如果USART_CR1中的TCIE为’1’,则产生中断。由软件序列清除该位(先读USART_SR,然后写入USART_DR)。 TC位也可以通过写入’0’来清除,只有在多缓存通讯中才推荐这种清除程序。

    0:发送还未完成;

    1:发送完成。

  • IDLE:监测到总线空闲 (IDLE line detected)

    当检测到总线空闲时,该位被硬件置位。如果USART_CR1中的IDLEIE为’1’,则产生中断。由软件序列清除该位(先读USART_SR,然后读USART_DR)。

    0:没有检测到空闲总线;

    1:检测到空闲总线。

    Tips: IDLE用于不知道到底有多少个字节,IDLE位不会再次被置高,直到RXNE位被置起(即又检测到一次空闲总线)

控制寄存器 (USART_CRx)

有CR1、CR2、CR3三个寄存器,具体的配置较复杂,具体的可以参考《STM32中文参考手册》。

USART API

接收

1
2
3
4
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size, uint32_t Timeout)
串口接收数据的库函数,阻塞的方式接收数据。

huart :要发送数据的串口指针,pData:接收数据缓存地址,注意此处的指针形式,Size:接收数据的长度(字节数) Timeout:数据接收等待时间,CPU等待这个时间用来接收数据。
1
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size)

使用中断的方式进行接受数据,在这里需要注意的一个点是,使用中断的方式接收到数据之后,需要在中断里面在调用一次HAL_UART_Receive_IT函数,重新开启下一次数据接收,否则会导致,接收完一次数据之后,不会接收下一次数据。

1
2
3
4
5
6
7
8
9
10
HAL_UART_Receive_IT(&huart2,&usart2_rdata,1);  
//IT:interrupt type control,中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART2) //串口屏,接收中断
{
HAL_UART_Receive_IT(&huart2,&usart2_rdata,1);
}
}
//接收到指定长度就会产生一次中断
1
2
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t*pData, uint16_t Size)
//接收到指定长度就会产生一次中断

发送

1
2
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size, uint32_t Timeout)
在发送方面,使用HAL_UART_Transmit阻塞数据发送的时候,一定要计算好发送数据的时间。如果发送时间到了,但是数据还没有发送完成的话,会导致没有发送的数据丢失
1
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t*pData, uint16_t Size)
1
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t*pData, uint16_t Size)

中断

1
2
3
4
5
6
7
8
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
1
2
3
4
5
6
7
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_TxCpltCallback could be implemented in the user file*/
}
1
2
3
4
5
6
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback could be implemented in the user file*/
}

Tips:callback函数需要重定义

实验

CubeMX:

  • 串口在Connectivity

  • Asynchronous adj.[电] 异步的;不同时的;不同期的

  • Parity 奇偶校验位

  • NVIC Interrupt打开串口中断

  • DMA setting当不知道要接收多少时用DMA空闲中断,ADD->Rx

Keil

  • 勾选use micro lib

Tips:KeilMDK配置项中Use MicroLIB是一个选项,用于选择是否使用microlib库。microlib库是一个简化的C标准库,它专门为需要在极少量内存中运行的深层嵌入式应用程序设计的。microlib库的优点是可以减少代码的大小,但缺点是功能有限,不符合ISO C标准和IEEE 754浮点算法标准。如果你想使用printf等函数,你可以选择Use MicroLIB选项,或者关闭半主机模式并重写相关的底层函数。

  • DMA空闲中断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 先打开串口空闲中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1, receive_data, 100);

//然后重写callback函数
void UART_IDLE_Callback(UART_HandleTypeDef *huart)

//最后在it.c中调用自己的中断,屏蔽默认的中断
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
UART_IDLE_Callback(&huart1); //调用自己的中断
return; //屏蔽默认的中断
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */

/* USER CODE END USART1_IRQn 1 */
}

串口调试助手

我用了xcom和sscom串口调试助手

  • Xcom.exe中可以监视数据传输
  • DTR(Data Terminal Ready(数据终端准备好)):由数据终端设备(如计算机)发出,表示它已经准备好接收数据了。当DTR为高电平时,表示数据终端设备已经打开了串口,并且可以接收数据。当DTR为低电平时,表示数据终端设备已经关闭了串口,或者不想接收数据了。
  • RTS(Request To Send(请求发送)):由数据终端设备发出,表示它想要发送数据了,并且请求对方的许可。当RTS为高电平时,表示数据终端设备有数据要发送,并且等待对方的应答。当RTS为低电平时,表示数据终端设备没有数据要发送,或者暂停发送数据。

一些经验

总结下来就是遇到串口不能使用的情况应该检查以下问题:

(1)看看串口有没有和其他串口冲突或者被占用

(2)代码有没有问题,实在不行先烧官方例程看看

(3)检查波特率,停止位,数据位,奇偶校验位这些什么的,一定要选对

Tips:对照Cube MX中的设置检查

(4)还有就是要勾选use micro lib

BONUS

  • uint8_t_16_t_t_t

    • 这些类型的来源:这些数据类型中都带有_t, _t 表示这些数据类型是通过typedef定义的,而不是新的数据类型。也就是说,它们其实是我们已知的类型的别名。

    • 使用这些类型的原因:方便代码的维护。比如,在C中没有bool型,于是在一个软件中,一个程序员使用int,一个程序员使用short,会比较混乱。最好用一个typedef来定义一个统一的bool

      1
      2
      typedef char bool;
      typedef unsigned char uint8_t;
    • uint8_t实际上是一个char。所以输出uint8_t类型的变量实际上输出其对应的字符,而不是数值。

  • 句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制。句柄本质就是对底层硬件的指针的使用。句柄是针对内核的。大家可以理解为句柄是一个指向指针的指针。

Problem

在前面两个使用EXTI中断的projects中没能收到USART传来的消息???

在最后的DMA中断中收到了

88a583cc4036de8ace38c8517d0075d

References