介绍

创建工程

使用ESPIDF创建S3系列芯片的工程,选择空模板,进行开发

在设置里将FLASH改实际大小,我是16M的FLASH。选择下载方式和端口,编译下载。

测试半双工RS-485收发器

SP3485EEN是一款应用广泛的3.3V半双工RS-485收发器,用于单片机和差分总线(A、B线)间的电平转换。

引脚编号引脚名称功能描述
1RO (Receiver Output)接收器输出。将总线上的差分信号转换为TTL电平,输出给单片机RX。
2/RE (Receiver Enable)接收器使能,低电平有效。低电平时使能接收器,高电平时禁用。
3DE (Driver Enable)驱动器(发送器)使能,高电平有效。高电平时使能发送器,低电平时禁用。
4DI (Driver Input)驱动器输入。将单片机的TX引脚TTL电平输入给芯片。
5GND电源地。
6A驱动器同相输出 / 接收器同相输入。接RS-485总线的A线。
7B驱动器反相输出 / 接收器反相输入。接RS-485总线的B线。
8VCC电源引脚,接3.3V电压。


硬件连接方式(单引脚控制DE和RE)是UART_MODE_RS485_HALF_DUPLEX模式的标准物理电路。

控制逻辑核心

发送数据时:将DE和/RE引脚置于发送模式(DE=1, /RE=1)。此时驱动器使能,接收器关闭,单片机从TX经DI发出的数据被转换为差分信号送到A、B总线。

接收数据时:将DE和/RE引脚置于接收模式(DE=0, /RE=0)。此时驱动器关闭,接收器使能,总线A、B上的差分信号经芯片转换为TTL电平,从RO传给单片机的RX。

程序设计

TTL转RS-485

不知为什么,我的另一个485转换芯片总是有问题(怀疑是坏了),所以现在先初始化一个通道使用。

定义头文件

// RS-485-SP3485EEN.h
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "driver/uart.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "driver/gpio.h"

typedef struct
{
    uart_port_t uart_num;
    gpio_num_t tx_pin;
    gpio_num_t rx_pin;
    gpio_num_t de_re_pin;
    char name[20];
} rs485_channel_t;

/**
 * @brief RS-485-SP3485EEN.h
 * DE和RE引脚接在一起使用,默认是下拉,通过控制DE_RE引脚的高低电平来控制发送和接收
 * DE_RE = HIGH  发送数据,接收器使能,低电平有效。低电平时使能接收器,高电平时禁用接收器
 * DE_RE = LOW   接收数据,驱动器(发送器)使能,高电平有效。高电平时使能发送器,低电平时禁用发送器
 *
 * 所以默认是接收状态,发送数据时需要先将DE_RE引脚拉高,发送完数据后再拉低
 */

// 第一个模块的引脚配置
#define RS_485_SP3485EEN_UART_PORT (UART_NUM_0)
#define RS_485_SP3485EEN_RO_PIN (GPIO_NUM_41)    // 接收器输出。将总线上的差分信号转换为TTL电平,输出给 单片机RX
#define RS_485_SP3485EEN_DE_RE_PIN (GPIO_NUM_42) // 数据使能,接收器使能。控制发送器和接收器是否工作
#define RS_485_SP3485EEN_DI_PIN (GPIO_NUM_44)    // 驱动器输入。单片机TX 发送的TTL电平信号,转换为总线上的差分信号

// 第二个模块的引脚配置
#define RS_485_SP3485EEN_2_UART_PORT (UART_NUM_2)
#define RS_485_SP3485EEN_2_RO_PIN (GPIO_NUM_43)   // 接收器输出。将总线上的差分信号转换为TTL电平,输出给 单片机RX
#define RS_485_SP3485EEN_2_DE_RE_PIN (GPIO_NUM_2) // 数据使能,接收器使能。控制发送器和接收器是否工作
#define RS_485_SP3485EEN_2_DI_PIN (GPIO_NUM_1)    // 驱动器输入。单片机TX 发送的TTL电平信号,转换为总线上的差分信号

// 公共配置
#define BUF_SIZE 256
#define BAUD_RATE 115200

// ----------------------------
// 通道数组
// ----------------------------

extern rs485_channel_t rs485_channels[];
#define NUM_CHANNELS (sizeof(rs485_channels) / sizeof(rs485_channels[0]))

void RS_485_init(uart_port_t uart_num, int tx_pin, int rx_pin, int de_re_pin);
void init_specific_rs485_channel(int channel_num); // 新增函数声明
void init_all_rs485_channels(void);

void rs485_send(uart_port_t uart_num, const uint8_t *data, size_t len);
int rs485_receive(uart_port_t uart_num, uint8_t *buffer, size_t buf_size, uint32_t timeout_ms);

BaseType_t start_rs485_rx_task_for_channel(int channel_num, UBaseType_t priority, uint32_t stack_size);

编写源文件

#include "RS-485-SP3485EEN.h"
#include <esp_log.h>

#define TAG "RS485_DRIVER"
// Timeout threshold for UART = number of symbols (~10 tics) with unchanged state on receive pin

#define ECHO_READ_TOUT (3) // 3.5T * 8 = 28 ticks, TOUT=3 -> ~24..33 ticks

// ----------------------------
// 通道数组定义
// ----------------------------
rs485_channel_t rs485_channels[] = {
    {RS_485_SP3485EEN_UART_PORT, RS_485_SP3485EEN_DI_PIN, RS_485_SP3485EEN_RO_PIN, RS_485_SP3485EEN_DE_RE_PIN, "RS485-1"},
    {RS_485_SP3485EEN_2_UART_PORT, RS_485_SP3485EEN_2_DI_PIN, RS_485_SP3485EEN_2_RO_PIN, RS_485_SP3485EEN_2_DE_RE_PIN, "RS485-2"}};

