串口收发程序设计
1.基本知识
1.1串口
串行接口是一种可以将接收来自CPU的并行数据字符转换为连续的串行数据流发送出去,同时可将接收的串行数据流转换为并行的数据字符供给CPU的器件。一般完成这种功能的电路,我们称为串行接口电路。
1.2波特率
这是一个衡量符号传输速率的参数。指的是信号被调制以后在单位时间内的变化,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。一般调制速率大于波特率,比如曼彻斯特编码)。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。
1.3停止位
用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
1.4STM32
STM32系列专为要求高性能、低成本、低功耗的嵌入式应用设计的ARM Cortex®-M0,M0+,M3, M4和M7内核(ST’s product portfolio contains a comprehensive range of microcontrollers, from robust, low-cost 8-bit MCUs up to 32-bit ARM-based Cortex®-M0 and M0+, Cortex®-M3, Cortex®-M4 Flash microcontrollers with a great choice of peripherals. ST has also extended this range to include an ultra-low-power MCU platform) [1] 。按内核架构分为不同产品:
主流产品(STM32F0、STM32F1、STM32F3)、超低功耗产品(STM32L0、STM32L1、STM32L4、STM32L4+)、高性能产品(STM32F2、STM32F4、STM32F7、STM32H7)
1.5DMA (直接存储器访问)
DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于[ CPU ](https://baike.baidu.com/item/ CPU /120556)的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。
1.6中断
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
2.串口收发程序
2.1库函数接口
函数名称 | 相应功能 |
---|---|
__HAL_DMA_GET_COUNTER | 获取DMA剩余未接收数据 |
HAL_UART_Transmit | 串口阻塞方式发送函数 |
HAL_UART_Transmit_IT | 串口中断方式发送函数 |
HAL_UART_Receive_IT | 串口中断方式接收数据 |
HAL_UART_Transmit_DMA | 串口DMA方式发送函数 |
HAL_UART_Receive_DMA | 串口DMA方式接收函数 |
HAL_UART_TxCpltCallback | 串口发送完成回调函数 |
HAL_UART_RxCpltCallback | 串口接收完成回调函数 |
HAL_UART_RxHalfCpltCallback | 串口接收过半回调函数 |
2.2流程设计
2.2.1基本流程
2.2.1.1串口收发
串口模式图:
SBUF:串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址。写操作时,写入的是发送寄存器,读操作时,读出的是接收寄存器
![image-20220111124105574](https://i-blog.csdnimg.cn/blog_migrate/707f6735682a17b68433de18f13f8c4f.png)
2.2.1.2串口中断
![image-20220111125423763](https://i-blog.csdnimg.cn/blog_migrate/e5c86cde7c90ca1027c77da3da1b4169.png)
中断发送过程
中断发送方式的基本过程是:每发送一个字节,该字节发送完毕引发中断,中断处理程序再发送下一个字节……直到发送完成。
-
第一个字节是怎样发送的?
因为发第一个字节之前,并没有发送字节完成所触发的中断,那第一个字节怎么被发送出去?这关系到中断在硬件层面的触发方式:如果是电平触发的中断,在中断式发送函数中会使能相应的中断,因为发送缓冲区为空,其所处的电平状态就在中断功能被使能后会直接触发中断,导致第一个字节在中断处理函数中被发送出去;如果是电平跳变触发的中断,为了引发中断,只好把第一个字节在中断式发送函数中“手动”发送出去,由此引发一个中断的“多米诺”效应,把后面的字节依次发送出去。
-
怎么才算“完成”?
完成是用参数指定的欲发送字节数决定的,但发送达到指定数目,中断处理函数中就失能相应中断,于是中断的“多米诺”链条停止。
数据接收流程
该接口不需要等待接收完成再返回,整个接收过程不需要用户干预,最好检查下返回值,看有没有调用成功,因为如果上次数据还没接收完整,处理忙状态,是不可以再发起新的接收过程的
2.2.2帧检测流程
![image-20220111121153090](https://i-blog.csdnimg.cn/blog_migrate/7a715473b73f49e42e0bbc8000fb1a0f.png)
3.考虑的问题
3.1突发数据量会很大
偶尔突发数据量会很大的情况,这样就可能会丢失数据。
解决方式;
1.DMA(或中断)一次接收多字节
2.DMA+空闲中断方式
空闲中断
空闲中断会在收到一个字节后指定时间内未收到下一个字节时产生,这样的话就可以在产生空闲中断时将收到的数据读走,而不会一直被缓存着
1.初始状态,head_ptr和stail_ptr都指向缓冲区首部,收到一帧数据后,没有新数据到来,触发空闲中断,在空闲中断回调函数中要做的操作就是读走这部分数据(图中1号区域,head_ptr与tail_ptr之间的数据),并且将head_ptr移动到tail_ptr的位置,模型如下:(只要产生空闲中断,都适用此流程)
2.继续接收新数据,此为触发半满中断的情况,在半满中断回调函数中的操作是将head_ptr
到tail_ptr
之间的数据写入fifo(图中2号区域
),然后移动head_ptr
到tail_ptr
的位置,模型如下:
3.继续接收数据直到产生满中断,在满中断回调函数中的操作也是将head_ptr
到tail_ptr
之间的数据写入fifo(图中3号区域
),然后移动head_ptr
到tail_ptr
的位置,模型如下:
3.2第一个字节的发送
中断在硬件层面的触发方式:如果是电平触发的中断,在中断式发送函数中会使能相应的中断,因为发送缓冲区为空,其所处的电平状态就在中断功能被使能后会直接触发中断,导致第一个字节在中断处理函数中被发送出去
3.3接受到大量数据
HAL库的串口收发由于加锁操作,当数据量太大时会出现接受失效的情况。
HAL_StatusTypeDef USART_SendData(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
HAL_StatusTypeDefr state = HAL_OK;
ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_RXNEIE_RXFNEIE);//关闭接收中断
ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_RE);//关闭接收
state = HAL_UART_Transmit(huart,pData,Size,Timeout);
HAL_UART_Receive_IT(&huart3,uart3_rxdata,1);//注意:此函数的输入参数需要根据自己的程序进行修改
ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_RE);//打开接收
ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_RXNEIE_RXFNEIE);//打开接收中断
return state;
}
USART_SendData()函数的思路比较简单,就是在发送前关闭接收中断,发送完成后再次打开串口接收中断,避免在串口发送过程中进入接收中断。
3.4接收不定长数据
4.参考文档
串口收发程序设计:
https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx