在网上看了很多的教程,我移植之后,一直存在最多收发200来个字节的问题,没有发现是哪里的原因,查阅了很多资料,总结出来一个靠谱的方案,实测收发2K以上的数据没有问题。记录如下。
参考链接: (主要参考了链接5中的代码)
1.C语言中volatile关键字的学习
2.【STM32】HAL库 STM32CubeMX教程十一—DMA (串口DMA发送接收)
3.C语言中volatile关键字的学习
4.一个严谨的STM32串口DMA发送&接收(1.5Mbps波特率)机制
5.真正实现了STM32 HAL串口不定长数据的接收发送功能(DMA方式,不用限定单次接收长度和添加结束标志)
1.配置STM32CUBE
- 我以STM32L071为例,该教程原封不动的移植到STM32F103我试过没有任何问题
1)时钟
自己看着配就行,配的时钟频率越高,程序执行速度越快,越不容易出问题
2)SWD两线刷写
不配置刷不进去程序
3)USART1
看图,参数设置中可以设置自己需要的波特率,我这里配置的115200
再USART1页面中还需要配置DMA Setting和NVIC Setting
4)生成代码
略
2.代码编写
1)增加位带操作、u8、u16、u32
我粘贴在了在gpio.h中,这个例程里头没有用到位带操作
//u8u16u32
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
//位带
typedef struct
{
u16 bit0 : 1;
u16 bit1 : 1;
u16 bit2 : 1;
u16 bit3 : 1;
u16 bit4 : 1;
u16 bit5 : 1;
u16 bit6 : 1;
u16 bit7 : 1;
u16 bit8 : 1;
u16 bit9 : 1;
u16 bit10 : 1;
u16 bit11 : 1;
u16 bit12 : 1;
u16 bit13 : 1;
u16 bit14 : 1;
u16 bit15 : 1;
} Bits16_TypeDef;
#define PAin(n) ( ( GPIOA->IDR&(1 << (n)) )>>n )
#define PBin(n) ( ( GPIOB->IDR&(1 << (n)) )>>n )
#define PCin(n) ( ( GPIOC->IDR&(1 << (n)) )>>n )
#define PDin(n) ( ( GPIOD->IDR&(1 << (n)) )>>n )
#define PEin(n) ( ( GPIOE->IDR&(1 << (n)) )>>n )
#define PFin(n) ( ( GPIOF->IDR&(1 << (n)) )>>n )
#define PAout(n) ( ((Bits16_TypeDef *)(&(GPIOA->ODR)))->bit##n )
#define PBout(n) ( ((Bits16_TypeDef *)(&(GPIOB->ODR)))->bit##n )
#define PCout(n) ( ((Bits16_TypeDef *)(&(GPIOC->ODR)))->bit##n )
#define PDout(n) ( ((Bits16_TypeDef *)(&(GPIOD->ODR)))->bit##n )
#define PEout(n) ( ((Bits16_TypeDef *)(&(GPIOE->ODR)))->bit##n )
#define PFout(n) ( ((Bits16_TypeDef *)(&(GPIOF->ODR)))->bit##n )
2)usart.c
- 加入DMA接收用缓存数组
/* USER CODE BEGIN 0 */
u8 RxBuffer[UART_RX_BUF_SIZE];
/* USER CODE END 0 */
- 加入三个函数,fputc用来printf打印输出,本例程没有用到该函数的功能
- HAL_UART_TxCpltCallback,串口空闲中断回调函数,当串口空闲时调用该函数
- ProcessData,用来将RxBuffer中的数据通过DMA发送至串口
/* USER CODE BEGIN 1 */
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,100);
return ch;
}
/*
*********************************************************************************************************
* 函 数 名: HAL_UART_IdleCallback
* 功能说明: 串口空闲中断回调函数,当串口空闲时调用该函数
* 形 参: *huart
* 返 回 值: 无
*********************************************************************************************************
*/
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)
{
__HAL_UART_CLEAR_IDLEFLAG(huart); //清除空闲标志位
HAL_UART_DMAStop(huart); //停止DMA,防止其继续接收数据捣乱
ProcessData(); //将DMA接收到的数据通过DMA发送至串口
HAL_UART_Receive_DMA(&huart1, RxBuffer, UART_RX_BUF_SIZE); //使用DMA继续接收数据到RxBuffer
}
/*
*********************************************************************************************************
* 函 数 名: ProcessData
* 功能说明: 将RxBuffer中的数据通过DMA发送至串口
* 形 参: *huart
* 返 回 值: 无
*********************************************************************************************************
*/
void ProcessData(void)
{
u32 len;
len = UART_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//已经接收了多少个字节 = 总共要接收的字节数 - NDTR
if(len > 0) //如果确实接收到了数据
{
HAL_UART_Transmit_DMA(&huart1, RxBuffer, len); //将RxBuffer中的数据通过DMA发送到串口
}
}
/* USER CODE END 1 */
这里如果是hlpuart的话,为__HAL_DMA_GET_COUNTER(&hdma_lpuart1_rx)
3)usart.h
- 头文件
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include "gpio.h"
/* USER CODE END Includes */
- 函数及变量声明,UART_RX_BUF_SIZE是RxBuffer数组的大小,可以随便设,不可超过65535
/* USER CODE BEGIN Private defines */
#define UART_RX_BUF_SIZE 2048
void ProcessData(void);
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart);
/* USER CODE END Private defines */
4)stm32l0xx_it.c
- 我们在usart.c中定义了空闲中断回调函数,应该在串口中断中引用一下
- 引用usart.h头文件,因为有空闲中断回调函数
/* USER CODE BEGIN Includes */
#include "usart.h"
/* USER CODE END Includes */
- 中断响应函数
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))
{
HAL_UART_IdleCallback(&huart1);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
5)main.c
- 定义一下RxBuffer
/* USER CODE BEGIN 0 */
extern u8 RxBuffer[UART_RX_BUF_SIZE];
/* USER CODE END 0 */
- 在MX_USART1_UART_Init()后加入以下指令,这些指令可以整合到一个函数中,有的人喜欢放MX_USART1_UART_Init()中,那样CUBE更新就没了。
- 使能接收中断和使能IDLE中断是必须开的
- DMA接收函数在while之前,保证了在进入while之前能够接收到DMA的指令,否则第一次数据是空的。
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); //使能接收中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
//DMA接收函数,此句一定要加,不加接收不到第一次传进来的实数据,是空的,且此时接收到的数据长度为缓存器的数据长度
HAL_UART_Receive_DMA(&huart1,RxBuffer,UART_RX_BUF_SIZE);
/* USER CODE END 2 */
3.实验结果
发送2047字节,接受到2047字节,实验成功。