// ============================
// UART 发送函数
// ============================
void rs485_send(uart_port_t uart_num, const uint8_t *data, size_t len)
{
    if (data == NULL || len == 0)
    {
        ESP_LOGW(TAG, "rs485_send: empty payload");
        return;
    }

    // 清空 RX,防止残留帧污染
    uart_flush_input(uart_num);

    // 发送数据
    int written = uart_write_bytes(uart_num, (const char *)data, len);
    if (written < 0)
    {
        ESP_LOGE(TAG, "UART%d TX write error (%d)", uart_num, written);
        return;
    }
    if ((size_t)written != len)
    {
        ESP_LOGW(TAG, "UART%d TX partial (%d/%d)", uart_num, written, len);
    }

    // 等待 TX 完成(根据字节数计算超时,带一点余量)
    uint32_t timeout_ms = 50 + (len * 11 * 1000) / (BAUD_RATE > 0 ? BAUD_RATE : 1);
    esp_err_t ret = uart_wait_tx_done(uart_num, pdMS_TO_TICKS(timeout_ms));
    if (ret != ESP_OK)
    {
        ESP_LOGW(TAG, "UART%d wait tx done failed: %d", uart_num, ret);
    }

    // Modbus RTU 3.5T 帧间静默(保留短延时)
    vTaskDelay(pdMS_TO_TICKS(5));

    ESP_LOGI(TAG, "UART%d TX done (%d bytes)", uart_num, written);
}

// ============================
// UART 接收函数
// ============================
int rs485_receive(uart_port_t uart_num, uint8_t *buffer, size_t buf_size, uint32_t timeout_ms)
{
    if (buffer == NULL || buf_size == 0)
    {
        ESP_LOGW(TAG, "rs485_receive: invalid buffer");
        return -1;
    }

    int len = uart_read_bytes(uart_num, buffer, buf_size, pdMS_TO_TICKS(timeout_ms));
    if (len > 0)
    {
        ESP_LOGI(TAG, "UART%d RX (%d bytes)", uart_num, len);
        // 不在这里打印数据,交由调用者(task)处理,避免重复日志
    }
    else if (len == 0)
    {
        ESP_LOGD(TAG, "UART%d RX timeout", uart_num);
    }
    else
    {
        ESP_LOGW(TAG, "UART%d RX error (%d)", uart_num, len);
    }

    return len;
}
// ============================
// RS485 初始化函数
// ============================
void RS_485_init(uart_port_t uart_num, int tx_pin, int rx_pin, int de_re_pin)
{
    uart_config_t uart_config = {
        .baud_rate = BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
        .rx_flow_ctrl_thresh = 122,
    };

    ESP_ERROR_CHECK(uart_driver_install(
        uart_num,
        BUF_SIZE * 2,
        BUF_SIZE * 2,
        0,
        NULL,
        0));

    ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));
    ESP_ERROR_CHECK(uart_set_pin(
        uart_num,
        tx_pin,
        rx_pin,
        de_re_pin,
        UART_PIN_NO_CHANGE));

    ESP_ERROR_CHECK(uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX));
    ESP_ERROR_CHECK(uart_set_rx_timeout(uart_num, ECHO_READ_TOUT));

    ESP_LOGI(TAG,
             "RS485 init OK: UART%d TX=%d RX=%d DE/RE=%d",
             uart_num, tx_pin, rx_pin, de_re_pin);
}

// ============================
// 初始化指定RS485通道
// ============================
void init_specific_rs485_channel(int channel_num)
{
    if (channel_num < 0 || channel_num >= NUM_CHANNELS)
    {
        ESP_LOGE(TAG, "Invalid channel number: %d", channel_num);
        return;
    }

    rs485_channel_t *ch = &rs485_channels[channel_num];

    // 初始化RS485硬件
    RS_485_init(ch->uart_num, ch->tx_pin, ch->rx_pin, ch->de_re_pin);

    ESP_LOGI(TAG, "Channel %d: %s (UART%d) initialized",
             channel_num, ch->name, ch->uart_num);

    uint8_t data[] = "RS485-";

    rs485_send(ch->uart_num, data, sizeof(data));
}

// ============================
// 初始化所有RS485通道
// ============================
void init_all_rs485_channels(void)
{
    ESP_LOGI(TAG, "Initializing %d RS485 channels", NUM_CHANNELS);

    for (int i = 0; i < NUM_CHANNELS; i++)
    {
        init_specific_rs485_channel(i);
    }
}

/* 接收任务:参数为通道索引 (int cast via intptr_t) */
static void rs485_rx_task(void *arg)
{
    int channel = (int)(intptr_t)arg;
    if (channel < 0 || channel >= NUM_CHANNELS)
    {
        ESP_LOGE(TAG, "rs485_rx_task: invalid channel %d", channel);
        vTaskDelete(NULL);
        return;
    }

    rs485_channel_t *ch = &rs485_channels[channel];

    /* 使用堆分配,避免栈溢出 */
    uint8_t *buf = malloc(BUF_SIZE);
    if (buf == NULL)
    {
        ESP_LOGE(TAG, "rs485_rx_task: malloc failed");
        vTaskDelete(NULL);
        return;
    }

    while (1)
    {
        int len = rs485_receive(ch->uart_num, buf, BUF_SIZE, 20); // 20ms timeout
        if (len > 0)
        {
            ESP_LOGI(TAG, "%s UART%d RX (%d bytes)", ch->name, ch->uart_num, len);
            ESP_LOG_BUFFER_HEX(TAG, buf, len);
            //ESP_LOGI(TAG, "UART%d RX: %s", ch->uart_num, buf);
        }
        else if (len == 0)
        {
            vTaskDelay(pdMS_TO_TICKS(50)); // 无数据短延时
        }
        else
        {
            ESP_LOGW(TAG, "%s UART%d RX error (%d)", ch->name, ch->uart_num, len);
            vTaskDelay(pdMS_TO_TICKS(200));
        }
    }
    /* 永久任务通常不会到这里;若退出则释放 */
    free(buf);
    vTaskDelete(NULL);
}

