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,两个芯片相互发送的信息,并且通过调试串口打印出来。
作者:非复吴下阿蒙