ESP32C3SuperMini

简介

ESP32C3SuperMini是一款基于 Espressif ESP32-C3 WiFi/蓝牙双模芯片的 IoT 迷你开发板。ESP32-C3 是一款32 位 RISC-V CPU,包含FPU(浮点单元),可进行32 位单精度运算,具有强大的计算能力。它具有出色的射频性能,支持IEEE 802.11 b/g/n WiFi和蓝牙 5 (LE)协议。该板附带外部天线,可增强无线应用的信号强度。它还具有小巧精致的外形并结合单面表面贴装设计。它配备了丰富的接口,有11个可用作PWM引脚的数字I/O和4个可用作ADC引脚的模拟I/O。它支持UART、I2C 和 SPI等四种串行接口。板上还有一个小的重置按钮和一个引导加载程序模式按钮。

参数描述

  • 强大的 CPU:ESP32-C3,32 位 RISC-V 单核处理器,运行频率高达 160 MHz
  • WiFi:802.11b/g/n协议、2.4GhHz、支持Station模式、SoftAP模式、SoftAP+Station模式、混杂模式
  • 蓝牙:Bluetooth 5.0 超低功耗:深度睡眠功耗约43μA 丰富的板子资源:400KB SRAM、384KB ROM
  • 内置4Mflash 。 芯片型号 :ESP32C3FN4 超小尺寸:小至拇指 (22.52x18mm)
  • 经典外形,适用于可穿戴设备和小型项目 可靠的安全功能:支持 AES-128/256、哈希、RSA、HMAC、数字签名和安全启动的加密硬件加速器
  • 丰富的接口:1xI2C、1xSPI、2xUART、11xGPIO(PWM)、4xADC 单面元件、表面贴装设计 板载LED蓝灯
  • GPIO8引脚:

在这里插入图片描述

舵机

简介

舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。目前在高档遥控玩具,如航模,包括飞机模型,潜艇模型;遥控机器人中已经使用得比较普遍。舵机是一种俗称,其实是一种伺服马达。

控制方式

舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分。以180度角度伺服为例,那么对应的控制关系是这样的:

  • 0.5ms--------------0度;
  • 1.0ms------------45度;
  • 1.5ms------------90度;
  • 2.0ms-----------135度;
  • 2.5ms-----------180度;
    在这里插入图片描述
    PWM信号与360舵机转速的关系:
  • 0.5ms----------------正向最大转速;
  • 1.5ms----------------速度为0;
  • 2.5ms----------------反向最大转速;
    这里使用360舵机实现。

开发环境

工程实现使用实时操作系统freertos架构,使用vscode配置开发环境并创建基础工程模板,具体配置方式见:https://zhuanlan.zhihu.com/p/1903777216376050145

配置成功的开发工程目录如下:
在这里插入图片描述

设计方案

主要分为3大部分,一是 wifi 连接管理任务,用来连接热点并检测连接状态,断开时重连;二是网络套接字任务,实时监听指定端口数据,在接收到指定数据时触发中断,唤醒任务三;三是舵机驱动任务,配置指定GPIO与PWM,当接收到唤醒时,输出PWM方波,驱动舵机旋转,实现开关。

你希望我基于你提供的这份 ESP32 代码,按照代码实现的方式来梳理并说明整个系统的设计思路,涵盖 WiFi 管理、UDP 通信和 PWM 舵机驱动三大核心模块。

一、整体设计架构

整个系统采用 FreeRTOS 多任务并发 设计模式,将不同功能解耦为独立任务,通过任务通知机制实现任务间通信,核心架构如下:

系统初始化

WiFi管理任务

UDP服务器任务

LED控制任务

PWM舵机驱动任务

GPIO中断任务

WiFi连接/重连

监听UDP端口

检测到run指令

触发PWM任务通知

触发LED任务通知

输出PWM波形驱动舵机

二、分模块设计思路详解

1. WiFi 连接管理模块

核心目标:实现稳定的 WiFi 连接,断连后自动重连,为 UDP 通信提供网络基础。

  • 初始化流程

    // 代码核心逻辑
    nvs_flash_init()esp_netif_init()esp_event_loop_create_default()wifi_init_config_t 配置 → esp_wifi_init() → 注册事件回调 → 设置WiFi参数 → esp_wifi_start()
    
    • 先初始化 NVS 闪存(存储 WiFi 配置),再初始化网络接口和事件循环;
    • 注册 WIFI_EVENTIP_EVENT 事件回调,通过 g_wifi_state 全局变量维护连接状态(断开/连接中/已连接);
    • 配置 WiFi 热点名称/密码,设置为 STA 模式并启动。
  • 保活机制

    • 创建 wifi_monitor_task 任务,每 5 秒检查一次 g_wifi_state
    • 若检测到断开状态(WIFI_DISCONNECTED),立即调用 esp_wifi_connect() 重连,确保网络可用性。
