当前位置: 首页 > news >正文

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

image-20250402180806337

克隆和解压后得到这仨文件

image-20250402153807835

将文件夹(\xtensa-lx106-elf-gcc8_4_0-esp-2020r3-win32\xtensa-lx106-elf)移到这里(\msys32\opt)

image-20250402153948141

将(ESP8266_RTOS_SDK)移到\msys32\home

image-20250402154413136

\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

image-20250402202205595

.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

image-20250403113856503

克隆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

image-20250403122007250

安装环境

ESP8266_RTOS_SDK中运行命令行

输入并运行

可能有报错 一般不用管先试试测试

python -m pip install --user -r requirements.txt

测试

/ESP8266_RTOS_SDK/examples/get-started/hello_world下打开命令行

输入 esp8266 后输入make 无报错即可

image-20250403131513060

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文件放在同一个文件夹里

image-20250106113610175

写入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

选择串口烧录设置

image-20250104105348338

在第一项中输入端口号即可

image-20250104105441724

编译

全部编译

make all

仅编译变化

make

清理编译内容

make clean
烧录

编译并烧录

make flash

打开串口

make monitor

编译烧录并打开串口

make flash monitor

常见错误解决方法

标题字段太长服务器无法解释(WEB服务器)

Header felds are too long for server to interpret

解决方案:

更改标题字段最大长度即可

终端输入

make menuconfig

进入功能设置

image-20250108222034091

选择HTTP服务器设置

image-20250108222008059

更改即可

image-20250108222103752

APIs 和示例

UART

加入头文件

#include "driver/uart.h"

设置参数

配置结构体
描述名称功能
intbaud_rate波特率
uart_word_length_tdata_bits字节长度,常用UART_DATA_8_BITS
uart_parity_tparity校验,常用UART_PARITY_DISABLE
uart_stop_bits_tstop_bits停止位,常用UART_STOP_BITS_1
uart_hw_flowcontrol_tflow_ctrl流控制,不用留空
uint8_trx_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_tuart_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_tuart_num串口号(0,1,2…)
intrx_buffer_size接收缓冲区大小
inttx_buffer_size发送缓冲区大小
intqueue_size消息队列大小
QueueHandle_t *uart_queue消息队列句柄
intno_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_tuart_num串口号(0,1,2…)
输出
void esp_vfs_dev_uart_use_driver(int uart_num)

发送字节

输入输出
描述名称功能
uart_port_tuart_num串口号(0,1,2…)
const char *src发送的数据
size_tsize数据大小
int输出发送的字节数
int uart_write_bytes(uart_port_t uart_num, const char *src, size_t size)

同时串口0也可使用printf 暂为发现设置的地方

注意:printf在收到\n时发送数据

读取字节

输入输出
描述名称功能
uart_port_tuart_num串口号(0,1,2…)
uint8_t *buf接收缓冲区
uint32_tlength接收数据大小
TickType_tticks_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_tevent_base事件(wifi使用WIFI_EVENT IP_EVENT)
int32_tevent_id事件id,ESP_EVENT_ANY_ID是全部事件
esp_event_handler_tevent_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_tevent_base事件(wifi使用WIFI_EVENT IP_EVENT)
int32_tevent_id事件id,ESP_EVENT_ANY_ID是全部事件
esp_event_handler_tevent_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_tmode模式,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_tinterface设置模式,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_config_t 结构体
描述名称功能
wifi_ap_config_tapAP模式使用
wifi_sta_config_tstaSTA模式使用
wifi_ap_config_t 结构体
描述名称功能
uint8_t *ssidwifi名称
uint8_t *passwordwifi密码
uint8_tssid_len名称长度
uint8_tchannel通道
wifi_auth_mode_tauthmode加密模式,常用WIFI_AUTH_WPA_WPA2_PSK,WIFI_AUTH_OPEN
uint8_tssid_hidden是否隐藏,默认0不隐藏
uint8_tmax_connection最大连接数,最大4
uint16_tbeacon_interval通讯间隔,默认100ms
wifi_sta_config_t 结构体
描述名称功能
uint8_t *ssidWiFi名称
uint8_t *passwordwifi密码
wifi_scan_method_tscan_method扫描模式
boolbssid_set是否设置目标AP的MAC地址
uint8_t *bssid目标AP的MAC地址
uint8_tchannel使用的通道
uint16_tlisten_interval接收信标的监听间隔
wifi_sort_method_tsort_method通过 RSSI 或安全模式在列表中连接 AP
wifi_fast_scan_threshold_tthreshold仅使用验证模式比所选验证模式更安全且信号强于最小 RSSI 的 AP
wifi_pmf_config_tpmf_cfg受保护管理框架的配置
uint32_trm_enabled是否为连接启用无线电测量
uint32_tbtm_enabled连接是否启用BTM
uint32_treserved保留

