最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

embedded - C Bare Metal LEDC module timer on ESP32 (emulated on wokwi) - Stack Overflow

programmeradmin0浏览0评论

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:

  • GPIO registers: pag. 69
  • LEDC registers: pag. 399
  • Peripheral signals list: pag. 58
  • GPIO output: pag. 52

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.

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:

  • GPIO registers: pag. 69
  • LEDC registers: pag. 399
  • Peripheral signals list: pag. 58
  • GPIO output: pag. 52

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.

Share Improve this question edited Mar 13 at 9:11 Lundin 217k46 gold badges279 silver badges433 bronze badges asked Mar 12 at 21:31 Michele Del GrossoMichele Del Grosso 393 bronze badges 12
  • 1 Is there something like a global interrupt mask on this ISA? Is it enabled by default or by the RTOS? Also where's the actual ISR, is it some weakly linked default one? – Lundin Commented Mar 13 at 8:59
  • Somewhat unrelated to your problem, stuff like this is invalid C: volatile uint32_t* interrupts_enable = LEDC_INT_ENA_REG;. In C, you cannot assign an integer to a pointer without casting. Here's a little tutorial for how to write code such as this properly: How to access a hardware register from firmware? – Lundin Commented Mar 13 at 9:02
  • 1 On the ESP32, you often have to first enable the clock and disable the reset state of a peripheral before you can use it. See e.g. the HAL/low-level code in the IDF. – JimmyB Commented Mar 13 at 9:49
  • @Lundin actually I don't know, I'm following the instructions in the Tech ref and it does not mention those things in LEDC chapter. During lunch break I may search something about it. Thanks for the C advice, my goal is to use Rust (I'm more confident with it) but until i get my phisical esp32 i have to try with emulators. – Michele Del Grosso Commented Mar 13 at 10:18
  • 1 Arguably not quite as "bare-metal" as you suggest you want if it has support for stdio and FreeRTOS already - quite a lot happens under those hoods! app_main() is an RTOS thread, not the C runtime main() entry point. However it is pretty well documented at docs.espressif/projects/esp-idf/en/stable/esp32/api-guides/… and probably the optimum starting point if you are not interested in the low-level "bring-up" of the part. – Clifford Commented Mar 21 at 19:04
 |  Show 7 more comments

1 Answer 1

Reset to default 1

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:

  • enabling the clock in register DPORT_PERIP_CLK_EN_REG
  • resetting timer after it has been configured

What I discovered:

  • to make the channel use the new duty value it has to be (re)started (bit 31 in LEDC_HSCH0_CONF1_REG)

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.

发布评论

评论列表(0)

  1. 暂无评论