问题:
最近发现,stm32cubemx最新版本f1系列的hal库(1.85版本)生成的hal库,其中stm32f1xx_hal_uart.c的库文件中,其串口发送接收存在一些问题:
1.没有使用 __HAL_LOCK
和 __HAL_UNLOCK
宏,锁机制。
2.pdata8bits
和 pdata16bits
未初始化为 NULL
,可能会导致未定义行为。
3.在遇到超时错误时,没有恢复 huart->RxState
状态。
这样子会出现什么问题呢?
1.串口中断进不去,主机发送消息,从机无回应
2.轮询模式下,打开串口,其会不断给电脑串口发信息,直到几千几万条后溢出。
(也有可能是我没理解到位,如果知道原因的大佬,可以在评论区给点意见)
解决方法:
我将stm32cubemx的hal库版本进行了修改,改成了1.81版本,进行通信,实验成功。
步骤如下:
1.点击库管理
2.找到自己芯片库
如果你是1.85版本,如果前面框为绿色,选中然后下面点击移除就行。
点击1.81版本,进行安装。下图2框取消勾选,3框选择1.81。
其他配置和上篇文章一致:
STM32-hal库学习(4)--usart/uart通信 (单向通信)(同时显示在oled)-CSDN博客
1.轮询模式
什么是轮询模式?
轮询模式利用阻塞模式收发数据
HAL_UART_Transmit():串口发送数据,使用超时管理机制
HAL_UART_Receive(): 串口接收数据,使用超时管理机制
其用于在没有中断机制或DMA机制的情况下,主动等待并处理外设的状态变化。在轮询模式下,CPU不断地检查外设的状态寄存器,以确定是否有数据可供处理。这种方式简单易用,但效率较低,因为CPU在等待期间不能处理其他任务。
程序:
main.c中加入
#include "stdio.h"
uint8_t RxDate[256];
因为oled函数里面没有显示hex类型函数,所以编写oled.c的hex显示函数:
void OLED_ShowHexArray(uint8_t x, uint8_t y, uint8_t *numArray, uint8_t Length, uint8_t size2, uint8_t Color_Turn)
{
uint8_t i;
uint8_t highNibble, lowNibble;
for (i = 0; i < Length; i++)
{
highNibble = (numArray[i] >> 4) & 0x0F;
lowNibble = numArray[i] & 0x0F;
// 显示高半字节
if (highNibble < 10)
{
OLED_ShowChar(x + (size2 / 2) * (2 * i), y, highNibble + '0', size2, Color_Turn);
}
else
{
OLED_ShowChar(x + (size2 / 2) * (2 * i), y, highNibble - 10 + 'A', size2, Color_Turn);
}
// 显示低半字节
if (lowNibble < 10)
{
OLED_ShowChar(x + (size2 / 2) * (2 * i + 1), y, lowNibble + '0', size2, Color_Turn);
}
else
{
OLED_ShowChar(x + (size2 / 2) * (2 * i + 1), y, lowNibble - 10 + 'A', size2, Color_Turn);
}
}
}
在oled.h加入:
void OLED_ShowHexArray(uint8_t x, uint8_t y, uint8_t *numArray, uint8_t Length, uint8_t size2, uint8_t Color_Turn);
在main.c实现轮询,
#include "string.h"
#include "stdio.h"
uint8_t RxDate[256];
unsigned int num = 0;
while(1)加入
cs
switch(HAL_UART_Receive(&huart1, RxDate, 200, 1))
{
case HAL_OK:
HAL_Delay(1);
HAL_UART_Transmit(&huart1, RxDate, 200, 1);
break;
case HAL_TIMEOUT:
if (huart1.RxXferCount != 200-1)
{
HAL_UART_Transmit(&huart1, RxDate, 200-1 - huart1.RxXferCount, 1);
}
else
{
HAL_Delay(1);
}
break;
case HAL_ERROR:
// 错误处理逻辑,可以根据需要添加
// 例如,重置 UART 或重新初始化
huart1.RxState = HAL_UART_STATE_READY;
__HAL_UNLOCK(&huart1);
break;
case HAL_BUSY:
// 处理 UART 忙碌状态的逻辑
// 可以选择等待一段时间再重试
HAL_Delay(1);
break;
default:
break;
}
OLED_ShowHexArray(48, 4, RxDate,1, 16, 0); // len 设置为 8,具体根据显示需求调整
}
代码解释:HAL_UART_Receive接收huart1句柄的数据,RxDate,长度为200,等待时间为0xffff(也就是1)。若接收完毕,返回HAL_OK,则发送数据回电脑。
测试:
2.中断模式:
USART的中断模式是一种数据传输方式,在这种模式下,当特定事件(如接收到一个字符或发送完一个字符)发生时,会触发中断请求,中断服务程序(ISR)负责处理这些事件。使用中断模式可以提高系统效率,因为在等待数据的过程中,CPU可以执行其他任务,而不需要不断轮询USART状态。
.中断模式收发数据
HAL_UART_Transmit_IT():串口中断模式发送
HAL_UART_Receive_IT(): 串口中断模式接收
cs
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
这个函数的目的是启动UART传输并以非阻塞的方式发送一定数量的数据。
参数说明:中断方式的收发函数只有三个参数
第一个参数是要使用的串口句柄地址
第二个参数是发送缓冲区的首地址,用于存放要发送的数据
第三个参数是发送缓冲区长度
cs
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
参数说明与发送函数类似,只是把第二个和第三个参数变为了接收缓冲区
程序:
main.c加入
uint8_t TxDate[64], RxDate[64];
uint8_t rxstate;
int main加入
HAL_UART_Receive_IT(&huart1,RxDate,1);
接收中断函数,我这里将长度设为了1,表示接收到一个字节,便接收完毕,返回一个字节,比如0x11 是一个十六进制数,表示的是一个字节(8位)数据。在计算机系统中,十六进制数 0x11
对应的二进制数是 00010001
,占用一个字节的存储空间。因此,0x11
占用1个字节。
while(1)加入
if(rxstate == 1)
{
rxstate = 0;
HAL_UART_Transmit_IT(&huart1,TxDate,1);
}
当接收标志位为1,则表示接收完成,如果标志位为1,便发送数据
最下面加上
cpp
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
memcpy(TxDate,RxDate,1);
rxstate = 1;
HAL_UART_Receive_IT(&huart1,RxDate,1);
}
}
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
}
memcpy(TxDate, RxDate, 1);
:将接收到的一个字节的数据从RxDate
复制到TxDate
。这里假设RxDate
是接收缓冲区,TxDate
是发送缓冲区。memcpy
函数用于内存拷贝,将RxDate
中的一个字节数据复制到TxDate
中。- 拷贝完成,标志位变成1
测试:
3.DMA模式:
什么是dma模式?
DMA(Direct Memory Access,直接内存访问)是一种计算机系统中用于数据传输的机制。它允许数据在外设和内存之间直接传输,而不需要CPU的介入,从而减轻了CPU的负担,提高了数据传输的效率。
举个例子:
想象一下我们搬家的场景:你要把家里的一些东西从旧房子搬到新房子。在传统的情况下,你可能要亲自搬每一箱东西,把它们从旧房子搬到新房子。这就相当于CPU传统地处理数据传输的方式。
现在,有一支搬家队,他们专门负责搬家。你只需要告诉他们从哪里搬,搬到哪里,然后他们就会自己完成这项任务。而你可以利用这段时间去做其他事情,不需要亲自动手。这就有点类似于DMA的工作原理。
在计算机中,CPU通常会处理数据的传输工作,就像你亲自搬家一样。但有了DMA,就好比有了一支专门负责数据传输的队伍。CPU只需要告诉DMA从哪里搬,搬到哪里,然后就可以去处理其他任务了。DMA负责在外设和内存之间直接传输数据,而不需要CPU一直参与。
简而言之,DMA就像是一支搬家队伍,负责在不需要CPU亲自操劳的情况下完成数据传输任务,从而提高了系统的效率。
DMA模式的优势
- 效率高:DMA能够以较高的效率传输数据,因为传输过程不需要经过CPU。
- 释放CPU资源:在数据传输过程中,CPU可以执行其他任务,避免了CPU因数据传输而被阻塞。
- 传输速度快:由于DMA控制器专门用于数据传输,其速度通常比通过CPU进行传输要快。
DMA模式的工作原理
- 配置DMA控制器:在使用DMA模式之前,需要配置DMA控制器,包括源地址、目的地址、传输数据的大小等。
- 启动DMA传输:配置完成后,启动DMA传输。DMA控制器将接管数据传输任务。
- 传输完成中断:在传输完成后,DMA控制器会生成一个中断,通知CPU传输已经完成。
DMA发送函数
cpp
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
参数类型和中断模式发送函数相同
DMA接收函数
cpp
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
参数类型和中断模式接收函数相同
HAL_UART_Transmit_DMA();串口DMA模式发送
HAL_UART_Transmit_DMA();串口DMA模式接收
HAL_UART_DMAPause() 暂停串口DMA
HAL_UART_DMAResume();恢复串口DMA
HAL_UART_DMAStop(); 结束串口DMA
因为比较多,关于代码部分,在日后文章中将具体写一下