一般只要设置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
boolblock是否阻塞,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输出错误码
wifi_ap_record_t 结构体
描述名称功能
uint8_tbssidAP的MAC地址
uint8_tssidAP的SSID
uint8_tprimaryAP的通道
wifi_second_chan_tsecondAP的次要通道
int8_trssiAP信号强度
int16_tfreq_offsetAP频偏
wifi_auth_mode_tauthmodeAP的加密模式
wifi_cipher_type_tpairwise_cipherAP对密码
wifi_cipher_type_tgroup_cipherAP组密码
wifi_ant_tant用于从AP接收信标的天线
uint32_tphy_11b11b模式标志
uint32_tphy_11g11g模式标志
uint32_tphy_11n11n模式标志
uint32_tphy_lr低速率标志
uint32_twpsWPS支持标志
uint32_treserved保留
wifi_country_tcountry国家信息
esp_err_t esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records);

修改主机名

输入输出
描述名称功能
tcpip_adapter_if_ttcpip_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_topen_modeNVS_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_tc_handle句柄
const char*key
const void*value设置缓冲区
lengthsize_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_tc_handle句柄
esp_err_t输出错误码
esp_err_t nvs_commit(nvs_handle_t c_handle)

读取数据

输入输出
描述名称功能
nvs_handle_tc_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_tc_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配置
输出
httpd_config_t 结构体
描述名称功能
unsigned inttask_priority运行服务器的FreeRTOS任务的优先级
size_tstack_size服务器任务允许的最大堆栈大小
uint16_tserver_portTCP端口号(主要数据流量)
uint16_tctrl_portUDP端口号(组件控制)
uint16_tmax_open_sockets最大客户端数量
uint16_tmax_uri_handlers最大uri头
uint16_tmax_resp_headers响应最大附加标头
uint16_tbacklog_conn积压连接数
boollru_purge_enable清除最后使用连接
uint16_trecv_wait_timeout接收超时(s)
uint16_tsend_wait_timeout发送超时(s)
void *global_user_ctx全局用户上下文
httpd_free_ctx_fn_tglobal_user_ctx_free_fn全局用户自由函数
void *global_transport_ctx全局传输内容
httpd_free_ctx_fn_tglobal_transport_ctx_free_fn全局传输自由函数
httpd_open_func_topen_fn自定义会话打开回调
httpd_close_func_tclose_fn自定义会话结束回调
esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)

可使用HTTPD_DEFAULT_CONFIG()初始化默认的配置

一般情况默认配置即可

设置页面
输入输出
描述名称功能
httpd_handle_thandle句柄
const httpd_uri_t *uri_handler配置
输出错误码
httpd_uri_t 结构体
描述名称功能
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_tbuf_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_tbuf_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_tval_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服务器

步骤:

  1. 建立套接字
  2. 收到信息时建立新线程
  3. 解析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)

输入输出
描述名称功能
intdomain协议族AF_INET(IPV4),AF_INET6(IPV6)
inttype类型,SOCK_STREAM(TCP),SOCK_DGRAM(UDP)
intprotocol协议,一般写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)

输入输出
描述名称功能
ints套接字编号
const struct sockaddr *name设置结构体,sockaddr_in(IPV4),sockaddr_in6(IPV6)强转而来
socklen_tnamelen设置结构体长度
int输出0成功,-1失败
sockaddr_in 结构体
描述名称功能
u8_tsin_len(未知,写0即可)
sa_family_tsin_family协议族AF_INET(IPV4),AF_INET6(IPV6)
in_port_tsin_port端口(注意使用lwip_htons来换序)
in_addrsin_addrIP地址,sin_addr.s_addr存放(注意使用lwip_htonl来换序)
charsin_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中网络相关的使用的都是网络字节顺序

函数名输入输出功能
htonluint32_t主机到网络(32位整数)
htonsuint16_t主机到网络(16位整数)
ntohluint32_t网络到主机(32位整数)
ntohsuint16_t网络到主机(16位整数)

关闭套接字(close)

输入输出
描述名称功能
ints套接字编号
int输出0成功,-1失败
int lwip_close(int s)

读套接字

接收数据(TCP常用)(read)
输入输出
描述名称功能
ints套接字编号
void *mem读取缓冲区
size_tlen缓冲区长度
ssize_t输出>0接收的字节,=0无数据,<0错误码

此函数为阻塞接收

ssize_t lwip_read(int s, void *mem, size_t len)
获取发送者信息接收(UDP常用)(recvfrom)

UDP面向无连接,当需要知道发送方时使用此函数

注意:lwip_read的实现也来源于此函数,不过TCP连接本身可以知道数据的发送者,因此一般不需要在接收函数处获取发送者信息

输入输出
描述名称功能
ints套接字编号
void *mem读取缓冲区
size_tlen缓冲区长度
intflags标志,一般写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)
输入输出
描述名称功能
ints套接字编号
void *mem发送缓冲区
size_tlen缓冲区长度
ssize_t输出<0失败,>0写入的字节,=0读取到文件的结束
ssize_t lwip_write(int s, const void *data, size_t size)
指定发送者发送(UDP常用)(sendto)
输入输出
描述名称功能
ints套接字编号
void *mem读取缓冲区
size_tlen缓冲区长度
intflags标志,一般写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)

输入输出
描述名称功能
ints套接字编号
intbacklog排队建立3次握手队列和刚刚建立3次握手队列的链接数和
int输出0成功,其他失败
int lwip_listen(int s, int backlog)

