STM32 与 ESP32-C3 的“无线配对”:WiFi 和蓝牙功能大揭秘


参考资料

ESP32-C3

AT固件烧录成功后,重启,串口输出日志如下:

image-20241215211656572

这里要注意以下AT固件的串口波特率配置,稍后STM32和与之通信时,需要配置相同的波特率

硬件连接

  • STM32F103ZET6
  • ESP32-C3FN4

image-20241215210956751

测试AT指令

Cube配置

串口1用来打印日志

image-20241215211448149

串口2用来和ESP32通信

波特率需要和ESP32 AT固件的保持一致:115200

image-20241215211520223

示例代码

esp32.h

//
// Created by 86157 on 2024/12/15.
//

#ifndef INC_40_ESP32_HAL_ESP32_H
#define INC_40_ESP32_HAL_ESP32_H

#include "stm32f1xx.h"

void ESP32_init(void);
void ESP32_SendAtCmd(char *atCmd);

#endif //INC_40_ESP32_HAL_ESP32_H

esp32.c

要特别注意复用缓冲区responseBuf时,如果上一次事务留存的数据影响下一次事务处理过程中的判断,需要在每次事务的开头将其清零。

//
// Created by 86157 on 2024/12/15.
//

#include "ESP32.h"
#include "usart.h"
#include <string.h>
#include "logger.h"

#define RXBUF_MAX_SIZE 256
#define RESPONSE_BUF_MAX_SIZE 512
#define AT_OK "OK"
#define AT_ERROR "ERROR"
#define AT_SUFFIX "\r\n"
#define AT_RST ("AT+RST" AT_SUFFIX)
#define AT_TEST ("AT" AT_SUFFIX)
#define AT_GMR ("AT+GMR" AT_SUFFIX)

uint8_t rxBuf[RXBUF_MAX_SIZE] = {0};
__IO uint8_t rxSize = 0;

uint8_t responseBuf[RESPONSE_BUF_MAX_SIZE] = {0};
__IO uint8_t responseSize = 0;

void ESP32_init(void) {
    MX_USART2_UART_Init();
    // 接收定长/空闲检测+中断 非阻塞接收数据到rxBuf中
    HAL_UARTEx_ReceiveToIdle_IT(&huart2, rxBuf, RXBUF_MAX_SIZE);
    // 发送复位指令
    ESP32_SendAtCmd(AT_RST);
    HAL_Delay(2000); // 等待芯片复位完成

    // 测试AT指令
    ESP32_SendAtCmd(AT_TEST);
    ESP32_SendAtCmd(AT_GMR);
}

void ESP32_SendAtCmd(char *atCmd) {
    responseSize = 0; // 记录发送指令后,收到的响应字节数量
    rxSize = 0; // 记录接收中断时收到的字节数量
    memset(responseBuf, 0, sizeof(responseBuf)); // 必须清零,避免残留数据影响对后续流程的判断
    HAL_UART_Transmit(&huart2, (uint8_t *) atCmd, strlen(atCmd), 1000);
    // 最多接收三次响应
    uint8_t rxCount = 3;
    do {
        // 轮询等待AT响应
        uint32_t timeout = 0xFFFFFF;
        while (rxSize == 0 && timeout--)
            ;
        // 收到一次响应,将接收缓冲区rxBuf收到的数据拷贝到响应结果中暂存
        memcpy(responseBuf + responseSize, rxBuf, rxSize);
        responseSize += rxSize;
        rxSize = 0; // 继续接收响应
    } while (strstr((char *) responseBuf, AT_OK) == NULL &&
             strstr((char *) responseBuf, AT_ERROR) == NULL && rxCount--);
    LOG_DEBUG("rxCount = %d, responseSize = %d, responseBuf = %s", rxCount, responseSize, (char *)responseBuf)
}

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
    if (huart == &huart2) {
        rxSize = Size;
        HAL_UARTEx_ReceiveToIdle_IT(&huart2, rxBuf, RXBUF_MAX_SIZE);
    }
}