/* 启动单个通道的接收任务
   返回 pdPASS 或 pdFAIL */
BaseType_t start_rs485_rx_task_for_channel(int channel_num, UBaseType_t priority, uint32_t stack_size)
{
    if (channel_num < 0 || channel_num >= NUM_CHANNELS)
    {
        ESP_LOGE(TAG, "start_rs485_rx_task_for_channel: invalid channel %d", channel_num);
        return pdFAIL;
    }
    char tname[16];
    snprintf(tname, sizeof(tname), "rs485_rx_%d", channel_num);
    return xTaskCreate(rs485_rx_task, tname, stack_size, (void *)(intptr_t)channel_num, priority, NULL);
}

/* 可选:启动所有通道的接收任务(示例默认优先级与栈)*/
void start_all_rs485_rx_tasks(UBaseType_t priority, uint32_t stack_size)
{
    for (int i = 0; i < NUM_CHANNELS; i++)
    {
        if (start_rs485_rx_task_for_channel(i, priority, stack_size) != pdPASS)
        {
            ESP_LOGW(TAG, "Failed to start RX task for channel %d", i);
        }
        else
        {
            ESP_LOGI(TAG, "Started RX task for channel %d", i);
        }
    }
}

主程序调用

#include "RS-485-SP3485EEN.h"


#define TAG "main"
void app_main(void)
{
    // 初始化NVS(如果工程中需要)
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }

    ESP_ERROR_CHECK(ret);

    // 初始化RS485
    init_specific_rs485_channel(0); // 初始化通道0
    start_rs485_rx_task_for_channel(0, 5, 4096); // 为通道0启动接收任务
  


    for (;;)
    {
        vTaskDelay(1000 / portTICK_PERIOD_MS);

    }
}

测试结果:

所以这个功能是可以用的。

移植espressif/ch390

我的以太网芯片使用的是CH390H-CH390H官方介绍,刚好在乐鑫有组件库,可以试着移植,节省时间。
下载地址:组件下载地址
CH390是一款工业级以太网控制器芯片,集成10/100M以太网介质传输层(MAC)和物理层收发器(PHY),支持CAT3、4、5及CAT5、6线缆连接,兼容10BASE-T和100BASE-TX标准,具备HP Auto-MDIX功能,采用低功耗设计,遵循IEEE 802.3u规范。CH390内置16K字节SRAM,支持3.3V或2.5V并行接口以及SPI串行接口。

将下载的组件copy到自己的组件,然后记得开启SPI-Ethernet模块的支持

编写头文件

在这个文件包含需要的文件,然后定义使用的引脚

#include <stdio.h>
#include "esp_eth.h"
#include "esp_eth_driver.h"
#include "esp_eth_mac_ch390.h"
#include "esp_eth_phy_ch390.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#include "driver/gpio.h"


#define ETH_CS_GPIO (GPIO_NUM_10)
#define ETH_MOSI_GPIO (GPIO_NUM_12)
#define ETH_MISO_GPIO (GPIO_NUM_13)
#define ETH_SCLK_GPIO (GPIO_NUM_11)
#define ETH_INT_GPIO (GPIO_NUM_14)
#define SPI_HOST SPI2_HOST
#define SPI_CLOCK_MHZ 10


void eth_init(void);

编写头文件

这个文件负责初始化SPI和使用以太网连接,并且显示以太网连接状态

/*
 * 文件: ETH_CH390H.c
 * 描述: CH390H SPI 以太网模块初始化与事件处理封装。
 *
 * 功能:
 *   - 初始化 SPI 总线并配置 CH390H 设备
 *   - 安装并启动 esp-eth 驱动
 *   - 注册以太网事件回调(连接/断开/启动/停止)和获取 IP 回调
 *
 * 用法:
 *   1. 在 app_main() 中调用 eth_init() 完成初始化,例如:
 *        eth_init();
 *   2. 如需自定义 GPIO/SPI 配置,可修改本文件顶部的宏定义。
 *   3. 如需自定义事件处理,修改 eth_event_handler 或 got_ip_event_handler。
 *
 * 注意:
 *   - 本模块使用 esp_netif 和 esp_event,调用前请确保没有重复初始化。
 *   - 调试时可通过调整 mac_config.rx_task_stack_size 或 SPI_CLOCK_MHZ 优化性能。
 */


#include "ETH_CH390H.h"

static const char *TAG = "eth_ch390h";



/* 事件处理函数
 * 处理 esp-eth 发出的以太网状态事件:
 *   - ETHERNET_EVENT_CONNECTED    : 已连接(物理链路/链路层可用)
 *   - ETHERNET_EVENT_DISCONNECTED : 断开(物理链路丢失)
 *   - ETHERNET_EVENT_START        : 以太网驱动启动
 *   - ETHERNET_EVENT_STOP         : 以太网驱动停止
 * 参数:
 *   arg        - 注册时传入的参数(当前未使用)
 *   event_base - 事件基(ETH_EVENT)
 *   event_id   - 事件 id
 *   event_data - 事件相关数据(视事件而定)
 */
