LwESP 是一个专门解析 Espressif 公司旗下 ESP 系列芯片 AT 指令的开源库,具有以下特性:
- 支持 Espressif 公司 ESP32, ESP32-C2, ESP32-C3, ESP32-C6 和 ESP8266 芯片。
- 独立平台,采用 C99 标准编写,易于移植。
- 允许不同的配置来优化客户的需求。
- 针对 RTOS 系统进行了优化。
- 有专门的 2 个线程来处理用户的输入和接收的数据
- Producer 线程:用于从应用程序接收用户命令并执行
- Process 线程:处理从 ESP 返回的数据
- 有专门的 2 个线程来处理用户的输入和接收的数据
- 支持在 LwESP 上直接运行以下应用:
- HTTP server
- MQTT client
- Cayenne MQTT server
- 嵌入其它 AT 指令,如 WPS
- 用户友好的 MIT License
综上,对于直接运行 AT 固件的 ESP 系列芯片,使用 LwESP 可以直接实现网络功能而无需去研究各种 AT 指令,从而可以让用户专注于应用即可。
这里顺带提一下笔者的开发环境:
- MCU:STM32F103C8T6
- Library: 标准库 en.stsw-stm32054_v3-6-0 (HAL 库和 LL 库对应参考即可)
- RTOS:FreeRTOSv202210.01-LTS
LwESP 移植可以分为以下几步:
- 下载源码
- 源码加入工程
- 接口移植
- 应用测试
下载源码
LwESP 的源码可以参考 GitHub 上的仓库。一般来说,源码下载只需要下载正式 release 的版本即可,不需要刻意下载 main 和 develop 分支。笔者在写这篇文档时最新的 release 版本为 Release v1.1.2-dev
。
源码加入工程
LwESP 采用 CMake
来构建系统。如果不支持 CMake
的话,LwESP 也支持将源码加入到自己的工程中来编译。笔者使用 KEIL 来编译 STM32,所以需要采用后者的编译方式。
将 LwESP 加入到自己的工程中也很简单,分为几下几步:
- 将
lwesp
目录拷贝到工程中。lwesp
目录中包含了 LwESP 实现的源码。 - 在 KEIL 的头文件搜索路径中增加
lwesp/src/include
。注意这里头文件的路径只需要lwesp/src/include
即可,因为在 LwESP 的 C 源文件中包含的头文件类似于#include "lwesp/lwesp.h
,已经以相对路径的方式包含了所需要的头文件,所以这里只包含lwesp/src/include
这一个头文件路径即可。 - 将
lwesp
目录下src
下的所有 C 源文件(除了子目录system
下的 C 源文件)加入到 KEIL 中。注意src
目录下又分为了好几个子目录,各个子目录下的 C 源文件都到加入到 KEIL 中。笔者这里就以src
目录下个各个子目录在 KEIL 中一一进行了逻辑划分。
- 将
system
下的 C 源文件lwesp_ll_stm32.c
和lwesp_sys_freertos.c
加入到 KEIL 中。(为什么单独是这 2 个文件,后面 接口移植 会说明) - 拷贝
lwesp/src/include/lwesp/lwesp_opts_template.h
头文件到工程中并重新命名为lwesp_opts.h
。注意放置头文件lwesp_opts.h
的所在目录也必须加入到 KEIL 头文件搜索路径中。
注:
system
下的 C 源文件主要分为 3 部分:
- 示例系统接口,主要针对的是 RTOS
- 示例芯片驱动,主要针对的是芯片的一些驱动,用于芯片和 ESP 设备之间的物理通信
- 示例内存管理
接口移植
分层结构
在正式移植 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
层的桥梁。主要分为以下几个接口:- 线程管理
- 二进制信号量管理
- 递归互斥管理
- 消息队列管理
- 当前时间状态信息
-
Low-level functions:该层中的接口负责
ESP AT Lib middleware
层和ESP8266 or ESP32 physical device
层之间的通信。
ESP8266 or ESP32 physical device:
移植
了解了 LwESP 的分层结构之后,其实移植工作主要做的工作就是完全实现 System functions & Low-level functions
层。而对于这一层,又可以分为实现 System functions
和 Low-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
文件,移植包括以下几步:
- 将
LL
开头的 LL 库接口替换成标准库的接口 - 将
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
修改后如下:
/**
* \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 所使用的 TX 和 RX 引脚外,LwESP 还约定了其它的一些引脚,对于 ESP32 系列,LwESP 还添加了宏定义 LWESP_RESET_PIN
用来控制 ESP32 RST 引脚。而对于 ESP8266 系列,LwESP 定义了 LWESP_GPIO0_PIN
,LWESP_GPIO2_PIN
和 LWESP_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
如下:
/**
******************************************************************************
* @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.c
和 netconn_client.c
。因为 main.c
中调用了这几个源文件中的接口,需要将它们也拷贝到自己的工程中。同时也需要将 snippets/include
中包含到的头文件也添加到自己的工程中,同时不要忘记头文件放置目录也需要添加到 KEIL 的头文件搜索路径中。
Demo 跑起来还需要配置以下参数:
- AP 的 SSID 和 PASSWORD
- TCP Server 的 IP 地址和连接端口
- 发送给 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
请求。
当 STM32 接收到 TCP Server 发送过来的数据时,LwESP 也会通知应用层接收到了多少字节的数据。
到这里,整个 LwESP 的移植和测试已经全部完成了。其实 LwESP 可以挖掘的功能还有很多,各位读者如果有兴趣可以自行去深入挖掘一下,期待与各位读者的技术交流~~~