main.c

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
  LOG_DEBUG("Hello, World!\n");
  ESP32_init();
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
}

测试

image-20241215212630869

image-20241215212920978

image-20241215212936964

image-20241215212950842

通过ESP连接热点

创建热点

使用手机创建一个2.4G的热点,设置热点名称和密码,以备后续esp32来连接该热点

示例代码

esp32.c

#define AT_CWMODE_STATION     ("AT+CWMODE=1" AT_SUFFIX)
#define AT_CWJAP              ("AT+CWJAP=\"" WIFI_NAME "\",\"" WIFI_PASSWORD "\"" AT_SUFFIX)
#define AT_CWSTATE            ("AT+CWSTATE?" AT_SUFFIX)

// 热点的名称和密码
#define WIFI_NAME       "HONOR 90"
#define WIFI_PASSWORD   "Zawalliswell1998"

void ESP32_ModeStation(void) {
    // 设置WIFI工作模式:station(作为终端连接热点)
    ESP32_SendAtCmd(AT_CWMODE_STATION);
    // 设置热点的名称和密码
    ESP32_SendAtCmd(AT_CWJAP);
    // 查看WIFI状态(是否连接成功)
    ESP32_SendAtCmd(AT_CWSTATE);
}

main.c

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
  LOG_DEBUG("Hello, World!\n");
  ESP32_init();
  ESP32_ModeStation();
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
}

完整代码

esp32.h

//
// Created by 86157 on 2024/12/15.
//

#ifndef INC_40_ESP32_HAL_ESP32_H
#define INC_40_ESP32_HAL_ESP32_H

#include "stm32f1xx.h"

void ESP32_init(void);
void ESP32_SendAtCmd(char *atCmd);
void ESP32_ModeStation(void);
#endif//INC_40_ESP32_HAL_ESP32_H

esp32.c

//
// Created by 86157 on 2024/12/15.
//

#include "esp32.h"
#include "logger.h"
#include "usart.h"
#include <string.h>

#define RXBUF_MAX_SIZE        256
#define RESPONSE_BUF_MAX_SIZE 512
#define AT_OK                 "OK"
#define AT_ERROR              "ERROR"
#define AT_SUFFIX             "\r\n"
#define AT_RST                ("AT+RST" AT_SUFFIX)
#define AT_TEST               ("AT" AT_SUFFIX)
#define AT_GMR                ("AT+GMR" AT_SUFFIX)
#define AT_CWMODE_STATION     ("AT+CWMODE=1" AT_SUFFIX)
#define AT_CWJAP              ("AT+CWJAP=\"" WIFI_NAME "\",\"" WIFI_PASSWORD "\"" AT_SUFFIX)
#define AT_CWSTATE            ("AT+CWSTATE?" AT_SUFFIX)

// 热点的名称和密码
#define WIFI_NAME       "HONOR 90"
#define WIFI_PASSWORD   "Zawalliswell1998"

uint8_t rxBuf[RXBUF_MAX_SIZE] = {0};
__IO uint8_t rxSize = 0;

uint8_t responseBuf[RESPONSE_BUF_MAX_SIZE] = {0};
__IO uint8_t responseSize = 0;

void ESP32_init(void) {
    MX_USART2_UART_Init();
    // 接收定长/空闲检测+中断 非阻塞接收数据到rxBuf中
    HAL_UARTEx_ReceiveToIdle_IT(&huart2, rxBuf, RXBUF_MAX_SIZE);
    // 发送复位指令
    ESP32_SendAtCmd(AT_RST);
    HAL_Delay(2000);// 等待芯片复位完成

    // 测试AT指令
    ESP32_SendAtCmd(AT_TEST);
    ESP32_SendAtCmd(AT_GMR);
}

void ESP32_ModeStation(void) {
    // 设置WIFI工作模式:station(作为终端连接热点)
    ESP32_SendAtCmd(AT_CWMODE_STATION);
    // 设置热点的名称和密码
    ESP32_SendAtCmd(AT_CWJAP);
    // 查看WIFI状态(是否连接成功)
    ESP32_SendAtCmd(AT_CWSTATE);
}

