stm32-hal库(5)--usart串口通信三种模式(主从通信)(关于通信失败和串口不断发送数据问题的解决)

问题:

最近发现,stm32cubemx最新版本f1系列的hal库(1.85版本)生成的hal库,其中stm32f1xx_hal_uart.c的库文件中,其串口发送接收存在一些问题:

1.没有使用 __HAL_LOCK__HAL_UNLOCK 宏,锁机制。

2.pdata8bitspdata16bits 未初始化为 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模式的优势

  1. 效率高:DMA能够以较高的效率传输数据,因为传输过程不需要经过CPU。
  2. 释放CPU资源:在数据传输过程中,CPU可以执行其他任务,避免了CPU因数据传输而被阻塞。
  3. 传输速度快:由于DMA控制器专门用于数据传输,其速度通常比通过CPU进行传输要快。

DMA模式的工作原理

  1. 配置DMA控制器:在使用DMA模式之前,需要配置DMA控制器,包括源地址、目的地址、传输数据的大小等。
  2. 启动DMA传输:配置完成后,启动DMA传输。DMA控制器将接管数据传输任务。
  3. 传输完成中断:在传输完成后,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

因为比较多,关于代码部分,在日后文章中将具体写一下

相关推荐
来自晴朗的明天35 分钟前
16、电压跟随器(缓冲器)电路
单片机·嵌入式硬件·硬件工程
钰珠AIOT1 小时前
在同一块电路板上同时存在 0805 0603 不同的封装有什么利弊?
嵌入式硬件
代码游侠1 小时前
复习——Linux设备驱动开发笔记
linux·arm开发·驱动开发·笔记·嵌入式硬件·架构
代码游侠12 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
xuxg200514 小时前
4G 模组 AT 命令解析框架课程正式发布
stm32·嵌入式·at命令解析框架
CODECOLLECT15 小时前
京元 I62D Windows PDA 技术拆解:Windows 10 IoT 兼容 + 硬解码模块,如何降低工业软件迁移成本?
stm32·单片机·嵌入式硬件
BackCatK Chen16 小时前
STM32+FreeRTOS:嵌入式开发的黄金搭档,未来十年就靠它了!
stm32·单片机·嵌入式硬件·freertos·低功耗·rtdbs·工业控制
全栈游侠19 小时前
STM32F103XX 02-电源与备份寄存器
stm32·单片机·嵌入式硬件
Lsir10110_19 小时前
【Linux】中断 —— 操作系统的运行基石
linux·运维·嵌入式硬件