static void eth_event_handler(void *arg, esp_event_base_t event_base,
                              int32_t event_id, void *event_data)
{
    switch (event_id)
    {
    case ETHERNET_EVENT_CONNECTED:
        ESP_LOGI(TAG, "以太网连接成功");
        break;
    case ETHERNET_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "以太网断开连接");
        break;
    case ETHERNET_EVENT_START:
        ESP_LOGI(TAG, "以太网开始工作");
        break;
    case ETHERNET_EVENT_STOP:
        ESP_LOGI(TAG, "以太网停止工作");
        break;
    default:
        break;
    }
}

/* 获取 IP 回调
 * 当网口获取到 IP(DHCP 或静态)时调用,打印分配到的 IP 信息。
 * 参数同上,event_data 可转换为 ip_event_got_ip_t* 来读取 ip 信息。
 */
static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
                                 int32_t event_id, void *event_data)
{
    ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
    ESP_LOGI(TAG, "获取到的IP: " IPSTR, IP2STR(&event->ip_info.ip));
}

/* eth_init
 * 初始化并启动 CH390H 以太网设备的封装函数:
 * 1. 初始化网络接口与默认事件循环
 * 2. 配置并初始化 SPI 总线(供 CH390H 使用)
 * 3. 配置 CH390H 的 mac/phy,并安装 esp-eth 驱动
 * 4. 将 esp-netif 绑定到以太网驱动,并注册事件回调
 * 5. 启动以太网驱动
 *
 * 注意:
 * - 若需要修改引脚或 SPI 频率,可在文件顶部宏中调整
 * - 可根据需要调整 mac_config、phy_config 中的参数以优化性能
 */
void eth_init(void)
{
    esp_netif_init();
    esp_event_loop_create_default();

    esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
    esp_netif_t *eth_netif = esp_netif_new(&cfg);

    /* 设置以太网设备在 DHCP/路由器中的主机名(需在启动前设置) */
    esp_netif_set_hostname(eth_netif, "Distributed Collector Gateway");

    gpio_install_isr_service(0);

    spi_bus_config_t buscfg = {
        .mosi_io_num = ETH_MOSI_GPIO,
        .miso_io_num = ETH_MISO_GPIO,
        .sclk_io_num = ETH_SCLK_GPIO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
    };
    ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));

    spi_device_interface_config_t spi_devcfg = {
        .mode = 0,
        .clock_speed_hz = SPI_CLOCK_MHZ * 1000 * 1000,
        .spics_io_num = ETH_CS_GPIO,
        .queue_size = 20,
    };

    eth_ch390_config_t ch390_config = ETH_CH390_DEFAULT_CONFIG(SPI_HOST, &spi_devcfg);
    ch390_config.int_gpio_num = ETH_INT_GPIO;

    eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
    mac_config.rx_task_stack_size = 4096;

    esp_eth_mac_t *mac = esp_eth_mac_new_ch390(&ch390_config, &mac_config);

    eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
    esp_eth_phy_t *phy = esp_eth_phy_new_ch390(&phy_config);

    esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);

    esp_eth_handle_t eth_handle = NULL;
    ESP_ERROR_CHECK(esp_eth_driver_install(&eth_config, &eth_handle));

    ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));

    esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, NULL);
    esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL);

    ESP_ERROR_CHECK(esp_eth_start(eth_handle));
}

注意cmake文件

idf_component_register(SRCS         
                    "ETH_CH390H.c"
                    "esp_eth_mac_ch390.c"
                    "esp_eth_phy_ch390.c"
                    INCLUDE_DIRS "include"
                    REQUIRES esp_eth esp_netif driver esp_timer  
                    )

不要忘记包含!

测试结果

在app_main中调用eth_init(); // 初始化以太网就可以正常连上。(记得插网线)。

这个是连接结果:

在路由器后台可以看见,设备成功连接:

通过MQTT协议连接自建EMQx

创建自定义组件

使用一下命令,下载使用这个组件,espressif/mqtt: ^1.0.0,在工程中添加MQTT组件,加入mqtt

idf_component_register(SRCS "MQTT_ESP.c"
                    PRIV_REQUIRES mqtt
                    INCLUDE_DIRS "include")

创建Kconfig文件以便配置连接参数

参考教程:ESP32S3项目配置ESP-IDF Kconfig文件配置

填写属于MQTT配置的Kconfig.projbuild文件

在ESP-IDF的menuconfig配置中,所有显示给用户的字符串都可以替换为中文,而内部的配置变量名和逻辑关键字必须保持英文不变。

menu "MQTT连接配置"

    config BROKER_URI
        string "MQTT服务器地址"
        default "wss://mqtt.eclipseprojects.io:443/mqtt"
        help
            本示例要连接的MQTT服务器的URL地址。

    config BROKER_CERTIFICATE_OVERRIDE
        string "服务器证书覆盖"
        default ""
        help
            如果服务器证书已从文本文件加载,请留空;否则请填写PEM格式证书的base64编码部分。

    config BROKER_CERTIFICATE_OVERRIDDEN
        bool
        default y if BROKER_CERTIFICATE_OVERRIDE != ""

endmenu

保存,清理构建,重新编译。

然后打开menuconfig就可以看见刚才写的内容:

使用定义的变量

定义好的变量在主目录的sdkconfig中,打开搜索自己的菜单名字(如:MQTT连接配置 )就可快速找到。

然后就可以直接使用这个变量了!!!!

可以丰富一下,添加一些常用的参数:

menu "MQTT连接配置"

    config BROKER_URI
        string "MQTT服务器地址"
        default "mqtt://mqtt.eclipseprojects.io:1883"
        help
            要连接的MQTT Broker的完整URL。例如:
            ws://broker.emqx.io:8083/mqtt (明文WebSocket)
            wss://broker.emqx.io:8084/mqtt (加密WebSocket)
            mqtt://192.168.1.100:1883 (明文TCP)

    config MQTT_CLIENT_ID
        string "客户端标识符"
        default "esp32_client_01"
        help
            MQTT协议中用于识别客户端的唯一ID。如果留空,部分服务器会自动生成。

    config MQTT_USERNAME
        string "用户名"
        default ""
        help
            用于连接MQTT服务器的用户名(如果需要认证)。如果无需认证,请留空。

    config MQTT_PASSWORD
        string "用户密码"
        default ""
        help
            用于连接MQTT服务器的密码(如果需要认证)。如果无需认证,请留空。

    config MQTT_PUB_TOPIC
        string "发布主题"
        default "/device/esp32/pub"
        help
            ESP32将向此主题(Topic)发布(Publish)消息。

    config MQTT_SUB_TOPIC
        string "订阅主题"
        default "/device/esp32/sub"
        help
            ESP32将订阅(Subscribe)此主题(Topic)以接收消息。

    config BROKER_CERTIFICATE_OVERRIDE
        string "服务器证书覆盖"
        default ""
        help
            如果服务器证书已从文本文件加载,请留空;否则请填写PEM格式证书的base64编码部分。

    config BROKER_CERTIFICATE_OVERRIDDEN
        bool
        default y if BROKER_CERTIFICATE_OVERRIDE != ""


    config BROKER_BIN_SIZE_TO_SEND
        # This option is not visible and is used only to set parameters for example tests
        # Here we configure the data size to send and to be expected in the python script
        int
        default 20000

endmenu

编写连接MQTT的程序

在menuconfig填写连接参数之后,就可以开始写程序了。这里我使用1883端口连接我的服务器,这样就不用部署证书了。

这些是源文件的代码:

#include <stdio.h>
#include "MQTT_ESP.h"

static const char *TAG = "mqtt_esp";




/**
 * @brief MQTT事件处理函数
 * 
 * 处理MQTT客户端的各种事件,包括连接、订阅、发布、数据接收和错误处理。
 * 
 * @param handler_args 事件处理器参数
 * @param base 事件基础类型
 * @param event_id 事件ID
 * @param event_data 指向MQTT事件数据的指针
 */
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32, base, event_id);
    esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id) {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        // 订阅并取消订阅测试主题
        msg_id = esp_mqtt_client_subscribe(client, CONFIG_MQTT_SUB_TOPIC, 1);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        break;

    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d, return code=0x%02x ", event->msg_id, (uint8_t)*event->data);
        // 发布测试消息到主题
        msg_id = esp_mqtt_client_publish(client, CONFIG_MQTT_PUB_TOPIC, "I am comming!", 0, 0, 0);
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
        break;
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");

        break;
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        // 错误类型处理分支
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
            ESP_LOGI(TAG, "Last error code reported from esp-tls: 0x%x", event->error_handle->esp_tls_last_esp_err);
            ESP_LOGI(TAG, "Last tls stack error number: 0x%x", event->error_handle->esp_tls_stack_err);
            ESP_LOGI(TAG, "Last captured errno : %d (%s)",  event->error_handle->esp_transport_sock_errno,
                     strerror(event->error_handle->esp_transport_sock_errno));
        } else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
            ESP_LOGI(TAG, "Connection refused error: 0x%x", event->error_handle->connect_return_code);
        } else {
            ESP_LOGW(TAG, "Unknown error type: 0x%x", event->error_handle->error_type);
        }
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}

/**
 * @brief 启动MQTT客户端应用程序
 * 
 * 初始化MQTT客户端配置,注册事件处理程序,并启动MQTT连接
 * 
 * @param 无参数
 * @return 无返回值
 */
void mqtt_app_start(void)
{
    // 配置MQTT客户端结构体,设置代理服务器地址和证书验证
    const esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = CONFIG_BROKER_URI,
       // .broker.verification.certificate = (const char *)mqtt_eclipseprojects_io_pem_start,
        .credentials.client_id = CONFIG_MQTT_CLIENT_ID,
        .credentials.username = CONFIG_MQTT_USERNAME,
        .credentials.authentication.password = CONFIG_MQTT_PASSWORD,
    };

    ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
    /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);

    esp_mqtt_client_start(client);
}

在头文件声明mqtt_app_start();这个函数,然后在main函数调用,就可以启动这个mqtt连接,并且测试发送,但是,为了避免在还没有连接网络成功的情况下就进行连接,需要在初始化网络连接之后,mqtt启动前,增加对网络连接状态的判断。

    // 等待网络连接建立
    ESP_LOGI(TAG, "Waiting for network connection...");
    esp_netif_t *eth_netif = esp_netif_get_handle_from_ifkey("ETH_DEF"); // 获取默认以太网接口

    // 循环等待直到获得IP地址
    while (true)
    {
        esp_netif_ip_info_t ip_info;
        if (esp_netif_get_ip_info(eth_netif, &ip_info) == ESP_OK && ip_info.ip.addr != 0)
        {
            ESP_LOGI(TAG, "Network connected with IP: " IPSTR, IP2STR(&ip_info.ip));
            break;
        }
        ESP_LOGI(TAG, "Waiting for IP address...");
        vTaskDelay(pdMS_TO_TICKS(1000)); // 等待1秒后重试
    }

    ESP_LOGI(TAG, "Starting MQTT client...");

