STM32-LwESP 移植

LwESP 是一个专门解析 Espressif 公司旗下 ESP 系列芯片 AT 指令的开源库,具有以下特性:

  1. 支持 Espressif 公司 ESP32, ESP32-C2, ESP32-C3, ESP32-C6 和 ESP8266 芯片。
  2. 独立平台,采用 C99 标准编写,易于移植。
  3. 允许不同的配置来优化客户的需求。
  4. 针对 RTOS 系统进行了优化。
    • 有专门的 2 个线程来处理用户的输入和接收的数据
      • Producer 线程:用于从应用程序接收用户命令并执行
      • Process 线程:处理从 ESP 返回的数据
  5. 支持在 LwESP 上直接运行以下应用:
    • HTTP server
    • MQTT client
    • Cayenne MQTT server
  6. 嵌入其它 AT 指令,如 WPS
  7. 用户友好的 MIT License

综上,对于直接运行 AT 固件的 ESP 系列芯片,使用 LwESP 可以直接实现网络功能而无需去研究各种 AT 指令,从而可以让用户专注于应用即可

这里顺带提一下笔者的开发环境:

  • MCU:STM32F103C8T6
  • Library: 标准库 en.stsw-stm32054_v3-6-0 (HAL 库和 LL 库对应参考即可)
  • RTOS:FreeRTOSv202210.01-LTS

LwESP 移植可以分为以下几步:

  1. 下载源码
  2. 源码加入工程
  3. 接口移植
  4. 应用测试

下载源码

LwESP 的源码可以参考 GitHub 上的仓库。一般来说,源码下载只需要下载正式 release 的版本即可,不需要刻意下载 main 和 develop 分支。笔者在写这篇文档时最新的 release 版本为 Release v1.1.2-dev

源码加入工程

LwESP 采用 CMake 来构建系统。如果不支持 CMake 的话,LwESP 也支持将源码加入到自己的工程中来编译。笔者使用 KEIL 来编译 STM32,所以需要采用后者的编译方式。

将 LwESP 加入到自己的工程中也很简单,分为几下几步:

  1. lwesp 目录拷贝到工程中。lwesp 目录中包含了 LwESP 实现的源码。
  2. 在 KEIL 的头文件搜索路径中增加 lwesp/src/include。注意这里头文件的路径只需要 lwesp/src/include 即可,因为在 LwESP 的 C 源文件中包含的头文件类似于 #include "lwesp/lwesp.h,已经以相对路径的方式包含了所需要的头文件,所以这里只包含 lwesp/src/include 这一个头文件路径即可。
  3. lwesp 目录下 src 下的所有 C 源文件(除了子目录 system 下的 C 源文件)加入到 KEIL 中。注意 src 目录下又分为了好几个子目录,各个子目录下的 C 源文件都到加入到 KEIL 中。笔者这里就以 src 目录下个各个子目录在 KEIL 中一一进行了逻辑划分。
  4. system 下的 C 源文件 lwesp_ll_stm32.clwesp_sys_freertos.c 加入到 KEIL 中。(为什么单独是这 2 个文件,后面 接口移植 会说明)
  5. 拷贝 lwesp/src/include/lwesp/lwesp_opts_template.h 头文件到工程中并重新命名为 lwesp_opts.h。注意放置头文件 lwesp_opts.h 的所在目录也必须加入到 KEIL 头文件搜索路径中。

注:

system 下的 C 源文件主要分为 3 部分:

  1. 示例系统接口,主要针对的是 RTOS
  2. 示例芯片驱动,主要针对的是芯片的一些驱动,用于芯片和 ESP 设备之间的物理通信
  3. 示例内存管理

接口移植

分层结构

在正式移植 LwESP 之前,首先要了解 LwESP 的分层结构

整个 LwESP 可以分为 4 层:

  • User application
  • ESP AT Lib middleware
  • System functions & Low-level functions
  • ESP8266 or ESP32 physical device

User application :

用户应用层。

ESP AT Lib middleware

该层不建议用户主动更改。该层是整个 LwESP 的核心层,负责 AT 命令的执行和分析从 ESP 返回的数据。

