cembeddedesp32bare-metal

C Bare Metal LEDC module timer on ESP32 (emulated on wokwi)


I'm trying to code on the emulated ESP32 using C and memory registers directly. I know there are great SDKs but I'd like to learn first what is going on under the hood, so i decided to try simple tasks with this approach.

This is what I'm using as base: Tech ref.

This is the first time I'm trying C, my goal is not to have the perfect code, I want to understand how things work.

What I was able to do: I was able to use simple output to turn on and off the led, and I was also able to control them using a button. To do this I used the simple LEDs in wokwi emulator without any resistor, I tried with the LEDC module with the same setup.

Where I had problems: I got stuck trying to use the LEDC module to turn (half) on and off an led. I configured the timer, the channel and gpio to use the specific channel but nothing happened (GPIO pin 26 with peripheral 71 selected; hPoint of the duty cicle at 0 and duty 'length' of 120; the timer used the same config of the code below).

Since it was not working: I tried a different strategy trying to print the timer (aka counter) value but it is always 0, i expected to see it increasing and when reached the overflow reset and start again its cycle.

So I'm seeking to understand why the timer always returns 0.

I leave here the code of my last attempt.

To help those who want to try:

The timer is set with resolution of 8 bit (range 0-256 if i am not wrong); divisor of 1 (it starts on bit 5 but has 8 fractional bits). I tried also higher divisor to change the frequency but was not able to print the timer value (or to make the LED turning half on in previous attempts). I enabled the REF_TICK but this was just a test since with 0 in that bit it did not worked.

My goal is to understand what I'm missing (or messing) and be able to get the timer value; after that I'll go further and set the channel and the GPIO pin.

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

#define GPIO_CFG_BASE 0x3FF44530;
#define LEDC_CONF_REG 0x3FF59190; //bit 1 to choose clk
#define LEDC_HSCH0_CONF0_REG 0x3FF59000;
#define LEDC_HSCH0_HPOINT_REG 0x3FF59004;
#define GPIO_ENABLE_W1TS 0x3FF44024;
#define LEDC_HSCH0_DUTY_REG 0x3FF59008;
#define LEDC_HSCH0_CONF1_REG 0x3FF5900C;
#define LEDC_HSTIMER0_CONF_REG 0x3FF59140;
#define LEDC_HSTIMER0_VALUE_REG 0x3FF59144;
#define LEDC_HSCH0_DUTY_R_REG 0x3FF59010;
#define LEDC_INT_RAW_REG 0x3FF59180;
#define LEDC_INT_ENA_REG 0x3FF59188;
void app_main() {

  volatile uint32_t* interrupts_enable = LEDC_INT_ENA_REG;
  *interrupts_enable |= (0b10001);

  volatile uint32_t* timer_cfg = LEDC_HSTIMER0_CONF_REG;
  *timer_cfg |= 8;
  *timer_cfg |= 1<<13;
  *timer_cfg |= 1<<25;
  volatile uint32_t* reader_timer = LEDC_HSTIMER0_VALUE_REG;
  volatile uint32_t* interrupt_reader = LEDC_INT_RAW_REG;
  //printf("%" PRIX32 "\n", *timer_cfg);
  volatile uint32_t* ls_cfg = 0x3FF59160;
  *ls_cfg |= 8;
  *ls_cfg |= 1<<13;
  *ls_cfg |= 3<<25;

  volatile uint32_t* reader_slow = 0x3FF59164;
  
  while (true) {
    printf("%" PRIu32 "timer\n",*reader_timer);
    printf("%" PRIu32 "timer SLOW\n",*reader_slow);
    printf("%" PRIu32 "interrupt\n",*interrupt_reader);
    vTaskDelay(20 / portTICK_PERIOD_MS);
  } 
}

I've tried reading the documentation to understand what I'm missing but I was not able to get it since there are not practical examples online.

I expected that after configured the timer, the channel and the gpio the LED followed the duty cycle. When I saw nothing was happening I decided to focus on single spot and tried to print the timer value (it controls the duty cycle) but seems like the timer does not start.


