Directly addressing the ESP32 registers for fast I/O operations

Vilius
Posts: 26
Joined: Mon Nov 13, 2023 9:22 am

Directly addressing the ESP32 registers for fast I/O operations

Postby Vilius » Mon Nov 13, 2023 9:35 am

Hi,



This is my very first project using ESP32. I got a Chinese dev board ESP WROOM - 32 but I already verified that it is working (led blink was successful.)



I have some experience with PIC32 register based programming, so I would like to start with the same philosophy on ESP. Of course, the ESP-IDF can provide ready to use complex USB, Ethernet, SPI etc. drivers. But I would like to start from the fundamentals of understanding the I/O based on directly setting the right registers. I am using visual studio code with the ESP-IDF framework. I found the chapter in a datasheet that explains how to do it, but I am not sure about how should I address the registers in ESP-IDF. Can someone write me the code equivalent of this very little datasheet chapter? Thank you in advance, I really hope that after this example I will be able to address the registers on my own.

P.S. I have written some very simple code:

Code: Select all

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

//I want to toggle GPIO pin 2

void app_main() {

    GPIO_FUNC2_OUT_SEL_CFG_REG = 0x100; //This step is provided in the datasheet
    
    while (1) {

        GPIO_OUT_DATA |= (1 << 2);
      
        vTaskDelay(1000 / portTICK_PERIOD_MS);

        GPIO_OUT_DATA &= ~(1 << 2);
        
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}
Even if the code is wrong, any register name that I write, the compiler treats it as undeclared function, am I missing any include? Also what is the syntax if I want to set a specific field (that has a name in the datasheet) of a register?
Attachments
1.PNG
1.PNG (62.03 KiB) Viewed 4103 times

MicroController
Posts: 1708
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Directly addressing the ESP32 registers for fast I/O operations

Postby MicroController » Mon Nov 13, 2023 3:30 pm

On an ESP, things don't work like on other, simpler MCUs.
While you can use direct register writes to control peripherals, it is very much not worth it to use as the 'default' approach.
Instead, I stongly suggest to use the provided IDF drivers; then, if at some point the driver doesn't do what you need, you could still resort to manual register access for a specific functionality.
Note that generally a whole bunch of boilerplate code is needed just to get a peripheral from reset into an operational state. You may have to power up the peripheral, enable its clock, (reset and) un-reset it before you can even access any of the registers, then route any input or output signals to the pins you want by configuring the GPIO matrix and/or the IO_MUX...

As stated in the docs, the IDF is structured into layers: the user-facing API layer ("driver"), which makes use of the underlying hardware abstraction layer (HAL), which in turn uses the low-level (LL) layer, which then sometimes uses specific macros to generate valid accesses to the hardware registers.
You can use functions from any of these layers yourself, but if you look at some of the drivers' code you may find that it's not reasonable to try and re-implement all of the needed boilerplate setup code.

Another issue comes in terms of interoperability with other components, IDF or third-party, which often rely on the IDF drivers, which in turn rely on being solely responsible for their corresponding hardware. This already starts with synchronization of concurrent multi-tasked accesses which the drivers manage internally. Bypass this and things may break in hard-to-debug ways.

That said, you can look at the GPIO low-level code to find how to access the GPIO registers :)
Or

Code: Select all

#include "soc/gpio_struct.h" // provides the GPIO global symbol and type.
...
GPIO.enable_w1ts = 0b1010;
GPIO.out_w1ts = 0b1010;
But note that some hardware registers may require 32-bit-accesses, so things like GPIO.pin[x].wakeup_enable = 1 may not work as intended.

Vilius
Posts: 26
Joined: Mon Nov 13, 2023 9:22 am

Re: Directly addressing the ESP32 registers for fast I/O operations

Postby Vilius » Mon Nov 13, 2023 3:51 pm

Ok, maybe I provided too little information on why do I need the direct register access.



I need to implement very fast GPIO read and write operations. How fast? Well my target is 20 Mhz, so quite fast... A friend of mine used to work with ESP32, he wrote a simple program for me that could toggle GPIO at 40 Mhz (he used led control ESPIDF library). When I saw it, I decided that ESP was the way to go for this project (keep in mind I do not need anything complicated code-wise, just GPIO operations). But when I use those built in GPIO_set_level type commands, I dont get anything near 40 Mhz. In fact, it's something like 500-700 khz. I understand the general idea of programming, so if 40 mhz is achievable in a high level language that has so many includes and adjacent files to it (I am talking about led control library), I cant even imagine what is the real speed limit for simple and optimized GPIO code written in low/register level (20 mhz for both reading and writing must not be a consideration at all.) That is my real goal and situation. As I said, this is my first ESP project. I would be grateful not only for bigger picture advice, but for a real starting point in programing (just one or few very basic lines of code on how you would achieve the described goal). If the way to do it is not via direct register access, so be it, but please give me a very very simple example, what the actual way is. Thank you one more time.

