C bare metal LEDC pwm module timer on ESP32
Posted: 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
This is the main file i use to test:
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)
}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));
}
}