STM32CubeMX配置串口DMA传输实现不定长数据收发

串口简介

串口是全双工的串行通信协议。串口通信指串口按位(bit)发送和接收字节(一个字节有8位)。尽管比特字节(byte)的串行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。串口通信协议是指规定了数据包的内容,内容包含了起始位、主体数据、校验位及停止位,双方需要约定一致的数据包格式才能正常收发数据的有关规范。串口通信协议是基于串口使得通信双方能够相互沟通信息的一种约定,其定义了双方遵循的协议数据帧格式和其传输方式。因为串口通信没有时钟线,说设备双方必须约定好相同的波特率,这样才能保证数据收发准确无误。常见的波特率有4800、9600、115200等。

起始位、停止位
数据包从起始位开始,到停止位结束。起始信号用逻辑0(最开始为拉高,当拉低后表示起始)的数据位表示,停止信号由0.5、1、1.5或2个逻辑1的数据位表示,只要双方约定一致即可。
有效数据
起始位之后便是传输的主体数据内容了,也称为有效数据,其长度一般被约定为5、6、7或8位长。
数据校验
由于在通讯过程中易受到外部干扰导致传输数据出现偏差,所以在有效数据之后加上校验位解决。校验方法有奇校验(odd)、偶校验(even)、0校验(space)、1校验(mark)及无校验(noparity)。
奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个8位长的有效数据为:01101001,此时共有4个“1”,为达到奇校验效果,校验位为“1”,最后传输的是8位有效数据加1位校验位,共9位。
偶校验刚好相反,要求有效数据和校验位的“1”数量为偶数,则此时为达到偶校验效果,校验位为“0”。而0校验则无论有效数据中是什么数据内容,校验位总是为“0”,1校验校验位总是为“1”。

CubeMX配置

配置好时钟。
串口配置:使用默认配置(传输数据长度为8 Bit,奇偶检验无,停止位为1 Bit, 接收和发送都使能)
在这里插入图片描述
打开DMA传输:
在这里插入图片描述
打开串口接收中断:
在这里插入图片描述生成工程;

添加代码

添加代码实现printf函数。
在usart.c中添加如下代码,记住一定要添加在
/* USER CODE BEGIN 1 /
/
USER CODE END 1 */
里面,否则一旦CubeMX重新生成工程,就会不见。

/* USER CODE BEGIN 0 */
#include "stdio.h"
/* USER CODE END 0 */

/* USER CODE BEGIN 1 */
#if 1
#pragma import(__use_no_semihosting)                             
struct __FILE
{
    
    
int handle;
};
FILE __stdout;      
void _sys_exit(int x)
{
    
    
   x = x;
}
int fputc(int ch, FILE *f)
{
    
         
  while((USART1->SR&0X40)==0);  
  USART1->DR = (uint8_t) ch;     
  return ch;
}
#endif

/* USER CODE END 1 */

然后就可以使用printf函数了。
这里我们先了解一下串口的IDLE中断
串口IDLE中断,能自动响应你从电脑(别的串口)接收到的不定长数据。而不是一直干等着。stm32接收数据时,并不会马上把数据马上处理掉,而是写到你定义的缓冲区里,然后你串口线上一个BYTE长度的时间如果没接收到数据,就会产生了IDLE中断(只会产生一次,别担心一直会卡在里面,除非等下次再接收时)。
接着在usart.c文件代码,重写串口的空闲中断回调函数:void USAR_UART_IDLECallback(UART_HandleTypeDef *huart,uint8_t rxlen )。HAL库中没有定义IDLE的中断处理函数,所以需要我们自己定义。
在此之前我们先在usart.h中定义一些缓存数组和一些会用到的变量,和添加必要的头文件。

#include "string.h"

/* USER CODE BEGIN Private defines */
#define UART1_RX_SIZE 100	

extern uint8_t UART1_RX_BUF[];  //串口接收缓存
extern uint8_t UART1_RX_LEN;  //接收到的数据量
extern uint8_t UART1_RX_Data[];  //数据缓存

/* USER CODE END Private defines */
void USAR_UART_IDLECallback(UART_HandleTypeDef *huart,uint8_t rxlen )
{
    
    
	if(huart == &huart1)  //判断是否为串口1产生中断
	{
    
    
		memcpy(UART1_RX_Data,UART1_RX_BUF,rxlen);  //将UART1_RX_BUF的数据复制到UART1_RX_Data中,最后两位是标志不是数据
		HAL_UART_Transmit_DMA(&huart1, UART1_RX_BUF,rxlen);//将接收到的不定长数据发送到上位机
	  rxlen = 0;//清除数据长度计数
HAL_UART_Receive_DMA(&huart1,UART1_RX_BUF,UART1_RX_SIZE);//重新打开DMA接收	        
	}
}
//写完后记得在usart.h文件中声明一下

在stm32f1xx_it.c,找到void USART1_IRQHandler(void)函数,添加我们自己的中断处理函数。

void USART1_IRQHandler(void)
{
    
    
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

	if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET))
	{
    
    
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);  //清楚空闲状态标志

		HAL_UART_DMAStop(&huart1);  //关闭DMA传输

		UART1_RX_LEN = UART1_RX_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);  //计算接收到的数据长度
		USAR_UART_IDLECallback(&huart1,UART1_RX_LEN );  //调用回调函数
	}
  /* USER CODE END USART1_IRQn 1 */
}

接着在main.c文件添加代码,使能串口空闲中断和启动DMA传输。还有记得观察一下ain函数中的DMA初始化函数有没有在串口初始化之前,我之前遇到过DMA初始化函数没在串口初始化之前导致程序没有达到预想的效果。

__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);  //使能空闲中断
	HAL_UART_Receive_DMA(&huart1,UART1_RX_BUF,UART1_RX_SIZE);  //启动DMA接收,UART1_RX_BUF:数据接收缓冲

接下我们就可以用串口做很多东西了,比如连接蓝牙,WiFi模块等进行控制单片机上的外设。
以蓝牙控制舵机为例,舵机驱动这一部分参考我前面的博客http://t.csdn.cn/4iO1r

void SG90_Select(void)
{
    
    
	if(UART1_RX_Data[0]=='S' && UART1_RX_Data[1] == 'W' && UART1_RX_Data[2] == '1') //UART1_RX_Data定义的数据接收缓冲数组
		{
    
    
			SG90_Rotate(&htim2,1,Degrees_0);
		}
    else if(UART1_RX_Data[0]=='S' && UART1_RX_Data[1] == 'W' && UART1_RX_Data[2] == '2')
    {
    
    
			SG90_Rotate(&htim2,1,Degrees_45);
		}
    else if(UART1_RX_Data[0]=='S' && UART1_RX_Data[1] == 'W' && UART1_RX_Data[2] == '3')
    {
    
    
			SG90_Rotate(&htim2,1,Degrees_90);
		}
    else if(UART1_RX_Data[0]=='S' && UART1_RX_Data[1] == 'W' && UART1_RX_Data[2] == '4')
    {
    
    
			SG90_Rotate(&htim2,1,Degrees_135);
		}
    else if(UART1_RX_Data[0]=='S' && UART1_RX_Data[1] == 'W' && UART1_RX_Data[2] == '5')
    {
    
    
			SG90_Rotate(&htim2,1,Degrees_180);
		}
}

猜你喜欢

转载自blog.csdn.net/qq_53000374/article/details/127810888