P.S. I have already set my core clock to 240 Mhz in the menuconfig settings

ESP_Sprite
Posts: 9730
Joined: Thu Nov 26, 2015 4:08 am

Re: Directly addressing the ESP32 registers for fast I/O operations

Postby ESP_Sprite » Tue Nov 14, 2023 3:30 am

Getting a 20MHz speed by simply toggling a GPIO is close to the limits of the original ESP32 chip (or even over... that's faster than I remember they go), as the GPIO block is on a somewhat slow APB bus. See e.g. here for more info. You'd normally use one of the plethora of other peripherals if you need fast communication (e.g. the I2S and RMT peripheral are super-flexible and generally can do more than you'd initially expect them to do.) If you still need GPIO, later chips like the ESP32-S3 have 'fast GPIO' which allows you to set/clear GPIO pins faster.

Vilius
Posts: 26
Joined: Mon Nov 13, 2023 9:22 am

Re: Directly addressing the ESP32 registers for fast I/O operations

Postby Vilius » Tue Nov 14, 2023 8:05 am

Ok, I will definitely consider switching to the S3 one. For now I still want to learn the fundamental register operations as it will be really useful in the future. I have included gpio_reg.h library from the: https://github.com/espressif/esp-idf/bl ... eg.h#L7131

There are all the register names defined with a memory address. Idea is that I create a pointer that points to the register (register`s memory address) and assign a value to it in my main program (thats what Chatgpt told me.) It seems logical, I was used to calling the register name directly (just a matter of preference.) The code compiles and runs on ESP32, but I can not get the led blinking as I did with built in commands. Is it still a matter of wrong syntax, or my setup sequence lacks some additional steps or other things?

Also I read in the datasheet that there is a thing called direct IO. Datasheet describes it as an on chip hardware setup, when some GPIO matrix or IO mux (not sure) is bypassed and that results in considerably better high speed IO performance (thats what the datasheets tells.) This goal is not important for now, but I just want to understand the concept of pin modes in ESP32. Can I theoretically use lets say UART or I2C pins with that high speed capabillity as simple GPIOs? On many microcontrollers, some pins are routed to the communication PHYs in hardware and can not be used for anything else but the only one intended function.

Code: Select all

#include <stdio.h>
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"    
#include "soc/soc.h"
#include "soc/gpio_struct.h"
#include "soc/gpio_reg.h"

//I want to toggle GPIO pin 2

void app_main() {

    //GPIO_FUNC2_OUT_SEL_CFG_REG = 0x0100; This step is provided in the datasheet
    
    volatile uint32_t *GPIOSetupRegister_pointer = (volatile uint32_t *)GPIO_FUNC2_OUT_SEL_CFG_REG;
    *GPIOSetupRegister_pointer = 0x0100;

    //gpio_set_direction(GPIO_NUM_2, GPIO_MODE_OUTPUT);
    
    // Datasheet tells to set corresponding GPIO_OUT_DATA register bits for the output, but there is no such register (its a register field), GPIO_OUT_REG must be what they meant
    
    volatile uint32_t *GPIODataRegister_pointer = (volatile uint32_t *)GPIO_OUT_REG;
    uint32_t currentValue = *GPIODataRegister_pointer;

    while (1) {
  
        currentValue |= (1 << 2);
        *GPIODataRegister_pointer = currentValue;

        //gpio_set_level(GPIO_NUM_2, 1);
        vTaskDelay(100 / portTICK_PERIOD_MS);

        currentValue &= ~(1 << 2);
        *GPIODataRegister_pointer = currentValue;

        //gpio_set_level(GPIO_NUM_2, 0);
        vTaskDelay(100 / portTICK_PERIOD_MS);

    }
}

MicroController
Posts: 1708
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Directly addressing the ESP32 registers for fast I/O operations

Postby MicroController » Tue Nov 14, 2023 2:54 pm

he used led control ESPIDF library
Note that this is actually "only" a driver for the LEDC hardware peripheral. This peripheral is capable of generating up to 40MHz (PWM) output in hardware, far beyond what would be possible via software GPIO control.

Who is online

Users browsing this forum: Google [Bot] and 109 guests