2. UDP 网络通信模块

核心目标:监听指定 UDP 端口,接收外部指令并触发舵机驱动逻辑。

  • 设计要点
    1. 懒加载模式:UDP 任务先等待 WiFi 连接成功(g_wifi_state == WIFI_CONNECTED)后,再创建套接字,避免无网络时的资源浪费;
    2. 非阻塞接收:使用 select() 函数设置 2 秒超时的非阻塞监听,既保证实时性,又避免任务卡死在 recvfrom() 阻塞中;
    3. 指令解析:接收到 UDP 数据后,通过 strncmp(rx_buffer, "run", 3) 检测指定指令,匹配成功后调用 pwm_isr_handler() 触发 PWM 任务通知;
    4. 鲁棒性处理:WiFi 断开时立即关闭套接字,等待重连后重新创建,避免无效的网络操作。
3. PWM 舵机驱动 + 任务通信模块

核心目标:通过 PWM 波形驱动舵机,通过 FreeRTOS 任务通知实现指令触发。

  • PWM 硬件初始化

    // 代码核心逻辑
    ledc_timer_config_t 配置定时器02kHz频率、10位占空比)→ ledc_timer_config()
    ledc_channel_config_t 配置通道0(GPIO2、绑定定时器0)→ ledc_channel_config()
    
    • 选择 LEDC 外设实现 PWM 输出(ESP32 原生支持,无需软件模拟);
    • 10 位占空比分辨率(0-1023),初始占空比 25(约0.5ms),适配舵机的基础驱动需求。
  • 任务通知机制(核心通信方式)

    1. 中断/指令触发端
      • GPIO 上升沿中断、UDP 指令触发均调用 xTaskNotifyFromISR()(或直接调用 ISR 函数),向目标任务发送通知;
      • 通知模式为 eSetValueWithOverwrite,覆盖式通知(保证最新指令生效,忽略旧通知)。
    2. PWM 任务接收端
      xTaskNotifyWait(0, 0xFFFFFFFF, &notify_value, portMAX_DELAY);
      
      • 阻塞等待通知(portMAX_DELAY 无限等待),接收到通知后执行 PWM 输出逻辑;
      • 输出逻辑:先以50Hz 频率、0.5ms高电平输出 1000ms → 停止 200ms → 切换为2.5ms高电平 输出 1000ms → 停止并恢复0.5ms高电平,完成一次舵机驱动。
  • LED 辅助模块

    • 作为 GPIO 中断的反馈,通过 led_task 接收 GPIO 中断通知,切换 LED 状态(亮/灭),直观展示中断触发效果;
    • 同样基于 xTaskNotifyWait 实现阻塞等待,保证仅在中断触发时执行状态切换。
4. 中断与任务优先级设计
  • 任务优先级分配(数值越大优先级越高):
    • PWM 舵机任务:7(最高,保证舵机驱动实时性);
    • UDP 服务器任务:6(次高,保证指令接收不丢失);
    • WiFi 监控/LED/周期任务:3(普通优先级,非核心任务);
  • 中断处理:GPIO 中断函数标记 IRAM_ATTR,放入 IRAM 中执行,避免缓存失效导致的延迟,保证中断响应速度。

三、关键细节设计

  1. 资源隔离:每个任务独立分配栈空间(如 UDP 任务 4096 字节,LED 任务 2048 字节),避免栈溢出;
  2. 状态同步:通过 volatile 修饰 g_wifi_state,保证多任务下的状态可见性;
  3. GPIO 配置:输入 GPIO4 配置为下拉模式 + 上升沿中断,避免误触发;输出 GPIO1(LED)、GPIO2(PWM)配置为推挽输出,无上下拉;
  4. 异常处理:所有 ESP32 API 调用均做错误检查(如 ESP_ERROR_CHECK),UDP 通信中对 socket()/bind()/recvfrom() 做返回值判断,保证系统稳定性。

四、代码

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "driver/ledc.h"

#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"

#include <stdatomic.h>

#include <string.h>

#include "lwip/sockets.h"
#include "lwip/inet.h"

#define TAG "GPIO_NOTIFY"

// 网络管理部分
static const char *WIFI_TAG = "WIFI";
typedef enum {
    WIFI_DISCONNECTED = 0,
    WIFI_CONNECTING,
    WIFI_CONNECTED
} wifi_state_t;

volatile wifi_state_t g_wifi_state = WIFI_DISCONNECTED;
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                               int32_t event_id, void *event_data)
{
    if (event_base == WIFI_EVENT) {

        if (event_id == WIFI_EVENT_STA_START) {
            ESP_LOGI(WIFI_TAG, "WiFi started");
            g_wifi_state = WIFI_CONNECTING;
            esp_wifi_connect();
        }

        else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
            ESP_LOGW(WIFI_TAG, "WiFi disconnected");
            g_wifi_state = WIFI_DISCONNECTED;
        }
    }

    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI(WIFI_TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
        g_wifi_state = WIFI_CONNECTED;
    }
}

