C bare metal LEDC pwm module timer on ESP32

vattav
Posts: 1
Joined: Tue May 27, 2025 1:25 am

C bare metal LEDC pwm module timer on ESP32

Postby vattav » Tue May 27, 2025 1:33 am

I'm new to C and ESP32 too. I currently write functions that generate PWM pulse. It's DOIT ESP32 DEVKIT V1 and the reference manual i'm using here https://www.espressif.com/sites/default ... ual_en.pdf. I got stuck here: No PWM pulse out. I've tried to figure it out but it can't. My goal to generate PWM with these parameters and set its duty cycle.
Also, I'm working with IO platform extension on VScode, in which i build it and flash code to ESP32 Sorry if something's wrong. I'm new and my English is bad
Just thankful to any help

My current code here

Code: Select all

#include "pwm.h"

// ===== Peripheral control register definitions =====
#define DPORT_PERIP_CLK_EN_REG       (*(volatile uint32_t*)0x3FF000C0)     // Peripheral clock enable
#define GPIO_ENABLE_W1TS_REG         (*(volatile uint32_t*)0x3FF44024)     // Enable GPIO output for GPIO < 32
#define GPIO_ENABLE1_REG             (*(volatile uint32_t*)0x3FF4402C)     // Enable GPIO output for GPIO >= 32
#define GPIO_FUNC_OUT_SEL_CFG_REG(n) (*(volatile uint32_t*)(0x3FF44530 + (n) * 0x4))   // GPIO output signal routing

// ===== IO_MUX register addresses for GPIO pins =====
static const uint32_t gpio_io_mux_addr[] = {
    [0]  = 0x3FF49044, [1]  = 0x3FF49088, [2]  = 0x3FF49040, [3]  = 0x3FF49084,
    [4]  = 0x3FF49048, [5]  = 0x3FF4906C, [12] = 0x3FF49034, [13] = 0x3FF49038,
    [14] = 0x3FF49030, [15] = 0x3FF4903C, [16] = 0x3FF4904C, [17] = 0x3FF49050,
    [18] = 0x3FF49070, [19] = 0x3FF49074, [21] = 0x3FF4907C, [22] = 0x3FF49080,
    [23] = 0x3FF4908C, [25] = 0x3FF49024, [26] = 0x3FF49028, [27] = 0x3FF4902C,
    [32] = 0x3FF4901C, [33] = 0x3FF49020, [34] = 0x3FF49014, [35] = 0x3FF49018,
    [36] = 0x3FF49004, [37] = 0x3FF49008, [38] = 0x3FF4900C, [39] = 0x3FF49010,
};

// ===== LEDC timer and channel base register definitions =====
#define LEDC_HSTIMER_CONF_REG(n) (*(volatile uint32_t*)(0x3FF59140 + (n) * 0x8))      // High-speed timer configuration
#define LEDC_HSCH_CONF0_REG(n)   (*(volatile uint32_t*)(0x3FF59000 + (n) * 0x14))     // Channel configuration 0
#define LEDC_HSCH_HPOINT_REG(n)  (*(volatile uint32_t*)(0x3FF59004 + (n) * 0x14))     // High point
#define LEDC_HSCH_DUTY_REG(n)    (*(volatile uint32_t*)(0x3FF59008 + (n) * 0x14))     // Duty cycle
#define LEDC_HSCH_CONF1_REG(n)   (*(volatile uint32_t*)(0x3FF5900C + (n) * 0x14))     // Channel configuration 1

