stm32双串口+DMA接收不定长数据,用于调试与ESP8266通信

本文介绍了在STM32上使用双串口和DMA解决与ESP8266通信时接收不定长数据的问题。通过串口空闲中断和DMA,实现了数据的完整传输。在串口空闲中断处理函数中,标记数据接收完成,并计算接收长度。遇到的问题是较长数据接收不全,可能需要在中断服务函数中添加延时。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

才发现CSDN把文章变成VIP可查看了,坑人!!!现已重新发布

------------------------------------------------------------------------------------------------------------------------------------

本文为本人学习esp8266过程的学习笔记,源码在最后,接收不定长数据的方法也可以应用在其它地方

由于项目需求,购买了ESP8266作为STM32数据发送到云服务器的桥梁。调试esp8266过程中也遇到了问题。后面采用了串口空闲中断+DMA接收的方法解决了接收不定长数据的问题

esp8266的工作原理我就不赘述了,我看的下面大佬的博客:

(29条消息) 基于STM32的ESP8266使用教程(一)_量化这个世界!-CSDN博客

(29条消息) 【常用模块】ESP8266 WIFI串口通信模块使用详解(实例:附STM32详细代码)_Yngz_Miao的博客-CSDN博客_esp8266wifi模块教程

问题:如何在PC端发送AT指令到stm32,stm32再传输给esp8266,最后将esp8266传回的数据通过串口打印出来。由于ESP8266返回的数据是不定长数据,并且没有规定好的结束符,因此无法用串口接收中断来处理数据。

解决方案:

对于接收不定长数据,如何知道完成了数据的接收呢?

最常用的办法是使用空闲中断,在串口空闲的时候,触发一次中断,通知内核,本次运输完成了。串口空闲中断的判定是:当串口开始接收数据后,检测到1字节数据的时间内没有数据发送,则认为串口空闲了。由于我们的内核在串口接收数据到空闲这段时间,是不受理串口数据的,所以我们还需要使用DMA来协助我们把数据传送到指定的地方,当数据传输完成后,通知内核去处理。

实现流程

(1)开启串口空闲中断: 在程序初始化时候,使能串口中断
(2)定义串口空闲中断处理函数: 在串口中断中添加串口空闲中断处理函数 ,用以标记数据接收完成,计算接收到数据的长度我采用了stm32上的双串口通信,串口1(Lpuart1)用来PC端发送数据以及接受数据,串口4(uart4)用来与esp8266通信。通信流程如下图:

PC发送指令到Lpuart1,在Lpuart1开启接收中断,在中断回调函数里,将接收到的数据发送给uart4,uart4采用DMA串口空闲中断接收(uart4的RX、TX与esp8266的TX,RX相连),uart4接收到的数据会直接发送给esp8266,esp8266返回来的数据通过printf函数(已重定向到串口1)打印到PC。

经过调试,功能有一点瑕疵:如果在进入串口4空闲中断服务函数时不加延时,那么8266返回的一些比较长的数据会接收不到(例如PC发送:AT+GMR),若有大佬知道是什么原因,还望指导小弟以下

实现流程:

在cubemx里使能两个串口,并在串口4开启DMA接收,模式设置为循环。

下面为代码(用到的代码都有注释解释):

usart.h里添加的代码

#include “stdio.h”

/*重定向printf函数*/
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */

/*串口2的接收缓冲变量*/
#define  Rxbuffer4_Size  1024
extern uint8_t c;
extern uint8_t RxBuffer_4[];
extern uint16_t RxNum_4;

/*串口L1的接收缓冲变量*/
extern uint8_t aRxBuffer;			//接收中断缓冲
extern uint8_t Uart1_RxBuff[256];		//接收缓冲
extern uint8_t Uart1_Rx_Cnt ;		//接收缓冲计数
extern uint8_t	cAlmStr[] ;

usart.c里添加的代码

/* USER CODE BEGIN 0 */
uint8_t RxBuffer_4[Rxbuffer4_Size];//数据缓冲数组
uint16_t RxNum_4 = 0;//接收的数据长度
/* USER CODE END 0 */

void MX_LPUART1_UART_Init(void)
{
    
    
    /* USER CODE BEGIN LPUART1_Init 2 */
    /*使能串口1的接收中断*/
    HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&aRxBuffer, 1);
    /* USER CODE END LPUART1_Init 2 */

}

void MX_UART4_Init(void)
{
    /* USER CODE BEGIN UART4_Init 2 */
	//开启串口4的空闲中断
	__HAL_UART_ENABLE_IT(&huart4, UART_IT_IDLE);
  /* USER CODE END UART4_Init 2 */
}


