ESPIDF备忘
ESP8266
环境搭建
Windows
首先确保安装好了vscode和git
在工作目录使用git 克隆这个
git clone --recursive https://github.com/espressif/ESP8266_RTOS_SDK.git
下载 集成环境和 ESP8266编译工具
旧版本的集成工具可能有问题 这里用20200601版本的
https://dl.espressif.com/dl/esp32_win32_msys2_environment_and_esp2020r2_toolchain-20200601.zip
https://dl.espressif.com/dl/xtensa-lx106-elf-gcc8_4_0-esp-2020r3-win32.zip
克隆和解压后得到这仨文件
将文件夹(
\xtensa-lx106-elf-gcc8_4_0-esp-2020r3-win32\xtensa-lx106-elf
)移到这里(\msys32\opt
)
将(
ESP8266_RTOS_SDK
)移到\msys32\home
\msys32\etc\profile.d
下的esp32_toolchain.sh
中更改为以下注意要用
/
代替\
export PATH="$PATH:/opt/xtensa-lx106-elf/bin"
export IDF_PATH=C:/Users/HZ12138/Desktop/temp/msys32/home/ESP8266_RTOS_SDK
vscode
在工作目录建立
.vscode/settings.json
和.vscode/c_cpp_properties.json
在
.vscode/settings.json
中写入以下代码注意
path
要写自己的路径
{
"terminal.integrated.profiles.windows": {
"esp8266shell": {
"path": "C:/Users/HZ12138/Desktop/temp/msys32/msys2_shell.cmd",
"args": [
"-defterm",
"-mingw32",
"-no-start",
"-here"
]
}
},
"terminal.integrated.defaultProfile.windows": "esp8266shell"
}
在
.vscode/c_cpp_properties.json
中写入以下代码注意
path
要写自己的路径
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**",
"C:/Users/HZ12138/Desktop/temp/msys32/home/ESP8266_RTOS_SDK/**",
"C:/Users/HZ12138/Desktop/temp/msys32/home/ESP8266_RTOS_SDK/components/freertos/port/esp8266/include"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"cStandard": "c17",
"cppStandard": "gnu++14",
"intelliSenseMode": "windows-gcc-x64"
}
],
"version": 4
}
Linux(Ubuntu)
linux下编译的非常快
这里选带图形化界面的
安装依赖
sudo apt-get install gcc git wget make libncurses-dev flex bison gperf vim python-is-python3 python3-pip
下载工具链 64位
解压后获得
wget https://dl.espressif.com/dl/xtensa-lx106-elf-gcc8_4_0-esp-2020r3-linux-amd64.tar.gz
克隆SDK
git clone --recursive https://github.com/espressif/ESP8266_RTOS_SDK.git
加入环境变量
sudo vim ~/.bashrc
export PATH=${JAVA_HOME}/bin:$PATH:/home/hz12138/Desktop/ESP/xtensa-lx106-elf/bin/
alias esp8266='export IDF_PATH=/home/hz12138/Desktop/ESP/ESP8266_RTOS_SDK/'
source ~/.bashrc
安装环境
在
ESP8266_RTOS_SDK
中运行命令行输入并运行
可能有报错 一般不用管先试试测试
python -m pip install --user -r requirements.txt
测试
在
/ESP8266_RTOS_SDK/examples/get-started/hello_world
下打开命令行输入 esp8266 后输入make 无报错即可
vscode
在
.vscode/c_cpp_properties.json
中写入以下代码注意
path
要写自己的路径
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/hz12138/Desktop/ESP/ESP8266_RTOS_SDK/**",
"/home/hz12138/Desktop/ESP/ESP8266_RTOS_SDK/components/freertos/port/esp8266/include"
],
"defines": [],
"compilerPath": "/bin/gcc",
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}
编译时先写入esp8266启用环境 再make等命令
环境相关
添加文件
步骤
- 建立文件夹
components/XXX
- 建立文件
XX.c
XX.h
component.mk
- 修改
main/CMakeLists.txt
- 导入头文件
#include "../components/my_UART/xx.h"
建立文件夹
components/XXX
注意一定是
components
文件夹下的子文件夹
建立文件
XX.c
XX.h
component.mk
C和H文件名任意,
component.mk
一定要是这个名字,内部可留空
修改
main/CMakeLists.txt
示例:
set(src "wifi_ctrl_aio.c" "xx/xx.c" ) set(inc "." "xx" ) idf_component_register( SRCS ${src} INCLUDE_DIRS ${inc})
添加大文件
主要用于添加html相关的文件
步骤
- 建立文件夹和文件
- 在
component.mk
中添加导入指令 - 使用编译好的数组即可
建立文件夹和文件
建议与c文件放在同一个文件夹里
写入
component.mk
要在同级目录中写入,如上图中的
component.mk
如下图,在
www/test.html
中写入COMPONENT_EMBED_TXTFILES += www/test.html
使用数组
编译出的文件开始地址和结束地址为
_binary_xx_html_start
,_binary_xx_html_start
,两个地址一减就是数组长度extern const unsigned char script_start[] asm("_binary_test_html_start"); extern const unsigned char script_end[] asm("_binary_test_html_end"); (size_t)script_end - (size_t)script_start;
编译烧录
设置烧录端口
终端输入
make menuconfig
选择串口烧录设置
在第一项中输入端口号即可
编译
全部编译
make all
仅编译变化
make
清理编译内容
make clean
烧录
编译并烧录
make flash
打开串口
make monitor
编译烧录并打开串口
make flash monitor
常见错误解决方法
标题字段太长服务器无法解释(WEB服务器)
Header felds are too long for server to interpret
解决方案:
更改标题字段最大长度即可
终端输入
make menuconfig
进入功能设置
选择HTTP服务器设置
更改即可
APIs 和示例
UART
加入头文件
#include "driver/uart.h"
设置参数
描述 | 名称 | 功能 |
---|---|---|
int | baud_rate | 波特率 |
uart_word_length_t | data_bits | 字节长度,常用UART_DATA_8_BITS |
uart_parity_t | parity | 校验,常用UART_PARITY_DISABLE |
uart_stop_bits_t | stop_bits | 停止位,常用UART_STOP_BITS_1 |
uart_hw_flowcontrol_t | flow_ctrl | 流控制,不用留空 |
uint8_t | rx_flow_ctrl_thresh | 接收流控,不用留空 |
typedef struct {
int baud_rate; /*!< UART baud rate*/
uart_word_length_t data_bits; /*!< UART byte size*/
uart_parity_t parity; /*!< UART parity mode*/
uart_stop_bits_t stop_bits; /*!< UART stop bits*/
uart_hw_flowcontrol_t flow_ctrl; /*!< UART HW flow control mode (cts/rts)*/
uint8_t rx_flow_ctrl_thresh; /*!< UART HW RTS threshold*/
} uart_config_t;
描述 | 名称 | 功能 |
---|---|---|
uart_port_t | uart_num | 串口号(0,1,2…) |
uart_config_t * | uart_conf | 配置结构体 |
esp_err_t | 输出 | 状态 |
esp_err_t uart_param_config(uart_port_t uart_num, uart_config_t *uart_conf)
初始化
描述 | 名称 | 功能 |
---|---|---|
uart_port_t | uart_num | 串口号(0,1,2…) |
int | rx_buffer_size | 接收缓冲区大小 |
int | tx_buffer_size | 发送缓冲区大小 |
int | queue_size | 消息队列大小 |
QueueHandle_t * | uart_queue | 消息队列句柄 |
int | no_use | 未使用 |
esp_err_t | 输出 | 状态 |
esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, QueueHandle_t *uart_queue, int no_use)
接收到来自串口数据时会发送给消息队列
启用UART
描述 | 名称 | 功能 |
---|---|---|
uart_port_t | uart_num | 串口号(0,1,2…) |
无 | 输出 |
void esp_vfs_dev_uart_use_driver(int uart_num)
发送字节
描述 | 名称 | 功能 |
---|---|---|
uart_port_t | uart_num | 串口号(0,1,2…) |
const char * | src | 发送的数据 |
size_t | size | 数据大小 |
int | 输出 | 发送的字节数 |
int uart_write_bytes(uart_port_t uart_num, const char *src, size_t size)
同时串口0也可使用
printf
暂为发现设置的地方注意:
printf
在收到\n
时发送数据
读取字节
描述 | 名称 | 功能 |
---|---|---|
uart_port_t | uart_num | 串口号(0,1,2…) |
uint8_t * | buf | 接收缓冲区 |
uint32_t | length | 接收数据大小 |
TickType_t | ticks_to_wait | 等待时间 |
int | 输出 | 接收的字节数 |
int uart_read_bytes(uart_port_t uart_num, uint8_t *buf, uint32_t length, TickType_t ticks_to_wait)
UART示例
示例
QueueHandle_t uart0_queue;
void uart0_server(void *pvParameters)
{
uart_event_t event;
uint8_t *RX_buf = (uint8_t *)malloc(1024);
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
};
uart_port_t uart_num = UART_NUM_0;
ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));
ESP_ERROR_CHECK(uart_driver_install(uart_num, 1024, 0, 100, &uart0_queue, 0));
esp_vfs_dev_uart_use_driver(uart_num);
while (true)
{
if (xQueueReceive(uart0_queue, (void *)&event, (portTickType)portMAX_DELAY))
{
if (event.type == UART_DATA)
{
uart_read_bytes(uart_num, RX_buf, event.size, portMAX_DELAY);
// uart_write_bytes(uart_num, (const char *)RX_buf, event.size);
printf("%s", RX_buf);
}
}
}
}
void app_main()
{
xTaskCreate(uart0_server, "uart0_task", 2048, NULL, 12, NULL);
}
WIFI
TCP/IP适配器
void tcpip_adapter_init(void)
调用后初始化AP模式下默认IP为
192.168.4.1
初始化事件循环
esp_err_t esp_event_loop_create_default()
事件循环:持续监测事件的发生
去初始化事件循环
esp_event_loop_delete_default
初始化WIFI
描述 | 名称 | 功能 |
---|---|---|
const wifi_init_config_t * | config | 配置结构体 |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_wifi_init(const wifi_init_config_t *config)
可以使用下面的代码来使用默认初始化
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
去初始化wifi
esp_err_t esp_wifi_deinit(void)
注册事件处理函数
描述 | 名称 | 功能 |
---|---|---|
esp_event_base_t | event_base | 事件(wifi使用WIFI_EVENT IP_EVENT ) |
int32_t | event_id | 事件id,ESP_EVENT_ANY_ID 是全部事件 |
esp_event_handler_t | event_handler | 事件处理的函数 |
void * | event_handler_arg | 传递的参数,NULL 即可 |
esp_err_t | 输出 | 错误码 |
当出现事件时(包括设备上下线等)会调用回调函数
esp_err_t esp_event_handler_register(esp_event_base_t event_base, int32_t event_id,
esp_event_handler_t event_handler, void* event_handler_arg)
回调函数示例
void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
调用示例
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
注销事件处理函数
描述 | 名称 | 功能 |
---|---|---|
esp_event_base_t | event_base | 事件(wifi使用WIFI_EVENT IP_EVENT ) |
int32_t | event_id | 事件id,ESP_EVENT_ANY_ID 是全部事件 |
esp_event_handler_t | event_handler | 事件处理的函数 |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_event_handler_unregister(esp_event_base_t event_base, int32_t event_id,esp_event_handler_t event_handler)
配置模式
描述 | 名称 | 功能 |
---|---|---|
wifi_mode_t | mode | 模式,WIFI_MODE_STA ,WIFI_MODE_AP ,WIFI_MODE_APSTA |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_wifi_set_mode(wifi_mode_t mode);
设置wifi
描述 | 名称 | 功能 |
---|---|---|
wifi_interface_t | interface | 设置模式,ESP_IF_WIFI_STA ,ESP_IF_WIFI_AP |
wifi_config_t * | conf | 设置结构体 |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf);
描述 | 名称 | 功能 |
---|---|---|
wifi_ap_config_t | ap | AP模式使用 |
wifi_sta_config_t | sta | STA模式使用 |
描述 | 名称 | 功能 |
---|---|---|
uint8_t * | ssid | wifi名称 |
uint8_t * | password | wifi密码 |
uint8_t | ssid_len | 名称长度 |
uint8_t | channel | 通道 |
wifi_auth_mode_t | authmode | 加密模式,常用WIFI_AUTH_WPA_WPA2_PSK ,WIFI_AUTH_OPEN |
uint8_t | ssid_hidden | 是否隐藏,默认0不隐藏 |
uint8_t | max_connection | 最大连接数,最大4 |
uint16_t | beacon_interval | 通讯间隔,默认100ms |
描述 | 名称 | 功能 |
---|---|---|
uint8_t * | ssid | WiFi名称 |
uint8_t * | password | wifi密码 |
wifi_scan_method_t | scan_method | 扫描模式 |
bool | bssid_set | 是否设置目标AP的MAC地址 |
uint8_t * | bssid | 目标AP的MAC地址 |
uint8_t | channel | 使用的通道 |
uint16_t | listen_interval | 接收信标的监听间隔 |
wifi_sort_method_t | sort_method | 通过 RSSI 或安全模式在列表中连接 AP |
wifi_fast_scan_threshold_t | threshold | 仅使用验证模式比所选验证模式更安全且信号强于最小 RSSI 的 AP |
wifi_pmf_config_t | pmf_cfg | 受保护管理框架的配置 |
uint32_t | rm_enabled | 是否为连接启用无线电测量 |
uint32_t | btm_enabled | 连接是否启用BTM |
uint32_t | reserved | 保留 |
一般只要设置wifi名和密码即可
开启wifi
esp_err_t esp_wifi_start(void);
停止wifi
esp_err_t esp_wifi_stop(void);
开始扫描wifi
描述 | 名称 | 功能 |
---|---|---|
const wifi_scan_config_t * | config | 扫描配置,可传入NULL |
bool | block | 是否阻塞,ture 时扫描完成返回,false 时立即返回 |
esp_err_t | 输出 | 错误码 |
此函数要在
STA
或者AP+STA
模式下使用
esp_err_t esp_wifi_scan_start(const wifi_scan_config_t *config, bool block);
获取扫描到的AP数量
描述 | 名称 | 功能 |
---|---|---|
uint16_t * | number | 传出参数,获取的AP数量 |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number);
获取扫描的wifi内容
描述 | 名称 | 功能 |
---|---|---|
uint16_t * | number | 输入:最大可扫描AP数量 输出:扫描到的数量 |
esp_err_t | 输出 | 错误码 |
描述 | 名称 | 功能 |
---|---|---|
uint8_t | bssid | AP的MAC地址 |
uint8_t | ssid | AP的SSID |
uint8_t | primary | AP的通道 |
wifi_second_chan_t | second | AP的次要通道 |
int8_t | rssi | AP信号强度 |
int16_t | freq_offset | AP频偏 |
wifi_auth_mode_t | authmode | AP的加密模式 |
wifi_cipher_type_t | pairwise_cipher | AP对密码 |
wifi_cipher_type_t | group_cipher | AP组密码 |
wifi_ant_t | ant | 用于从AP接收信标的天线 |
uint32_t | phy_11b | 11b模式标志 |
uint32_t | phy_11g | 11g模式标志 |
uint32_t | phy_11n | 11n模式标志 |
uint32_t | phy_lr | 低速率标志 |
uint32_t | wps | WPS支持标志 |
uint32_t | reserved | 保留 |
wifi_country_t | country | 国家信息 |
esp_err_t esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records);
修改主机名
描述 | 名称 | 功能 |
---|---|---|
tcpip_adapter_if_t | tcpip_if | |
const char * | hostname | 主机名 |
esp_err_t | 输出 | 错误码 |
注意要将
LWIP_NETIF_HOSTNAME
宏定义打开
esp_err_t tcpip_adapter_set_hostname(tcpip_adapter_if_t tcpip_if, const char *hostname)
示例
AP模式
/**
* @brief AP模式下设备连接的回调函数
* @param mac 设备的MAC地址
* @author HZ12138
* @date 2025-01-08 12:32:22
*/
__WEAK void wifi_ap_STAconnect(uint64_t mac)
{
}
/**
* @brief AP模式下设备断开的回调函数
* @param mac 设备的MAC地址
* @author HZ12138
* @date 2025-01-08 12:33:02
*/
__WEAK void wifi_ap_STAdisconnect(uint64_t mac)
{
}
/**
* @brief wifi事件处理函数(AP)
* @param arg 参数
* @param event_base 事件名
* @param event_id 事件ID
* @param event_data 数据
* @author HZ12138
* @date 2025-01-06 21:20:12
*/
void wifi_ap_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
uint64_t mac;
wifi_event_ap_staconnected_t *event;
// AP模式
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED)
{ // 设备连接
event = (wifi_event_ap_staconnected_t *)event_data;
mac = ((uint64_t)event->mac[5] << 40) | ((uint64_t)event->mac[4] << 32) | ((uint64_t)event->mac[3] << 24) | ((uint64_t)event->mac[2] << 16) | ((uint64_t)event->mac[1] << 8) | event->mac[0];
wifi_ap_STAconnect(mac);
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED)
{ // 设备断开
event = (wifi_event_ap_staconnected_t *)event_data;
mac = ((uint64_t)event->mac[5] << 40) | ((uint64_t)event->mac[4] << 32) | ((uint64_t)event->mac[3] << 24) | ((uint64_t)event->mac[2] << 16) | ((uint64_t)event->mac[1] << 8) | event->mac[0];
wifi_ap_STAdisconnect(mac);
}
}
/**
* @brief wifi初始化为服务端
* @param SSID wifi名称
* @param password wifi密码
* @return 错误码
* @author HZ12138
* @date 2025-01-06 21:21:33
*/
esp_err_t wifi_AP_init(char *SSID, char *password)
{
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
wifi_config_t wifi_config = {
.ap = {
.ssid_len = strlen(SSID),
.max_connection = 4, // 最大连接数
.authmode = WIFI_AUTH_WPA_WPA2_PSK // 加密方式
}};
esp_err_t err;
// 写入SSID和密码
strcpy((char *)wifi_config.ap.ssid, SSID);
strcpy((char *)wifi_config.ap.password, password);
// 如果密码为空,则设置为开放模式
if (strlen(password) == 0)
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
tcpip_adapter_init(); // 初始化tcpip适配器
MY_CHECK_ERR(esp_event_loop_create_default()); // 初始化事件循环
MY_CHECK_ERR(esp_wifi_init(&cfg)); // 初始化wifi
MY_CHECK_ERR(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_ap_event_handler, NULL)); // 注册事件处理函数
MY_CHECK_ERR(esp_wifi_set_mode(WIFI_MODE_AP)); // 设置为AP模式
MY_CHECK_ERR(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config)); // 设置AP模式的wifi配置
MY_CHECK_ERR(esp_wifi_start()); // 开启wifi
return ERR_OK;
}
STA模式
/**
* @brief wifi事件处理函数(STA)
* @param arg 参数
* @param event_base 事件名
* @param event_id 事件ID
* @param event_data 数据
* @author HZ12138
* @date 2025-01-08 00:22:32
*/
void wifi_STA_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
ip_event_got_ip_t *event;
uint8_t queue_data;
if (event_base == WIFI_EVENT)
{
switch (event_id)
{
case WIFI_EVENT_STA_START: // WIFI以STA模式启动后触发此事件
wifi_STA_disconnect_times = 0;
esp_wifi_connect(); // 启动WIFI连接
break;
case WIFI_EVENT_STA_CONNECTED: // WIFI连上路由器后,触发此事件
wifi_STA_disconnect_times = 0;
// printf("connected to AP\n");
break;
case WIFI_EVENT_STA_DISCONNECTED: // WIFI从路由器断开连接后触发此事件
wifi_STA_disconnect_times++;
queue_data = WIFI_FAIL_BIT;
xQueueOverwrite(wifi_STA_connect_status, &queue_data);
vTaskDelay(5000 / portTICK_PERIOD_MS);
esp_wifi_connect(); // 继续重连
break;
default:
break;
}
}
if (event_base == IP_EVENT) // IP相关事件
{
switch (event_id)
{
case IP_EVENT_STA_GOT_IP: // 只有获取到路由器分配的IP,才认为是连上了路由器
event = (ip_event_got_ip_t *)event_data;
wifi_STA_disconnect_times = 0;
queue_data = WIFI_CONNECTED_BIT;
xQueueOverwrite(wifi_STA_connect_status, &queue_data);
wifi_ip = event->ip_info.ip;
// printf("got ip:%s\n", ip4addr_ntoa(&wifi_ip));
break;
}
}
}
/**
* @brief wifi初始化为客户端
* @param SSID wifi名称
* @param password wifi密码
*
* @return 错误码
* @author HZ12138
* @date 2025-01-08 00:22:32
*/
esp_err_t wifi_STA_init(char *SSID, char *password, uint8_t *mac)
{
// WIFI配置
wifi_config_t wifi_config =
{
.sta =
{
.threshold.authmode = WIFI_AUTH_WPA2_PSK, // 加密方式
.pmf_cfg =
{
.capable = true,
.required = false},
},
};
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t err;
wifi_STA_connect_status = xQueueCreate(1, sizeof(uint8_t));
if (strlen(SSID) != 0)
{
tcpip_adapter_init(); // 用于初始化tcpip协议栈
MY_CHECK_ERR(esp_event_loop_create_default()); // 创建默认系统事件调度循环
MY_CHECK_ERR(esp_wifi_init(&cfg)); // 初始化WIFI
// 注册事件
MY_CHECK_ERR(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_STA_event_handler, NULL));
MY_CHECK_ERR(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_STA_event_handler, NULL));
// 如果有mac地址,设置BSSID
if (mac != NULL)
{
wifi_config.sta.bssid_set = 1; // 设置BSSID
memcpy(wifi_config.sta.bssid, mac, 6);
}
// 写入SSID和密码
strcpy((char *)wifi_config.sta.ssid, SSID);
strcpy((char *)wifi_config.sta.password, password);
// 启动WIFI
MY_CHECK_ERR(esp_wifi_set_mode(WIFI_MODE_STA)); // 设置工作模式为STA
MY_CHECK_ERR(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); // 设置wifi配置
MY_CHECK_ERR(esp_wifi_start()); // 启动WIFI
}
else
{ // 没有SSID单启动不连接wifi
MY_CHECK_ERR(esp_event_loop_create_default()); // 创建默认事件循环,该循环将处理所有的网络事件
MY_CHECK_ERR(esp_wifi_init(&cfg)); // 配置 WiFi 模块
MY_CHECK_ERR(esp_wifi_set_mode(WIFI_MODE_STA)); // 设置 WiFi 模式为 STA(Station 模式)
MY_CHECK_ERR(esp_wifi_start()); // 启动WiFi
}
return ESP_OK;
}
扫描wif
/**
* @brief 扫描wifi热点
* @attention 请在wifi_init_STA("","")函数后调用,即只打开不连接
* @param ap_records 热点信息
* @param ap_num 热点数量
* @return 错误码
* @author HZ12138
* @date 2025-01-08 00:22:32
*/
esp_err_t wifi_scan(wifi_ap_record_t *ap_records, uint16_t *ap_num)
{
esp_err_t err;
MY_CHECK_ERR(esp_wifi_scan_start(NULL, true)); // 开始扫描可用的Wi-Fi热点
MY_CHECK_ERR(esp_wifi_scan_get_ap_num(ap_num)); // 获取可用 Wi-Fi 热点数量
MY_CHECK_ERR(esp_wifi_scan_get_ap_records(ap_num, ap_records)); // 获取所有可用 Wi-Fi 热点信息
return ESP_OK;
}
关闭wifi
/**
* @brief 释放wifi资源
* @return 错误码
* @author HZ12138
* @date 2025-01-08 00:22:32
*/
esp_err_t wifi_deinit(void)
{
esp_err_t err;
esp_event_loop_delete_default();
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_ap_event_handler);
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_STA_event_handler);
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, &wifi_STA_event_handler);
MY_CHECK_ERR(esp_wifi_stop());
MY_CHECK_ERR(esp_wifi_deinit());
return ESP_OK;
}
非易失性存储器(NVS)
加载NVS
esp_err_t nvs_flash_init(void)
加载NVS,在使用NVS之前调用
打开NVS
描述 | 名称 | 功能 |
---|---|---|
const char* | name | 命名空间名称 |
nvs_open_mode_t | open_mode | NVS_READONLY 只读 NVS_READWRITE 可读写 |
nvs_handle_t * | out_handle | 句柄 |
esp_err_t | 输出 | 错误码 |
esp_err_t nvs_open(const char* name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle)
设置数据
描述 | 名称 | 功能 |
---|---|---|
nvs_handle_t | c_handle | 句柄 |
const char* | key | 键 |
const void* | value | 设置缓冲区 |
length | size_t | 数据长度 |
esp_err_t | 输出 | 错误码 |
esp_err_t nvs_set_blob(nvs_handle_t c_handle, const char* key, const void* value, size_t length)
调用后需要调用
nvs_commit
后才能写入NVS
发布数据
描述 | 名称 | 功能 |
---|---|---|
nvs_handle_t | c_handle | 句柄 |
esp_err_t | 输出 | 错误码 |
esp_err_t nvs_commit(nvs_handle_t c_handle)
读取数据
描述 | 名称 | 功能 |
---|---|---|
nvs_handle_t | c_handle | 句柄 |
const char* | key | 键 |
void* | out_value | 读取缓冲区 |
size_t* | length | 读取的数据大小 |
esp_err_t | 输出 | 错误码 |
esp_err_t nvs_get_blob(nvs_handle_t c_handle, const char* key, void* out_value, size_t* length)
这个函数需要读取两次,第一次读取数据大小,第二次读取出数据内容
示例
// 获取读取长度 *len = 0; err = nvs_get_blob(my_handle, key, NULL, len); if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err; // 读取数据 err = nvs_get_blob(my_handle, key, buf, len); if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
关闭NVS
描述 | 名称 | 功能 |
---|---|---|
nvs_handle_t | c_handle | 句柄 |
无 | 输出 |
void nvs_close(nvs_handle_t handle)
卸载NVS(可选)
描述 | 名称 | 功能 |
---|---|---|
esp_err_t | 错误码 |
esp_err_t nvs_flash_deinit(void)
卸载NVS,在使用NVS之后调用
NVS使用示例
/**
* @brief 向NVS中写入数据
* @param name_space 命名空间
* @param key 键
* @param buf 缓冲区
* @param len 写入的数据长度
* @return 错误码 0成功
* @author HZ12138
* @date 2025-01-05 21:17:42
*/
esp_err_t NVS_write_data(char *name_space, char *key, void *buf, size_t len)
{
nvs_handle_t my_handle;
esp_err_t err;
// 打开NVS
nvs_flash_init();
err = nvs_open(name_space, NVS_READWRITE, &my_handle);
if (err != ESP_OK)
return err;
// 设置数据
err = nvs_set_blob(my_handle, key, buf, len);
if (err != ESP_OK)
return err;
// 更新数据
err = nvs_commit(my_handle);
if (err != ESP_OK)
return err;
// 关闭NVS
nvs_close(my_handle);
nvs_flash_deinit();
return ESP_OK;
}
/**
* @brief NVS读取数据
* @param name_space 命名空间
* @param key 键
* @param buf 缓冲区
* @param len 读取的数据长度
* @return 错误码 0成功
* @author HZ12138
* @date 2025-01-05 21:20:19
*/
esp_err_t NVS_read_data(char *name_space, char *key, void *buf, size_t *len)
{
nvs_handle_t my_handle;
esp_err_t err;
// 打开NVS
nvs_flash_init();
err = nvs_open(name_space, NVS_READWRITE, &my_handle);
if (err != ESP_OK)
return err;
// 获取读取长度
*len = 0;
err = nvs_get_blob(my_handle, key, NULL, len);
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
return err;
// 读取数据
err = nvs_get_blob(my_handle, key, buf, len);
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
return err;
// 关闭NVS
nvs_close(my_handle);
nvs_flash_deinit();
return ESP_OK;
}
Web
此部分需要导入头文件
esp_netif.h
,esp_http_server.h
服务器
开启HTTP服务器
描述 | 名称 | 功能 |
---|---|---|
httpd_handle_t * | handle | 句柄 |
const httpd_config_t * | config | 配置 |
无 | 输出 |
描述 | 名称 | 功能 |
---|---|---|
unsigned int | task_priority | 运行服务器的FreeRTOS任务的优先级 |
size_t | stack_size | 服务器任务允许的最大堆栈大小 |
uint16_t | server_port | TCP端口号(主要数据流量) |
uint16_t | ctrl_port | UDP端口号(组件控制) |
uint16_t | max_open_sockets | 最大客户端数量 |
uint16_t | max_uri_handlers | 最大uri头 |
uint16_t | max_resp_headers | 响应最大附加标头 |
uint16_t | backlog_conn | 积压连接数 |
bool | lru_purge_enable | 清除最后使用连接 |
uint16_t | recv_wait_timeout | 接收超时(s) |
uint16_t | send_wait_timeout | 发送超时(s) |
void * | global_user_ctx | 全局用户上下文 |
httpd_free_ctx_fn_t | global_user_ctx_free_fn | 全局用户自由函数 |
void * | global_transport_ctx | 全局传输内容 |
httpd_free_ctx_fn_t | global_transport_ctx_free_fn | 全局传输自由函数 |
httpd_open_func_t | open_fn | 自定义会话打开回调 |
httpd_close_func_t | close_fn | 自定义会话结束回调 |
esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
可使用
HTTPD_DEFAULT_CONFIG()
初始化默认的配置一般情况默认配置即可
设置页面
描述 | 名称 | 功能 |
---|---|---|
httpd_handle_t | handle | 句柄 |
const httpd_uri_t * | uri_handler | 配置 |
输出 | 错误码 |
描述 | 名称 | 功能 |
---|---|---|
const char * | uri | 路径 |
esp_err_t (*handler)(httpd_req_t *r) | handler | 回调函数,收到请求时 |
void * | user_ctx | 数据指针 |
esp_err_t httpd_register_uri_handler(httpd_handle_t handle,const httpd_uri_t *uri_handler)
设置响应类型
描述 | 名称 | 功能 |
---|---|---|
httpd_req_t * | r | 请求体 |
const char * | type | 类型,可选HTTPD_TYPE_JSON ,HTTPD_TYPE_TEXT ,HTTPD_TYPE_OCTET |
输出 | 错误码 |
esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type)
发送响应数据
描述 | 名称 | 功能 |
---|---|---|
httpd_req_t * | r | 请求体 |
const char * | buf | 发送缓冲区 |
ssize_t | buf_len | 发送数据长度 |
输出 | 错误码 |
esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len)
获取请求参数长度
描述 | 名称 | 功能 |
---|---|---|
httpd_req_t * | r | 请求体 |
输出 | 参数字符串长度 |
size_t httpd_req_get_url_query_len(httpd_req_t *r)
获取请求参数字符串
描述 | 名称 | 功能 |
---|---|---|
httpd_req_t * | r | 请求体 |
char * | buf | 缓冲区 |
size_t | buf_len | 缓冲区大小 |
输出 | 错误码 |
esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *buf, size_t buf_len)
查找请求参数
描述 | 名称 | 功能 |
---|---|---|
const char * | qry_str | 参数字符串 |
const char * | key | 健 |
char * | val | 值缓冲区 |
size_t | val_size | 缓冲区大小 |
输出 | 错误码 |
esp_err_t httpd_query_key_value(const char *qry_str, const char *key, char *val, size_t val_size)
关闭HTTP服务器
描述 | 名称 | 功能 |
---|---|---|
httpd_handle_t * | handle | 句柄 |
无 | 输出 |
esp_err_t httpd_stop(httpd_handle_t handle)
示例
泛地址响应服务器
ESP8266的HTTP服务器不支持通配符
因此使用tcp套接字建立了一个HTTP服务器
步骤:
- 建立套接字
- 收到信息时建立新线程
- 解析HTTP数据
代码
/**
* @brief 向套接字中写入数据(任意路径服务器用)
* @param fd 套接字编号
* @param buffer 缓冲区
* @param length 写入长度
* @return 错误码
* @author HZ12138
* @date 2025-01-10 17:17:09
*/
int web_any_path_server_write(int fd, void *buffer, int length)
{
int bytes_left;
int written_bytes;
char *ptr;
ptr = (char *)buffer;
bytes_left = length;
while (bytes_left > 0)
{
written_bytes = lwip_send(fd, ptr, bytes_left, 0);
if (written_bytes <= 0)
{
if (errno == EINTR)
written_bytes = 0;
else
return (-1);
}
bytes_left -= written_bytes;
ptr += written_bytes;
vTaskDelay(10);
}
return (0);
}
/**
* @brief 任意路径web服务器GET回调
* @param mwrite 写入套接字函数
* @param socket_num 套接字编号
* @param uri uri字符串
* @param body 请求体字符串
* @return 错误码
* @author HZ12138
* @date 2025-01-10 22:08:46
*/
__WEAK int web_any_path_server_GET_callback(int (*mwrite)(int fd, void *buffer, int length), int socket_num, char *uri, char *body)
{
char buf[300];
// recv(socket_num, buf, 299, 0);
// printf("%s\n", buf);
sprintf(buf, HTTP_200, (int)(index_html_end - index_html_start) - 1);
mwrite(socket_num, (void *)buf, strlen(buf));
mwrite(socket_num, (void *)index_html_start, index_html_end - index_html_start - 1);
return 0;
}
/**
* @brief 任意路径web服务器POST回调
* @param mwrite 写入套接字函数
* @param socket_num 套接字编号
* @param uri uri字符串
* @param body 请求体字符串
* @return 错误码
* @author HZ12138
* @author HZ12138
* @date 2025-01-10 22:27:14
*/
__WEAK int web_any_path_server_POST_callback(int (*mwrite)(int fd, void *buffer, int length), int socket_num, char *uri, char *body)
{
return 0;
}
/**
* @brief 任意路径web服务器处理函数
* @param pfd 套接字编号
* @author HZ12138
* @date 2025-01-10 21:53:29
*/
void web_any_path_server_request(void *pfd)
{
char buf[2048] = {0}; // 数据缓冲器
int socket_num = *(int *)pfd;
int bytes_recvd = 0;
char uri[300] = {0};
char *temp = NULL;
char *temp2 = NULL;
char *body = NULL;
char method[10] = {0};
vTaskDelay(30);
bytes_recvd = lwip_recv(socket_num, buf, 2048 - 1, 0);
if (bytes_recvd <= 0) // 读取数据失败
goto requst_error;
temp = strstr(buf, "HTTP"); // 寻找HTTP标志
if (temp == NULL) // 未找到HTTP
goto requst_error;
// 获取方法名 解析请求的第一个空格
temp = NULL;
temp = strstr(buf, " ");
strncpy(method, buf, temp - buf);
// printf("method:%s\n", method);
// 获取uri 解析第一二个空格间内容 temp现在指向第一个空格位置
temp2 = strstr(++temp, " ");
strncpy(uri, temp, temp2 - temp);
// printf("uri:%s\n", uri);
// 获取body 解析/r/n/r/n处
body = strstr(buf, "\r\n\r\n");
body += 4;
// printf("body:%s\n", body);
if (strcmp(method, "GET") == 0) // 响应GET请求
{
if (web_any_path_server_GET_callback(web_any_path_server_write, socket_num, uri, body) < 0)
goto requst_error;
}
else if (strcmp(method, "POST") == 0) // 响应POST请求
{
if (web_any_path_server_POST_callback(web_any_path_server_write, socket_num, uri, body) < 0)
goto requst_error;
}
else // 其他请求不响应
{
if (web_any_path_server_write(socket_num, (void *)HTTP_400, strlen(HTTP_400)) < 0)
goto requst_error;
}
vTaskDelay(30);
requst_error:
lwip_close(socket_num);
vTaskDelete(NULL);
}
/**
* @brief 任意路径web服务器任务
* @author HZ12138
* @date 2025-01-10 16:42:02
*/
void web_any_path_server_task(void *pvParameters)
{
int sockfd, new_fd;
struct sockaddr_in my_addr; // 本方地址信息
struct sockaddr_in their_addr; // 对方地址信息
socklen_t sin_size;
struct timeval tv; // 发送接收超时时间
tv.tv_sec = 10;
tv.tv_usec = 0;
sin_size = sizeof(struct sockaddr_in);
sockfd = lwip_socket(AF_INET, SOCK_STREAM, 0); // 建立socket
if (sockfd == -1) // 套接字建立失败
goto web_err;
my_addr.sin_family = AF_INET; // 该属性表示接收本机或其他机器传输
my_addr.sin_port = lwip_htons(80); // 端口
my_addr.sin_addr.s_addr = lwip_htonl(INADDR_ANY); // 本机IP
bzero(&(my_addr.sin_zero), 8); // 将其他置0
if (lwip_bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) < 0) // 绑定套接字数据
goto web_err;
lwip_listen(sockfd, 8); // 开启监听 ,第二个参数是最大监听数
while (1)
{
new_fd = lwip_accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); // 在这里阻塞直到接收到消息
if (new_fd == -1)
{ // 接收信息失败
}
else
{
// 多线程写法
lwip_setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
lwip_setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
int *para_fd = malloc(sizeof(int));
*para_fd = new_fd; // 编号
xTaskCreate(&web_any_path_server_request, "socket_task", 1024 * 4, para_fd, 6, NULL); // 建立任务
}
vTaskDelay(10);
}
web_err:
vTaskDelete(NULL);
}
/**
* @brief web任意路径解析启动函数
* @author HZ12138
* @date 2025-01-10 16:38:55
*/
void web_start_any_path_server(void)
{
xTaskCreate(&web_any_path_server_task, "webserver_task", 2048, NULL, 5, NULL);
}
客户端
用套接字写的HTTP客户端
/**
* @brief 解析域名获取地址信息
* @param url_server 域名地址
* @param port 端口
* @return 地址信息
* @author HZ12138
* @date 2025-01-09 22:46:48
*/
struct addrinfo *DNS_get_addrinfo(const char *url_server, const char *port)
{
const struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *res;
int err;
if (port == NULL)
err = getaddrinfo(url_server, "80", &hints, &res);
else
err = getaddrinfo(url_server, port, &hints, &res);
if (err != 0 || res == NULL)
{
return NULL;
}
return res;
}
/**
* @brief http读取函数示例
* @param socket_num 套接字编号
* @author HZ12138
* @date 2025-01-09 22:34:03
*/
void read_test(int socket_num)
{
int read_num;
char recv_buf[100];
do
{
memset(recv_buf, 0, 100);
read_num = lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1);
uart_write_bytes(UART_NUM_0, recv_buf, read_num);
} while (read_num > 0);
}
/**
* @brief 发送http请求
* @param url_server 服务器根域名如(example.com)
* @param url 请求地址(http://example.com/)
* @param method 方法,大写(GET,POST)
* @param body 请求体
* @param read_after 读取处理函数,内部调用 lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1)来读取
* @return 错误码
* @author HZ12138
* @date 2025-01-09 22:34:37
*/
esp_err_t web_http_request(const char *url_server, const char *url, const char *method, const char *body, void (*read_after)(int socket_num))
{
struct addrinfo *res;
int socket_num;
char *REQUEST = malloc(200 + strlen(url_server) + strlen(url) + strlen(body));
sprintf(REQUEST,
"%s %s HTTP/1.0\r\n"
"Host: %s\r\n"
"User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0\r\n"
"Content-Length: %d\r\n"
"\r\n"
"%s",
method, url, url_server, strlen(body), body);
res = DNS_get_addrinfo(url_server, NULL); // 解析域名
// 建立套接字
socket_num = lwip_socket(res->ai_family, res->ai_socktype, 0);
if (socket_num < 0)
{
freeaddrinfo(res);
return ESP_FAIL;
}
// 套接字连接
if (lwip_connect(socket_num, res->ai_addr, res->ai_addrlen) != 0)
{
lwip_close(socket_num);
freeaddrinfo(res);
return ESP_FAIL;
}
// 写入请求头
if (lwip_write(socket_num, REQUEST, strlen(REQUEST)) < 0)
{
lwip_close(socket_num);
return ESP_FAIL;
}
free(REQUEST);
freeaddrinfo(res);
// 设置超时时间
struct timeval receiving_timeout;
receiving_timeout.tv_sec = 5;
receiving_timeout.tv_usec = 0;
if (lwip_setsockopt(socket_num, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout, sizeof(receiving_timeout)) < 0)
{
lwip_close(socket_num);
return ESP_FAIL;
}
// bzero(recv_buf, buf_len);
// r = read(s, recv_buf, buf_len - 1);
read_after(socket_num); // 读取数据函数根据情况写 使用此函数读 read(s, recv_buf, buf_len - 1);
lwip_close(socket_num);
return ESP_OK;
}
套接字
此部分API来自
lwip
建立套接字(socket)
描述 | 名称 | 功能 |
---|---|---|
int | domain | 协议族AF_INET (IPV4),AF_INET6 (IPV6) |
int | type | 类型,SOCK_STREAM (TCP),SOCK_DGRAM (UDP) |
int | protocol | 协议,一般写IPPROTO_IP 即可,自动寻找 |
int | 输出 | 错误-1,成功返回套接字编号 |
int lwip_socket(int domain, int type, int protocol)
int sock = -1;
sock = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);//UDP
sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);//TCP
绑定套接字(设置参数)(bind)
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
const struct sockaddr * | name | 设置结构体,sockaddr_in (IPV4),sockaddr_in6 (IPV6)强转而来 |
socklen_t | namelen | 设置结构体长度 |
int | 输出 | 0成功,-1失败 |
描述 | 名称 | 功能 |
---|---|---|
u8_t | sin_len | (未知,写0即可) |
sa_family_t | sin_family | 协议族AF_INET (IPV4),AF_INET6 (IPV6) |
in_port_t | sin_port | 端口(注意使用lwip_htons 来换序) |
in_addr | sin_addr | IP地址,sin_addr.s_addr 存放(注意使用lwip_htonl 来换序) |
char | sin_zero[SIN_ZERO_LEN] | 不使用 |
IPV6未用到过,用时再说
int lwip_bind(int s, const struct sockaddr *name, socklen_t namelen)
示例
struct sockaddr_in saddr = {0};
int err = 0;
saddr.sin_family = PF_INET;
saddr.sin_port = lwip_htons(port);
saddr.sin_addr.s_addr = lwip_htonl(INADDR_ANY);
err = lwip_bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
字节转换转换函数
主机字节顺序和网络字节顺序之间转化
lwip
中网络相关的使用的都是网络字节顺序
函数名 | 输入输出 | 功能 |
---|---|---|
htonl | uint32_t | 主机到网络(32位整数) |
htons | uint16_t | 主机到网络(16位整数) |
ntohl | uint32_t | 网络到主机(32位整数) |
ntohs | uint16_t | 网络到主机(16位整数) |
关闭套接字(close)
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
int | 输出 | 0成功,-1失败 |
int lwip_close(int s)
读套接字
接收数据(TCP常用)(read)
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
void * | mem | 读取缓冲区 |
size_t | len | 缓冲区长度 |
ssize_t | 输出 | >0接收的字节,=0无数据,<0错误码 |
此函数为阻塞接收
ssize_t lwip_read(int s, void *mem, size_t len)
获取发送者信息接收(UDP常用)(recvfrom)
UDP面向无连接,当需要知道发送方时使用此函数
注意:
lwip_read
的实现也来源于此函数,不过TCP连接本身可以知道数据的发送者,因此一般不需要在接收函数处获取发送者信息
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
void * | mem | 读取缓冲区 |
size_t | len | 缓冲区长度 |
int | flags | 标志,一般写0阻塞接收即可 |
struct sockaddr * | from | 输出,发送者信息sockaddr_in (IPV4),sockaddr_in6 (IPV6)强转而来 |
socklen_t * | fromlen | 前项长度 |
ssize_t | 输出 | >0接收的字节,=0无数据,<0错误码 |
ssize_t lwip_recvfrom(int s, void *mem, size_t len, int flags,struct sockaddr *from, socklen_t *fromlen)
写套接字
发送数据(TCP常用)(write)
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
void * | mem | 发送缓冲区 |
size_t | len | 缓冲区长度 |
ssize_t | 输出 | <0失败,>0写入的字节,=0读取到文件的结束 |
ssize_t lwip_write(int s, const void *data, size_t size)
指定发送者发送(UDP常用)(sendto)
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
void * | mem | 读取缓冲区 |
size_t | len | 缓冲区长度 |
int | flags | 标志,一般写0即可 |
struct sockaddr * | to | 接收者信息sockaddr_in (IPV4),sockaddr_in6 (IPV6)强转而来 |
socklen_t * | tolen | 前项长度 |
ssize_t | 输出 | <0失败,>0写入的字节,=0读取到文件的结束 |
ssize_t lwip_sendto(int s, const void *data, size_t size, int flags,const struct sockaddr *to, socklen_t tolen)
监听套接字(listen)
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
int | backlog | 排队建立3次握手队列和刚刚建立3次握手队列的链接数和 |
int | 输出 | 0成功,其他失败 |
int lwip_listen(int s, int backlog)
接受套接字(accept)
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
struct sockaddr * | to | 接收者信息sockaddr_in (IPV4),sockaddr_in6 (IPV6)强转而来 |
socklen_t * | tolen | 前项长度 |
ssize_t | 输出 | -1失败,成功返回套接字编号 |
int lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen)
设置套接字额外参数(setsockopt)
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
int | level | 协议层,套接字用SOL_SOCKET |
int | optname | 选项名,见下表 |
const void * | optval | 选项值缓冲区 |
socklen_t | optlen | 选项值长度 |
int | 输出 | 0成功,<0失败 |
int lwip_setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen)
获取套接字额外参数(getsockopt)
描述 | 名称 | 功能 |
---|---|---|
int | s | 套接字编号 |
int | level | 协议层,套接字用SOL_SOCKET |
int | optname | 选项名,见下表 |
const void * | optval | 选项值缓冲区 |
socklen_t | optlen | 选项值长度 |
int | 输出 | 0成功,<0失败 |
int lwip_getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen)
额外参数表
不全,我用到时更新
参数名 | 功能 | 类型 | 读写 |
---|---|---|---|
SO_RCVTIMEO | 接收数据的超时时间 | struct timeval | 可读写 |
SO_SNDTIMEO | 发送数据的超时时间 | struct timeval | 可读写 |
TCP连接(客户端)
步骤
- 建立套接字(
lwip_socket
) - 设置远端IP和端口结构体(
struct sockaddr_in
) - 连接服务器(
lwip_connect
) - 操作数据(
lwip_write
,lwip_read
) - 关闭套接字(可选)
设计一个变量来存储tcp客户端列表
可以建立多个tcp连接
int tcp_client_socket[TCP_Client_MAX_NUM] = {0};
/**
* @brief 创建TCP套接字
* @param ip 服务器IP地址
* @param port 服务器端口
* @return 套接字编号 -1为创建失败
* @author HZ12138
* @date 2025-01-13 23:31:32
*/
int tcp_socket_create(ip4_addr_t ip, uint16_t port)
{
struct sockaddr_in saddr = {0};
int sock = -1;
int err = 0;
// 设置服务器地址
saddr.sin_family = AF_INET;
saddr.sin_port = lwip_htons(port);
saddr.sin_addr.s_addr = ip.addr;
sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); // 建立socket
if (sock < 0)
return -1;
err = lwip_connect(sock, (struct sockaddr *)&saddr, sizeof(saddr)); // 连接服务器
if (err < 0)
{
lwip_close(sock);
return -1;
}
return sock;
}
/**
* @brief 发送数据
* @param from 调用自 F_TCP_client
* @param num 客户端编号
* @param buf 数据
* @param buf_len 数据长度
* @return 发送数据长度 -1为发送失败
* @author HZ12138
* @date 2025-01-13 23:15:07
*/
int tcp_client_send_data(uint8_t from, uint8_t num, uint8_t *buf, int buf_len)
{
if (tcp_client_socket[num] <= 0)
return -1;
return lwip_write(tcp_client_socket[num], buf, buf_len);
}
/**
* @brief TCP读取回调
* @param from 调用自 F_TCP_client
* @param num 客户端编号
* @param buf 数据
* @param buf_len 数据长度
* @author HZ12138
* @date 2025-01-13 23:18:05
*/
__WEAK void tcp_read_callback(uint8_t from, int num, uint8_t *buf, int buf_len)
{
printf("tcp_%d:\n", num);
uart_write_bytes(UART_NUM_0, (const char *)buf, buf_len);
printf("\n");
tcp_client_send_data(F_TCP_client, num, buf, buf_len);
}
/**
* @brief TCP客户端失败回调
* @param num 客户端编号
* @author HZ12138
* @date 2025-01-13 23:49:13
*/
__WEAK void tcp_client_fail_callback(uint8_t num)
{
printf("tcp_%d close\n", num);
}
/**
* @brief TCP客户端任务
* @param pvParameters 客户端编号
* @author HZ12138
* @date 2025-01-13 23:14:52
*/
void tcp_client_task(void *pvParameters)
{
int num = (int)pvParameters;
int buf_len;
uint8_t buf[1024];
while (1)
{
buf_len = lwip_read(tcp_client_socket[num], buf, sizeof(buf));
if (buf_len > 0)
tcp_read_callback(F_TCP_client, num, buf, buf_len);
else if (buf_len < 0)
{
if (tcp_client_socket[num] > 0)
lwip_close(tcp_client_socket[num]);
tcp_client_fail_callback(num);
break;
}
vTaskDelay(pdMS_TO_TICKS(10));
}
vTaskDelete(NULL);
}
/**
* @brief 创建TCP客户端
* @param num 客户端编号
* @param ip_1 服务器IP地址
* @param ip_2 服务器IP地址
* @param ip_3 服务器IP地址
* @param ip_4 服务器IP地址
* @param port 服务器端口
* @return 错误码
* @author HZ12138
* @date 2025-01-13 23:05:31
*/
int tcp_client_open(uint8_t num, uint8_t ip_1, uint8_t ip_2, uint8_t ip_3, uint8_t ip_4, uint16_t port)
{
ip4_addr_t ip;
int socket_num;
IP4_ADDR(&ip, ip_1, ip_2, ip_3, ip_4);
socket_num = tcp_socket_create(ip, port);
tcp_client_socket[num] = socket_num;
if (socket_num <= 0)
return -1;
xTaskCreate(tcp_client_task, "tcp_client_task", 1024 * 2, (void *)num, 2, NULL);
return 0;
}
/**
* @brief 关闭TCP客户端
* @param num 客户端编号
* @author HZ12138
* @date 2025-01-14 10:33:20
*/
void tcp_client_close(uint8_t num)
{
if (tcp_client_socket[num] > 0)
lwip_close(tcp_client_socket[num]);
tcp_client_socket[num] = 0;
}
TCP连接(服务器)
步骤
- 建立服务器套接字 (
lwip_socket
) - 设置本地的IP和端口(
lwip_bind
) - 开始监听 (
lwip_listen
) - 建立连接套接字(
lwip_accept
) - 操作数据 (
lwip_read
,lwip_write
)
/**
* @brief TCP服务器读取回调
* @param num 服务器编号
* @param sock 套接字
* @param buf 数据
* @param buf_len 数据长度
* @author HZ12138
* @date 2025-01-14 16:18:12
*/
__WEAK void tcp_server_read_callback(int num, int sock, uint8_t *buf, int buf_len)
{
printf("tcp_server_%d_%d:\n", num, sock);
uart_write_bytes(UART_NUM_0, (const char *)buf, buf_len);
printf("\n");
lwip_write(sock, buf, buf_len);
}
/**
* @brief TCP服务器关闭回调
* @param num 服务器编号
* @author HZ12138
* @date 2025-01-14 13:53:29
*/
__WEAK void tcp_server_fail_callback(uint8_t num)
{
printf("tcp_server%d close\n", num);
}
/**
* @brief TCP服务器任务
* @param pvParameters 数据 [0]服务器编号 [1]套接字 [2]连接编号
* @author HZ12138
* @date 2025-01-14 16:15:04
*/
void tcp_server_task(void *pvParameters)
{
int num = ((int *)pvParameters)[0];
int sock = ((int *)pvParameters)[1];
int i = ((int *)pvParameters)[2];
int buf_len;
uint8_t buf[1024];
while (1)
{
buf_len = lwip_read(sock, buf, sizeof(buf)); // 读取数据
if (buf_len > 0) // 读取到数据时回调
tcp_server_read_callback(num, sock, buf, buf_len);
if (buf_len < 0) // 客户端错误时关闭
break;
if (tcp_server_socket[num] <= 0) // 关闭服务器时关闭客户端
break;
vTaskDelay(pdMS_TO_TICKS(10));
}
// 关闭连接
tcp_server_connect_socket[num][i] = -1;
lwip_close(sock);
vTaskDelete(NULL);
}
/**
* @brief 多线程建立TCP连接
* @param pvParameters 服务器编号
* @author HZ12138
* @date 2025-01-14 13:20:43
*/
void tcp_server_create_link_tesk(void *pvParameters)
{
int num = (int)pvParameters;
int sock = tcp_server_socket[num];
int remote_sock;
int data[3];
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
while (1)
{
if (tcp_server_socket[num] <= 0) // 检查服务器是否关闭
break;
// 建立连接
remote_sock = lwip_accept(sock, (struct sockaddr *)&client_addr, &client_addr_len);
if (remote_sock > 0)
for (int i = 0; i < TCP_Server_MAX_CONNECT_NUM; i++)
{
if (tcp_server_connect_socket[num][i] <= 0) // 寻找空闲连接
{
// 写入参数
tcp_server_connect_socket[num][i] = remote_sock;
data[0] = num;
data[1] = remote_sock;
data[2] = i;
xTaskCreate(tcp_server_task, "tcp_server_task", 1024 * 2, (void *)data, 1, NULL);
break;
}
}
else
break;
vTaskDelay(pdMS_TO_TICKS(10));
}
lwip_close(sock);
tcp_server_fail_callback(num);
vTaskDelete(NULL);
}
/**
* @brief TCP服务器初始化
* @param num 服务器编号
* @param port 端口
* @return 错误码
* @author HZ12138
* @date 2025-01-14 16:15:46
*/
int tcp_server_init(uint8_t num, uint16_t port)
{
int socket_num = -1;
struct sockaddr_in saddr = {0};
err_t err;
if (num >= TCP_Server_MAX_NUM)
return -1;
if (tcp_server_socket[num] > 0)
return -1;
// 创建套接字
socket_num = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (socket_num < 0)
return -1;
// 绑定端口
saddr.sin_family = AF_INET;
saddr.sin_port = lwip_htons(port);
saddr.sin_addr.s_addr = lwip_htonl(INADDR_ANY);
err = lwip_bind(socket_num, (struct sockaddr *)&saddr, sizeof(saddr));
if (err < 0)
goto err;
// 监听端口
err = lwip_listen(socket_num, TCP_Server_MAX_CONNECT_NUM);
if (err < 0)
goto err;
// 创建建立连接任务
tcp_server_socket[num] = socket_num;
xTaskCreate(tcp_server_create_link_tesk, "tcp_server_create_link_tesk", 1024 * 2, (void *)num, 2, NULL);
return 0;
err:
lwip_close(socket_num);
return -1;
}
/**
* @brief TCP服务器广播
* @param num 服务器编号
* @param buf 数据
* @param buf_len 数据长度
* @return 发送客户端数量 -1为发送失败
* @author HZ12138
* @date 2025-01-14 16:52:24
*/
int tcp_server_Broadcast(int num, uint8_t *buf, int buf_len)
{
int send_len = 0;
for (int i = 0; i < TCP_Server_MAX_CONNECT_NUM; i++)
{
if (tcp_server_connect_socket[num][i] > 0)
{
send_len++;
if (lwip_write(tcp_server_connect_socket[num][i], buf, buf_len) < 0)
return -1;
}
}
return send_len;
}
/**
* @brief 关闭TCP服务器
* @param num 服务器编号
* @author HZ12138
* @date 2025-01-14 22:44:17
*/
void tcp_server_close(uint8_t num)
{
int socket_num;
socket_num = tcp_server_socket[num];
if (socket_num > 0) // 在关闭前判断是否已经关闭
lwip_close(socket_num);
tcp_server_socket[num] = 0;
}
UDP发送
步骤
- 建立套接字 (
lwip_socket
) - 发送数据 (
lwip_sendto
)
/**
* @brief UDP发送数据
* @param ip 目标IP
* @param port 目标端口
* @param buf 数据
* @param buf_len 数据长度
* @return 发送数据长度 -1为发送失败
* @author HZ12138
* @date 2025-01-14 22:37:35
*/
int udp_send_data(ip4_addr_t ip, uint16_t port, uint8_t *buf, int buf_len)
{
struct sockaddr_in saddr = {0};
int sock = -1;
int len = 0;
// 设置服务器地址
saddr.sin_family = AF_INET;
saddr.sin_port = lwip_htons(port);
saddr.sin_addr.s_addr = ip.addr;
sock = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); // 建立socket
if (sock < 0)
return -1;
len = lwip_sendto(sock, buf, buf_len, 0, (struct sockaddr *)&saddr, sizeof(saddr)); // 发送数据
if (len < 0)
{ // 连接失败
lwip_close(sock);
return -1;
}
lwip_close(sock);
return len;
}
UDP监听
步骤
- 建立套接字 (
lwip_socket
) - 设置本地的端口 (
lwip_bind
) - 读取数据 (
lwip_recvfrom
)
/**
* @brief UDP读取回调
* @param num UDP编号
* @param ip 来源IP
* @param port 来源端口
* @param buf 数据
* @param buf_len 数据长度
* @author HZ12138
* @date 2025-01-14 22:37:52
*/
__WEAK void udp_read_callback(uint8_t num, ip4_addr_t ip, uint16_t port, uint8_t *buf, int buf_len)
{
// printf("%s\n", ip4addr_ntoa(&ip));
// printf("%d\n", port);
printf("udp_%d:\n", num);
uart_write_bytes(UART_NUM_0, (const char *)buf, buf_len);
printf("\n");
udp_send_data(ip, port, buf, buf_len);
}
/**
* @brief UDP监听任务
* @param pvParameters UDP编号
* @author HZ12138
* @date 2025-01-14 22:38:14
*/
void udp_listen_task(void *pvParameters)
{
int num = (int)pvParameters;
int socket_num = udp_socket[num];
int buf_len;
ip4_addr_t ip;
uint16_t port;
uint8_t buf[1024];
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
while (1)
{
buf_len = lwip_recvfrom(socket_num, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &client_addr_len);
printf("udp:%d:\n", buf_len);
if (buf_len > 0)
{
port = lwip_ntohs(client_addr.sin_port);
ip.addr = client_addr.sin_addr.s_addr;
udp_read_callback(num, ip, port, buf, buf_len);
}
if (buf_len < 0)
break;
vTaskDelay(pdMS_TO_TICKS(10));
}
lwip_close(socket_num);
vTaskDelete(NULL);
}
/**
* @brief UDP监听初始化
* @param num UDP编号
* @param port 端口
* @return 错误码 -1为创建失败 0为创建成功
* @author HZ12138
* @date 2025-01-14 22:38:27
*/
int udp_listen_init(uint8_t num, uint16_t port)
{
int socket_num = -1;
struct sockaddr_in saddr = {0};
err_t err;
if (num >= UDP_LISTEN_MAX_NUM)
return -1;
if (udp_socket[num] > 0)
return -1;
saddr.sin_family = AF_INET;
saddr.sin_port = lwip_htons(port);
saddr.sin_addr.s_addr = lwip_htonl(INADDR_ANY);
socket_num = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (socket_num < 0)
return -1;
err = lwip_bind(socket_num, (struct sockaddr *)&saddr, sizeof(saddr));
if (err < 0)
{
lwip_close(socket_num);
return -1;
}
udp_socket[num] = socket_num;
xTaskCreate(udp_listen_task, "udp_listen_task", 1024 * 2, (void *)num, 2, NULL);
return 0;
}
/**
* @brief 关闭UDP监听
* @param num UDP编号
* @author HZ12138
* @date 2025-01-14 22:44:26
*/
void udp_listen_close(uint8_t num)
{
int socket_num;
socket_num = udp_socket[num];
if (socket_num > 0) // 在关闭前判断是否已经关闭
lwip_close(socket_num);
udp_socket[num] = 0;
}
MQTT
初始化客户端
描述 | 名称 | 功能 |
---|---|---|
const esp_mqtt_client_config_t * | config | 配置参数 |
esp_mqtt_client_handle_t | 输出 | 句柄 |
描述 | 名称 | 功能 |
---|---|---|
const char * | host | 域名或ip |
uint32_t | port | 端口 |
const char * | username | 用户名 |
const char * | password | 密码 |
``` |
esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *config)
设置事件回调
描述 | 名称 | 功能 |
---|---|---|
esp_mqtt_client_handle_t | client | 客户端句柄 |
esp_mqtt_event_id_t | event | 事件ID,ESP_EVENT_ANY_ID 所有事件 |
esp_event_handler_t | event_handler | 回调函数 |
void* | event_handler_arg | 参数 |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client, esp_mqtt_event_id_t event, esp_event_handler_t event_handler, void* event_handler_arg)
回调函数示例
描述 | 名称 | 功能 |
---|---|---|
void * | event_handler_arg | 事件处理参数 |
esp_event_base_t | event_base | 事件唯一标识 |
int32_t | event_id | 事件ID |
void* | event_data | 数据MQTT_EVENT_DATA 时是esp_mqtt_event_handle_t |
void MQTT_event_fun(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
开启客户端
描述 | 名称 | 功能 |
---|---|---|
esp_mqtt_client_handle_t | client | 客户端句柄 |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)
订阅主题
描述 | 名称 | 功能 |
---|---|---|
esp_mqtt_client_handle_t | client | 客户端句柄 |
const char * | topic | 主题 |
int | qos | 0至少一次,1至多一次,2只有一次 |
esp_err_t | 输出 | 错误码 |
连接到服务器后再调用
int esp_mqtt_client_subscribe(esp_mqtt_client_handle_t client, const char *topic, int qos)
重连
描述 | 名称 | 功能 |
---|---|---|
esp_mqtt_client_handle_t | client | 客户端句柄 |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_mqtt_client_reconnect(esp_mqtt_client_handle_t client)
发布
描述 | 名称 | 功能 |
---|---|---|
esp_mqtt_client_handle_t | client | 客户端句柄 |
const char * | topic | 主题 |
const char * | data | 数据 |
int | len | 数据长度 |
int | qos | 0至少一次,1至多一次,2只有一次 |
int | retain | 保留,一般写0即可 |
esp_err_t | 输出 | 错误码 |
连接到服务器后再调用
int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic, const char *data, int len, int qos, int retain)
示例
bool MQTT_connect_OK = false; // MQTT连接状态标志
esp_mqtt_client_handle_t MQTT_client;
/**
* @brief MQTT读取回调函数
* @param topic 主题
* @param topic_len 主题长度
* @param data 数据
* @param data_len 数据长度
* @author HZ12138
* @date 2025-01-15 16:49:46
*/
__WEAK void MQTT_read_callback(char *topic, int topic_len, char *data, int data_len)
{
printf("topic:%s\n", topic);
printf("data:%s\n", data);
}
/**
* @brief MQTT发布函数
* @param topic 主题
* @param data 数据
* @param data_len 数据长度
* @param qos 服务质量
* @author HZ12138
* @date 2025-01-15 16:50:20
*/
void MQTT_pubslish(char *topic, uint8_t *data, int data_len, int qos)
{
esp_mqtt_client_publish(MQTT_client, topic, (const char *)data, data_len, qos, 0);
}
/**
* @brief MQTT事件处理函数
* @param event_handler_arg 事件处理参数
* @param event_base 事件唯一标识
* @param event_id 事件ID
* @param event_data 事件数据
* @author HZ12138
* @date 2025-01-15 16:50:37
*/
void MQTT_event_fun(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
esp_mqtt_event_handle_t event;
switch (event_id)
{
case MQTT_EVENT_CONNECTED: // 连接上MQTT服务器
MQTT_connect_OK = true;
break;
case MQTT_EVENT_DISCONNECTED: // 断开MQTT服务器连接 自动重连
MQTT_connect_OK = false;
esp_mqtt_client_reconnect(MQTT_client);
break;
case MQTT_EVENT_DATA:
event = (esp_mqtt_event_handle_t)event_data; // 获取数据
MQTT_read_callback(event->topic, event->topic_len, event->data, event->data_len);
break;
default:
break;
}
}
/**
* @brief MQTT订阅任务
* @param pvParameters 主题
* @author HZ12138
* @date 2025-01-15 16:51:18
*/
void MQTT_subscribe_task(void *pvParameters)
{
char topic[50];
strcpy(topic, (char *)pvParameters);
while (1)
{
if (MQTT_connect_OK)
{
esp_mqtt_client_subscribe(MQTT_client, topic, 1); // 订阅一个测试主题
break;
}
vTaskDelay(pdMS_TO_TICKS(100));
}
vTaskDelete(NULL);
}
/**
* @brief MQTT初始化函数
* @param host 服务器地址
* @param port 服务器端口
* @param username 用户名
* @param password 密码
* @param topic 订阅主题
* @author HZ12138
* @date 2025-01-15 16:51:36
*/
void MQTT_init(char *host, int port, char *username, char *password, char *topic)
{
esp_mqtt_client_config_t mqtt_cfg = {
.host = host,
.port = port,
.username = username,
.password = password,
};
MQTT_client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(MQTT_client, ESP_EVENT_ANY_ID, MQTT_event_fun, NULL);
esp_mqtt_client_start(MQTT_client);
xTaskCreate(MQTT_subscribe_task, "MQTT_subscribe_task", 1024, (void *)topic, 2, NULL);
}
SPISSF
分区表设置
根目录建立文件
partitions.csv
# Name, Type, SubType, Offset, Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 712K,
storage, data, spiffs, , 248K,
进入配置页面
make menuconfig
选择分区表
选择使用外部文件
默认的文件名就是之前建立的文件,也可以这里修改
只支持一级目录,基本目录需要写入分请设置文件
初始化
描述 | 名称 | 功能 |
---|---|---|
const esp_vfs_spiffs_conf_t * | conf | 设置结构体 |
esp_err_t | 输出 | 错误码 |
描述 | 名称 | 功能 |
---|---|---|
const char* | base_path | 与文件系统关联的文件路径前缀 |
const char* | partition_label | 可选,要使用的 SPIFFS 分区的标签。如果设置为 NULL,将使用具有 subtype=spiffs 的第一个分区 |
size_t | max_files | 可同时打开的最大文件数 |
bool | format_if_mount_failed | 如果为 True,则挂载失败时将格式化文件系统 |
esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf)
读写等操作
使用标准C语言API即可
网页配网
先查看NVS中是否有可用的配置信息
如果有则连接wifi
如果无则扫描wifi后进入AP模式开启配网网页
调用
SETNET_init()
以开始因用到主机名修改 要将
LWIP_NETIF_HOSTNAME
宏定义打开
#ifdef USE_WEBSETNET
// 用完后free
char *SETNET_AP_str;
typedef struct SETNET_data_t
{
char ssid[32];
char password[64];
uint8_t mac[6];
char name[32];
uint8_t flag;
} SETNET_data_t;
SETNET_data_t SETNET_data = {0};
QueueHandle_t SETNET_net_opt_OK;
/**
* @brief 获取AP列表字符串
* @author HZ12138
* @date 2025-01-17 23:07:21
*/
void SETNET_get_ap_str(void)
{
char *mytemp;
wifi_ap_record_t ap_list[20];
uint16_t ap_num = 0;
// 扫描wifi
wifi_STA_init("", "", NULL);
wifi_scan(ap_list, &ap_num);
// 初始化变量
SETNET_AP_str = (char *)malloc(1024);
mytemp = (char *)malloc(100);
memset(SETNET_AP_str, 0, 1024);
memset(mytemp, 0, 100);
// 生成字符串
for (int i = 0; i < ap_num; i++)
{
strcat(SETNET_AP_str, (char *)ap_list[i].ssid); // 写入ssid
// 写入rssi
memset(mytemp, 0, 100);
sprintf(mytemp, ",%d,", ap_list[i].rssi);
strcat(SETNET_AP_str, mytemp);
// 写入MAC
memset(mytemp, 0, 100);
sprintf(mytemp, MACSTR, ap_list[i].bssid[0], ap_list[i].bssid[1], ap_list[i].bssid[2], ap_list[i].bssid[3], ap_list[i].bssid[4], ap_list[i].bssid[5]);
strcat(SETNET_AP_str, mytemp);
// 去除最后一个逗号
if (i == ap_num - 1)
break;
strcat(SETNET_AP_str, "|");
}
free(mytemp);
}
/**
* @brief 配网主任务
* @author HZ12138
* @date 2025-01-18 23:29:19
*/
void SETNET_main_task(void *pvParameters)
{
size_t NVS_read_len = 0xffff;
NVS_read_data("SETNET_data", "SETNET", &SETNET_data, &NVS_read_len);
// printf("NVS_read_len:%d flag:%d ssid:%s password:%s mac:" MACSTR " name:%s\n", NVS_read_len, SETNET_data.flag, SETNET_data.ssid, SETNET_data.password, SETNET_data.mac[0], SETNET_data.mac[1], SETNET_data.mac[2], SETNET_data.mac[3], SETNET_data.mac[4], SETNET_data.mac[5], SETNET_data.name);
if (SETNET_data.flag == 0 || NVS_read_len == 0)
{
// 先获取AP列表
SETNET_get_ap_str();
vTaskDelay(pdMS_TO_TICKS(1000));
wifi_deinit();
// 初始化为AP AP名为ESP-(本设备MAC号)
uint8_t mac[6];
char *SSID;
SSID = (char *)malloc(100);
memset(SSID, 0, 100);
esp_efuse_mac_get_default(mac);
sprintf(SSID, "ESP-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
wifi_AP_init(SSID, "");
free(SSID);
// 设置强制门户
SETNET_net_opt_OK = xSemaphoreCreateBinary();
DNS_start_server();
web_start_any_path_server();
// 等待网页获取到数据
if (xSemaphoreTake(SETNET_net_opt_OK, portMAX_DELAY) == pdTRUE)
{
vTaskDelay(pdMS_TO_TICKS(500));
SETNET_data.flag = 1;
// printf("flag:%d ssid:%s password:%s mac:" MACSTR " name:%s\n", SETNET_data.flag, SETNET_data.ssid, SETNET_data.password, SETNET_data.mac[0], SETNET_data.mac[1], SETNET_data.mac[2], SETNET_data.mac[3], SETNET_data.mac[4], SETNET_data.mac[5], SETNET_data.name);
// 将配置写入NVS
NVS_write_data("SETNET_data", "SETNET", &SETNET_data, sizeof(SETNET_data_t));
// 重启
wifi_deinit();
esp_restart();
}
}
else
{
if (SETNET_data.mac[0] != 0)
{ // 有指定MAC设置
// printf("connecting to wifi with mac\n");
wifi_STA_init(SETNET_data.ssid, SETNET_data.password, SETNET_data.mac);
}
else
{ // 无指定MAC设置 通过SSID连接
// printf("connecting to wifi without mac\n");
wifi_STA_init(SETNET_data.ssid, SETNET_data.password, NULL);
}
// 有传入设备名
if (strlen(SETNET_data.name) != 0)
tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, SETNET_data.name);
while (1)
{
// 等待连接成功
if (wifi_STA_connect_success == 1)
{
// printf("wifi OK,ip:%s \n", ip4addr_ntoa(&wifi_ip));
break;
}
// 如果连接失败次数大于8次则重启
if (wifi_STA_connect_fail_times > 8)
{
// printf("wifi connect fail\n");
// 清除标志
SETNET_data.flag = 0;
NVS_write_data("SETNET_data", "SETNET", &SETNET_data, sizeof(SETNET_data_t));
esp_restart();
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
vTaskDelete(NULL);
}
/**
* @brief 启动网页配网
* @author HZ12138
* @date 2025-01-18 23:28:38
*/
void SETNET_init(void)
{
xTaskCreate(SETNET_main_task, "SETNET_main_task", 1024 * 5, NULL, 2, NULL);
}
/**
* @brief 解析setweb参数
* @param source 源字符串
* @param ssid ssid
* @param mac mac地址
* @param password 密码
* @param name 设备名
* @author HZ12138
* @date 2025-01-18 16:49:13
*/
void setweb_get_opt(char *source, char *ssid, uint8_t *mac, char *password, char *name)
{
// 如果有一个为空则退出
if (source == NULL || ssid == NULL || mac == NULL || password == NULL || name == NULL)
goto err;
char mac_temp[100];
uint64_t mac_temp_int = 0;
// 解析参数
sscanf(source, "ssid=%[^&]&mac=%[^&]&password=%[^&]&name=%s", ssid, mac_temp, password, name);
if (strlen(mac_temp) != 12)
{
memset(mac, 0, 6);
goto err;
}
// 解析mac
for (int i = 0; i < 12; i++)
{
mac_temp_int <<= 4;
if (mac_temp[i] >= '0' && mac_temp[i] <= '9')
mac_temp_int += mac_temp[i] - '0';
else if (mac_temp[i] >= 'A' && mac_temp[i] <= 'F')
mac_temp_int += mac_temp[i] - 'A' + 10;
else if (mac_temp[i] >= 'a' && mac_temp[i] <= 'f')
mac_temp_int += mac_temp[i] - 'a' + 10;
else
goto err;
}
mac[0] = (mac_temp_int >> 40) & 0xff;
mac[1] = (mac_temp_int >> 32) & 0xff;
mac[2] = (mac_temp_int >> 24) & 0xff;
mac[3] = (mac_temp_int >> 16) & 0xff;
mac[4] = (mac_temp_int >> 8) & 0xff;
mac[5] = mac_temp_int & 0xff;
// printf("ssid:%s\n", ssid);
// printf("mac:" MACSTR "\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// printf("password:%s\n", password);
// printf("name:%s\n", name);
err:
return;
}
/**
* @brief 重新定义函数,如不用则关闭宏定义
* @author HZ12138
* @date 2025-01-18 17:04:25
*/
int web_any_path_server_GET_callback(int (*mwrite)(int fd, void *buffer, int length), int socket_num, char *uri, char *body)
{
char buf[300];
// 从文件中读取网页
extern const uint8_t setweb_html_start[] asm("_binary_setwifiweb_html_start");
extern const uint8_t setweb_html_end[] asm("_binary_setwifiweb_html_end");
// 发送网页
sprintf(buf, HTTP_200, (int)(setweb_html_end - setweb_html_start) - 1);
mwrite(socket_num, (void *)buf, strlen(buf));
mwrite(socket_num, (void *)setweb_html_start, setweb_html_end - setweb_html_start - 1);
return 0;
}
/**
* @brief 重新定义函数,如不用则关闭宏定义
* @author HZ12138
* @date 2025-01-18 17:04:25
*/
int web_any_path_server_POST_callback(int (*mwrite)(int fd, void *buffer, int length), int socket_num, char *uri, char *body)
{
if (strcmp(uri, "/getWIFI") == 0)
{ // 获取AP列表
mwrite(socket_num, (void *)SETNET_AP_str, strlen(SETNET_AP_str));
}
else if (strcmp(uri, "/set_wifi") == 0)
{ // 设置wifi
char res[300] = {0};
// printf("body:%s\n", body);
memset(SETNET_data.ssid, 0, 32);
memset(SETNET_data.mac, 0, 6);
memset(SETNET_data.password, 0, 64);
memset(SETNET_data.name, 0, 32);
// 解析参数
setweb_get_opt(body, SETNET_data.ssid, SETNET_data.mac, SETNET_data.password, SETNET_data.name);
// 发送设置信息后的页面
sprintf(res, "connecting to ssid:%s mac:" MACSTR " and will set name as %s ....\n ESP will restart after 5s", SETNET_data.ssid,
SETNET_data.mac[0], SETNET_data.mac[1], SETNET_data.mac[2], SETNET_data.mac[3], SETNET_data.mac[4], SETNET_data.mac[5],
SETNET_data.name);
mwrite(socket_num, (void *)res, strlen(res));
// 给主任务发送信号
xSemaphoreGive(SETNET_net_opt_OK);
}
return 0;
}
#endif
OTA
头文件
esp_ota_ops.h
分区表设置
具体设置见SPISSF
此处给出OTA用的CSV文件示例
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
ota_0, app, ota_0, , 1M,
ota_1, app, ota_1, , 1M,
otadata, data, ota, , 0x2000,
storage, data, spiffs, , 500K,
factory
存放出厂应用
ota_0
,ota_1
存放OTA应用
otadata
存放数据,建议放到出厂和OTA分区之后
出厂后运行在
factory
中的应用,进行OTA是写入并重启后运行
ota_0
中的应用,下次OTA是写入并重启后运行
ota_1
中的应用再下次是
ota_0
,以此类推
获取当前运行分区
描述 | 名称 | 功能 |
---|---|---|
const esp_partition_t | 输出 | 分区信息 |
描述 | 名称 | 功能 |
---|---|---|
esp_partition_type_t | type | 分区类型(app/data) |
esp_partition_subtype_t | subtype | 分区子类型 |
uint32_t | address | 起始地址 |
uint32_t | size | 大小(bytes) |
char | label | 名称(char[17]) |
bool | encrypted | 分区加密为true |
const esp_partition_t* esp_ota_get_running_partition(void)
获取下一个分区
描述 | 名称 | 功能 |
---|---|---|
const esp_partition_t * | start_from | 传入NULL 表示当前即可 |
const esp_partition_t | 输出 | 分区信息 |
const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t *start_from)
打开OTA分区
描述 | 名称 | 功能 |
---|---|---|
const esp_partition_t * | partition | 要写入的分区 |
size_t | image_size | 镜像大小,可写(OTA_SIZE_UNKNOWN ) |
esp_ota_handle_t * | out_handle | (输出)句柄 |
esp_err_t | 输出 | 错误码 |
获取操作句柄后写入数据
esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp_ota_handle_t *out_handle)
写入数据
描述 | 名称 | 功能 |
---|---|---|
esp_ota_handle_t | handle | 句柄 |
const void * | data | 数据 |
size_t | size | 大小 |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)
结束写入
描述 | 名称 | 功能 |
---|---|---|
esp_ota_handle_t | handle | 句柄 |
esp_err_t | 输出 | 错误码 |
进行校验后关闭句柄
esp_err_t esp_ota_end(esp_ota_handle_t handle)
设置下次启动分区
描述 | 名称 | 功能 |
---|---|---|
const esp_partition_t * | partition | 分区信息 |
esp_err_t | 输出 | 错误码 |
esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)
示例
const char *TGA_OTA = "OTA";
/**
* @brief ESP OTA 下载回调
* @param socket_num 套接字号
* @param para 参数
* @author HZ12138
* @date 2025-01-24 13:14:15
*/
void OTA_esp_update_action(int socket_num, void *para)
{
const esp_partition_t *update_partition = NULL;
esp_ota_handle_t update_handle = 0;
int read_num;
uint8_t recv_buf[100];
uint8_t *temp = NULL;
esp_err_t err;
update_partition = esp_ota_get_next_update_partition(NULL);
// 读取第一帧 带有请求头
memset(recv_buf, 0, 100);
read_num = lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1);
CHECK_OTA_VOID_LESS0(read_num, "OTA_esp_update_action: read failed");
// 寻找成功标志
temp = (uint8_t *)strstr((const char *)recv_buf, "200 OK");
if (temp == NULL)
{
ESP_LOGE(TGA_OTA, "OTA_esp_update_action: download failed");
return;
}
// 寻找body
do
{ // 循环读取直到找到body
temp = (uint8_t *)strstr((const char *)recv_buf, "\r\n\r\n"); // 寻找分隔符
if (temp != NULL)
break;
// 读取新一帧
memset(recv_buf, 0, 100);
read_num = lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1);
CHECK_OTA_VOID_LESS0(read_num, "OTA_esp_update_action: read failed");
} while (read_num > 0);
// 创建ota句柄
CHECK_OTA_VOID_ERR(esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle), "OTA_esp_update_action: esp_ota_begin failed, error=%s");
// 写入第一帧
temp += 4;
ESP_LOGI(TGA_OTA, "OTA_esp_update_action: write first frame");
CHECK_OTA_VOID_ERR(esp_ota_write(update_handle, temp, read_num - (temp - recv_buf)), "OTA_esp_update_action: esp_ota_write first failed, err: %s");
// uart_write_bytes(UART_NUM_0, "data:", 5);
// uart_write_bytes(UART_NUM_0, (const char *)temp, read_num - (temp - recv_buf));
// 写入其他帧
do
{
// 读取帧
memset(recv_buf, 0, 100);
read_num = lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1);
// 校验并写入
CHECK_OTA_VOID_LESS0(read_num, "OTA_esp_update_action: read failed");
CHECK_OTA_VOID_ERR(esp_ota_write(update_handle, recv_buf, read_num), "OTA_esp_update_action: esp_ota_write failed, err: %s");
// uart_write_bytes(UART_NUM_0, (const char *)recv_buf, read_num);
} while (read_num > 0);
ESP_LOGI(TGA_OTA, "OTA_esp_update_action: update success");
CHECK_OTA_VOID_ERR(esp_ota_end(update_handle), "OTA_esp_update_action: esp_ota_end failed,err: %s"); // 结束写入OTA
CHECK_OTA_VOID_ERR(esp_ota_set_boot_partition(update_partition), "OTA_esp_update_action: esp_ota_set_boot_partition failed,err: %s"); // 设置启动分区
ESP_LOGI(TGA_OTA, "will restart");
esp_restart();
}
/**
* @brief ESP OTA 任务
* @param pvParameters 版本号
* @author HZ12138
* @date 2025-01-24 13:15:06
*/
void OTA_esp_task(void *pvParameters)
{
uint32_t version = (uint32_t)pvParameters;
uint8_t v0, v1, v2;
char url[100];
esp_err_t err;
// 拼接url
v0 = (version >> 16) & 0xff;
v1 = (version >> 8) & 0xff;
v2 = version & 0xff;
sprintf(url, "http://" OTA_url_base OTA_url_path "/esp/%d.%d.%d", v0, v1, v2);
ESP_LOGI(TGA_OTA, "OTA_ESP_url:%s", url);
CHECK_OTA_VOID_ERR(web_http_request(OTA_url_base, url, "GET", "", OTA_esp_update_action, NULL), "OTA_esp_task: web_http_request failed, err: %s");
vTaskDelay(pdMS_TO_TICKS(1000));
vTaskDelete(NULL);
}
/**
* @brief ESP OTA 启动
* @param version 版本号 0x010203->1.2.3
* @author HZ12138
* @date 2025-01-24 13:15:29
*/
void OTA_esp_start(uint32_t version)
{
xTaskCreate(OTA_esp_task, "OTA_esp_task", 1024 * 9, (void *const)version, 2, NULL);
}