// ===== PWM initialization =====
void pwm_init(uint8_t timer_num, uint8_t channel_num, uint8_t resolution_bits, uint8_t gpio_num, uint32_t freq_hz) {
    // Enable LEDC module clock (bit 11 = LEDC_EN)
    DPORT_PERIP_CLK_EN_REG |= (1u << 11);

    // Configure LEDC high-speed timer
    volatile uint32_t* timer_conf = &LEDC_HSTIMER_CONF_REG(timer_num);

    *timer_conf &= ~(0x1F);                      // Clear resolution bits [4:0]
    *timer_conf |= (resolution_bits & 0x1F);     // Set resolution (duty precision)

    // Compute clock divider: 80 MHz / (freq * 2^resolution)
    uint32_t divider = 80000000UL / (freq_hz * (1UL << resolution_bits));

    *timer_conf &= ~(0x3FFFFu << 5);             // Clear divider bits [22:5]
    *timer_conf |= ((divider & 0x3FFFFu) << 5);  // Set divider

    *timer_conf &= ~(1u << 23);                  // Clear pause bit to start timer

    // Configure LEDC channel
    LEDC_HSCH_CONF0_REG(channel_num) &= ~0x3;                // Clear timer select bits [1:0]
    LEDC_HSCH_CONF0_REG(channel_num) |= (timer_num & 0x3);   // Assign timer to channel

    LEDC_HSCH_CONF0_REG(channel_num) |= (1u << 2);           // Enable output for channel (bit 2)

    LEDC_HSCH_HPOINT_REG(channel_num) = 0;                   // Set high point to 0 (no delay)

    LEDC_HSCH_DUTY_REG(channel_num) = (0 << 4);              // Set initial duty cycle = 0
    LEDC_HSCH_CONF1_REG(channel_num) |= (1u << 31);          // Trigger duty update (bit 31)

    // Route PWM signal to GPIO
    GPIO_FUNC_OUT_SEL_CFG_REG(gpio_num) = 0x40 + channel_num;  // 0x40 = PWM0 base output index

    // Enable GPIO as output
    if (gpio_num < 32) {
        GPIO_ENABLE_W1TS_REG = (1u << gpio_num);
    } else {
        GPIO_ENABLE1_REG |= (1u << (gpio_num - 32));
    }

    // Configure GPIO function via IO_MUX
    volatile uint32_t* io_mux_reg = (volatile uint32_t*)gpio_io_mux_addr[gpio_num];
    *io_mux_reg &= ~(0b111 << 12);               // Clear FUNC_SEL bits [14:12]
    *io_mux_reg |= (2 << 12);                    // Set function to FUNC2 = output
}

// ===== Update PWM duty cycle =====
void pwm_set_duty(uint8_t channel_num, uint32_t duty_value) {
    LEDC_HSCH_DUTY_REG(channel_num) = (duty_value << 4);     // Set duty (shifted left 4 bits)
    LEDC_HSCH_CONF1_REG(channel_num) |= (1u << 31);          // Trigger update (bit 31)
}
This is the main file i use to test:

Code: Select all

#include <stdio.h>
#include <stdint.h>
#include "pwm.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define PWM_TIMER      0
#define PWM_CHANNEL    0
#define PWM_RES_BITS   10               
#define PWM_GPIO       18               
#define PWM_FREQ_HZ    5000             

void app_main(void) {

    pwm_init(PWM_TIMER, PWM_CHANNEL, PWM_RES_BITS, PWM_GPIO, PWM_FREQ_HZ);
    
    uint32_t duty = 0;
    uint32_t max_duty = (1 << PWM_RES_BITS);

    while (1) {
        pwm_set_duty(PWM_CHANNEL, duty);

        duty += max_duty / 10;
        if (duty > max_duty) duty = 0;

        vTaskDelay(pdMS_TO_TICKS(500)); 
    }
}

ahsrabrifat
Posts: 201
Joined: Sat Jan 18, 2025 2:31 pm

Re: C bare metal LEDC pwm module timer on ESP32

Postby ahsrabrifat » Thu May 29, 2025 10:09 am

You need to enable the channel by setting bit 0 in LEDC_HSCH_CONF1_REG(channel_num).

Code: Select all

LEDC_HSCH_CONF1_REG(channel_num) |= (1 << 0); // LEDC_HSCHx_CONF1[0]: LEDC_HSCHx_CONF1.EN
Without this, the channel stays disabled!
Duty cycle trigger bit (bit 31) is not enough alone
You are using:

Code: Select all

LEDC_HSCH_CONF1_REG(channel_num) |= (1 << 31);
This only updates the duty register, not enabling the channel output, which requires bit 0 set as well.

User avatar
ok-home
Posts: 157
Joined: Sun May 02, 2021 7:23 pm
Location: Russia Novosibirsk
Contact:

Re: C bare metal LEDC pwm module timer on ESP32

Postby ok-home » Fri May 30, 2025 12:36 am

I'm new to C and ESP32 too. I currently write functions that generate PWM pulse. It's DOIT ESP32 DEVKIT V1 and the reference manual i'm using here https://www.espressif.com/sites/default ... ual_en.pdf. I got stuck here: No PWM pulse out. I've tried to figure it out but it can't. My goal to generate PWM with these parameters and set its duty cycle.
hi
I don't really understand what is the need to program ledc pwm on bare metal ?
Using the standard idf driver is many times easier and gives no owerhead in operation.
https://docs.espressif.com/projects/esp ... /ledc.html
But if you want to use hal or ll driver layer, it gives code portability between esp32 series of crystals.
https://github.com/espressif/esp-idf/bl ... ledc_hal.h
https://github.com/espressif/esp-idf/bl ... /ledc_ll.h
and/or standard register description
https://github.com/espressif/esp-idf/bl ... ledc_reg.h
https://github.com/espressif/esp-idf/bl ... c_struct.h

Who is online

Users browsing this forum: meta-externalagent and 3 guests