void wifi_init_sta(void)
{
    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);

    esp_netif_init();
    esp_event_loop_create_default();
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);

    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
    esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL);

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "wifi_name",      // 替换为自己的热点名
            .password = "wifi_passwd",// 替换为自己的热点密码
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };

    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
    esp_wifi_start();
}

void wifi_monitor_task(void *arg)
{
    while (1) {
        if (g_wifi_state == WIFI_DISCONNECTED) {
            ESP_LOGW("WIFI_MON", "WiFi lost, reconnecting...");
            g_wifi_state = WIFI_CONNECTING;
            esp_wifi_connect();
        }

        vTaskDelay(pdMS_TO_TICKS(5000)); // 每5秒检查一次
    }
}

// GPIO 灯控部分
/* GPIO 定义 */
#define GPIO_INPUT      GPIO_NUM_4
#define GPIO_LED        GPIO_NUM_1

/* 任务句柄 */
static TaskHandle_t led_task_handle = NULL;
static TaskHandle_t pwm_task_handle = NULL;
/* ==================== LED 任务 ==================== */
static void led_task(void *arg)
{
    uint32_t notify_value;
    uint32_t level;

    /* 初始化 LED GPIO */
    gpio_config_t led_cfg = {
        .pin_bit_mask = 1ULL << GPIO_LED,
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    gpio_config(&led_cfg);

    level = 0;

    gpio_set_level(GPIO_LED, level);  // 初始熄灭

    ESP_LOGI(TAG, "LED task started, waiting for notification...");

    while (1)
    {
        level = 1 - level;       // 切换 LED 状态
        /* 阻塞等待 ISR 通知 */
        xTaskNotifyWait(
            0,                  // 不清除进入时的通知
            0xFFFFFFFF,         // 退出时清除所有通知
            &notify_value,      // 接收通知值
            portMAX_DELAY       // 一直等
        );

        ESP_LOGI(TAG, "Notification received, turn LED change");

        gpio_set_level(GPIO_LED, level);   // 点亮 LED
    }
}

/* ==================== GPIO 中断处理函数 ==================== */
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* 向 LED 任务发送通知 */
    xTaskNotifyFromISR(
        led_task_handle,            // 要通知的任务
        1,                          // 通知值(这里值本身无所谓)
        eSetValueWithOverwrite,     // 覆盖方式
        &xHigherPriorityTaskWoken
    );

    /* 如果唤醒了更高优先级任务,立刻切换 */
    if (xHigherPriorityTaskWoken)
    {
        portYIELD_FROM_ISR();
    }
}

static void IRAM_ATTR pwm_isr_handler(void *arg)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* 向 LED 任务发送通知 */
    xTaskNotifyFromISR(
        pwm_task_handle,            // 要通知的任务
        1,                          // 通知值(这里值本身无所谓)
        eSetValueWithOverwrite,     // 覆盖方式
        &xHigherPriorityTaskWoken
    );

    /* 如果唤醒了更高优先级任务,立刻切换 */
    if (xHigherPriorityTaskWoken)
    {
        portYIELD_FROM_ISR();
    }
}

// GPIO PWM 部分
void pwm_hw_init(void)
{
    // 不同频率,定时器初始化
    ledc_timer_config_t timer0 = {
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .timer_num = LEDC_TIMER_0,
        .freq_hz = 2000,        // 2 kHz
        .duty_resolution = LEDC_TIMER_10_BIT,
        .clk_cfg = LEDC_AUTO_CLK
    };
    ledc_timer_config(&timer0);

}

void pwm_ctrl_task(void *arg)
{
    // TickType_t lastWake = xTaskGetTickCount();
    ledc_channel_config_t ch0 = {
        .gpio_num = 2,
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .channel = LEDC_CHANNEL_0,
        .timer_sel = LEDC_TIMER_0,
        .duty = 25, // (25/1024)*20 = %
    };
    int flage = 0;
    uint32_t notify_value;

    while (1)
    {
        /* 阻塞等待 ISR 通知 */
        xTaskNotifyWait(
            0,                  // 不清除进入时的通知
            0xFFFFFFFF,         // 退出时清除所有通知
            &notify_value,      // 接收通知值
            portMAX_DELAY       // 一直等
        );
        // 开启 PWM
        ESP_LOGI("PWM","PWM ON");
        if(flage == 0)
        {
            ledc_channel_config(&ch0);
            flage = 1;
        }
        else
        {
            // ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, 2000);    // 修改频率为2kHz
            ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 25);
            ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
        }
        vTaskDelay(pdMS_TO_TICKS(5000));
        ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
        vTaskDelay(pdMS_TO_TICKS(200));
        // ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, 400);    // 修改频率为400Hz
        ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 128);  // 确保占空比不变
        ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);    // 生效占空比
        vTaskDelay(pdMS_TO_TICKS(5000));

        // 停止 PWM
        ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
        ESP_LOGI("PWM","PWM OFF");
        //vTaskDelay(pdMS_TO_TICKS(20000));
    }
}

