定时器与 PWM 输出详解

📅 2026/7/1 7:06:36
定时器与 PWM 输出详解
引言上一章我们学习了 GPIO 与中断机制让 ESP32-S3 能够感知外部事件并响应。但在实际嵌入式项目中我们常常需要精确的时间控制——比如每隔 100ms 采集一次传感器数据或者输出特定频率的方波驱动舵机。这些需求都离不开定时器和PWM这两个核心外设。本章将深入讲解 ESP32-S3 的硬件定时器TIMG和 LEDC 控制器PWM并结合 LED 呼吸灯和舵机控制两个实战案例让你彻底掌握定时与调光的编程技巧。一、硬件定时器基础1.1 什么是硬件定时器硬件定时器是 MCU 内部的一个独立硬件模块它依靠自己的时钟源进行计数不占用 CPU 资源。当计数值达到预设的阈值时可以触发中断或执行特定操作。ESP32-S3 内部集成了4 个 64 位通用定时器Timer Group 0 和 Timer Group 1每组含 2 个定时器每个定时器都可独立配置。1.2 定时器的核心参数参数说明典型值分频系数divider对 APB 时钟分频降低计数频率2~65536计数方向向上计数或向下计数向上计数自动重载auto-reload计满后是否自动重新开始使能报警值alarm value触发中断的目标计数值依需求设定ESP32-S3 的定时器时钟源为APB 时钟通常为 80 MHz经过分频后得到计数时钟。例如 80 MHz 除以 80 得到 1 MHz即每微秒计数一次。二、定时器编程实战2.1 定时器基本配置使用 ESP-IDF 的timer_group驱动库来配置定时器步骤如下#includeesp_timer.h#includedriver/gptimer.h// 定时器回调函数staticbool IRAM_ATTRtimer_callback(gptimer_handle_ttimer,constgptimer_alarm_event_data_t*edata,void*user_data){// 定时器中断中做轻量级处理// 实际业务通过信号量通知任务层BaseType_t high_task_awakepdFALSE;SemaphoreHandle_t sem(SemaphoreHandle_t)user_data;xSemaphoreGiveFromISR(sem,high_task_awake);return(high_task_awakepdTRUE);}voidtimer_example_init(void){gptimer_handle_tgptimerNULL;// 1. 配置定时器参数gptimer_config_ttimer_config{.clk_srcGPTIMER_CLK_SRC_DEFAULT,// 默认时钟源.directionGPTIMER_COUNT_UP,// 向上计数.resolution_hz1*1000*1000,// 分辨率1 MHz1 µs/步};gptimer_new_timer(timer_config,gptimer);// 2. 配置报警gptimer_alarm_config_talarm_config{.alarm_count1000000,// 1,000,000 次计数 1 秒.reload_count0,// 重载值自动重载时归零.flags.auto_reload_on_alarmtrue,// 开启自动重载};gptimer_set_alarm_action(gptimer,alarm_config);// 3. 注册回调函数SemaphoreHandle_t semxSemaphoreCreateBinary();gptimer_event_callbacks_tcbs{.on_alarmtimer_callback,};gptimer_register_event_callbacks(gptimer,cbs,sem);// 4. 使能并启动定时器gptimer_enable(gptimer);gptimer_start(gptimer);}2.2 软定时器esp_timer除了硬件定时器ESP-IDF 还提供了基于系统时钟的软定时器APIesp_timer用法更简单精度为微秒级适合大多数非实时苛刻场景#includeesp_timer.hvoidperiodic_timer_callback(void*arg){// 此回调运行在任务上下文中可以调用 printfprintf(1 秒时间到\n);}voidapp_main(void){constesp_timer_create_args_ttimer_args{.callbackperiodic_timer_callback,.nameperiodic_1s};esp_timer_handle_ttimer;esp_timer_create(timer_args,timer);esp_timer_start_periodic(timer,1000000);// 每秒触发一次// 也可以启动单次定时器// esp_timer_start_once(timer, 5000000); // 5 秒后触发一次}何时用硬件定时器何时用软定时器场景推荐高精度时序控制PWM、步进电机硬件定时器中断中做精确延迟硬件定时器周期性任务数据采集、状态轮询软定时器超时管理、延迟调度软定时器三、LEDC 控制器ESP32-S3 的 PWM 外设3.1 什么是 PWMPWMPulse Width Modulation脉宽调制通过调节方波的占空比来模拟模拟量输出。占空比越高等效电压越高。高电平时间 ──┐ │ ┌────────┐ ┌────────┐ │ │ │ │ │ └────┘ └────┘ └──── ← 周期 T → 占空比 高电平时间 / T占空比LED 亮度舵机角度0%熄灭0°50%半亮90°100%最亮180°3.2 LEDC 控制器架构ESP32-S3 的LEDCLED Controller是一个专为 PWM 输出设计的硬件控制器具备以下特点6 个高速通道 6 个低速通道共 12 个独立 PWM 通道8/10/12/13/14/15/16/20 位分辨率灵活选择自动时钟管理低速通道可在睡眠模式下工作硬件渐变fade无需 CPU 干预即可平滑改变占空比3.3 配置步骤与核心 API#includedriver/ledc.h#defineLEDC_TIMERLEDC_TIMER_0#defineLEDC_MODELEDC_LOW_SPEED_MODE#defineLEDC_CHANNELLEDC_CHANNEL_0#defineLEDC_GPIOGPIO_NUM_48// 板载 LED 引脚#defineLEDC_RESOLUTIONLEDC_TIMER_13_BIT// 13 位分辨率0~8191#defineLEDC_FREQ_HZ5000// 5 kHz PWM 频率voidpwm_init(void){// 1. 配置定时器模式ledc_timer_config_ttimer_conf{.speed_modeLEDC_MODE,.timer_numLEDC_TIMER,.duty_resolutionLEDC_RESOLUTION,.freq_hzLEDC_FREQ_HZ,.clk_cfgLEDC_AUTO_CLK,};ledc_timer_config(timer_conf);// 2. 配置通道ledc_channel_config_tchannel_conf{.gpio_numLEDC_GPIO,.speed_modeLEDC_MODE,.channelLEDC_CHANNEL,.intr_typeLEDC_INTR_DISABLE,.timer_selLEDC_TIMER,.duty0,// 初始占空比 0.hpoint0,};ledc_channel_config(channel_conf);}// 设置占空比立即生效voidset_pwm_duty(uint32_tduty){ledc_set_duty(LEDC_MODE,LEDC_CHANNEL,duty);ledc_update_duty(LEDC_MODE,LEDC_CHANNEL);}3.4 硬件渐变平滑调光LEDC 最强大的特性之一是硬件自动渐变无需 CPU 逐级调节// 配置并启动渐变voidfade_to_brightness(uint32_ttarget_duty,intfade_ms){ledc_set_fade_with_time(LEDC_MODE,LEDC_CHANNEL,target_duty,fade_ms);ledc_fade_start(LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT);}渐变期间 CPU 可以处理其他任务渐变由硬件独立完成。这对于呼吸灯、舞台灯光效果等场景非常实用。四、实战案例案例一呼吸灯效果将一个 LED 在亮和灭之间平滑渐变营造呼吸效果#includestdio.h#includefreertos/FreeRTOS.h#includefreertos/task.h#includedriver/ledc.h#defineLEDC_GPIOGPIO_NUM_48#defineLEDC_TIMERLEDC_TIMER_0#defineLEDC_MODELEDC_LOW_SPEED_MODE#defineLEDC_CHANNELLEDC_CHANNEL_0#defineLEDC_RESOLUTIONLEDC_TIMER_13_BIT// 0~8191#defineLEDC_FREQ_HZ5000#defineMAX_DUTY8191voidapp_main(void){// 配置 LEDCledc_timer_config_ttimer_conf{.speed_modeLEDC_MODE,.timer_numLEDC_TIMER,.duty_resolutionLEDC_RESOLUTION,.freq_hzLEDC_FREQ_HZ,.clk_cfgLEDC_AUTO_CLK,};ledc_timer_config(timer_conf);ledc_channel_config_tchan_conf{.gpio_numLEDC_GPIO,.speed_modeLEDC_MODE,.channelLEDC_CHANNEL,.intr_typeLEDC_INTR_DISABLE,.timer_selLEDC_TIMER,.duty0,.hpoint0,};ledc_channel_config(chan_conf);printf(Breathing LED started!\n);while(1){// 从暗到亮渐变 1.5 秒ledc_set_fade_with_time(LEDC_MODE,LEDC_CHANNEL,MAX_DUTY,1500);ledc_fade_start(LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT);vTaskDelay(2000/portTICK_PERIOD_MS);// 从亮到暗渐变 1.5 秒ledc_set_fade_with_time(LEDC_MODE,LEDC_CHANNEL,0,1500);ledc_fade_start(LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT);vTaskDelay(2000/portTICK_PERIOD_MS);}}案例二舵机控制舵机是机器人项目的核心执行部件通过 PWM 控制旋转角度。标准舵机的控制信号如下脉宽高电平时间对应角度1.0 ms0°1.5 ms90°中位2.0 ms180°舵机对 PWM 频率有严格要求——通常为50 Hz周期 20 ms。#includestdio.h#includefreertos/FreeRTOS.h#includefreertos/task.h#includedriver/ledc.h#defineSERVO_GPIOGPIO_NUM_4#defineSERVO_TIMERLEDC_TIMER_0#defineSERVO_MODELEDC_LOW_SPEED_MODE#defineSERVO_CHANNELLEDC_CHANNEL_0#defineSERVO_RESOLUTIONLEDC_TIMER_14_BIT// 14 位0~16383#defineSERVO_FREQ_HZ50// 50 Hz// 脉宽换算50 Hz 对应周期 20 ms// 14 位分辨率16383 → 20 ms// 1 ms 16383 * 1 / 20 ≈ 819// 1.5 ms 12292.0 ms 1638#definePULSE_0DEG819// 1.0 ms → 0°#definePULSE_90DEG1229// 1.5 ms → 90°#definePULSE_180DEG1638// 2.0 ms → 180°voidservo_set_angle(uint8_tangle){// 将角度0~180线性映射到脉宽值uint32_tpulsePULSE_0DEG(uint32_t)(PULSE_180DEG-PULSE_0DEG)*angle/180;ledc_set_duty(SERVO_MODE,SERVO_CHANNEL,pulse);ledc_update_duty(SERVO_MODE,SERVO_CHANNEL);}voidapp_main(void){// 配置 LEDC 定时器ledc_timer_config_ttimer_conf{.speed_modeSERVO_MODE,.timer_numSERVO_TIMER,.duty_resolutionSERVO_RESOLUTION,.freq_hzSERVO_FREQ_HZ,.clk_cfgLEDC_AUTO_CLK,};ledc_timer_config(timer_conf);// 配置舵机通道ledc_channel_config_tchan_conf{.gpio_numSERVO_GPIO,.speed_modeSERVO_MODE,.channelSERVO_CHANNEL,.intr_typeLEDC_INTR_DISABLE,.timer_selSERVO_TIMER,.duty0,.hpoint0,};ledc_channel_config(chan_conf);printf(Servo control started!\n);while(1){// 0° → 90° → 180° → 90° → 0° 往复运动servo_set_angle(0);vTaskDelay(1000/portTICK_PERIOD_MS);servo_set_angle(90);vTaskDelay(1000/portTICK_PERIOD_MS);servo_set_angle(180);vTaskDelay(1000/portTICK_PERIOD_MS);servo_set_angle(90);vTaskDelay(1000/portTICK_PERIOD_MS);}}注意舵机功耗较大不要直接从 ESP32-S3 的 3.3V 引脚供电应使用外部 5V 电源且信号地GND与 ESP32-S3 共地。五、总结本章我们学习了 ESP32-S3 上与时间和波形相关的两个核心模块硬件定时器GPTimer高精度计数适合精确时序控制和周期性中断软定时器esp_timer使用简单适合周期性任务调度LEDC 控制器灵活的 PWM 输出支持 12 个独立通道和硬件渐变实战案例呼吸灯展示了硬件渐变的优雅应用舵机控制则展示了 PWM 在机器人领域的核心地位掌握定时器和 PWM 后你已具备开发定时采集、灯光控制、电机驱动等常见嵌入式功能的基础能力。下篇预告第4章UART 串口通信—— 串口是嵌入式开发中最重要的调试工具和通信接口我们将学习 ESP32-S3 的 UART 驱动、printf 重定向和串口协议解析。本文基于 ESP-IDF v5.x 编写PWM 引脚号请根据实际开发板调整。舵机供电需外接电源切勿直接使用开发板 3.3V 引脚驱动。