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 <string.h>
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 <Arduino.h>
#include <WiFiMulti.h>
#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,两个芯片相互发送的信息,并且通过调试串口打印出来。

作者:非复吴下阿蒙

物联沃分享整理
物联沃-IOTWORD物联网 » ESP32与STM32(不定长数据)的串口通讯

发表回复