// 周期性任务,模拟中断触发
void PeriodTask_isr(void *param)
{
    TickType_t lastWake = xTaskGetTickCount();

    while (1)
    {
        gpio_isr_handler(NULL);
        // 周期性执行
        vTaskDelayUntil(&lastWake, pdMS_TO_TICKS(10000));
    }
}

// 网络任务部分

#define UDP_PORT 3358
#define BUF_SIZE 512

void udp_server_task(void *arg)
{
    char rx_buffer[BUF_SIZE];
    struct sockaddr_in addr, source_addr;
    socklen_t socklen = sizeof(source_addr);

    while (1) 
    {

        // 等待 WiFi 连接
        while (g_wifi_state != WIFI_CONNECTED) {
            vTaskDelay(pdMS_TO_TICKS(1000));
        }

        int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
        if (sock < 0) {
            vTaskDelay(pdMS_TO_TICKS(1000));
            continue;
        }

        addr.sin_family = AF_INET;
        addr.sin_port = htons(UDP_PORT);
        addr.sin_addr.s_addr = htonl(INADDR_ANY);

        bind(sock, (struct sockaddr *)&addr, sizeof(addr));

        ESP_LOGI("UDP", "UDP server started");

        while (g_wifi_state == WIFI_CONNECTED) {

            fd_set readfds;
            FD_ZERO(&readfds);
            FD_SET(sock, &readfds);

            struct timeval tv;
            tv.tv_sec = 2;   // 2秒超时
            tv.tv_usec = 0;

            int ret = select(sock + 1, &readfds, NULL, NULL, &tv);

            if (ret < 0) {
                ESP_LOGE("UDP", "select error");
                break;
            } 
            else if (ret == 0) {
                // 超时
                ESP_LOGD("UDP", "select timeout");
                continue;
            }

            if (FD_ISSET(sock, &readfds)) {
                int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0,
                                   (struct sockaddr *)&source_addr, &socklen);
                if (len < 0) {
                    ESP_LOGW("UDP", "recv error");
                    break;
                }

                rx_buffer[len] = '\0';
                ESP_LOGI("UDP", "Data: %s", rx_buffer);
                // 比较 rx_buffer 与字符串 "run" 是否相等
                if (strncmp(rx_buffer, "run", 3) == 0) 
                {
                    ESP_LOGI("UDP", "ctrl successfully!!");
                    pwm_isr_handler(NULL);
                }
            }
        }

        close(sock);
        ESP_LOGW("UDP", "Socket closed, wait reconnect");
    }
}
/* ==================== 主函数 ==================== */
void app_main(void)
{
    /* 配置输入 GPIO */
    gpio_config_t input_cfg = {
        .pin_bit_mask = 1ULL << GPIO_INPUT,
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_ENABLE,   // 默认拉低
        .intr_type = GPIO_INTR_POSEDGE,         // 上升沿中断
    };
    gpio_config(&input_cfg);
    pwm_hw_init();
    wifi_init_sta();

    /* 创建 LED 任务 */
    xTaskCreate(
        led_task,
        "led_task",
        2048,
        NULL,
        3,
        &led_task_handle
    );

    /* 创建周期性任务, 周期性执行中断触发函数  */
    xTaskCreate(
        PeriodTask_isr,
        "PeriodTask_isr",
        2048,
        NULL,
        3,
        NULL
    );
    // 创建 PWM 控制任务
    xTaskCreate(
        pwm_ctrl_task,
        "pwm_ctrl_task",
        2048,
        NULL,
        7,
        &pwm_task_handle
    );

    /* 创建 WiFi 监控任务 */
    xTaskCreate(
        wifi_monitor_task, 
        "wifi_monitor", 
        4096, 
        NULL, 
        3, 
        NULL);

    /* 创建 UDP 服务器任务 */
    xTaskCreate(
        udp_server_task, 
        "udp_server_task", 
        4096, 
        NULL, 
        6, 
        NULL);

    /* 安装 GPIO ISR 服务 */
    gpio_install_isr_service(0);

    /* 挂载 GPIO 中断 */
    gpio_isr_handler_add(GPIO_INPUT, gpio_isr_handler, NULL);

    ESP_LOGI(TAG, "System initialized. Waiting for GPIO interrupt...");
}

五、结果

在这里插入图片描述

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