接着就可以初始化mqtt,这是main的全部代码:

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());

    // 初始化以太网,这里包含了    esp_netif_init();和esp_event_loop_create_default();
    eth_init();

    // 初始化RS485
    init_specific_rs485_channel(0);              // 初始化通道0
    start_rs485_rx_task_for_channel(0, 5, 4096); // 为通道0启动接收任务

    // 等待网络连接建立
    ESP_LOGI(TAG, "Waiting for network connection...");
    esp_netif_t *eth_netif = esp_netif_get_handle_from_ifkey("ETH_DEF"); // 获取默认以太网接口

    // 循环等待直到获得IP地址
    while (true)
    {
        esp_netif_ip_info_t ip_info;
        if (esp_netif_get_ip_info(eth_netif, &ip_info) == ESP_OK && ip_info.ip.addr != 0)
        {
            ESP_LOGI(TAG, "Network connected with IP: " IPSTR, IP2STR(&ip_info.ip));
            break;
        }
        ESP_LOGI(TAG, "Waiting for IP address...");
        vTaskDelay(pdMS_TO_TICKS(1000)); // 等待1秒后重试
    }

    ESP_LOGI(TAG, "Starting MQTT client...");

    // 启动MQTT客户端
    mqtt_app_start();

    for (;;)
    {
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

在终端查看日志,是正常的顺序:

服务器也看见设备上线并且订阅成功:

这样就成功完成了MQTT部分的编写。

将消息发送到服务器

增加可供外部调用的消息发布函数

  1. 在源文件加上全局的客户端句柄:
// 全局MQTT客户端句柄
static esp_mqtt_client_handle_t g_client = NULL;
  1. 更改启动中的句柄:
/**
 * @brief 启动MQTT客户端应用程序
 * 
 * 初始化MQTT客户端配置,注册事件处理程序,并启动MQTT连接
 * 
 * @param 无参数
 * @return 无返回值
 */
void mqtt_app_start(void)
{
    // 配置MQTT客户端结构体,设置代理服务器地址和证书验证
    const esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = CONFIG_BROKER_URI,
       // .broker.verification.certificate = (const char *)mqtt_eclipseprojects_io_pem_start,
        .credentials.client_id = CONFIG_MQTT_CLIENT_ID,
        .credentials.username = CONFIG_MQTT_USERNAME,
        .credentials.authentication.password = CONFIG_MQTT_PASSWORD,
    };

    ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
    g_client = esp_mqtt_client_init(&mqtt_cfg);
    /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
    esp_mqtt_client_register_event(g_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);

    esp_mqtt_client_start(g_client);
}
  1. 接着在文件的末尾加上主角并在头文件声明
/**
 * @brief 发布MQTT消息
 * 
 * 提供一个外部接口来发布MQTT消息
 * 
 * @param topic 发布的主题
 * @param data 要发布的数据
 * @param len 数据长度
 * @param qos QoS级别 (0, 1, 或 2)
 * @param retain 是否保留消息
 * @return 消息ID,如果失败则返回负数
 */