System functions & Low-level functions
用户需要完全实现该层的接口。

  • System functions:该层中的接口是 RTOS 和 ESP AT Lib middleware 层的桥梁。主要分为以下几个接口:

    1. 线程管理
    2. 二进制信号量管理
    3. 递归互斥管理
    4. 消息队列管理
    5. 当前时间状态信息
  • Low-level functions:该层中的接口负责 ESP AT Lib middleware 层和 ESP8266 or ESP32 physical device 层之间的通信。

ESP8266 or ESP32 physical device

移植

了解了 LwESP 的分层结构之后,其实移植工作主要做的工作就是完全实现 System functions & Low-level functions 层。而对于这一层,又可以分为实现 System functionsLow-level functions

System functions 的实现需要根据 MCU 所使用的 RTOS 决定。笔者使用的 RTOS 是 FreeRTOS。对于 FreeRTOS,LwESP 已经提供了现成的示例,路径为 lwesp\src\system\lwesp_sys_freertos.c。只需要将文件拷贝到自己的工程中即可。

Low-level functions 的实现需要根据 MCU 所使用的库来决定。对于 STM32 而言,库可以分为标准库、HAL 库和 LL 库三种。LwESP 提供了基于 LL 库的示例,路径为 lwesp\src\system\lwesp_ll_stm32.c。如果使用的是 LL 库来开发 STM32,则可以直接将其拷贝到自己的工程中。不过 lwesp_ll_stm32.c 中关于 RTOS 的接口是基于 CMSIS-OS,所以如果使用的 RTOS 不是 CMSIS-OS 的话,还需要将 CMSIS-OS 的系统接口替换成自己 RTOS 的系统接口。

笔者的开发环境是 标准库+FreeRTOS 的组合。所以对于 lwesp_ll_stm32.c 文件,移植包括以下几步:

  1. LL 开头的 LL 库接口替换成标准库的接口
  2. os 开头的 CMSIS-OS 接口替换成 FreeRTOS 接口

通过 lwesp_ll_stm32.c 文件的头部说明可以了解到,LwESP 是使用 UART + DMA 的方式来接收从 ESP 返回的数据。所以这里也需要根据自己使用的 MCU 来确定使用的 UART 和 DMA。笔者这是使用 USART2 和 ESP 通信,根据 STM32F103C8T6 的 Datasheet 可知 USART2 的 DMA 接收通道为 DMA1_Channel6

整个 lwesp_ll_stm32.c 修改后如下:

c 复制代码
/**
 * \file            lwesp_ll_stm32.c
 * \brief           Generic STM32 driver, included in various STM32 driver variants
 */

/*
 * Copyright (c) 2020 Tilen MAJERLE
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * This file is part of LwESP - Lightweight ESP-AT parser library.
 *
 * Author:          Tilen MAJERLE <tilen@majerle.eu>
 * Version:         v1.1.2-dev
 */

/*
 * How it works
 *
 * On first call to \ref lwesp_ll_init, new thread is created and processed in usart_ll_thread function.
 * USART is configured in RX DMA mode and any incoming bytes are processed inside thread function.
 * DMA and USART implement interrupt handlers to notify main thread about new data ready to send to upper layer.
 *
 * More about UART + RX DMA: https://github.com/MaJerle/stm32-usart-dma-rx-tx
 *
 * \ref LWESP_CFG_INPUT_USE_PROCESS must be enabled in `lwesp_config.h` to use this driver.
 */
#include "stm32f10x.h"

#include "lwesp/lwesp.h"
#include "lwesp/lwesp_mem.h"
#include "lwesp/lwesp_input.h"
#include "system/lwesp_ll.h"

#if !__DOXYGEN__

/* USART */
#define LWESP_USART                           USART2
#define LWESP_USART_CLK                       RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE)
#define LWESP_USART_IRQ                       USART2_IRQn
#define LWESP_USART_IRQHANDLER                USART2_IRQHandler
#define LWESP_USART_RDR_NAME                  DR

