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);
#elsepos = 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);
#elseDMA_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);
#elseDMA_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);
#elseDMA_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_CUSTOMstatic 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 startupfile (startup_stm32f10x_xx.s) before to branch to application main.To reconfigure the default setting of SystemInit() function, refer tosystem_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();elsereturn -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.rhkb.cn/news/247003.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java基础知识-异常

资料来自黑马程序员 异常 异常&#xff0c;就是不正常的意思。在生活中:医生说,你的身体某个部位有异常,该部位和正常相比有点不同,该部位的功能将受影响.在程序中的意思就是&#xff1a; 异常 &#xff1a;指的是程序在执行过程中&#xff0c;出现的非正常的情况&#xff0c;…

Qt : Style Sheet

When a style sheet is active, the QStyle returned by QWidget::style() is a wrapper “style sheet” style, not the platform-specific style. The wrapper style ensures that any active style sheet is respected and otherwise forwards the drawing operations to t…

一天吃透面试八股文

内容摘自我的学习网站&#xff1a;topjavaer.cn 分享50道Java并发高频面试题。 线程池 线程池&#xff1a;一个管理线程的池子。 为什么平时都是使用线程池创建线程&#xff0c;直接new一个线程不好吗&#xff1f; 嗯&#xff0c;手动创建线程有两个缺点 不受控风险频繁创…

mac裁剪图片

今天第一次用mac裁剪图片&#xff0c;记录一下过程&#xff0c;差点我还以为我要下载photoshop了&#xff0c; 首先准备好图片 裁剪的目的是把图片的标题给去掉&#xff0c;但是不能降低分辨率&#xff0c;否则直接截图就可以了 解决办法 打开原始图片(不要使用预览&#xf…

程序媛的mac修炼手册-- 如何用Python节省WPS会员费

上篇分享了如何用微博爬虫&#xff0c;咱举例爬了女明星江疏影的微博数据。今天就用这些数据&#xff0c;给大家安利一下怎么用Python实现WPS中部分Excel付费功能。 MacOS系统自带的工具&#xff0c;绝大多数都非常顶&#xff0c;除Numbers外。当然&#xff0c;page比起word来&…

Spring MVC 请求流程

SpringMVC 请求流程 一、DispatcherServlet 是一个 Servlet二、Spring MVC 的完整请求流程 Spring MVC 框架是基于 Servlet 技术的。以请求为驱动&#xff0c;围绕 Servlet 设计的。Spring MVC 处理用户请求与访问一个 Servlet 是类似的&#xff0c;请求发送给 Servlet&#xf…

数据结构-线性表

文章目录 数据结构—线性表1.线性表的定义和基本操作线性表的定义线性表的特点线性表的基本操作 2.线性表的顺序存储和链式存储表示顺序存储链式存储单链表循环链表双向链表 数据结构—线性表 1.线性表的定义和基本操作 线性表的定义 定义&#xff1a;线性表是具有相同数据类…

51单片机ESP8266

一、MQTT透传AT固件 安信可提供的烧录WiFi固件工具&#xff1a; 链接: https://docs.ai-thinker.com/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B72 安信可提供的固件库链接: https://docs.ai-thinker.com/%E5%9B%BA%E4%BB%B6%E6%B1%87%E6%80%BB 经过测试&#xff0c;选择这个不可以…

景联文科技大模型数据集更新!教育题库新增高质量数学题、逻辑推理题及英文题

苏格拉底曾以“点燃火焰”的理念来诠释教育。随着大语言模型在教育中的不断应用&#xff0c;教育与AI的深度融合&#xff0c;让我们看到了“点燃火焰”的理念的更多可能性。 大语言模型可以通过与学生的互动&#xff0c;为他们提供个性化的学习体验&#xff0c;更好地满足学习需…

3. SQL 语言

重点&#xff1a; MySQL 的 三种安装方式&#xff1a;包安装&#xff0c;二进制安装&#xff0c;源码编译安装。 MySQL 的 基本使用 MySQL 多实例 DDLcreate alter drop DML insert update delete DQL select 3&#xff09;SQL 语言 3.1&#xff09;关系型数据库的常见…

