Espressif 玩转 PWM

本文详细探讨了ESP32-C3中PWM输出频率的设定问题,包括其与定时器时钟源、时钟分频系数和计数器计数范围的关系。作者通过公式解析和代码示例说明了如何设置这些参数以达到期望的10KHz频率和50%占空比,同时指出在实际操作中可能遇到的错误及解决办法,强调了计数器计数范围的限制对满足频率公式的必要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文只针对 ESP32-C3

Espressif 素来以完善的文档著称,但在使用 PWM 用来控灯时却产生了诸多疑问,翻遍 datasheetESP-IDF Programming Guide 也没有找到想要的答案,无奈只能自己手撸一下代码。

PWM 输出频率与什么因素有关?

datasheet 可知,PWM 的输出频率满足以下公式:
Formula
从上述公式可以知道,PWM 输出频率只跟三个因素有关:

  1. 定时器的时钟源 LEDC_CLKx
  2. 时钟分频系数 LEDC_CLK_DIVx
  3. 计数器的计数范围 LEDC_TIMERx_DUTY_RES

我们在来看下 LEDC 的整体框图:
Block Diagram
整体的逻辑就很好理解了,PWM 通过定时器 Timer 来实现。Timer 的时钟源(即 LEDC_CLKx)可以来自 APB_CLKFOSC_CLK 或者 XTAL_CLK 。经过时钟分频器(最大为 18 bits)之后产生 ref_pulsex 信号供计数器(最大为 14 bits)使用。

PWM 输出频率如何设置?

我们现在知道了 PWM 的输出频率是受以下三个因素的影响:

  1. 定时器的时钟源 LEDC_CLKx
  2. 时钟分频系数 LEDC_CLK_DIVx
  3. 计数器的计数范围 LEDC_TIMERx_DUTY_RES

那我们应该如何来设置这三个因素来实现我们想要输出的频率呢?其实 Espressif 能让我们设置的仅仅只有 LEDC_CLKxLEDC_TIMERx_DUTY_RES 这两个参数,至于 LEDC_CLK_DIVx 参数通过公式来判断是否满足要求即可。一般来说,LEDC_CLKx 设置为 APB_CLK 即可,在 ESP32-C3 上为 80 MHz

这里就以 IDF v4.4 为例,假设我们要输出的频率为 10 KHz,占空比为 50%

我在代码里设置时钟源为 APB_CLK,计数器的计数范围为 14 bit,占空比为 8191 (((2 ** 14) - 1) * 50% = 8191),输出频率为 10 KHz。

#define LEDC_TIMER              LEDC_TIMER_0
#define LEDC_MODE               LEDC_LOW_SPEED_MODE
#define LEDC_OUTPUT_IO          (5) // Define the output GPIO
#define LEDC_CHANNEL            LEDC_CHANNEL_0
#define LEDC_DUTY_RES           LEDC_TIMER_14_BIT
#define LEDC_DUTY               (8191)
#define LEDC_FREQUENCY          (10000) // Frequency in Hertz. Set frequency at 10 kHz

static void example_ledc_init(void)
{
    // Prepare and then apply the LEDC PWM timer configuration
    ledc_timer_config_t ledc_timer = {
        .speed_mode       = LEDC_MODE,
        .timer_num        = LEDC_TIMER,
        .duty_resolution  = LEDC_DUTY_RES,
        .freq_hz          = LEDC_FREQUENCY,  // Set output frequency at 10 kHz
        .clk_cfg          = LEDC_USE_APB_CLK
    };
    ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

    // Prepare and then apply the LEDC PWM channel configuration
    ledc_channel_config_t ledc_channel = {
        .speed_mode     = LEDC_MODE,
        .channel        = LEDC_CHANNEL,
        .timer_sel      = LEDC_TIMER,
        .intr_type      = LEDC_INTR_DISABLE,
        .gpio_num       = LEDC_OUTPUT_IO,
        .duty           = LEDC_DUTY, // Set duty to 50%
        .hpoint         = 0
    };
    ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}

运行的时候肯定会报以下错误:

E (269) ledc: requested frequency and duty resolution can not be achieved, try reducing freq_hz or duty_resolution. div_param=125

其实这里就是我不明白的地方,明明设置的都对,但为什么却给报这种错误呢?其实这里的真正原因就是没有满足 PWM 的输出频率公式的要求

我们在看回看一下 PWM 的输出频率公式,在这个公式中,其实我们已经确定了几个变量:

  1. fPWM = 10000
  2. fLEDC_CLKx = 80000000
  3. 2LEDC_TIMERx_DUTY_RES = 214 = 16384

LEDC_CLK_DIVx 即为 0。显然不满足公式的要求。在 IDF 提供的 LEDC 源文件 ledc.c 中对时钟分频系数 LEDC_CLK_DIVx 做了检查:

// Setting the LEDC timer divisor with the given source clock, frequency and resolution.
static esp_err_t ledc_set_timer_div(ledc_mode_t speed_mode, ledc_timer_t timer_num, ledc_clk_cfg_t clk_cfg, int freq_hz, int duty_resolution)
{
    uint32_t div_param = 0;
    uint32_t precision = ( 0x1 << duty_resolution );
    ledc_clk_src_t timer_clk_src = LEDC_APB_CLK;
    // Calculate the divisor
    // User specified source clock(RTC8M_CLK) for low speed channel
    if ((speed_mode == LEDC_LOW_SPEED_MODE) && (clk_cfg == LEDC_USE_RTC8M_CLK)) {
        if(s_ledc_slow_clk_8M == 0) {
            if (ledc_slow_clk_calibrate() == false) {
                goto error;
            }
        }
        div_param = ( (uint64_t) s_ledc_slow_clk_8M << 8 ) / freq_hz / precision;
    } else {
        // Automatically select APB or REF_TICK as the source clock.
        if (clk_cfg == LEDC_AUTO_CLK) {
            // Try calculating divisor based on LEDC_APB_CLK
            div_param = ( (uint64_t) LEDC_APB_CLK_HZ << 8 ) / freq_hz / precision;
            if (div_param > LEDC_TIMER_DIV_NUM_MAX) {
                // APB_CLK results in divisor which too high. Try using REF_TICK as clock source.
                timer_clk_src = LEDC_REF_TICK;
                div_param = ((uint64_t) LEDC_REF_CLK_HZ << 8) / freq_hz / precision;
            } else if (div_param < 256) {
                // divisor is too low
                goto error;
            }
        // User specified source clock(LEDC_APB_CLK_HZ or LEDC_REF_TICK)
        } else {
            timer_clk_src = (clk_cfg == LEDC_USE_REF_TICK) ? LEDC_REF_TICK : LEDC_APB_CLK;
            uint32_t src_clk_freq = ledc_get_src_clk_freq(clk_cfg);
            div_param = ( (uint64_t) src_clk_freq << 8 ) / freq_hz / precision;
        }
    }
    if (div_param < 256 || div_param > LEDC_TIMER_DIV_NUM_MAX) {
        goto error;
    }
    if (speed_mode == LEDC_LOW_SPEED_MODE) {
        portENTER_CRITICAL(&ledc_spinlock);
        ledc_hal_set_slow_clk(&(p_ledc_obj[speed_mode]->ledc_hal), clk_cfg);
        portEXIT_CRITICAL(&ledc_spinlock);
    }
    //Set the divisor
    ledc_timer_set(speed_mode, timer_num, div_param, duty_resolution, timer_clk_src);
    // reset the timer
    ledc_timer_rst(speed_mode, timer_num);
    return ESP_OK;
error:
    ESP_LOGE(LEDC_TAG, "requested frequency and duty resolution can not be achieved, try reducing freq_hz or duty_resolution. div_param=%d",
        (uint32_t ) div_param);
    return ESP_FAIL;
}

所以,要满足输出 10 KHz 频率,占空比为 50% 的 PWM 波,只能改变计数器的计数范围 LEDC_TIMERx_DUTY_RES。以时钟分频系数 LEDC_CLK_DIVx 为 1 来计算:

  1. fPWM = 10000
  2. fLEDC_CLKx = 80000000
  3. LEDC_CLK_DIVx = 1

则 2LEDC_TIMERx_DUTY_RES 要不大于 8000,则计数器的计数范围 LEDC_TIMERx_DUTY_RES 最大只能取到 12 bits。

#define LEDC_TIMER              LEDC_TIMER_0
#define LEDC_MODE               LEDC_LOW_SPEED_MODE
#define LEDC_OUTPUT_IO          (5) // Define the output GPIO
#define LEDC_CHANNEL            LEDC_CHANNEL_0
#define LEDC_DUTY_RES           LEDC_TIMER_12_BIT
#define LEDC_DUTY               (2047)
#define LEDC_FREQUENCY          (10000) // Frequency in Hertz. Set frequency at 10 kHz

static void example_ledc_init(void)
{
    // Prepare and then apply the LEDC PWM timer configuration
    ledc_timer_config_t ledc_timer = {
        .speed_mode       = LEDC_MODE,
        .timer_num        = LEDC_TIMER,
        .duty_resolution  = LEDC_DUTY_RES,
        .freq_hz          = LEDC_FREQUENCY,  // Set output frequency at 10 kHz
        .clk_cfg          = LEDC_USE_APB_CLK
    };
    ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

    // Prepare and then apply the LEDC PWM channel configuration
    ledc_channel_config_t ledc_channel = {
        .speed_mode     = LEDC_MODE,
        .channel        = LEDC_CHANNEL,
        .timer_sel      = LEDC_TIMER,
        .intr_type      = LEDC_INTR_DISABLE,
        .gpio_num       = LEDC_OUTPUT_IO,
        .duty           = LEDC_DUTY, // Set duty to 50%
        .hpoint         = 0
    };
    ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式工程狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值