/* USART TX PIN */
#define LWESP_USART_TX_PORT_CLK               RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE)
#define LWESP_USART_TX_PORT                   GPIOA
#define LWESP_USART_TX_PIN                    GPIO_Pin_2

/* USART RX PIN */
#define LWESP_USART_RX_PORT_CLK               RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE)
#define LWESP_USART_RX_PORT                   GPIOA
#define LWESP_USART_RX_PIN                    GPIO_Pin_3

/* DMA settings */
#define LWESP_USART_DMA                       DMA1
#define LWESP_USART_DMA_CLK                   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE)
#define LWESP_USART_DMA_RX_CH                 DMA1_Channel6
#define LWESP_USART_DMA_RX_IRQ                DMA1_Channel6_IRQn
#define LWESP_USART_DMA_RX_IRQHANDLER         DMA1_Channel6_IRQHandler

/* RESET PIN */
#define LWESP_RESET_PORT_CLK                  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)
#define LWESP_RESET_PORT                      GPIOA
#define LWESP_RESET_PIN                       GPIO_Pin_4

/* DMA flags management */
#define LWESP_USART_DMA_RX_CLEAR_TC           DMA_ClearFlag(DMA1_IT_TC6)
#define LWESP_USART_DMA_RX_CLEAR_HT           DMA_ClearFlag(DMA1_IT_HT6)

#if !LWESP_CFG_INPUT_USE_PROCESS
#error "LWESP_CFG_INPUT_USE_PROCESS must be enabled in `lwesp_config.h` to use this driver."
#endif /* LWESP_CFG_INPUT_USE_PROCESS */

#if !defined(LWESP_USART_DMA_RX_BUFF_SIZE)
#define LWESP_USART_DMA_RX_BUFF_SIZE      0x1000
#endif /* !defined(LWESP_USART_DMA_RX_BUFF_SIZE) */

#if !defined(LWESP_MEM_SIZE)
#define LWESP_MEM_SIZE                    0x1000
#endif /* !defined(LWESP_MEM_SIZE) */

#if !defined(LWESP_USART_RDR_NAME)
#define LWESP_USART_RDR_NAME              RDR
#endif /* !defined(LWESP_USART_RDR_NAME) */

/* USART memory */
static uint8_t      usart_mem[LWESP_USART_DMA_RX_BUFF_SIZE];
static uint8_t      is_running, initialized;
static size_t       old_pos;

/* USART thread */
static void usart_ll_thread(void* arg);
static TaskHandle_t usart_ll_thread_id;

/* Message queue */
static QueueHandle_t usart_ll_mbox_id;

/**
 * \brief           USART data processing
 */
static void
usart_ll_thread(void* arg) {
    size_t pos;

    LWESP_UNUSED(arg);

    while (1) {
        void* d;
        /* Wait for the event message from DMA or USART */
        xQueueReceive(usart_ll_mbox_id, &d, portMAX_DELAY);

        /* Read data */
#if defined(LWESP_USART_DMA_RX_STREAM)
        pos = sizeof(usart_mem) - LL_DMA_GetDataLength(LWESP_USART_DMA, LWESP_USART_DMA_RX_STREAM);
#else
        pos = sizeof(usart_mem) - DMA_GetCurrDataCounter(LWESP_USART_DMA_RX_CH);
#endif /* defined(LWESP_USART_DMA_RX_STREAM) */
        if (pos != old_pos && is_running) {
            if (pos > old_pos) {
                lwesp_input_process(&usart_mem[old_pos], pos - old_pos);
            } else {
                lwesp_input_process(&usart_mem[old_pos], sizeof(usart_mem) - old_pos);
                if (pos > 0) {
                    lwesp_input_process(&usart_mem[0], pos);
                }
            }
            old_pos = pos;
            if (old_pos == sizeof(usart_mem)) {
                old_pos = 0;
            }
        }
    }
}

/**
 * \brief           Configure UART using DMA for receive in double buffer mode and IDLE line detection
 */