/*添加重定向的代码*/
PUTCHAR_PROTOTYPE
{
  /* e.g. write a character to the LPUART1 and Loop until the end of transmission */
  HAL_UART_Transmit( &hlpuart1, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;
}

stm32g4xx_it.c里添加的代码

/*在串口4的中断服务函数里添加下面的内容*/
void UART4_IRQHandler(void)
{
	//判断是否是空闲中断
	if(__HAL_UART_GET_FLAG(&huart4, UART_FLAG_IDLE) == SET)   
	{  
			HAL_Delay(100);//如果没有这个延时,接收8266回来的数据有时会无法显示,例如发送AT+GMR
						  //例如发送AT+GMR,返回的数据就无法正常显示,具体原因我也还没搞懂
			printf("\r\n我是串口2,我收到数据了\r\n");
			//清除空闲中断标志(否则会一直不断进入中断)
			__HAL_UART_CLEAR_IDLEFLAG(&huart4);
			//暂停DMA接收
			HAL_UART_DMAStop(&huart4);
			RxNum_4 = Rxbuffer4_Size - __HAL_DMA_GET_COUNTER(&hdma_uart4_rx);
			printf("\r\n我是串口4,我收到的数据长度为:\r\n%s\r\n",RxBuffer_4);
			//将数据缓冲数组清零
			memset(RxBuffer_4 ,0,Rxbuffer4_Size);
			//使能下一次的DMA接收
			HAL_UART_Receive_DMA(&huart4,RxBuffer_4,Rxbuffer4_Size);
	
	}
}

/*中断回调函数*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
	 
	if(huart->Instance == LPUART1) 
	{
			if(Uart1_Rx_Cnt >= 255)  //溢出判断
			{
				Uart1_Rx_Cnt = 0;
				memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff));//用来清空数组,memset是一个清空数组函数
				HAL_UART_Transmit(&hlpuart1, (uint8_t *)&cAlmStr, sizeof(cAlmStr),0xFFFF);	
			}
			else
			{
				Uart1_RxBuff[Uart1_Rx_Cnt++] = aRxBuffer;   //接收数据转存
				
			  if((Uart1_RxBuff[Uart1_Rx_Cnt-1] == 0x0A)&&(Uart1_RxBuff[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
				{
					printf("\r\n我是串口1,我现在发送指令过去了,现在是第%d次发送\r\n",c);
					c++;
					HAL_UART_Transmit(&huart4, (uint8_t *)&Uart1_RxBuff, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
					while(HAL_UART_GetState(&hlpuart1) == HAL_UART_STATE_BUSY_TX)//检测UART发送结束
					Uart1_Rx_Cnt = 0;
					printf("\r\n我是串口1,我现在要清空数组了\r\n");
					memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff)); //用来清空数组,memset是一个清空数组函数
				}
			}
			
			HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&aRxBuffer, 1);   //再开启接收中断
		}			
}

 工程文件:

链接:https://pan.baidu.com/s/1JiPnx1QgEbOCdMDVv1Up_A 
提取码:at89

### STM32ESP8266之间通过串口实现数据交换 #### 硬件连接方式 为了使STM32能够ESP8266正常通信,两者间的硬件连接至关重要。通常情况下,需要将STM32的USART TX引脚连接到ESP8266的RXD引脚;同样地,STM32的RX引脚应连接至ESP8266的TXD引脚[^1]。 另外值得注意的是,由于部分型号的ESP8266工作电压为3.3V逻辑电平,而某些STM32系列可能支持5V耐受输入或同样是3.3V逻辑电平,在这种情况下可以直接相连而不必担心损坏设备。然而如果存在不兼容情况,则需采用合适的电平转换电路来确保信号传输的安全性和准确性。 #### 软件编程接口配置 对于软件方面而言,主要涉及到初始化设置以及发送接收函数编写两大部分: - **初始化设置** 在开始之前要先完成对STM32内部USART外设的相关参数设定,比如波特率、停止位数等。这里假设已经完成了基本的库文件引入和必要的头文件声明等工作之后,可以按照如下方式进行初始化操作: ```c // 初始化USART端口 void USART_Init(void){ // 配置GPIO模式, 设置对应的引脚功能为复用推挽输出/浮空输入 GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // PA9-TX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // PA10-RX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置USART参数 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No ; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1,&USART_InitStructure); // 开启中断并允许接收中断请求 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 启动USART USART_Cmd(USART1,ENABLE); } ``` 上述代码片段展示了如何针对特定的MCU(如STM32F1xx)及其上的USART1进行基础性的初始化配置过程。 - **发送接收函数编写** 当准备就绪后就可以着手于具体的收发处理了。下面给出了一组简单的例子用于说明怎样向ESP8266发送命令字符串以及从其处获取响应信息的方法: ```c #include "usart.h" char buffer[64]; // 缓冲区大小可根据实际需求调整 // 发送AT指令给ESP8266 void Send_AT_Command(char *cmd){ while(*cmd != '\0'){ USART_SendData(USART1,*cmd++); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){}; } } // 接收来自ESP8266的信息 uint8_t Receive_Data_From_ESP(){ uint8_t i=0; char ch; do{ if(USART_GetITStatus(USART1, USART_IT_RXNE)!=RESET){ ch = USART_ReceiveData(USART1); buffer[i++] = ch; if(ch=='\r'||ch=='\n') break; } }while(i<sizeof(buffer)-1); buffer[i]='\0'; return i>0?i:0; } ``` 以上两个辅助函数分别实现了向ESP8266发送指定字符序列的功能以及等待直到接收到完整的回应为止的过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值