接受套接字(accept)

输入输出
描述名称功能
ints套接字编号
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)

输入输出
描述名称功能
ints套接字编号
intlevel协议层,套接字用SOL_SOCKET
intoptname选项名,见下表
const void *optval选项值缓冲区
socklen_toptlen选项值长度
int输出0成功,<0失败
int lwip_setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen)

获取套接字额外参数(getsockopt)

输入输出
描述名称功能
ints套接字编号
intlevel协议层,套接字用SOL_SOCKET
intoptname选项名,见下表
const void *optval选项值缓冲区
socklen_toptlen选项值长度
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连接(客户端)

步骤

  1. 建立套接字(lwip_socket)
  2. 设置远端IP和端口结构体(struct sockaddr_in)
  3. 连接服务器(lwip_connect)
  4. 操作数据(lwip_write,lwip_read)
  5. 关闭套接字(可选)

设计一个变量来存储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连接(服务器)

步骤

  1. 建立服务器套接字 (lwip_socket)
  2. 设置本地的IP和端口( lwip_bind)
  3. 开始监听 (lwip_listen)
  4. 建立连接套接字( lwip_accept)
  5. 操作数据 (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发送

步骤

  1. 建立套接字 (lwip_socket)
  2. 发送数据 (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监听

步骤

  1. 建立套接字 (lwip_socket)
  2. 设置本地的端口 (lwip_bind)
  3. 读取数据 (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输出句柄
esp_mqtt_client_config_t 结构体
描述名称功能
const char *host域名或ip
uint32_tport端口
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_tclient客户端句柄
esp_mqtt_event_id_tevent事件ID,ESP_EVENT_ANY_ID所有事件
esp_event_handler_tevent_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_tevent_base事件唯一标识
int32_tevent_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_tclient客户端句柄
esp_err_t输出错误码
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)

订阅主题

输入输出
描述名称功能
esp_mqtt_client_handle_tclient客户端句柄
const char *topic主题
intqos0至少一次,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_tclient客户端句柄
esp_err_t输出错误码
esp_err_t esp_mqtt_client_reconnect(esp_mqtt_client_handle_t client)

发布

输入输出
描述名称功能
esp_mqtt_client_handle_tclient客户端句柄
const char *topic主题
const char *data数据
intlen数据长度
intqos0至少一次,1至多一次,2只有一次
intretain保留,一般写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

选择分区表

选择使用外部文件

image-20250119230333875

image-20250119230433054

默认的文件名就是之前建立的文件,也可以这里修改

image-20250119230514745

只支持一级目录,基本目录需要写入分请设置文件

初始化

输入输出
描述名称功能
const esp_vfs_spiffs_conf_t *conf设置结构体
esp_err_t输出错误码
esp_vfs_spiffs_conf_t 结构体
描述名称功能
const char*base_path与文件系统关联的文件路径前缀
const char*partition_label可选,要使用的 SPIFFS 分区的标签。如果设置为 NULL,将使用具有 subtype=spiffs 的第一个分区
size_tmax_files可同时打开的最大文件数
boolformat_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_t 结构体
描述名称功能
esp_partition_type_ttype分区类型(app/data)
esp_partition_subtype_tsubtype分区子类型
uint32_taddress起始地址
uint32_tsize大小(bytes)
charlabel名称(char[17])
boolencrypted分区加密为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_timage_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_thandle句柄
const void *data数据
size_tsize大小
esp_err_t输出错误码
esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)

结束写入

输入输出
描述名称功能
esp_ota_handle_thandle句柄
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);
}

相关文章:

  • AlgerMusic 4.2.0 |开源音乐软件,支持B站和网易云音乐播放及批量下载
  • PyTorch 深度学习 || 6. Transformer | Ch6.3 Transformer 简单案例
  • Tire树(字典树)
  • mysql-sql_mode参数类型
  • 知识表示方法之五:脚本表示法
  • 4.nRF52xx蓝牙学习(GPIOTE与外部中断)
  • 新增一种线性回归的增量学习框架,已更新31个模型!Matlab回归预测大合集又更新啦!
  • 【备赛】蓝桥杯实现多个LED联合控制
  • Java 21新特性实战:虚拟线程如何让吞吐量提升10倍?
  • WPS宏开发手册——附录
  • PostgreSQL迁移
  • vs中两个项目同在一个解决方案时,只生成一个的bug
  • Redis 渐进式rehash怎么判定rehash完成了?
  • Java基础 4.6
  • 算法专题(八):分治-归并排序
  • Cyber Weekly #50
  • TCPIP详解 卷1协议 一 概述
  • 【C/C++】打开转盘锁(leetcode T752)
  • Java EE期末总结(第四章)
  • VBA之Excel应用第四章第二节:单元格对象的行、列属性
  • 企业网站快照更新/网络推广和网站推广
  • 典型b2b模式的网站/b2b网站免费推广平台
  • 建设一个普通的网站需要多少钱/整站优化报价
  • 新手怎么做网站内容维护/微信管理系统
  • 二手手机回收网站开发/项目外包平台
  • 网站页面改版/优化大师使用心得