static void
configure_uart(uint32_t baudrate) {
    static USART_InitTypeDef USART_InitStruct = { 0 };
    static DMA_InitTypeDef DMA_InitStruct = { 0 };
    GPIO_InitTypeDef GPIO_InitStructure = { 0 };
    NVIC_InitTypeDef NVIC_InitStructure = { 0 };

    if (!initialized) {
        /* Enable peripheral clocks */
        LWESP_USART_CLK;
        LWESP_USART_DMA_CLK;
        LWESP_USART_TX_PORT_CLK;
        LWESP_USART_RX_PORT_CLK;

#if defined(LWESP_RESET_PIN)
        LWESP_RESET_PORT_CLK;
#endif /* defined(LWESP_RESET_PIN) */

#if defined(LWESP_GPIO0_PIN)
        LWESP_GPIO0_PORT_CLK;
#endif /* defined(LWESP_GPIO0_PIN) */

#if defined(LWESP_GPIO2_PIN)
        LWESP_GPIO2_PORT_CLK;
#endif /* defined(LWESP_GPIO2_PIN) */

#if defined(LWESP_CH_PD_PIN)
        LWESP_CH_PD_PORT_CLK;
#endif /* defined(LWESP_CH_PD_PIN) */

#if defined(LWESP_RESET_PIN)
        /* Configure RESET pin */
        memset(&GPIO_InitStructure, 0, sizeof(GPIO_InitTypeDef));
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Pin = LWESP_RESET_PIN;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(LWESP_RESET_PORT, &GPIO_InitStructure);
#endif /* defined(LWESP_RESET_PIN) */

#if defined(LWESP_GPIO0_PIN)
        /* Configure GPIO0 pin */
        gpio_init.Pin = LWESP_GPIO0_PIN;
        LL_GPIO_Init(LWESP_GPIO0_PORT, &gpio_init);
        LL_GPIO_SetOutputPin(LWESP_GPIO0_PORT, LWESP_GPIO0_PIN);
#endif /* defined(LWESP_GPIO0_PIN) */

#if defined(LWESP_GPIO2_PIN)
        /* Configure GPIO2 pin */
        gpio_init.Pin = LWESP_GPIO2_PIN;
        LL_GPIO_Init(LWESP_GPIO2_PORT, &gpio_init);
        LL_GPIO_SetOutputPin(LWESP_GPIO2_PORT, LWESP_GPIO2_PIN);
#endif /* defined(LWESP_GPIO2_PIN) */

#if defined(LWESP_CH_PD_PIN)
        /* Configure CH_PD pin */
        gpio_init.Pin = LWESP_CH_PD_PIN;
        LL_GPIO_Init(LWESP_CH_PD_PORT, &gpio_init);
        LL_GPIO_SetOutputPin(LWESP_CH_PD_PORT, LWESP_CH_PD_PIN);
#endif /* defined(LWESP_CH_PD_PIN) */

        /* Configure USART pins */
        /* TX PIN */
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Pin = LWESP_USART_TX_PIN;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(LWESP_USART_TX_PORT, &GPIO_InitStructure);

        /* RX PIN */
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_InitStructure.GPIO_Pin = LWESP_USART_RX_PIN;
        GPIO_Init(LWESP_USART_TX_PORT, &GPIO_InitStructure);

        /* Configure UART */
        USART_DeInit(LWESP_USART);
        USART_InitStruct.USART_BaudRate = baudrate;
        USART_InitStruct.USART_WordLength = USART_WordLength_8b;
        USART_InitStruct.USART_StopBits = USART_StopBits_1;
        USART_InitStruct.USART_Parity = USART_Parity_No;
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
        USART_Init(LWESP_USART, &USART_InitStruct);

        /* Enable USART interrupts and DMA request */
        USART_ITConfig(LWESP_USART, USART_IT_IDLE, ENABLE);
        USART_ITConfig(LWESP_USART, USART_IT_PE, ENABLE);
        USART_ITConfig(LWESP_USART, USART_IT_ERR, ENABLE);
        USART_DMACmd(LWESP_USART, USART_DMAReq_Rx, ENABLE);

        /* Enable USART interrupts */
        memset(&NVIC_InitStructure, 0, sizeof(NVIC_InitTypeDef));
        NVIC_InitStructure.NVIC_IRQChannel = LWESP_USART_IRQ;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 7;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);

        /* Configure DMA */
        is_running = 0;
#if defined(LWESP_USART_DMA_RX_STREAM)
        LL_DMA_DeInit(LWESP_USART_DMA, LWESP_USART_DMA_RX_STREAM);
        dma_init.Channel = LWESP_USART_DMA_RX_CH;
#else
        // DMA_DeInit(LWESP_USART_DMA_RX_CH);
#endif /* defined(LWESP_USART_DMA_RX_STREAM) */

        DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(LWESP_USART->LWESP_USART_RDR_NAME);
        DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)usart_mem;
        DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
        DMA_InitStruct.DMA_BufferSize = sizeof(usart_mem);
        DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
        DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
        DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
        DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
        DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
        DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;