void ESP32_SendAtCmd(char *atCmd) {
    responseSize = 0;// 记录发送指令后,收到的响应字节数量
    rxSize = 0;      // 记录接收中断时收到的字节数量
    memset(responseBuf, 0,
           sizeof(responseBuf));// 必须清零,避免残留数据影响对后续流程的判断
    HAL_UART_Transmit(&huart2, (uint8_t *) atCmd, strlen(atCmd), 1000);
    // 最多接收五次响应
    uint8_t rxCount = 5;
    do {
        // 轮询等待AT响应
        uint32_t timeout = 0xFFFFFF;
        while (rxSize == 0 && timeout--)
            ;
        // 收到一次响应,将接收缓冲区rxBuf收到的数据拷贝到响应结果中暂存
        memcpy(responseBuf + responseSize, rxBuf, rxSize);
        responseSize += rxSize;
        rxSize = 0;// 继续接收响应
    } while (strstr((char *) responseBuf, AT_OK) == NULL &&
             strstr((char *) responseBuf, AT_ERROR) == NULL && rxCount--);
    LOG_DEBUG("rxCount = %d, responseSize = %d, responseBuf = %s", rxCount, responseSize,
              (char *) responseBuf)
}

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
    if (huart == &huart2) {
        rxSize = Size;
        HAL_UARTEx_ReceiveToIdle_IT(&huart2, rxBuf, RXBUF_MAX_SIZE);
    }
}

main.c

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
  LOG_DEBUG("Hello, World!\n");
  ESP32_init();
  ESP32_ModeStation();
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

测试

image-20241216102311387

image-20241216102410687

通过ESP搭建TCPServer

启动TCP服务

AT指令启动TCP服务

#define AT_CIPMUX_MULTI      ("AT+CIPMUX=1" AT_SUFFIX)
#define AT_CREATE_TCP_SERVER ("AT+CIPSERVER=1," TCP_SERVER_PORT AT_SUFFIX)
#define AT_IPD_MODE1         ("AT+CIPDINFO=1" AT_SUFFIX)

// TCP服务端口
#define TCP_SERVER_PORT "8080"

void ESP32_TCP_StartServer(void) {
    // 启用复用连接
    ESP32_SendAtCmd(AT_CIPMUX_MULTI);
    // 启动TCP服务,指定端口
    ESP32_SendAtCmd(AT_CREATE_TCP_SERVER);
    /*
     * 设置入站数据包格式(incoming package data)
     * 0: does not show the remote host and port in “+IPD” and “+CIPRECVDATA” messages.
     * 1: show the remote host and port in “+IPD” and “+CIPRECVDATA” messages.
     * */
    ESP32_SendAtCmd(AT_IPD_MODE1);
}

轮询接收TCP入站数据包

void ESP32_TCP_HandleReceive(void) {
    if (rxSize) {
        LOG_DEBUG("%.*s", rxSize, rxBuf);
        rxSize = 0;
    }
}
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
  LOG_DEBUG("Hello, World!\n");
  ESP32_init();
  ESP32_ModeStation();
  ESP32_TCP_StartServer();
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    ESP32_TCP_HandleReceive();
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

测试TCP连接

注意:

  • 在手机热点上查看分配给esp、电脑的IP地址,本例中为 192.168.201.181192.168.201.38
  • 远程端口要和AT指令 AT+CIPSERVER中指定的端口号一致,本地端口填0会自动分配

image-20241216110308642

连接后,我们的 ESP32_TCP_HandleReceive 会打印ESP发给我们的TCP入站数据包

image-20241216114314439

image-20241216110907876

入站数据包解析

esp32.c

