> 技术文档 > ESP32与STM32(不定长数据)的串口通讯

ESP32与STM32(不定长数据)的串口通讯

ESP32与STM32作为两款嵌入式入门时最常接触到的芯片,接下来我分享一下我学习时实现的两芯片之间的通讯功能。

环境:
STM32:STM32CubeMX + Keil(Vscode)
ESP32:Platformio

只使用CubeMX + keil和arduino的小伙伴也可以放心食用。

1.接线图

我使用的是STM32F407ZGT6的最小系统板和ESPWROOM32板,其他型号的板子也不影响。

另外,两个板子需要使用同一种电源,即共地。TX->RX  RX->TX。

我们使用ESP32的串口2和STM32的串口3来进行通讯,其实只需要不使用两个板子的串口1通讯即可,因为串口1一般是用来打印调试信息的。

2.STM32CubeMx代码生成

基本的Cubemx时钟配置,SYS的配置这里不再过多赘述,直接讲串口通讯的配置。串口通信这里我们使用串口的DMA的接收与DMA的发送,为CPU减轻负担。

选择异步通讯模式,基本参数设置:15200的波特率、8位数据、1位停止位、其余默认即可。

然后点击DMA Settings ,再点击Add,增加USART3_RX和USART3_TX,增加后不需要修改,默认即可。

然后点击Parameter Settingsp旁边的NVIC设置,勾选USART3 gloable interrupt,因为要使用串口接收中断。

至此,CubeMx的配置已经完成,点击GENERATE CODE生成代码。

3.STM32的代码编写

其余代码不必在意,只需要在CubeMX生成的代码中添加上后面截图示意的代码即可完成功能。

ARRSIZE是我字节定义的一个宏定义。它长下面这样,使用sizeof来计算数组个数也是可以的。
 

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
/* USER CODE BEGIN Header *//** ****************************************************************************** * @file  : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2024 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** *//* USER CODE END Header *//* Includes ------------------------------------------------------------------*/#include \"main.h\"#include \"cmsis_os.h\"#include \"dma.h\"#include \"i2c.h\"#include \"rtc.h\"#include \"tim.h\"#include \"usart.h\"#include \"usb_otg.h\"#include \"gpio.h\"/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#include \"OLED.h\"#include \"stdio.h\"#include int fputc(int ch, FILE *f) // printf{ HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); // printf // HAL_UART_Transmit_DMA(&huart1, (uint8_t *)&ch, 1); return (ch);}int fgetc(FILE *f){ uint8_t ch; HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); // scanf return ch;}/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*//* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*//* USER CODE BEGIN PD *//* USER CODE END PD *//* Private macro -------------------------------------------------------------*//* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV */uint8_t serial_rxbuf[50];uint8_t serial3_rxbuf[50];DMA_HandleTypeDef hdma_u1_rx;DMA_HandleTypeDef hdma_u1_tx;/* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/void SystemClock_Config(void);void MX_FREERTOS_Init(void);/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 */void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, unsigned short Size) // uint16_t{ if (huart == &huart3) { printf(\"%s\\r\\n\", serial3_rxbuf); HAL_UARTEx_ReceiveToIdle_DMA(&huart3, serial3_rxbuf, sizeof(serial3_rxbuf)); } __HAL_DMA_DISABLE_IT(&hdma_u1_rx, DMA_IT_HT); // 关闭DMA传输过半中断,如果上面采用中断模式则不需要}void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ static uint8_t add = 0; if (GPIO_Pin == USER_KEY_Pin) { __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, add * 1000); __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_2, 0); __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_3, add * 1000); __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_4, 0); add += 1; if (add == 7) { add = 0; } }}/* USER CODE END 0 *//** * @brief The application entry point. * @retval int */int main(void){ /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_TIM3_Init(); MX_I2C1_Init(); MX_I2C2_Init(); MX_USART2_UART_Init(); MX_USART3_UART_Init(); MX_TIM2_Init(); MX_TIM4_Init(); MX_USART1_UART_Init(); MX_RTC_Init(); MX_USB_OTG_FS_PCD_Init(); /* USER CODE BEGIN 2 */ /* PWM初始化 */ HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2); /*编码器初始化 */ HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL); // 启动编码器 HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL); /*DMA初始化*/ HAL_UARTEx_ReceiveToIdle_DMA(&huart1, serial_rxbuf, ARRAY_SIZE(serial_rxbuf)); __HAL_DMA_DISABLE_IT(&hdma_u1_rx, DMA_IT_HT); // 关闭数据过半中断以获取完整数据 HAL_UARTEx_ReceiveToIdle_DMA(&huart3, serial3_rxbuf, ARRAY_SIZE(serial3_rxbuf)); __HAL_DMA_DISABLE_IT(&hdma_u1_rx, DMA_IT_HT); // 关闭数据过半中断以获取完整数据 /*其余初始化 */ OLED_Init(); /* USER CODE END 2 */ /* Init scheduler */ osKernelInitialize(); /* Call init function for freertos objects (in cmsis_os2.c) */ MX_FREERTOS_Init(); /* Start scheduler */ osKernelStart(); /* We should never get here as control is now taken by the scheduler */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1)