#if defined(LWESP_USART_DMA_RX_STREAM)
        LL_DMA_Init(LWESP_USART_DMA, LWESP_USART_DMA_RX_STREAM, &dma_init);
#else
        DMA_Init(LWESP_USART_DMA_RX_CH, &DMA_InitStruct);
#endif /* defined(LWESP_USART_DMA_RX_STREAM) */

        /* Enable DMA interrupts */
#if defined(LWESP_USART_DMA_RX_STREAM)
        LL_DMA_EnableIT_HT(LWESP_USART_DMA, LWESP_USART_DMA_RX_STREAM);
        LL_DMA_EnableIT_TC(LWESP_USART_DMA, LWESP_USART_DMA_RX_STREAM);
        LL_DMA_EnableIT_TE(LWESP_USART_DMA, LWESP_USART_DMA_RX_STREAM);
        LL_DMA_EnableIT_FE(LWESP_USART_DMA, LWESP_USART_DMA_RX_STREAM);
        LL_DMA_EnableIT_DME(LWESP_USART_DMA, LWESP_USART_DMA_RX_STREAM);
#else
        DMA_ITConfig(LWESP_USART_DMA_RX_CH, DMA_IT_HT, ENABLE);
        DMA_ITConfig(LWESP_USART_DMA_RX_CH, DMA_IT_TC, ENABLE);
        DMA_ITConfig(LWESP_USART_DMA_RX_CH, DMA_IT_TE, ENABLE);

#endif /* defined(LWESP_USART_DMA_RX_STREAM) */

        /* Enable DMA interrupts */
        memset(&NVIC_InitStructure, 0, sizeof(NVIC_InitTypeDef));
        NVIC_InitStructure.NVIC_IRQChannel = LWESP_USART_DMA_RX_IRQ;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);

        old_pos = 0;
        is_running = 1;

        /* Start DMA and USART */
#if defined(LWESP_USART_DMA_RX_STREAM)
        LL_DMA_EnableStream(LWESP_USART_DMA, LWESP_USART_DMA_RX_STREAM);
#else
        DMA_Cmd(LWESP_USART_DMA_RX_CH, ENABLE);
#endif /* defined(LWESP_USART_DMA_RX_STREAM) */
        USART_Cmd(LWESP_USART, ENABLE);

        /* Read from the USART_SR register followed by a write to the USART_DR register to clear TC flag */
        USART_GetFlagStatus(LWESP_USART, USART_FLAG_TC);
    } else {
        vTaskDelay(100);
        USART_Cmd(LWESP_USART, DISABLE);
        USART_InitStruct.USART_BaudRate = baudrate;
        USART_Init(LWESP_USART, &USART_InitStruct);
        USART_Cmd(LWESP_USART, ENABLE);

        /* Read from the USART_SR register followed by a write to the USART_DR register to clear TC flag */
        USART_GetFlagStatus(LWESP_USART, USART_FLAG_TC);
    }

    /* Create mbox and start thread */
    if (usart_ll_mbox_id == NULL) {
        usart_ll_mbox_id = xQueueCreate(10, sizeof(void*));
    }
    if (usart_ll_thread_id == NULL) {
        xTaskCreate(usart_ll_thread, "usart_ll_thread", 1024, NULL, 10, &usart_ll_thread_id);
    }
}

#if defined(LWESP_RESET_PIN)
/**
 * \brief           Hardware reset callback
 */