void ESP32_TCP_HandleReceive(uint16_t *connectionId, uint8_t *ip, uint16_t *port,
                             uint16_t *dataSize, uint8_t *data) {
    if (rxSize) {
        // 解析esp抄送的入站数据包,示例:\r\n+IPD,0,5,"192.168.201.38",51105:hello\r\n
        if (strstr((char *) rxBuf, "+IPD")) {
            LOG_DEBUG("<%s>", rxBuf)
            sscanf((char *) rxBuf, "%*[\r\n]+IPD,%hu,%hu,\"%[^\"]\",%hu", connectionId,
                   dataSize, ip, port);
            // 数据中可能包含\r\n,scanf遇到后会停止匹配,因此需要单独解析
            strtok((char *) rxBuf, ":");
            memcpy(data, strtok(NULL, ":"), *dataSize);
            LOG_DEBUG("connectionId = %hu, ip = %s, port = %hu, dataSize = %hu, data = "
                      "%.*s",
                      *connectionId, ip, *port, *dataSize, *dataSize, data);
        }
        memset(rxBuf, 0, sizeof(rxBuf));
        rxSize = 0;
    }
}

main.c

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
  LOG_DEBUG("Hello, World!\n");
  ESP32_init();
  ESP32_ModeStation();
  ESP32_TCP_StartServer();
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
  uint16_t connectionId = 0;
  uint8_t ip[16] = {0};
  uint16_t port = 0;
  uint16_t dataSize = 0;
  uint8_t data[256] = {0};