int mqtt_publish_message(const char* topic, const char* data, int len, int qos, int retain)
{
    if (g_client == NULL) {
        ESP_LOGE(TAG, "MQTT client not initialized");
        return -1;
    }

    int msg_id = esp_mqtt_client_publish(g_client, topic, data, len, qos, retain);
    if (msg_id < 0) {
        ESP_LOGE(TAG, "Failed to publish message to topic: %s", topic);
        return -1;
    }

    ESP_LOGI(TAG, "Published message to topic: %s, msg_id: %d", topic, msg_id);
    return msg_id;
}
  1. 在main中测试功能,间隔1s发送消息(后续记得删掉):
    for (;;)
    {
        mqtt_publish_message(CONFIG_MQTT_PUB_TOPIC, "Hello from ESP32!", 18, 1, 0);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
  1. 测试结果

在MQTTx中可以监视到所有上传的消息:

功能正常!!!!!!!!!!!

建立转发任务

#include "MQTT_ESP.h" // 包含MQTT头文件RS-485-SP3485EEN.c中包含。将原有的接收任务进行用MQTT协议转发至服务器

idf_component_register(SRCS "RS-485-SP3485EEN.c"
                    INCLUDE_DIRS "include"
                    REQUIRES nvs_flash driver MQTT_ESP mqtt
                    )

代码改动:

/* 接收任务:参数为通道索引 (int cast via intptr_t) */
static void rs485_rx_task(void *arg)
{
    int channel = (int)(intptr_t)arg;
    if (channel < 0 || channel >= NUM_CHANNELS)
    {
        ESP_LOGE(TAG, "rs485_rx_task: invalid channel %d", channel);
        vTaskDelete(NULL);
        return;
    }

    rs485_channel_t *ch = &rs485_channels[channel];

    /* 使用堆分配,避免栈溢出 */
    uint8_t *buf = malloc(BUF_SIZE);
    if (buf == NULL)
    {
        ESP_LOGE(TAG, "rs485_rx_task: malloc failed");
        vTaskDelete(NULL);
        return;
    }

    while (1)
    {
        int len = rs485_receive(ch->uart_num, buf, BUF_SIZE, 20); // 20ms timeout
        if (len > 0)
        {
            ESP_LOGI(TAG, "%s UART%d RX (%d bytes)", ch->name, ch->uart_num, len);
            ESP_LOG_BUFFER_HEX(TAG, buf, len);
            
            
            // 发布数据到MQTT
            int ret = mqtt_publish_message(CONFIG_MQTT_PUB_TOPIC, (char*)buf, len, 0, 0);
            if (ret < 0) {
                ESP_LOGW(TAG, "Failed to publish RS485 data to MQTT topic %s", CONFIG_MQTT_PUB_TOPIC);
            } else {
                ESP_LOGI(TAG, "Published RS485 data to MQTT topic %s, msg_id: %d", CONFIG_MQTT_PUB_TOPIC, ret);
            }
        }
        else if (len == 0)
        {
            vTaskDelay(pdMS_TO_TICKS(50)); // 无数据短延时
        }
        else
        {
            ESP_LOGW(TAG, "%s UART%d RX error (%d)", ch->name, ch->uart_num, len);
            vTaskDelay(pdMS_TO_TICKS(200));
        }
    }
    /* 永久任务通常不会到这里;若退出则释放 */
    free(buf);
    vTaskDelete(NULL);
}

这样就可以将接收到的数据转发到服务器,这是为后续网关转发功能做基础。

增加状态指示灯

我的灯是低电平点亮,细节不说,看全部代码:

头文件:

#ifndef STATUS_LED_H
#define STATUS_LED_H

#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// LED 状态枚举
typedef enum {
    LED_OFF = 0,
    LED_ON = 1,
    LED_BLINK_SLOW,
    LED_BLINK_FAST,
    LED_HEARTBEAT
} led_state_t;

/**
 * @brief 初始化状态LED
 * 
 * 配置两个LED引脚为输出模式并启动控制任务
 */
void status_led_init(void);

/**
 * @brief 控制指定LED的状态
 * 
 * @param led_num LED编号 (1 或 2)
 * @param state LED状态 (0=关闭, 1=开启)
 */
void status_led_set(uint8_t led_num, uint8_t state);

/**
 * @brief 切换指定LED的状态
 * 
 * @param led_num LED编号 (1 或 2)
 */
void status_led_toggle(uint8_t led_num);

/**
 * @brief 设置LED为闪烁模式
 * 
 * @param led_num LED编号 (1 或 2)
 * @param mode 闪烁模式 (0=慢闪, 1=快闪, 2=心跳)
 */
void status_led_blink_mode(uint8_t led_num, uint8_t mode);

/**
 * @brief 销毁LED控制任务
 * 
 * @param led_num LED编号 (1 或 2)
 */
void status_led_deinit(uint8_t led_num);

/**
 * @brief 获取LED当前状态
 * 
 * @param led_num LED编号 (1 或 2)
 * @return 当前LED状态
 */
led_state_t status_led_get_state(uint8_t led_num);

#endif // STATUS_LED_H

源文件:

#include <stdio.h>
#include "STATUS_LED.h"

static const char *TAG = "status_led";

// LED 引脚定义
#define STATUS_LED_1_PIN    (GPIO_NUM_8)
#define STATUS_LED_2_PIN    (GPIO_NUM_18)


// LED 控制结构体
typedef struct {
    gpio_num_t pin;
    led_state_t state;
    int blink_period_ms;
    bool current_level;  // 实际输出电平
    TaskHandle_t task_handle;
    bool task_running;
} status_led_t;

static status_led_t led1 = {.pin = STATUS_LED_1_PIN, .state = LED_OFF, .task_running = false};
static status_led_t led2 = {.pin = STATUS_LED_2_PIN, .state = LED_OFF, .task_running = false};

/**
 * @brief LED控制任务
 * 
 * @param param 指向LED结构体的指针
 */
static void led_control_task(void *param)
{
    status_led_t *led = (status_led_t *)param;
    
    while(led->task_running) {
        switch(led->state) {
            case LED_OFF:
                gpio_set_level(led->pin, 1); // 低电平点亮,所以高电平关闭
                vTaskDelay(100 / portTICK_PERIOD_MS); // 延迟100ms后再次检查状态
                break;
                
            case LED_ON:
                gpio_set_level(led->pin, 0); // 低电平点亮
                vTaskDelay(100 / portTICK_PERIOD_MS); // 延迟100ms后再次检查状态
                break;
                
            case LED_BLINK_SLOW:
                led->current_level = !led->current_level;
                gpio_set_level(led->pin, !led->current_level); // 反转输出以适应低电平点亮
                vTaskDelay(500 / portTICK_PERIOD_MS); // 慢闪周期500ms
                break;
                
            case LED_BLINK_FAST:
                led->current_level = !led->current_level;
                gpio_set_level(led->pin, !led->current_level); // 反转输出以适应低电平点亮
                vTaskDelay(100 / portTICK_PERIOD_MS); // 快闪周期100ms
                break;
                
            case LED_HEARTBEAT:
                // 心跳模式:快速亮两次,然后长灭
                gpio_set_level(led->pin, 0); // 点亮
                vTaskDelay(100 / portTICK_PERIOD_MS);
                gpio_set_level(led->pin, 1); // 熄灭
                vTaskDelay(100 / portTICK_PERIOD_MS);
                gpio_set_level(led->pin, 0); // 点亮
                vTaskDelay(100 / portTICK_PERIOD_MS);
                gpio_set_level(led->pin, 1); // 熄灭
                vTaskDelay(800 / portTICK_PERIOD_MS); // 间隔800ms
                break;
                
            default:
                gpio_set_level(led->pin, 1); // 默认关闭
                vTaskDelay(100 / portTICK_PERIOD_MS);
                break;
        }
    }
    
    // 任务结束前关闭LED(高电平关闭)
    gpio_set_level(led->pin, 1);
    vTaskDelete(NULL);
}

/**
 * @brief 初始化GPIO引脚为输出模式
 * 
 * @param led_pin LED使用的GPIO引脚
 */
static void init_gpio_pin(gpio_num_t led_pin)
{
    gpio_config_t io_conf = {};
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = (1ULL << led_pin);
    io_conf.pull_down_en = 0;
    io_conf.pull_up_en = 0;
    gpio_config(&io_conf);
    
    // 初始状态设为高电平(LED熄灭,适用于低电平点亮的LED)
    gpio_set_level(led_pin, 1);
}

/**
 * @brief 初始化状态LED
 * 
 * 配置两个LED引脚为输出模式并设置初始状态
 */
void status_led_init(void)
{
    init_gpio_pin(STATUS_LED_1_PIN);
    init_gpio_pin(STATUS_LED_2_PIN);
    
    // 启动LED控制任务
    led1.task_running = true;
    xTaskCreate(led_control_task, "led1_control", 2048, &led1, 5, &led1.task_handle);
    
    led2.task_running = true;
    xTaskCreate(led_control_task, "led2_control", 2048, &led2, 5, &led2.task_handle);
    
    ESP_LOGI(TAG, "Status LEDs initialized on pins GPIO%d and GPIO%d", 
             STATUS_LED_1_PIN, STATUS_LED_2_PIN);
}

/**
 * @brief 控制指定LED的状态
 * 
 * @param led_num LED编号 (1 或 2)
 * @param state LED状态 (0=关闭, 1=开启)
 */
void status_led_set(uint8_t led_num, uint8_t state)
{
    status_led_t *led = NULL;
    
    if (led_num == 1) {
        led = &led1;
    } else if (led_num == 2) {
        led = &led2;
    } else {
        ESP_LOGE(TAG, "Invalid LED number: %d", led_num);
        return;
    }
    
    if (state == 1) {
        led->state = LED_ON;
    } else {
        led->state = LED_OFF;
    }
    
    ESP_LOGD(TAG, "LED %d set to %s", led_num, state ? "ON" : "OFF");
}

/**
 * @brief 切换指定LED的状态
 * 
 * @param led_num LED编号 (1 或 2)
 */
void status_led_toggle(uint8_t led_num)
{
    status_led_t *led = NULL;
    
    if (led_num == 1) {
        led = &led1;
    } else if (led_num == 2) {
        led = &led2;
    } else {
        ESP_LOGE(TAG, "Invalid LED number: %d", led_num);
        return;
    }
    
    if (led->state == LED_ON) {
        led->state = LED_OFF;
    } else if (led->state == LED_OFF) {
        led->state = LED_ON;
    }
    
    ESP_LOGD(TAG, "LED %d toggled", led_num);
}

/**
 * @brief 设置LED为闪烁模式
 * 
 * @param led_num LED编号 (1 或 2)
 * @param mode 闪烁模式 (0=慢闪, 1=快闪, 2=心跳)
 */
void status_led_blink_mode(uint8_t led_num, uint8_t mode)
{
    status_led_t *led = NULL;
    
    if (led_num == 1) {
        led = &led1;
    } else if (led_num == 2) {
        led = &led2;
    } else {
        ESP_LOGE(TAG, "Invalid LED number: %d", led_num);
        return;
    }
    
    switch(mode) {
        case 0: // 慢闪
            led->state = LED_BLINK_SLOW;
            break;
        case 1: // 快闪
            led->state = LED_BLINK_FAST;
            break;
        case 2: // 心跳
            led->state = LED_HEARTBEAT;
            break;
        default:
            ESP_LOGE(TAG, "Invalid blink mode: %d", mode);
            return;
    }
    
    ESP_LOGD(TAG, "LED %d set to blink mode %d", led_num, mode);
}

/**
 * @brief 销毁LED控制任务
 * 
 * @param led_num LED编号 (1 或 2)
 */
void status_led_deinit(uint8_t led_num)
{
    status_led_t *led = NULL;
    
    if (led_num == 1) {
        led = &led1;
    } else if (led_num == 2) {
        led = &led2;
    } else {
        ESP_LOGE(TAG, "Invalid LED number: %d", led_num);
        return;
    }
    
    if (led->task_running) {
        led->task_running = false;
        if (led->task_handle) {
            vTaskDelete(led->task_handle);
            led->task_handle = NULL;
        }
        gpio_set_level(led->pin, 1); // 关闭LED(高电平关闭)
    }
}

/**
 * @brief 获取LED当前状态
 * 
 * @param led_num LED编号 (1 或 2)
 * @return 当前LED状态
 */
led_state_t status_led_get_state(uint8_t led_num)
{
    if (led_num == 1) {
        return led1.state;
    } else if (led_num == 2) {
        return led2.state;
    } else {
        ESP_LOGE(TAG, "Invalid LED number: %d", led_num);
        return LED_OFF;
    }
}

在main中使用,用于指示状态(简陋版本,待优化

void app_main(void)
{

    status_led_init();

    status_led_blink_mode(1, 1); // LED 1 快闪

    ESP_ERROR_CHECK(nvs_flash_init());

    // 初始化以太网,这里包含了    esp_netif_init();和esp_event_loop_create_default();
    eth_init();

    // 初始化RS485
    init_specific_rs485_channel(0);              // 初始化通道0
    start_rs485_rx_task_for_channel(0, 5, 4096); // 为通道0启动接收任务

    // 等待网络连接建立
    ESP_LOGI(TAG, "Waiting for network connection...");
    esp_netif_t *eth_netif = esp_netif_get_handle_from_ifkey("ETH_DEF"); // 获取默认以太网接口

    // 循环等待直到获得IP地址
    while (true)
    {
        esp_netif_ip_info_t ip_info;
        if (esp_netif_get_ip_info(eth_netif, &ip_info) == ESP_OK && ip_info.ip.addr != 0)
        {
            ESP_LOGI(TAG, "Network connected with IP: " IPSTR, IP2STR(&ip_info.ip));
            break;
        }
        ESP_LOGI(TAG, "Waiting for IP address...");
        vTaskDelay(pdMS_TO_TICKS(1000)); // 等待1秒后重试
    }
    status_led_blink_mode(1, 0); // LED 1 慢闪

    status_led_blink_mode(2, 1); // LED 2 快闪
    ESP_LOGI(TAG, "Starting MQTT client...");

    // 启动MQTT客户端
    mqtt_app_start();

    // 等待MQTT连接建立
    ESP_LOGI(TAG, "Waiting for MQTT connection...");
    vTaskDelay(pdMS_TO_TICKS(2000)); // 等待2秒,确保MQTT连接建立

    status_led_blink_mode(2, 0); // LED 2 慢闪

    for (;;)
    {

        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

==后续需要集成在真这个的状态监测中!==