static uint8_t
reset_device(uint8_t state) {
    if (state) {
        /* Activate reset line */
        GPIO_ResetBits(LWESP_RESET_PORT, LWESP_RESET_PIN);
    } else {
        GPIO_SetBits(LWESP_RESET_PORT, LWESP_RESET_PIN);
    }
    return 1;
}
#endif /* defined(LWESP_RESET_PIN) */

/**
 * \brief           Send data to ESP device
 * \param[in]       data: Pointer to data to send
 * \param[in]       len: Number of bytes to send
 * \return          Number of bytes sent
 */
static size_t
send_data(const void* data, size_t len) {
    const uint8_t* d = data;

    for (size_t i = 0; i < len; ++i, ++d) {
        USART_SendData(LWESP_USART, (uint8_t)(*d));
        while (USART_GetFlagStatus(LWESP_USART, USART_FLAG_TC) == RESET) {}
    }
    return len;
}

/**
 * \brief           Callback function called from initialization process
 */
lwespr_t
lwesp_ll_init(lwesp_ll_t* ll) {
#if !LWESP_CFG_MEM_CUSTOM
    static uint8_t memory[LWESP_MEM_SIZE];
    lwesp_mem_region_t mem_regions[] = {
        { memory, sizeof(memory) }
    };

    if (!initialized) {
        lwesp_mem_assignmemory(mem_regions, LWESP_ARRAYSIZE(mem_regions));  /* Assign memory for allocations */
    }
#endif /* !LWESP_CFG_MEM_CUSTOM */

    if (!initialized) {
        ll->send_fn = send_data;                /* Set callback function to send data */
#if defined(LWESP_RESET_PIN)
        ll->reset_fn = reset_device;            /* Set callback for hardware reset */
#endif /* defined(LWESP_RESET_PIN) */
    }

    configure_uart(ll->uart.baudrate);          /* Initialize UART for communication */
    initialized = 1;
    return lwespOK;
}

/**
 * \brief           Callback function to de-init low-level communication part
 */
lwespr_t
lwesp_ll_deinit(lwesp_ll_t* ll) {
    if (usart_ll_mbox_id != NULL) {
        QueueHandle_t tmp = usart_ll_mbox_id;
        usart_ll_mbox_id = NULL;
        vQueueDelete(tmp);
    }
    if (usart_ll_thread_id != NULL) {
        TaskHandle_t tmp = usart_ll_thread_id;
        usart_ll_thread_id = NULL;
        vTaskDelete(tmp);
    }
    initialized = 0;
    LWESP_UNUSED(ll);
    return lwespOK;
}

/**
 * \brief           UART global interrupt handler
 */
void
LWESP_USART_IRQHANDLER(void) {
    USART_ClearFlag(LWESP_USART, USART_IT_PE);
    USART_ClearFlag(LWESP_USART, USART_IT_FE);
    USART_ClearFlag(LWESP_USART, USART_IT_ORE_ER);
    USART_ClearFlag(LWESP_USART, USART_IT_NE);

    if (USART_GetITStatus(LWESP_USART, USART_IT_IDLE) != RESET) {
        /* Clear IDLE bit */
        USART_ReceiveData(LWESP_USART);
        USART_ClearITPendingBit(LWESP_USART, USART_IT_IDLE);
    }

    if (usart_ll_mbox_id != NULL) {
        void* d = (void*)1;
        xQueueSendToBackFromISR(usart_ll_mbox_id, &d, NULL);
    }
}

/**
 * \brief           UART DMA stream/channel handler
 */
void
LWESP_USART_DMA_RX_IRQHANDLER(void) {
    LWESP_USART_DMA_RX_CLEAR_TC;
    LWESP_USART_DMA_RX_CLEAR_HT;

    if (usart_ll_mbox_id != NULL) {
        void* d = (void*)1;
        xQueueSendToBackFromISR(usart_ll_mbox_id, &d, NULL);
    }
}

#endif /* !__DOXYGEN__ */

