【笔记】STM32CubeMx+串口空闲中断+DMA——利用函数HAL_UARTEx_ReceiveToIdle_DMA实现不定长数据接收——STM32F103ZET6(匿名上位机/助手基本收发可用)

        注意:利用HAL库的 HAL_UARTEx_ReceiveToIdle_DMA()函数,代码比较简洁, 如果编辑器找不到函数HAL_UARTEx_ReceiveToIdle_DMA(),需要更新HAL库至最新版本。

一、串口及DMA的CubeMx配置

56954478bb9f40ee9c10cf04e653cd12.png

5b83808e496d4cffa9162345de0d3238.png 

打开串口接收DMA,模式选择Normal,然后再回调函数HAL_UARTEx_RxEventCallback中重新调用HAL_UARTEx_ReceiveToIdle_DMA函数即可实现不断接收不定长数据的效果。034f6b30f46c4151bde072c17708e8f8.png

 配置波特率:c3196971caec44e59d50ab40b2fb4d9e.png

注意要打开串口的全局中断,把相应的DMA中断关闭,对于不定长数据,DMA中断判别不了到底要接收几个字节的数据才中断,而串口可以,否则容易引起异常。如果是定长的数据的话,可以关闭串口中断,直接开DMA中断。​​​​646f3f7d752f4da389bb06719629e970.png

 

点击侧边栏的NVIC选项,取消选中 Force DMA channels Interrupts,这样DMA就可以自定义DMA中断优先级了。

c8e25a7ae11b440486a540fd2a445f06.png

 配置时钟树:

fd52419b13204ce9a4a4c8919b8b6472.png

227f50e94a944d77adb66ca0ecfe7ff8.png 

85de7fb8e484409f813f4af7c56fe4bc.png 然后即可生成代码。

 程序部分:

#include "stdio.h"
#include "string.h"
#include <stdarg.h>
#include "usart.h"
#include "dma.h"

b46eae5e953b4a2883166e0dbcd3047d.png

 13431dd5c7074a03adb0559dba2fb812.png

uint8_t rx_buffer[BUF_SIZE];  // 创建接收缓存,大小为BUF_SIZE

287bf268122f4c8c9230db71333d49fe.png

#define BUF_SIZE 200
extern uint8_t rx_buffer[BUF_SIZE];  // 创建接收缓存,大小为BUF_SIZE

e8fe840ffc6d488e8be0565a5c152f34.png

HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE);

094302da1e36447ab1caa237eea152bc.png

 这里也加入这个函数。

最后调用事件回调函数HAL_UARTEx_RxEventCallback();

ed297f54476e43f0ba597f5040498793.png

 

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{	
    if (huart->Instance == USART1)
    {
        Size = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        HAL_UART_Transmit(&huart1, rx_buffer, Size, 0xffff);	//将接受到的数据再发回上位机
        memset(rx_buffer, 0, Size);
		
		//放在void USART1_IRQHandler(void)函数里,不要放在这
		//HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE);不要放在这里,如果放在这里,不仅上位机的波特率改变不能正常接收,就算改回去了也会接收不了
    }
}

测试:匿名助手及其他串口助手

4f7dab8ca126499f98b35ebae08e1ba9.png

6bc165a5a2e5432cb566f8cd6cb78e2b.png 

 增强代码移植性:

main.h:

b1daefeecb14408296970ffccdb59f6d.png

//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

//Ex_NVIC_Config专用定义
#define GPIO_A 0
#define GPIO_B 1
#define GPIO_C 2
#define GPIO_D 3
#define GPIO_E 4
#define GPIO_F 5
#define GPIO_G 6 

#define FTIR   1  //下降沿触发
#define RTIR   2  //上升沿触发

//JTAG模式设置定义
#define JTAG_SWD_DISABLE   0X02
#define SWD_ENABLE         0X01
#define JTAG_SWD_ENABLE    0X00	

串口重定向paintf

e530b89acc1f43a989e21bbd61e89e89.png

 记得.h文件声明函数。

void my_printf(int ch,uint8_t *format, ...)
{
  char buf[512]; //定义临时数组,根据实际发送大小微调
	
  va_list args;
  va_start(args, format);
  uint16_t len = vsnprintf((char *)buf, sizeof(buf), (char *)format, args);
  va_end(args);
	if(ch == 1)
		HAL_UART_Transmit(&huart1,(uint8_t *)buf,len,1000);
//	else if(ch == 2)
//		HAL_UART_Transmit(&huart2,(uint8_t *)buf,len,1000);
//	else if(ch == 3)
//		HAL_UART_Transmit(&huart3,(uint8_t *)buf,len,1000);
}

void u1_printf(uint8_t *format)
{
	my_printf(1,format);
}

//void u2_printf(char *format)
//{
//	my_printf(2,format);
//}
//void u3_printf(char *format)
//{
//	my_printf(3,format);
//}

这样,可以直接用u1_printf发送了,那么下面可以修改为:

becd59ce345f4450bd9382f8a5dae271.png

 27606f8efa9c4498a1431d86b6ceba7b.png

 

 

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
STM32 中,可以通过 DMA 直接将串口接收到的数据存储到内存中,从而减轻 CPU 的负担。在使用 DMA 接收串口数据时,可以通过 `HAL_UARTEx_RxEventCallback` 函数来处理 DMA 接收数据完成后的回调。 `HAL_UARTEx_RxEventCallback` 的函数原型为: ```c void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart); ``` 其中,`huart` 是串口的句柄指针。 在使用 DMA 接收串口数据时,需要先开启 DMA,然后在回调函数中处理接收到的数据。下面是一个使用 DMA 接收串口数据的例子: ```c #include "stm32f4xx_hal.h" #define UART_RX_BUFFER_SIZE 256 UART_HandleTypeDef huart2; DMA_HandleTypeDef hdma_usart2_rx; uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE]; void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(uartHandle->Instance==USART2) { /* USART2 clock enable */ __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**USART2 GPIO Configuration PA2 ------> USART2_TX PA3 ------> USART2_RX */ GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USART2 DMA Init */ /* USART2_RX Init */ hdma_usart2_rx.Instance = DMA1_Stream5; hdma_usart2_rx.Init.Channel = DMA_CHANNEL_4; hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_rx.Init.Mode = DMA_CIRCULAR; hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW; hdma_usart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx); /* USART2 interrupt Init */ HAL_NVIC_SetPriority(USART2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(USART2_IRQn); } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART2) { // 处理接收到的数据 // ... } } int main(void) { HAL_Init(); __HAL_RCC_GPIOA_CLK_ENABLE(); // 配置串口 huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } // 配置 DMA 接收 HAL_UART_Receive_DMA(&huart2, uart_rx_buffer, UART_RX_BUFFER_SIZE); while (1); } ``` 在上面的代码中,我们在 `HAL_UART_MspInit` 函数中初始化了 DMA,并将 DMA串口绑定起来。在 `main` 函数中,我们使用 `HAL_UART_Receive_DMA` 开启了 DMA 接收并指定了接收缓冲区的地址和大小。当 DMA 接收完成后,会自动触发 `HAL_UART_RxCpltCallback` 回调函数,我们可以在该函数中处理接收到的数据。 需要注意的是,在使用 DMA 接收串口数据时,需要确保接收缓冲区的大小大于等于最大接收数据度,否则可能会出现数据溢出的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值