目录
1. 创建工程
在MobaXterm_Personal输入命令:
idf.py create-project led
进行创建工程:
进入到创建的工程,继续输入命令,编译工程文件:
idf.py build
2. 代码编辑
2.1 打开工程
在VS code打开刚刚创建的工程:
项目工程如下:
2.2 包含头文件
首先点击键盘:Shift+Ctrl+P,可以找到:
要是没找到直接对照输入搜索也可以,找到后点击,这样做的目的是包含头文件,否则之后写入的头文件会显示找不大路径。
该工程所要使用的头文件:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
2.3 创建任务
首先定义一下LED灯所在的GPIO口:
#define LED_GPIO GPIO_NUM_27
创建任务函数,大多都是现成的API接口直接调用就行:
void led_run_task(void* param)
{
int gpio_level = 0;
while(1)
{
gpio_level = gpio_level ? 0 : 1;
//API设置指定GPIO引脚的电平状态
gpio_set_level(LED_GPIO,gpio_level);
// 任务延迟500毫秒
// pdMS_TO_TICKS将毫秒转换为FreeRTOS的时钟节拍
vTaskDelay(pdMS_TO_TICKS(500));
}
}
2.4 GPIO口初始化
这里STM32是直接创建一个函数进行初始化操作,在这里是使用结构体进行初始化,然后再调用API接口,将初始化信息写入到底层代码当中,其中对于结构体成员:
gpio_config_t led_cfg = {
.intr_type = GPIO_INTR_DISABLE, // 禁用 GPIO 中断(LED 控制不需要中断)
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式(控制 LED 亮灭)
.pin_bit_mask = (1 << LED_GPIO), // 设置要配置的 GPIO 引脚(通过位掩码指定)
.pull_down_en = GPIO_PULLDOWN_DISABLE,// 禁用内部下拉电阻
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用内部上拉电阻
};
将配置好的结构体配置到硬件:
// 应用 GPIO 配置到硬件
// 参数:led_cfg 是已定义好的 GPIO 配置结构体(gpio_config_t 类型)
gpio_config(&led_cfg);
2.5 创建控制任务
函数原型:
BaseType_t xTaskCreatePinnedToCore(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称(字符串标识)
const uint32_t usStackDepth, // 任务堆栈大小(单位:字)
void * const pvParameters, // 任务参数(传递给任务函数)
UBaseType_t uxPriority, // 任务优先级(数值越大优先级越高)
TaskHandle_t * const pvCreatedTask, // 任务句柄(用于管理任务)
const BaseType_t xCoreID // 指定运行的核心(0 或 1,或 tskNO_AFFINITY)
);
部分参数注解:
usStackDepth
类型:uint32_t
作用:指定任务堆栈大小,单位是字(4字节)。
本例 2048 表示堆栈大小为 2048 * 4 = 8192 字节(8KB)。
注意:需根据任务复杂度调整,过小会导致堆栈溢出。
uxPriority
类型:UBaseType_t
作用:任务优先级(0 为最低,configMAX_PRIORITIES-1 为最高)。
本例 3 表示中等优先级(具体取决于系统配置)。
优先级规则:高优先级任务会抢占低优先级任务的 CPU 时间。
xCoreID
类型:BaseType_t
作用:指定任务运行的核心(仅限多核系统如 ESP32)。
0:核心 0(通常运行 WiFi/BT 协议栈)
1:核心 1(通常运行用户任务)
tskNO_AFFINITY(-1):由调度器自动分配
本例 1 表示强制任务运行在核心 1,避免干扰核心 0 的系统任务。
配置文件如下:
xTaskCreatePinnedToCore(
led_run_task, // 任务函数
"led", // 任务名称
2048, // 堆栈大小(8192 字节),8KB
NULL, // 无参数传递
3, // 优先级
NULL, // 不需要任务句柄
1 // 运行在核心 1
);
2.6 完整代码
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#define LED_GPIO GPIO_NUM_27
void led_run_task(void* param)
{
int gpio_level = 0;
while(1)
{
gpio_level = gpio_level ? 0 : 1;
gpio_set_level(LED_GPIO,gpio_level);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void app_main(void)
{
gpio_config_t led_cfg ={
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1 << LED_GPIO),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
};
gpio_config(&led_cfg);
xTaskCreatePinnedToCore(led_run_task,"led",2048,NULL,3,NULL,1);
}
3. 运行
保存代码,返回到MobaXterm_Personal输入编译命令:
idf.py build
输入烧录命令:
idf.py flash
可以看到led进行简单的闪烁。
4. PWM拓展
4.1 增加头文件
因为我们需要使用PWM的相关API,这些API函数存放在ledc文件中:
#include "driver/ledc.h"
4.2 初始化定时器
相关参数:
其结构体:
typedef struct {
ledc_mode_t speed_mode; // PWM 速度模式(高速/低速)
ledc_timer_bit_t duty_resolution; // 占空比分辨率(位数)
ledc_timer_t timer_num; // 定时器编号(0-3)
uint32_t freq_hz; // PWM 频率(Hz)
ledc_clk_cfg_t clk_cfg; // 时钟源配置(可选)
} ledc_timer_config_t;
speed_mode
类型:
ledc_mode_t
作用:选择 PWM 控制器的工作模式。
可选值:
LEDC_HIGH_SPEED_MODE
:高速模式(由 APB 时钟驱动,80MHz)
LEDC_LOW_SPEED_MODE
:低速模式(由 RTC 时钟驱动,8MHz)
duty_resolution
类型:
ledc_timer_bit_t
作用:设置占空比的分辨率(即 PWM 精度)。
可选值:
LEDC_TIMER_1_BIT
到LEDC_TIMER_20_BIT
(1~20 位)。计算方式:
若设为
LEDC_TIMER_10_BIT
,则占空比范围为0 ~ 1023
(2^10 - 1)。占空比实际值 =
(duty / 2^resolution) * 100%
。权衡:
分辨率越高,PWM 越精细,但频率上限会降低(因计数器位数增加)。
timer_num
类型:
ledc_timer_t
作用:选择硬件定时器编号(LEDC 模块共有 4 个定时器)。
可选值:
LEDC_TIMER_0
到LEDC_TIMER_3
。注意:
每个定时器可独立配置频率和分辨率,但需分配给不同的通道使用。
freq_hz
类型:
uint32_t
作用:设置 PWM 信号的频率(单位 Hz)。
取值范围:
受限于分辨率和时钟源。例如:
10 位分辨率 + 80MHz 时钟 → 最大频率约
78.125kHz
(80MHz / 2^10
)。典型 LED 调光频率:
1kHz ~ 5kHz
(避免人眼可见闪烁)。
clk_cfg
(可选)
类型:
ledc_clk_cfg_t
作用:选择时钟源(通常无需手动配置,默认为
LEDC_AUTO_CLK
)。可选值:
LEDC_USE_APB_CLK
:APB 时钟(高速模式默认)
LEDC_USE_RTC8M_CLK
:RTC 8MHz 时钟(低速模式默认)
LEDC_AUTO_CLK
:自动选择(推荐)。
编写代码:
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE, // 低速模式(RTC时钟驱动)
.duty_resolution = LEDC_TIMER_13_BIT, // 13位占空比分辨率(0~8191)
.timer_num = LEDC_TIMER_0, // 使用定时器0
.freq_hz = 5000, // PWM频率5kHz
.clk_cfg = LEDC_AUTO_CLK, // 自动选择时钟源
};
ledc_timer_config(&ledc_timer); // 应用配置
4.3 初始化ledc通道
结构体定义:
typedef struct {
int gpio_num; // GPIO 引脚号
ledc_mode_t speed_mode; // 速度模式(高速/低速)
ledc_channel_t channel; // PWM 通道号(0-7)
ledc_timer_sel_t timer_sel; // 绑定的定时器编号(0-3)
uint32_t duty; // 初始占空比(0 ~ 2^duty_resolution - 1)
int hpoint; // 相位偏移(通常设为0)
struct {
unsigned int output_invert: 1; // 是否输出电平反转
} flags; // 附加标志位
} ledc_channel_config_t;
代码编写:
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_LOW_SPEED_MODE, // 低速模式(RTC时钟驱动)
.channel = LEDC_CHANNEL_0, // 使用通道0
.timer_sel = LEDC_TIMER_0, // 绑定到定时器0
.gpio_num = LED_GPIO, // PWM输出引脚(需替换为具体GPIO号)
.duty = 0, // 初始占空比为0(LED默认熄灭)
.intr_type = LEDC_INTR_DISABLE, // 禁用中断
};
ledc_channel_config(&ledc_channel);
4.4 渐变服务配置
4.4.1 安装渐变服务
函数功能:
esp_err_t ledc_fade_func_install(int intr_alloc_flags);
作用:安装 LEDC 渐变(淡入淡出)功能所需的软件服务和中断。
调用时机:在需要使用 LEDC 渐变功能(如呼吸灯效果)之前调用,通常只需调用一次。
底层操作:
初始化内部数据结构。
注册中断服务程序(ISR),用于处理占空比自动渐变。
ledc_fade_func_install(0);//默认中断优先级
4.4.2 配置渐变参数
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 8191, 2000);
LEDC_LOW_SPEED_MODE
:低速模式(需与定时器配置一致)。
LEDC_CHANNEL_0
:操作通道 0。
8191
:目标占空比(由于之前定时器为 13 位分辨率,范围 0~8191)。
2000
:渐变时间(2000ms,即 2 秒)。
4.4.3 启动渐变
ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT);
LEDC_LOW_SPEED_MODE
:低速模式(需与定时器配置一致)。
LEDC_CHANNEL_0
:操作通道 0。
LEDC_FADE_NO_WAIT
:非阻塞模式,函数立即返回,渐变在后台运行。注意:若需等待渐变完成,可使用
LEDC_FADE_WAIT_DONE
,但会阻塞当前任务。
4.4.4 回调函数
事件标志位定义:
#define FULL_EV_BIT BIT0 // 满占空比事件标志(LED亮度100%)
#define EMPTY_EV_BIT BIT1 // 零占空比事件标志(LED亮度0%)
事件组句柄:
static EventGroupHandle_t ledc_event_handle;
回调函数:
bool IRAM_ATTR ledc_finish_cb(const ledc_cb_param_t *param, void *user_arg) {
BaseType_t taskWoken = pdFALSE;
if(param->duty) {
xEventGroupSetBitsFromISR(ledc_event_handle, FULL_EV_BIT, &taskWoken);
} else {
xEventGroupSetBitsFromISR(ledc_event_handle, EMPTY_EV_BIT, &taskWoken);
}
return taskWoken;
}
IRAM_ATTR
:强制将函数放入IRAM(避免Flash访问延迟,确保中断实时性)。
param->duty
:判断当前占空比是否非零(duty=0
表示LED完全熄灭)。
xEventGroupSetBitsFromISR
:在中断中安全设置事件标志,taskWoken
用于触发上下文切换。
4.4.5 回调
ledc_cbs_t cbs = {
.fade_cb = ledc_finish_cb, // 已定义回调函数
};
ledc_cb_register(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, &cbs, NULL);
4.4.6 任务函数
void led_run_task(void* param)
{
EventBits_t ev;
while(1)
{
ev = xEventGroupWaitBits(ledc_event_handle, FULL_EV_BIT|EMPTY_EV_BIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(5000));
if(ev & FULL_EV_BIT)
{
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,0,2000);
ledc_fade_start(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,LEDC_FADE_NO_WAIT);
}
if(ev & EMPTY_EV_BIT)
{
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,8191,2000);
ledc_fade_start(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,LEDC_FADE_NO_WAIT);
}
ledc_cbs_t cbs =
{
.fade_cb = ledc_finish_cb,
};
ledc_cb_register(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,&cbs,NULL);
}
}
4.4.7 完整代码
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#define LED_GPIO GPIO_NUM_27
#define FULL_EV_BIT BIT0
#define EMPTY_EV_BIT BIT1
static EventGroupHandle_t ledc_event_handle;
bool IRAM_ATTR ledc_finish_cb(const ledc_cb_param_t *param, void *user_arg)
{
BaseType_t taskWoken = pdFALSE;
if(param -> duty)
{
xEventGroupSetBitsFromISR(ledc_event_handle,FULL_EV_BIT,&taskWoken);
}
else
{
xEventGroupSetBitsFromISR(ledc_event_handle,EMPTY_EV_BIT,&taskWoken);
}
return taskWoken;
}
void led_run_task(void* param)
{
EventBits_t ev;
while(1)
{
ev = xEventGroupWaitBits(ledc_event_handle, FULL_EV_BIT|EMPTY_EV_BIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(5000));
if(ev & FULL_EV_BIT)
{
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,0,2000);
ledc_fade_start(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,LEDC_FADE_NO_WAIT);
}
if(ev & EMPTY_EV_BIT)
{
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,8191,2000);
ledc_fade_start(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,LEDC_FADE_NO_WAIT);
}
ledc_cbs_t cbs =
{
.fade_cb = ledc_finish_cb,
};
ledc_cb_register(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,&cbs,NULL);
}
}
void app_main(void)
{
gpio_config_t led_cfg ={
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1 << LED_GPIO),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
};
gpio_config(&led_cfg);
//安装渐变服务
ledc_fade_func_install(0);
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_13_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK,
};
ledc_timer_config(&ledc_timer);
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.gpio_num = LED_GPIO,
.duty = 0,
.intr_type = LEDC_INTR_DISABLE,
};
ledc_channel_config(&ledc_channel);
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,8191,2000);
ledc_fade_start(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,LEDC_FADE_NO_WAIT);
ledc_cbs_t cbs =
{
.fade_cb = ledc_finish_cb,
};
ledc_cb_register(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,&cbs,NULL);
xTaskCreatePinnedToCore(led_run_task,"led",2048,NULL,3,NULL,1);
}
ps:上边代码有一些错误,以下是订正版本:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#define LED_GPIO GPIO_NUM_27
#define FULL_EV_BIT BIT0
#define EMPTY_EV_BIT BIT1
static EventGroupHandle_t ledc_event_handle;
bool IRAM_ATTR ledc_finish_cb(const ledc_cb_param_t *param, void *user_arg)
{
BaseType_t taskWoken = pdFALSE;
if(param->duty > 0) {
xEventGroupSetBitsFromISR(ledc_event_handle, FULL_EV_BIT, &taskWoken);
} else {
xEventGroupSetBitsFromISR(ledc_event_handle, EMPTY_EV_BIT, &taskWoken);
}
return taskWoken;
}
void led_run_task(void* param)
{
EventBits_t ev;
while(1) {
ev = xEventGroupWaitBits(ledc_event_handle,
FULL_EV_BIT | EMPTY_EV_BIT,
pdTRUE, // 自动清除标志位
pdFALSE, // 不需要所有位
portMAX_DELAY); // 无限等待
if(ev & FULL_EV_BIT) {
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0, 2000);
ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT);
}
else if(ev & EMPTY_EV_BIT) {
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 8191, 2000);
ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT);
}
}
}
void app_main(void)
{
// 初始化事件组
ledc_event_handle = xEventGroupCreate();
assert(ledc_event_handle != NULL);
// GPIO配置
gpio_config_t led_cfg = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1 << LED_GPIO),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
};
gpio_config(&led_cfg);
// 安装LEDC渐变服务
ESP_ERROR_CHECK(ledc_fade_func_install(0));
// 定时器配置
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_13_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK,
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// 通道配置(修正:移除不存在的intr_type字段)
ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.gpio_num = LED_GPIO,
.duty = 0,
.hpoint = 0, // 必须添加
.flags = {
.output_invert = 0 // 明确设置
}
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
// 注册回调(只需一次)
ledc_cbs_t cbs = {
.fade_cb = ledc_finish_cb,
};
ESP_ERROR_CHECK(ledc_cb_register(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, &cbs, NULL));
// 启动初始渐变
ESP_ERROR_CHECK(ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 8191, 2000));
ESP_ERROR_CHECK(ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT));
// 创建任务
xTaskCreatePinnedToCore(led_run_task, "led", 2048, NULL, 3, NULL, 1);
}