这里在补充一下,除了 USART2 所使用的 TXRX 引脚外,LwESP 还约定了其它的一些引脚,对于 ESP32 系列,LwESP 还添加了宏定义 LWESP_RESET_PIN 用来控制 ESP32 RST 引脚。而对于 ESP8266 系列,LwESP 定义了 LWESP_GPIO0_PIN,LWESP_GPIO2_PINLWESP_CH_PD_PIN。对于感兴趣的读者,可以参考 LwESP 的官方文档中的 Examples and demos

到这里,整个移植工作就全部完成了,剩下的工作就是写个应用 Demo 来测试下。

应用测试

LwESP 已经帮我们实现了大部分的应用接口。对于经常使用的一些 TCP, UDP, MQTT 和 HTTP 的功能,LwESP 在目录 snippets 下提供了现成的源文件。LwESP 也针对 STM32 提供了现成的应用示例,只需要参考下即可,路径为 examples\stm32

笔者这里参考 Demo netconn_client_rtos_stm32l496g_discovery,简单的写了个应用。就简简单单的在加入 AP 后与指定的 TCP Server 建立连接,之后发送一个 GET 请求。main.c 如下:

c 复制代码
/**
  ******************************************************************************
  * @file    Project/STM32F10x_StdPeriph_Template/main.c 
  * @author  MCD Application Team
  * @version V3.6.0
  * @date    20-September-2021
  * @brief   Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2011 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.
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include <stdio.h>
#include "stm32f10x.h"
#include "lwesp_opts.h"

#include "FreeRTOS.h"
#include "task.h"

#include "printf.h"
#include "delay.h"

#include "lwesp/lwesp.h"
#include "station_manager.h"
#include "netconn_client.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
lwespr_t lwesp_callback_func(lwesp_evt_t* evt) {
    switch (lwesp_evt_get_type(evt)) {
        case LWESP_EVT_AT_VERSION_NOT_SUPPORTED: {
            lwesp_sw_version_t v_min, v_curr;

            lwesp_get_min_at_fw_version(&v_min);
            lwesp_get_current_at_fw_version(&v_curr);

            printf("Current ESP8266 AT version is not supported by library!\r\n");
            printf("Minimum required AT version is: %d.%d.%d\r\n", (int)v_min.major, (int)v_min.minor, (int)v_min.patch);
            printf("Current AT version is: %d.%d.%d\r\n", (int)v_curr.major, (int)v_curr.minor, (int)v_curr.patch);
            break;
        }
        case LWESP_EVT_INIT_FINISH: {
            printf("Library initialized!\r\n");
            break;
        }
        case LWESP_EVT_RESET_DETECTED: {
            printf("Device reset detected!\r\n");
            break;
        }
        case LWESP_EVT_WIFI_IP_ACQUIRED: {        /* We have IP address and we are fully ready to work */
            if (lwesp_sta_is_joined()) {          /* Check if joined on any network */
                lwesp_sys_thread_create(NULL, "netconn_client", (lwesp_sys_thread_fn)netconn_client_thread, NULL, 512, LWESP_SYS_THREAD_PRIO);
            }
            break;
        }
        case LWESP_EVT_WIFI_CONNECTED: {
            printf("Successfully joined AP\r\n");
            break;
        }
        default:
            break;
    }
    return lwespOK;
}

void join_ap_test(void *pvParameters)
{
    /* Initialize ESP with default callback function */
    printf("Initializing LwESP\r\n");
    if (lwesp_init(lwesp_callback_func, 1) != lwespOK) {
        printf("Cannot initialize LwESP!\r\n");
    } else {
        printf("LwESP initialized!\r\n");
    }

    /*
     * Continuously try to connect to WIFI network
     * but only in case device is not already connected
     */
    while (1) {
        if (!lwesp_sta_is_joined()) {
            /*
             * Connect to access point.
             *
             * Try unlimited time until access point accepts up.
             * Check for station_manager.c to define preferred access points ESP should connect to
             */
            connect_to_preferred_access_point(1);
        }
        vTaskDelay(1000);
    }

    vTaskDelete(NULL);
}

/**
  * @brief  Configures the nested vectored interrupt controller.
  * @param  None
  * @retval None
  */