在main.c文件中/* USER CODE BEGIN 2 */这一栏开启串口3接收空闲中断,DMA方式(通过DMA(直接内存访问)方式接收UART(通用异步收发传输器)数据直到发生空闲(IDLE)事件)。并且关闭半字节传输。


相关变量定义(定义在:/* USER CODE BEGIN PV */内即可):

注意:这里的接收缓存变量只定义了50个字节,如果接收超过了50个字节,会丢失后面的数据。可以根据自己需要增加。

另外:我们可以把printf重定向到串口1(通过USB转TTL工具CH340模块把串口1连接到电脑上),用于打印我们接收到的ESP32的数据,后续直接使用printf就可以打印到串口1了,重定向这里也可以尝试换成DMA方式。

然后在/* USER CODE BEGIN 0 */里添加串口中断事件回调函数,然后打印串口3的接收缓冲,再开启接收空闲中断,以及关闭半字节中断。其实HAL_UARTEx_RxEventCallback这个函数就是标准库函数中的USART3_IRQHandler函数所调用的HAL_UART_IRQHandler调用的,他在stm32f4xx_it.c中已经自动生成。

然后我们在while(1)死循环中每隔5秒向串口3发送一次数据。如果是使用RTOS的朋友在任务中添加发送函数即可。

第一张图是裸机(跑裸机的朋友不需要看第二张图),第二张图是跑FreeRTOS。

/* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ HAL_Delay(5000); HAL_UART_Transmit_DMA(&huart3, \"hello,i am stm32\", sizeof(\"hello,i am stm32\")); /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
/* USER CODE BEGIN Header_StartDefaultTask *//** * @brief Function implementing the defaultTask thread. * @param argument: Not used * @retval None *//* USER CODE END Header_StartDefaultTask */void StartDefaultTask(void *argument){ /* USER CODE BEGIN StartDefaultTask */ /* Infinite loop */ for (;;) { osDelay(5000); HAL_UART_Transmit_DMA(&huart3, \"hello,i am stm32\", sizeof(\"hello,i am stm32\")); } /* USER CODE END StartDefaultTask */}

4.ESP32的代码编写

setup初始化中添加上两个串口初始化,串口2用于与STM32通讯,loop循环中添加如下代码,如果message非空那么就使用串口0 Serial.prtln打印接收到的数据到电脑(使用usb线把ESP32连接到电脑)。然后向STM32发送一个字符串“ESP32接收到数据”表示已接收。

#include #include #define WIFINAME \"A5602\"#define WIFIPASSWORD \"02946BD8\"void setup(){ Serial2.begin(115200); Serial.begin(115200);}void loop(){ String message = Serial2.readString(); if (!message.isEmpty()) { Serial.println(\"接收到数据: \" + message); Serial2.println(\"ESP32接收到数据\"); // 可以选择添加换行符 }}

5.打开串口工具查看调试信息

打开串口工具并找到对应的COM口。就可以看到每隔5S,两个芯片相互发送的信息,并且通过调试串口打印出来。