Solution

  • I was able to make it work thanks to the advices of Lundin and JimmyB in the comments.

    NOTE: This code works on my phisical Esp32(DEVKIT 1) since I got it. I have not tested it on the emulator.

    What I was missing:

    What I discovered:

    I post here a working code in case someone gets stuck like me.

    #include <stdio.h>
    #include <unistd.h>
    #include <inttypes.h>
    
    #define GPIO_OUT_W1TS_REG (*(volatile uint32_t*)0x3FF44008)
    #define GPIO_OUT_W1TC_REG (*(volatile uint32_t*)0x3FF4400C)
    #define GPIO_ENABLE_W1TS_REG (*(volatile uint32_t*)0x3FF44024)
    #define GPIO_ENABLE_W1TC_REG (*(volatile uint32_t*)0x3FF44028)
    #define GPIO_FUNC0_OUT_SEL_CFG_REG 0x3FF44530
    #define LEDC_CONF_REG (*(volatile uint32_t*)0x3FF59190)
    #define LEDC_HSCH0_CONF0_REG (*(volatile uint32_t*)0x3FF59000)
    #define LEDC_HSCH0_CONF1_REG (*(volatile uint32_t*)0x3FF5900C)
    #define LEDC_HSCH0_DUTY_REG (*(volatile uint32_t*)0x3FF59008)
    #define LEDC_HSCH0_DUTY_R_REG (*(volatile uint32_t*)0x3FF59010) 
    #define LEDC_HSCH0_HPOINT_REG (*(volatile uint32_t*)0x3FF59004)
    #define LEDC_HSTIMER0_CONF_REG (*(volatile uint32_t*)0x3FF59140)
    #define IO_MUX_GPIO26_REG (*(volatile uint32_t*)0x3FF49028)
    #define DPORT_PERIP_CLK_EN_REG (*(volatile uint32_t*)0x3FF000C0)
    #define DPORT_PERIP_RST_EN_REG (*(volatile uint32_t*)0x3FF000C4)
    #define LEDC_HSTIMER0_VALUE_REG (*(volatile uint32_t*)0x3FF59144)
    #define resolution (uint)8
    
    void app_main(void)
    {
        printf("test\n");
    
        DPORT_PERIP_CLK_EN_REG |= (1<<11);// enable clock for ledc
    
        LEDC_HSTIMER0_CONF_REG &= ~(0xf);
        LEDC_HSTIMER0_CONF_REG |= resolution; //resolution = 8 bit
        uint divider = 80000000 / (5000 * 256);
        LEDC_HSTIMER0_CONF_REG |= (divider<<13);
    
    
        LEDC_HSCH0_CONF0_REG &= ~(0b00); //timer 0
        LEDC_HSCH0_CONF0_REG |= (1<<2); //enale output channel
    
        LEDC_HSCH0_HPOINT_REG = 1; // value to set high
        LEDC_HSCH0_DUTY_REG &= ~(0xffffff);
        LEDC_HSCH0_DUTY_REG |= (20<<4); // low duty cycle
        uint low = 1; // flag to control next duty value
    
        // gpio setting
        volatile uint32_t* gpio26_cfg = (volatile uint32_t*)GPIO_FUNC0_OUT_SEL_CFG_REG + 26;
        // peripheral 71 -> hschan0
        *gpio26_cfg = 71;
        GPIO_ENABLE_W1TS_REG |= (1<<26);
        // function 2
        IO_MUX_GPIO26_REG &= ~(0b111 << 12);
        IO_MUX_GPIO26_REG |= (2<<12);
    
        LEDC_HSCH0_CONF1_REG |= (1<<31); // start channel duty cycle
        LEDC_HSTIMER0_CONF_REG &= ~(1<<24); //reset timer
        uint counter = 0;
        while (1) {
            if (counter == 2){
                if (low == 0) {
                    LEDC_HSCH0_DUTY_REG &= ~(0xffffff);
                    LEDC_HSCH0_DUTY_REG |= (30<<4);
                    low = 1;
                    LEDC_HSCH0_CONF1_REG |= (1<<31); // start channel duty cycle
                } else {
                    LEDC_HSCH0_DUTY_REG &= ~(0xffffff);
                    LEDC_HSCH0_DUTY_REG |= (128<<4);
                    low = 0;
                    LEDC_HSCH0_CONF1_REG |= (1<<31); // start channel duty cycle
                }
                counter = 0;
            }
            printf("timer value: %" PRIu32 "\n", LEDC_HSTIMER0_VALUE_REG);
            printf("duty value: %" PRIu32 "\n", LEDC_HSCH0_DUTY_R_REG);
            printf("counter: %d\n", counter);
            sleep(1);
            counter++;
        }
    }
    
    

    Since I am a newbie in C please feel free to correct me as Lundin did, I will appreciate it.