void NVIC_Configuration(void)
{
    /* Configure the NVIC Preemption Priority Bits */  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
}

/**
  * @brief  Main program.
  * @param  None
  * @retval None
  */
int main(void)
{
    /*!< At this stage the microcontroller clock setting is already configured, 
        this is done through SystemInit() function which is called from startup
        file (startup_stm32f10x_xx.s) before to branch to application main.
        To reconfigure the default setting of SystemInit() function, refer to
        system_stm32f10x.c file
        */

    /* Add your application code here
        */
    BaseType_t xReturn = pdPASS;

    NVIC_Configuration();

    debug_uart_init();
    printf("Debug UART output success\r\n");
	
    xReturn = xTaskCreate(join_ap_test, "join_ap_test", 1024 * 2, NULL, 1, NULL);
    if(pdPASS == xReturn)
        vTaskStartScheduler();
    else
        return -1;

    /* Infinite loop */
    while (1);
}

#ifdef  USE_FULL_ASSERT

/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t* file, uint32_t line)
{ 
    /* User can add his own implementation to report the file name and line number,
        ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

    /* Infinite loop */
    while (1)
    {
    }
}
#endif

要运行这个 Demo,还需要 snippets 目录下的几个源文件 station_manager.c, utils.cnetconn_client.c。因为 main.c 中调用了这几个源文件中的接口,需要将它们也拷贝到自己的工程中。同时也需要将 snippets/include 中包含到的头文件也添加到自己的工程中,同时不要忘记头文件放置目录也需要添加到 KEIL 的头文件搜索路径中。

Demo 跑起来还需要配置以下参数:

  1. AP 的 SSID 和 PASSWORD
  2. TCP Server 的 IP 地址和连接端口
  3. 发送给 TCP Server 的数据

AP 的 SSID 和 PASSWORD 可以在 station_manager.c 中的 ap_list_preferred 中定义:

c 复制代码
/*
 * List of preferred access points for ESP device
 * SSID and password
 *
 * ESP will try to scan for access points
 * and then compare them with the one on the list below
 */
static const ap_entry_t ap_list_preferred[] = {
    //{ .ssid = "SSID name", .pass = "SSID password" },
    { .ssid = "HUAWEI-tangxiang", .pass = "xxxxxxxx" },
};

TCP Server 的 IP 地址和连接端口 可以在 netconn_client.c 中定义:

c 复制代码
/**
 * \brief           Host and port settings
 */
#define NETCONN_HOST        "106.14.142.xxx"
#define NETCONN_PORT        8001

发送给 TCP Server 的数据 可以在 netconn_client.c 中定义,LwESP 默认在 TCP 连接建立后发送一个 GET 请求。

c 复制代码
/**
 * \brief           Request header to send on successful connection
 */
static const char
request_header[] = ""
                   "GET / HTTP/1.1\r\n"
                   "Host: " NETCONN_HOST "\r\n"
                   "Connection: close\r\n"
                   "\r\n";

配置完成后,就可以将 STM32 的 USART2 的 TX 和 RX 引脚与 ESP32 相连,同时可以将 STM32 上定义的 RESET 引脚连接到 ESP32 上的 RST 引脚上。等待 STM32 连接上指定的 AP 后就会自动和指定的 TCP Server 建立 TCP 连接,连接成功后就会自动发送一个 GET 请求。

当 STM32 接收到 TCP Server 发送过来的数据时,LwESP 也会通知应用层接收到了多少字节的数据。

到这里,整个 LwESP 的移植和测试已经全部完成了。其实 LwESP 可以挖掘的功能还有很多,各位读者如果有兴趣可以自行去深入挖掘一下,期待与各位读者的技术交流~~~

相关推荐
智商偏低2 小时前
单片机之helloworld
单片机·嵌入式硬件
June bug2 小时前
【软考中级·软件评测师】下午题·面向对象测试之架构考点全析:分层、分布式、微内核与事件驱动
经验分享·分布式·职场和发展·架构·学习方法·测试·软考
青牛科技-Allen3 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森5 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白5 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D5 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术8 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt9 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘9 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang9 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c