基于 esp32-c3 supermini 的远程开关
舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。目前在高档遥控玩具,如航模,包括飞机模型,潜艇模型;遥控机器人中已经使用得比较普遍。舵机是一种俗称,其实是一种伺服马达。
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 多任务并发 设计模式,将不同功能解耦为独立任务,通过任务通知机制实现任务间通信,核心架构如下:
二、分模块设计思路详解
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_EVENT和IP_EVENT事件回调,通过g_wifi_state全局变量维护连接状态(断开/连接中/已连接); - 配置 WiFi 热点名称/密码,设置为 STA 模式并启动。
-
保活机制:
- 创建
wifi_monitor_task任务,每 5 秒检查一次g_wifi_state; - 若检测到断开状态(
WIFI_DISCONNECTED),立即调用esp_wifi_connect()重连,确保网络可用性。
- 创建
2. UDP 网络通信模块
核心目标:监听指定 UDP 端口,接收外部指令并触发舵机驱动逻辑。
- 设计要点:
- 懒加载模式:UDP 任务先等待 WiFi 连接成功(
g_wifi_state == WIFI_CONNECTED)后,再创建套接字,避免无网络时的资源浪费; - 非阻塞接收:使用
select()函数设置 2 秒超时的非阻塞监听,既保证实时性,又避免任务卡死在recvfrom()阻塞中; - 指令解析:接收到 UDP 数据后,通过
strncmp(rx_buffer, "run", 3)检测指定指令,匹配成功后调用pwm_isr_handler()触发 PWM 任务通知; - 鲁棒性处理:WiFi 断开时立即关闭套接字,等待重连后重新创建,避免无效的网络操作。
- 懒加载模式:UDP 任务先等待 WiFi 连接成功(
3. PWM 舵机驱动 + 任务通信模块
核心目标:通过 PWM 波形驱动舵机,通过 FreeRTOS 任务通知实现指令触发。
-
PWM 硬件初始化:
// 代码核心逻辑 ledc_timer_config_t 配置定时器0(2kHz频率、10位占空比)→ ledc_timer_config() ledc_channel_config_t 配置通道0(GPIO2、绑定定时器0)→ ledc_channel_config()- 选择 LEDC 外设实现 PWM 输出(ESP32 原生支持,无需软件模拟);
- 10 位占空比分辨率(0-1023),初始占空比 25(约0.5ms),适配舵机的基础驱动需求。
-
任务通知机制(核心通信方式):
- 中断/指令触发端:
- GPIO 上升沿中断、UDP 指令触发均调用
xTaskNotifyFromISR()(或直接调用 ISR 函数),向目标任务发送通知; - 通知模式为
eSetValueWithOverwrite,覆盖式通知(保证最新指令生效,忽略旧通知)。
- GPIO 上升沿中断、UDP 指令触发均调用
- PWM 任务接收端:
xTaskNotifyWait(0, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY);- 阻塞等待通知(
portMAX_DELAY无限等待),接收到通知后执行 PWM 输出逻辑; - 输出逻辑:先以50Hz 频率、0.5ms高电平输出 1000ms → 停止 200ms → 切换为2.5ms高电平 输出 1000ms → 停止并恢复0.5ms高电平,完成一次舵机驱动。
- 阻塞等待通知(
- 中断/指令触发端:
-
LED 辅助模块:
- 作为 GPIO 中断的反馈,通过
led_task接收 GPIO 中断通知,切换 LED 状态(亮/灭),直观展示中断触发效果; - 同样基于
xTaskNotifyWait实现阻塞等待,保证仅在中断触发时执行状态切换。
- 作为 GPIO 中断的反馈,通过
4. 中断与任务优先级设计
- 任务优先级分配(数值越大优先级越高):
- PWM 舵机任务:7(最高,保证舵机驱动实时性);
- UDP 服务器任务:6(次高,保证指令接收不丢失);
- WiFi 监控/LED/周期任务:3(普通优先级,非核心任务);
- 中断处理:GPIO 中断函数标记
IRAM_ATTR,放入 IRAM 中执行,避免缓存失效导致的延迟,保证中断响应速度。
三、关键细节设计
- 资源隔离:每个任务独立分配栈空间(如 UDP 任务 4096 字节,LED 任务 2048 字节),避免栈溢出;
- 状态同步:通过
volatile修饰g_wifi_state,保证多任务下的状态可见性; - GPIO 配置:输入 GPIO4 配置为下拉模式 + 上升沿中断,避免误触发;输出 GPIO1(LED)、GPIO2(PWM)配置为推挽输出,无上下拉;
- 异常处理:所有 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, // 退出时清除所有通知
¬ify_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, // 退出时清除所有通知
¬ify_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...");
}
五、结果

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

所有评论(0)