为什么 FPGA 比 CPU 和 GPU 快?

FPGA、GPU 与 CPU——AI 应用的硬件选择 现场可编程门阵列 (FPGA) 为人工智能 (AI) 应用带来许多优势。图形处理单元 (GPU) 和传统中央处理单元 (CPU) 相比如何&#xff1f; 人工智能&#xff08;AI&#xff09;一词是指能够以类似于人类的方式做出决策的非人类机器智能。这包…

2024年搭建幻兽帕鲁服务器价格多少?如何自建Palworld?

自建幻兽帕鲁服务器租用价格表&#xff0c;2024阿里云推出专属幻兽帕鲁Palworld游戏优惠服务器&#xff0c;配置分为4核16G和4核32G服务器&#xff0c;4核16G配置32.25元/1个月、3M带宽96.75元/1个月、8核32G配置10M带宽90.60元/1个月&#xff0c;8核32G配置3个月271.80元。ECS…

专有钉钉开发记录,及问题总结

先放几个专有钉钉开发文档 专有钉钉官网的开发指南 服务端(后端)api文档 前端api文档 前端开发工具下载地址 小程序配置文件下载地址 后端SDK包下载地址 专有钉钉域名是openplatform.dg-work.cn 开发记录 开发专有钉钉时有时会遇到要使用钉钉的api&#xff1b;通过 my 的方…

移动Web——平面转换-平移

1、平面转换-平移 取值 像素单位数值百分比&#xff08;参照盒子自身尺寸计算结果&#xff09;正负均可 技巧 translate()只写一个值&#xff0c;表示沿着X轴移动单独设置X或Y轴移动距离&#xff1a;translateX()或translateY() <!DOCTYPE html> <html lang"en&q…

Oracle篇—分区表的管理(第二篇,总共五篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

Go语言安装及开发环境配置

目录 官网 国内 Linux(CentOS & Ubuntu)安装 环境变量设置 命令行下开发 开发模式执行 编译 IDE下开发 插件安装 安装依赖工具 运行 常见问题 1、dial tcp 172.217.160.113:443: i/o timeout 2、VS Code不能完美显示zsh问题 官网 访问Golang官网的下载链接&a…

Unity3d C#实现三维场景中图标根据相机距离动态缩放功能

前言 如题的需求&#xff0c;其实可以通过使用UI替代场景中的图标来实现&#xff0c;不过这样UI的处理稍微麻烦&#xff0c;而且需要在图标上添加粒子特效使用SpriteRender更方便快捷。这里就根据相机离图标的位置来计算图标的缩放大小即可。这样基本保持了图标的大小&#xf…

2024新版68套Axure RP大数据可视化大屏模板及通用组件+PSD源文件

Axure RP数据可视化大屏模板及通用组件库2024新版重新制作了这套新的数据可视化大屏模板及通用组件库V2版。新版本相比于V1版内容更加丰富和全面&#xff0c;但依然秉承“敏捷易用”的制作理念&#xff0c;这套作品也同样延续着我们对细节的完美追求&#xff0c;整个设计制作过…

《动手学深度学习(PyTorch版)》笔记3.1

Chapter3 Linear Neural Networks 3.1 Linear Regression 3.1.1 Basic Concepts 我们通常使用 n n n来表示数据集中的样本数。对索引为 i i i的样本&#xff0c;其输入表示为 x ( i ) [ x 1 ( i ) , x 2 ( i ) , . . . , x n ( i ) ] ⊤ \mathbf{x}^{(i)} [x_1^{(i)}, x_2…

k8s学习-DaemonSet和Job

1.1DaemonSet是什么 Deployment部署的副本Pod会分布在各个Node上&#xff0c;每个Node都可能运行好几个副本。DaemonSet的不同之处在于&#xff1a;每个Node上最多只能运行⼀个副本。DaemonSet的典型应用场景有&#xff1a; &#xff08;1&#xff09;在集群的每个节点上运⾏存…