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:STM32F103RCT6
  • 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

Release v1.12 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 中一一进行了逻辑划分。
    Project Directories
  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. 示例内存管理

System Directory

接口移植

分层结构

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

Architecture

整个 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 通信,根据 STM32F103RCT6 的 Datasheet 可知 USART2 的 DMA 接收通道为 DMA1_Channel6

DMA

整个 lwesp_ll_stm32.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

Examples

笔者这里参考 Demo netconn_client_rtos_stm32l496g_discovery,简单的写了个应用。就简简单单的在加入 AP 后与指定的 TCP Server 建立连接,之后发送一个 GET 请求。main.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 的头文件搜索路径中。

Snippets

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

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

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

/*
 * 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 中定义:

/**
 * \brief           Host and port settings
 */
#define NETCONN_HOST        "106.14.142.xxx"
#define NETCONN_PORT        8001

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

/**
 * \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 请求。

Connect

TCP Server Receive

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

TCP Server Send

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/355332.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】第三十九站:可重入函数、volatile、SIGCHLD信号

文章目录 一、可重入函数二、volatile三、SIGCHLD信号 一、可重入函数 如下图所示&#xff0c;当我们进行链表的头插的时候&#xff0c;我们刚刚执行完第一条语句的时候&#xff0c;突然收到一个信号&#xff0c;然后我们这个信号的自定义捕捉方法中&#xff0c;正好还有一个头…

Python模拟艾里光束:光可以不沿直线传播

文章目录 Airy光束有限能量Airy光束 Airy光束 在光学领域&#xff0c;傍轴近似下光束传输遵循方程 i ∂ ϕ ∂ z 1 z a ∂ 2 ϕ ∂ x 2 0 i\frac{\partial\phi}{\partial z}\frac{1}{z}\frac{a\partial^2\phi}{\partial x^2}0 i∂z∂ϕ​z1​∂x2a∂2ϕ​0 其中 k 2 π n …

【发展】不确定时代下的从容 —— 终局思维、长期主义与复利

文章目录 一、终局思维1、电影 《蝴蝶效应》2、未来是什么样的 二、长期主义1、这是一个不确定的时代2、做难但正确的事情 三、复利1、复利思维2、马太效应 一、终局思维 终局思维 在面对很多选择时&#xff0c;从终点出发考虑问题&#xff0c;来决定当下的选择。 1、电影 《蝴…

容器和虚拟机的对比

容器和虚拟机的对比 容器和虚拟机在与硬件和底层操作系统交互的方式上有所不同 虚拟化 使多个操作系统能够同时在一个硬件平台上运行。 使用虚拟机监控程序将硬件分为多个虚拟硬件系统&#xff0c;从而允许多个操作系统并行运行。 需要一个完整的操作系统环境来支持该应用。…

从零开始:CentOS系统下搭建DNS服务器的详细教程

前言 如果你希望在CentOS系统上建立自己的DNS服务器,那么这篇文章绝对是你不容错过的宝藏指南。我们提供了详尽的步骤和实用技巧,让你能够轻松完成搭建过程。从安装必要的软件到配置区域文件,我们都将一一为你呈现。无论你的身份是运维人员,还是程序员,抑或是对网络基础设…

GitLab16.8配置webhooks、Jenkins2.4配置GitLab插件实现持续集成、配置宝塔面板实现持续部署

看本篇文章的前提是已经部署完GItlab和Jenkins服务器&#xff0c;已经可以手动构建成功&#xff0c;并且经过了很多次实践&#xff0c;对这两款软件基本熟悉。 建议大家按以下顺序看 前端自动化&#xff08;其一&#xff09;部署gitlab https://blog.csdn.net/weixin_45062076…

DolphinScheduler + Amazon EMR Serverless 的集成实践

01 背景 Apache DolphinScheduler 是一个分布式的可视化 DAG 工作流任务调度开源系统&#xff0c;具有简单易用、高可靠、高扩展性、⽀持丰富的使用场景、提供多租户模式等特性。适用于企业级场景&#xff0c;提供了一个可视化操作任务、工作流和全生命周期数据处理过程的解决方…

2024.1.24 C++QT 作业

思维导图 练习题 1.提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 #include <iostream> #include <string.h> #include <array> using namespace std;int main() {string str;cout << "…

《微信小程序开发从入门到实战》学习九十六

7.2 基础内容组件 7.2.4 progress组件 progress组件的示例代码如下&#xff1a; <progress percent"20" show-info /> 7.3 表单组件 表单组件是用于收集信息的组件。第三章介绍了许多表单组件&#xff0c;包括form、input、textarea、picker、switch、butt…

在WebSocket中使用Redis出现空指针异常解决方案

文章目录 在WebSocket中使用Redis1.问题描述2.原因3.解决步骤1.新建一个SpringUtil.java类&#xff0c;通过getBean的方法主动获取实例2.在WebSocketSingleServer.java中导入 在WebSocket中使用Redis 1.问题描述 在controller 和 service中都可以正常使用Redis&#xff0c;在…

【Javaweb程序设计】【C00161】基于SSM电子产品交易管理系统(论文+PPT)

基于SSM电子产品交易管理系统&#xff08;论文PPT&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的电子产品交易系统 本系统分为前台用户和后台管理员2个功能模块. 前台用户模块&#xff1a;当游客打开系统的网址后&#xff0c;首先看到的就…

qemu + vscode图形化调试linux kernel

一、背景 使用命令行连接gdb 在调试时&#xff0c;虽然可以通过tui enable 显示源码&#xff0c;但还是存在设置断点麻烦&#xff08;需要对着源码设置&#xff09;&#xff0c;terminal显示代码不方便&#xff0c;不利于我们学习&#xff1b;另外在gdb 下p命令显示结构体内容…

猫用空气净化器哪款牌子好?好用能吸毛的宠物空气净化器推荐

作为一个养猫多年的铲屎官&#xff0c;我真的无法抗拒猫星人的可爱魅力&#xff01;以前&#xff0c;每当我路过宠物店&#xff0c;我总会忍不住停下来&#xff0c;在玻璃窗前停留半个小时以上。但是后来&#xff0c;我终于有了自己的猫咪。每天都能享受到给它摸小肚子的乐趣&a…

腾讯云幻兽帕鲁服务器创建教程,附4核16G服务器价格表

腾讯云0基础搭建帕鲁服务器4C16G14M服务器稳定无卡顿&#xff0c;先下载SteamCMD&#xff0c;并运行&#xff1b;然后下载Palserver&#xff0c;修改服务ini配置&#xff0c;启动PalServer&#xff0c;进入游戏服务器。腾讯云百科txybk.com分享腾讯云创建幻兽帕鲁服务器教程&am…

gdb 调试 - 在vscode图形化展示在远程的gdb debug过程

前言 本地机器的操作系统是windows&#xff0c;远程机器的操作系统是linux&#xff0c;开发在远程机器完成&#xff0c;本地只能通过ssh登录到远程。现在目的是要在本地进行图形化展示在远程的gdb debug过程。&#xff08;注意这并不是gdb remote &#xff01;&#xff01;&am…

产业需求大数据平台

产业需求大数据平台&#xff0c;依托大数据、NLP等技术&#xff0c;全面采集区域产业人才需求数据&#xff0c;从宏观、中观、微观三个层面对产业需求进行分析&#xff0c;并匹配学校自身的办学定位、专业布局、人才培养目标、培养规格和课程设置&#xff0c;进行专业设置匹配度…

【JavaScript基础入门】03 JavaScript 基础语法(一)

JavaScript 基础语法&#xff08;一&#xff09; 目录 JavaScript 基础语法&#xff08;一&#xff09;1. JS 初体验2. JavaScript注释2.1 单行注释2.2 多行注释 3. JavaScript结束符4. JavaScript输入输出语句 1. JS 初体验 JS 有3种书写位置&#xff0c;分别为内联、内部和外…

2024.1.28 GNSS 学习笔记

1.基于 地球自转改正卫地距 以及 伪距码偏差 重构定位方程&#xff1a; 先验残差计算公式如下所示&#xff1a; 2.观测值如何定权&#xff1f;权重如何确定&#xff1f; 每个卫星的轨钟精度以及电离层模型修正后的误差都有差异&#xff0c;所以我们不能简单的将各个观测值等权…

Hotspot源码解析-第25章-类的初始化

第25章-类的初始化 这一章主要是讲类的初始化操作&#xff0c;后续类加载章节中也会用到这一章的知识&#xff0c;只不过&#xff0c;这里就讲&#xff0c;是因为虚拟在初始化过程中&#xff0c;需要对基础类&#xff0c;比如System/Thread等类进行初始化操作&#xff0c;所以…

DL/T645、IEC104转OPC UA网关BE112

随着电力系统信息化建设和数字化转型的进程不断加速&#xff0c;对电力能源的智能化需求也日趋增强。健全稳定的智慧电力系统能够为工业生产、基础设施建设以及国防建设提供稳定的能源支持。在此背景下&#xff0c;高性能的工业电力数据传输解决方案——协议转换网关应运而生&a…
最新文章