while (1)
{
    ESP32_TCP_HandleReceive(&connectionId, ip, &port, &dataSize, data);
    memset(ip, 0, sizeof(ip));
    memset(data, 0, sizeof(data));
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

测试

image-20241216132411773

发送数据给客户端

esp32.c

#define AT_CIPMUX_MULTI      ("AT+CIPMUX=1" AT_SUFFIX)
#define AT_CREATE_TCP_SERVER ("AT+CIPSERVER=1," TCP_SERVER_PORT AT_SUFFIX)
#define AT_IPD_MODE1         ("AT+CIPDINFO=1" AT_SUFFIX)
#define AT_CIPSEND           ("AT+CIPSEND=%hu,%hu" AT_SUFFIX)

char sendBuf[64] = {0};
void ESP32_TCP_HandleTransmit(uint16_t connectionId, uint8_t *data, uint16_t dataSize) {
    if (dataSize <= 0) {
        return;
    }
    memset(sendBuf, 0, sizeof(sendBuf) / sizeof(sendBuf[0]));
    sprintf(sendBuf, AT_CIPSEND, connectionId, dataSize);
    ESP32_SendAtCmd(sendBuf);
    HAL_UART_Transmit(&huart2, data, dataSize, 1000);
    ESP32_WaitResponse();
}

static void ESP32_WaitResponse(void) {
    responseSize = 0;// 记录发送指令后,收到的响应字节数量
    rxSize = 0;      // 记录每次接收中断收到的字节数量
    // 必须清零,避免残留数据影响对后续流程的判断
    memset(responseBuf, 0, sizeof(responseBuf));
    // 最多接收五次响应
    uint8_t rxCount = 5;
    do {
        // 轮询等待AT响应
        uint32_t timeout = 0xFFFFFF;
        while (rxSize == 0 && timeout--)
            ;
        // 收到一次响应,将接收缓冲区rxBuf收到的数据拷贝到响应结果中暂存
        memcpy(responseBuf + responseSize, rxBuf, rxSize);
        responseSize += rxSize;
        rxSize = 0;// 继续接收响应
    } while (strstr((char *) responseBuf, AT_OK) == NULL &&
             strstr((char *) responseBuf, AT_ERROR) == NULL && rxCount--);
    LOG_DEBUG("rxCount = %d, responseSize = %d, responseBuf = %s", rxCount, responseSize,
              (char *) responseBuf)
}

main.c

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
  LOG_DEBUG("Hello, World!\n");
  ESP32_init();
  ESP32_ModeStation();
  ESP32_TCP_StartServer();
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
  uint16_t connectionId = 0;
  uint8_t ip[16] = {0};
  uint16_t port = 0;
  uint16_t dataSize = 0;
  uint8_t data[256] = {0};
while (1)
{
    memset(ip, 0, sizeof(ip));
    memset(data, 0, sizeof(data));
    dataSize = 0;
    ESP32_TCP_HandleReceive(&connectionId, ip, &port, &dataSize, data);
    ESP32_TCP_HandleTransmit(connectionId, data, dataSize);
    /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

测试

image-20241216175721948

image-20241216175909143

完整代码

esp32.h

//
// Created by 86157 on 2024/12/15.
//

#ifndef INC_40_ESP32_HAL_ESP32_H
#define INC_40_ESP32_HAL_ESP32_H

#include "stm32f1xx.h"

void ESP32_init(void);
void ESP32_SendAtCmd(char *atCmd);
void ESP32_ModeStation(void);
void ESP32_TCP_StartServer(void);
void ESP32_TCP_HandleReceive(uint16_t *connectionId, uint8_t *ip, uint16_t *port,
                             uint16_t *dataSize, uint8_t *data);
void ESP32_TCP_HandleTransmit(uint16_t connectionId, uint8_t *data, uint16_t dataSize);
#endif//INC_40_ESP32_HAL_ESP32_H

esp32.c

//
// Created by 86157 on 2024/12/15.
//

#include "esp32.h"
#include "logger.h"
#include "usart.h"
#include <string.h>

#define RXBUF_MAX_SIZE        256
#define RESPONSE_BUF_MAX_SIZE 512

#define AT_OK                "OK"
#define AT_ERROR             "ERROR"
#define AT_SUFFIX            "\r\n"
#define AT_RST               ("AT+RST" AT_SUFFIX)
#define AT_TEST              ("AT" AT_SUFFIX)
#define AT_GMR               ("AT+GMR" AT_SUFFIX)
#define AT_CWMODE_STATION    ("AT+CWMODE=1" AT_SUFFIX)
#define AT_CWJAP             ("AT+CWJAP=\"" WIFI_NAME "\",\"" WIFI_PASSWORD "\"" AT_SUFFIX)
#define AT_CWSTATE           ("AT+CWSTATE?" AT_SUFFIX)
#define AT_CIPMUX_MULTI      ("AT+CIPMUX=1" AT_SUFFIX)
#define AT_CREATE_TCP_SERVER ("AT+CIPSERVER=1," TCP_SERVER_PORT AT_SUFFIX)
#define AT_IPD_MODE1         ("AT+CIPDINFO=1" AT_SUFFIX)
#define AT_CIPSEND           ("AT+CIPSEND=%hu,%hu" AT_SUFFIX)

// TCP服务端口
#define TCP_SERVER_PORT "8080"
// 热点的名称和密码
#define WIFI_NAME     "HONOR 90"
#define WIFI_PASSWORD "Zawalliswell1998"

uint8_t rxBuf[RXBUF_MAX_SIZE] = {0};
__IO uint8_t rxSize = 0;

uint8_t responseBuf[RESPONSE_BUF_MAX_SIZE] = {0};
__IO uint8_t responseSize = 0;

static void ESP32_WaitResponse(void);

void ESP32_init(void) {
    MX_USART2_UART_Init();
    // 接收定长/空闲检测+中断 非阻塞接收数据到rxBuf中
    HAL_UARTEx_ReceiveToIdle_IT(&huart2, rxBuf, RXBUF_MAX_SIZE);
    // 发送复位指令
    ESP32_SendAtCmd(AT_RST);
    HAL_Delay(2000);// 等待芯片复位完成

    // 测试AT指令
    ESP32_SendAtCmd(AT_TEST);
    ESP32_SendAtCmd(AT_GMR);
}

void ESP32_ModeStation(void) {
    // 设置WIFI工作模式:station(作为终端连接热点)
    ESP32_SendAtCmd(AT_CWMODE_STATION);
    // 设置热点的名称和密码
    ESP32_SendAtCmd(AT_CWJAP);
    // 查看WIFI状态(是否连接成功)
    ESP32_SendAtCmd(AT_CWSTATE);
}

void ESP32_TCP_StartServer(void) {
    // 启用复用连接
    ESP32_SendAtCmd(AT_CIPMUX_MULTI);
    // 启动TCP服务,指定端口
    ESP32_SendAtCmd(AT_CREATE_TCP_SERVER);
    /*
     * 设置入站数据包格式(incoming package data)
     * 0: does not show the remote host and port in “+IPD” and “+CIPRECVDATA” messages.
     * 1: show the remote host and port in “+IPD” and “+CIPRECVDATA” messages.
     * */
    ESP32_SendAtCmd(AT_IPD_MODE1);
}

void ESP32_TCP_HandleReceive(uint16_t *connectionId, uint8_t *ip, uint16_t *port,
                             uint16_t *dataSize, uint8_t *data) {
    if (rxSize) {
        // 解析esp抄送的入站数据包,示例:\r\n+IPD,0,5,"192.168.201.38",51105:hello\r\n
        if (strstr((char *) rxBuf, "+IPD")) {
            //LOG_DEBUG("<%s>", rxBuf)
            sscanf((char *) rxBuf, "%*[\r\n]+IPD,%hu,%hu,\"%[^\"]\",%hu", connectionId,
                   dataSize, ip, port);
            // 数据中可能包含\r\n,scanf遇到后会停止匹配,因此需要单独解析
            strtok((char *) rxBuf, ":");
            memcpy(data, strtok(NULL, ":"), *dataSize);
            LOG_DEBUG("connectionId = %hu, ip = %s, port = %hu, dataSize = %hu, data = "
                      "%.*s",
                      *connectionId, ip, *port, *dataSize, *dataSize, data);
        }
        memset(rxBuf, 0, sizeof(rxBuf));
        rxSize = 0;
    }
}

char sendBuf[64] = {0};
void ESP32_TCP_HandleTransmit(uint16_t connectionId, uint8_t *data, uint16_t dataSize) {
    if (dataSize <= 0) {
        return;
    }
    memset(sendBuf, 0, sizeof(sendBuf) / sizeof(sendBuf[0]));
    sprintf(sendBuf, AT_CIPSEND, connectionId, dataSize);
    ESP32_SendAtCmd(sendBuf);
    HAL_UART_Transmit(&huart2, data, dataSize, 1000);
    ESP32_WaitResponse();
}

void ESP32_SendAtCmd(char *atCmd) {
    HAL_UART_Transmit(&huart2, (uint8_t *) atCmd, strlen(atCmd), 1000);
    ESP32_WaitResponse();
}

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
    if (huart == &huart2) {
        rxSize = Size;
        HAL_UARTEx_ReceiveToIdle_IT(&huart2, rxBuf, RXBUF_MAX_SIZE);
    }
}

static void ESP32_WaitResponse(void) {
    responseSize = 0;// 记录发送指令后,收到的响应字节数量
    rxSize = 0;      // 记录每次接收中断收到的字节数量
    // 必须清零,避免残留数据影响对后续流程的判断
    memset(responseBuf, 0, sizeof(responseBuf));
    // 最多接收五次响应
    uint8_t rxCount = 5;
    do {
        // 轮询等待AT响应
        uint32_t timeout = 0xFFFFFF;
        while (rxSize == 0 && timeout--)
            ;
        // 收到一次响应,将接收缓冲区rxBuf收到的数据拷贝到响应结果中暂存
        memcpy(responseBuf + responseSize, rxBuf, rxSize);
        responseSize += rxSize;
        rxSize = 0;// 继续接收响应
    } while (strstr((char *) responseBuf, AT_OK) == NULL &&
             strstr((char *) responseBuf, AT_ERROR) == NULL && rxCount--);
    LOG_DEBUG("rxCount = %d, responseSize = %d, responseBuf = %s", rxCount, responseSize,
              (char *) responseBuf)
}

main.c

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
  LOG_DEBUG("Hello, World!\n");
  ESP32_init();
  ESP32_ModeStation();
  ESP32_TCP_StartServer();
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
  uint16_t connectionId = 0;
  uint8_t ip[16] = {0};
  uint16_t port = 0;
  uint16_t dataSize = 0;
  uint8_t data[256] = {0};
while (1)
{
    memset(ip, 0, sizeof(ip));
    memset(data, 0, sizeof(data));
    dataSize = 0;
    ESP32_TCP_HandleReceive(&connectionId, ip, &port, &dataSize, data);
    ESP32_TCP_HandleTransmit(connectionId, data, dataSize);
    /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

实验-ESP32蓝牙透传

官方示例

Establish SPP connection between ESP32 and mobile phone and transmit data in UART-Bluetooth LE passthrough mode

在ESP32和手机之间建立SPP(Serial Port Profile, UART-Bluetooth LE passthrough mode)连接后,能够实现MCU串口和手机之间经过透传通道(对两者之间BLE的存在无感知)收发原始数据。

透传的好处(MCU对于BLE无感知):

  1. 与之前的WIFI案例对比,MCU接收数据不需要解析 +IPD格式的数据包了,通过串口接收到的就是手机发送的原始数据
  2. MCU发送数据直接发到串口,不需要先指定AT指令了(对比之前案例中的 AT+CIPSEND

image-20241216212311794

蓝牙广播&连接

void ESP32_Bluetooth_Init(void) {
    ESP32_init();
    // 示例:https://docs.espressif.com/projects/esp-at/en/latest/esp32/AT_Command_Examples/bluetooth_le_at_examples.html#establish-spp-connection-between-esp32-and-mobile-phone-and-transmit-data-in-uart-bluetooth-le-passthrough-mode
    // 初始化蓝牙,模式为服务端(等待被连接)
    ESP32_SendAtCmd(AT_BLEINIT_SERVER);
    // 服务端创建GATT服务
    ESP32_SendAtCmd(AT_BLEGATTSSRVCRE);
    // 服务端启动服务
    ESP32_SendAtCmd(AT_BLEGATTSSRVSTART);
    // 服务端设置广播参数,例如广播时间间隔
    ESP32_SendAtCmd(AT_BLEADVPARAM);
    // 服务端设置广播数据,例如蓝牙名称
    ESP32_SendAtCmd(AT_BLEADVDATAEX);
    // 服务端开始广播
    ESP32_SendAtCmd(AT_BLEADVSTART);
}

启动日志

image-20241216213633239

连接蓝牙

image-20241216213925196

连接建立/断开日志

image-20241216214315568

WIFI连接/断开日志

此前的案例对WIFI的配置被保存到了NVS(Non-Volatile Storage非易失性存储)中。

Non-Volatile Storage Library

AT+CWJAP中的note

image-20241216214350072

手机发送数据

image-20241216215443827

image-20241216215514376

设置连接为透传模式

初始化时增加透传相关参数的提前配置

void ESP32_Bluetooth_Init(void) {
    ESP32_init();
    // 示例:https://docs.espressif.com/projects/esp-at/en/latest/esp32/AT_Command_Examples/bluetooth_le_at_examples.html#establish-spp-connection-between-esp32-and-mobile-phone-and-transmit-data-in-uart-bluetooth-le-passthrough-mode
    // 初始化蓝牙,模式为服务端(等待被连接)
    ESP32_SendAtCmd(AT_BLEINIT_SERVER);
    // 服务端创建GATT服务
    ESP32_SendAtCmd(AT_BLEGATTSSRVCRE);
    // 服务端启动服务
    ESP32_SendAtCmd(AT_BLEGATTSSRVSTART);
    // 服务端设置广播参数,例如广播时间间隔
    ESP32_SendAtCmd(AT_BLEADVPARAM);
    // 服务端设置广播数据,例如蓝牙名称
    ESP32_SendAtCmd(AT_BLEADVDATAEX);
    // 服务端开始广播
    ESP32_SendAtCmd(AT_BLEADVSTART);

    // 配置透传参数
    ESP32_SendAtCmd(AT_BLESPPCFG);
    // 透传模式下,Wi-Fi、socket、Bluetooth LE 或 Bluetooth 状态改变时会打印提示信息
    ESP32_SendAtCmd(AT_SYSMSG);
}

启动蓝牙后根据ESP的连接状态提示信息进入或退出透传

esp进入透传模式后,就不能给esp发送AT指令了,因为通过串口发给esp的,esp会原样通过蓝牙发送给客户端。详见 note

void ESP32_Bluetooth_HandleReceive(void) {
    if (rxSize) {
        LOG_DEBUG("%.*s", rxSize, rxBuf)
        if (ESP32_Bluetooth_HandleConnChange() == BLE_DATA) {
            LOG_DEBUG("收到客户端数据 = %s", rxBuf);
        }
        rxSize = 0;
        memset(rxBuf, 0, sizeof(rxBuf));
    }
}

/*
 * 蓝牙连接/断开
 * [DEBUG esp32.c:86] +BLECONN:0,"60:8f:c2:b1:9a:22"
 * [DEBUG esp32.c:86] +BLEDISCONN:0,"60:8f:c2:b1:9a:22"
 *
 * [DEBUG esp32.c:86] +BLECONNPARAM:0,0,0,6,0,500
 * [DEBUG esp32.c:86] +BLESETPHY:"60:8f:c2:b1:9a:22",2,2
 * [DEBUG esp32.c:86] +BLECONNPARAM:0,0,0,24,0,500
 *
 * WIFI连接/断开
 * [DEBUG esp32.c:86] WIFI DISCONNECT
 * [DEBUG esp32.c:86] WIFI CONNECTED
 * [DEBUG esp32.c:86] WIFI GOT IP
 *
 * 客户端(如手机蓝牙调试助手)发来的数据
 * [DEBUG esp32.c:86] +WRITE:0,1,3,,4,1234
 * */
ESP32_BLE_STATE ESP32_Bluetooth_HandleConnChange(void) {
    if (strstr((char *)rxBuf, "+BLECONN:")) {
        // 连接建立,开启透传(蓝牙收到的数据原样发给串口,串口发给蓝牙的数据原样发给客户端)
        LOG_DEBUG("连接建立成功,准备使能透传")
        ESP32_SendAtCmd(AT_BLESPP);  // 使能透传
        LOG_DEBUG("连接建立成功,使能透传完成")
        return BLE_CONNECT;
    }
    if (strstr((char *)rxBuf, "+BLEDISCONN:")) {
        LOG_DEBUG("连接断开,关闭透传模式")
        // 连接断开,关闭透传,进入正常命令模式。要特别注意透传模式下,直接发送“+++”才能返回正常命令模式
        HAL_UART_Transmit(&huart2, (uint8_t *)"+++", 3, 10);
        HAL_Delay(1000); // 至少等待1s以确保esp返回命令模式
        LOG_DEBUG("进入正常命令模式")
        // 继续广播,等待连接
        LOG_DEBUG("开始广播")
        ESP32_SendAtCmd(AT_BLEADVSTART);
        return BLE_DISCONNECT;
    }
    if(strstr((char *)rxBuf, "WIFI")) {
        LOG_DEBUG("wifi状态变化")
        return BLE_WIFI_CHANGE;
    }
    if (strstr((char *)rxBuf, "+BLECONNPARAM:") || strstr((char *)rxBuf, "+BLESETPHY:")) {
        return BLE_OTHER;
    }
    return BLE_DATA;
}

手机连接蓝牙并发送数据

image-20241217092335587

image-20241217083900288

手机断开连接 & 重连

image-20241217092746436

蓝牙向手机发送数据

通过串口透传SPP可以直接将数据发送到客户端

image-20241217093457819

image-20241217093220260

注意事项

image-20241217094852017

THN END


文章作者: 安文
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 安文 !
  目录