Mimic 8 bit wide data-bus using dedicated gpio

djixon
Posts: 113
Joined: Sun Oct 01, 2023 7:48 pm

Mimic 8 bit wide data-bus using dedicated gpio

Postby djixon » Thu Oct 19, 2023 7:00 am

I have to achieve data bus behavior on ESP32 S3 using dedicated gpio. The goal is to make the same 8 pins with bi-directional capabilities on request. However, making an 8 pins bundle (for example with input enabled) returns handle on that bundle which is fine for reading. Is it possible to reconfigure the same bundle for output purpose (when needed) without destroying such a handle or I have to destroy (reading bundle) and recreate (writing bundle on the same pins) every time I have to read/write on data bus? Keep in mind that any reading or writing to those 8 pads "simulating" the data bus must be performed within a same clock cycle.

(For documentation: The VSCode examples for dedicated gpio are all with pads of fixed behavior. Some pads are output and some are inputs but they do not change their direction behavior at runtime. So those examples are not the best representative which effectively shows those usual things related to data bus like, making data bus for reading, reading byte, making it for writing and writing byte. I understand that it could be done by creating/destroying dedicated gpio bundle every time data bus has to be read/write but it doesn't look as the most efficient way. It is simply too many operations for such a basic thing and any bi-directional communication in between ESP32 and other peripheral chips, using data bus would suffer if switching from in/out direction would cost such a huge amount of clock cycles)

What is the best approach to solve that problem? Is it possible to have, at the same time, two different bundles (using the same pads) where one created handle is for reading and another handle for writing and leaving them permanent all the time and using one when reading from data bus is required and another for writing to data bus?

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

Re: Mimic 8 bit wide data-bus using dedicated gpio

Postby MicroController » Thu Oct 19, 2023 11:35 pm

AFAICT this is not possible. There are only 8 dedicated GPIO 'slots' available, and each of them is exclusively allocated by one bundle. This alone makes it impossible to have 8 output + 8 input dedicated GPIOs allocated at the same time.
Do you actually need dedicated GPIO? Note that you can read or write up to 32 GPIO pins at the same time via a single GPIO register access. Maybe the extra latency of a few clock cycles is still acceptable for your use case.

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

Re: Mimic 8 bit wide data-bus using dedicated gpio

Postby ESP_Sprite » Fri Oct 20, 2023 1:39 am

At least conceptually (as in ignoring what restrictions the driver applies), you could configure both the inputs and outputs to the same GPIOs, and use the OE invert bit in the GPIO matrix to switch between inputs and outputs. That would mean bus turnaround would be pretty slow, however.

djixon
Posts: 113
Joined: Sun Oct 01, 2023 7:48 pm

Re: Mimic 8 bit wide data-bus using dedicated gpio

Postby djixon » Sun Oct 22, 2023 12:24 pm

Thank for the answers to both of you guys. I hope that some of you experts, at least now see, the needs for internal function (let's say in the matrix or IO or completely independent) which would provide the same functionality as the regular data bus (which is so common on many other microcontrollers) allowing other users to redesign their old hardware designs and allowing them to use ESP32 as replacement for old microcontrollers. There are still many chips out there, where high speed data bus is still MUST have for their working.

@microcontroler, @ESP-Sprite can you provide some link or some examples with usage of function 0x100 in the matrix? Or any other universal (not related to S3 family but workable on raw ESP32) examples related to reading/writing the 8 bunch of pins within the same cycle.

Is it possible for ULP processor to execute instruction(s) which would read/write 8 bits on sequential pads? Some examples on that?

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

Re: Mimic 8 bit wide data-bus using dedicated gpio

Postby MicroController » Sun Oct 22, 2023 3:58 pm

There are still many chips out there, where high speed data bus is still MUST have for their working.
Could you please narrow down what "high speed" means in your case?

In the meantime, check out the S3's LCD and octal-SPI peripherals.

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

Re: Mimic 8 bit wide data-bus using dedicated gpio

Postby ok-home » Sun Oct 22, 2023 5:37 pm

Hi
If you want a really high speed bidirectional bus - see octal spi

if you only need gpio on dedicated gpio, configure input and output gpio to the same pins.
Set GPIO_FUNCx_OEN_SEL -> 1 (Force the output enable signal to be sourced from GPIO_ENABLE_REG[x]).
OEN switching will be done with one write to the GPIO_ENABLE_REG(W1TS/W1TC) register, reading will be available at any time. This is the maximum you can get in gpio exchange mode via CPU.

djixon
Posts: 113
Joined: Sun Oct 01, 2023 7:48 pm

Re: Mimic 8 bit wide data-bus using dedicated gpio

Postby djixon » Sun Oct 22, 2023 7:25 pm

@MicroController: It would be nice to have 60 MB/s in both directions. @240 MHz it would mean one read (or write) per 4 clock cycles. But anyway, what speeds are achievable by using IO matrix function 0x100? Is there any example of proper usage of that function?

@Ok-Home: I'm not sure I understand where did you perform switching when 8 bits are required as inputs from pads and where is switching when those same GPIOs are required to output 8 bit value. Which register determines in/out direction? What are necessary steps to execute for a list of pads to be configured as an input? And what are necessary steps after that to reconfigure those same pads as outputs? Suppose I want GPIO6 up to GPIO13 (that is 8 bits wide) to set up like you proposed. Can you please write a part for initialization those bits for inputs (and read some value from those pads) then reconfigure them for outputs (and write some byte out to those pads)?

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

Re: Mimic 8 bit wide data-bus using dedicated gpio

Postby ok-home » Mon Oct 23, 2023 3:27 pm

hi
this will help you a little to achieve 60 MB/s
but switching in/out with one entry to the register is below
reading is always on

Code: Select all

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

void dedic_task(void *p)
{
    const int bundleA_gpios[] = {6, 7,8,9,10,11,12,13};
    gpio_config_t io_conf = {
        .mode = GPIO_MODE_INPUT_OUTPUT,
        .pull_down_en = true,
    };
    uint64_t oemask = 0; // gpio mask -> enable/disable output
    for (int i = 0; i < sizeof(bundleA_gpios) / sizeof(bundleA_gpios[0]); i++)
    {
        io_conf.pin_bit_mask = 1ULL << bundleA_gpios[i];
        gpio_config(&io_conf);
        oemask |=  io_conf.pin_bit_mask; // gpio mask -> enable/disable output
    }
    // Create bundleA, output only
    dedic_gpio_bundle_handle_t bundleA = NULL;
    dedic_gpio_bundle_config_t bundleA_config = {
        .gpio_array = bundleA_gpios,
        .array_size = sizeof(bundleA_gpios) / sizeof(bundleA_gpios[0]),
        .flags = {
            .out_en = 1,
            .in_en = 1 // input mode already connected
        },
    };
    ESP_ERROR_CHECK(dedic_gpio_new_bundle(&bundleA_config, &bundleA));

// switch OEN control to register GPIO_ENABLE_W1TS_REG/REG_WRITE(GPIO_ENABLE_W1TC_REG

    for (int i = 0; i < sizeof(bundleA_gpios) / sizeof(bundleA_gpios[0]); i++)
    {
        uint32_t tmp = REG_READ(GPIO_FUNC0_OUT_SEL_CFG_REG+bundleA_gpios[i]*4);
        REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG+bundleA_gpios[i]*4,tmp | 1<<10);
    }

    while (1)
    {
        REG_WRITE(GPIO_ENABLE_W1TS_REG,oemask); // enable output
        for (int i = 0; i < 255; i++)
        {
            dedic_gpio_bundle_write(bundleA, 0xff, i);
        }

        REG_WRITE(GPIO_ENABLE_W1TC_REG,oemask); // disable output - out data always pulldown

        for (int i = 0; i < 255; i++)
        {
            dedic_gpio_bundle_write(bundleA, 0xff, i);
        }

        REG_WRITE(GPIO_ENABLE_W1TS_REG,oemask); // enable output
        
        for (int i = 0; i < 255; i++)
        {
            dedic_gpio_bundle_write(bundleA, 0xff, i);
        }
          
        vTaskDelay(1);
    }
}

void app_main(void)
{
    //logic_analyzer_ws_server();
    xTaskCreatePinnedToCore(dedic_task, "dedic", 4096, NULL, 5, NULL, 1);
    while (1)
    {
        vTaskDelay(10);
    }
}

djixon
Posts: 113
Joined: Sun Oct 01, 2023 7:48 pm

Re: Mimic 8 bit wide data-bus using dedicated gpio

Postby djixon » Tue Oct 24, 2023 7:15 pm

Thanks a lot @ok-home.

Such an example (or similar) should be part of TRM because usage of GPIO_ENABLE_W1TS_REG and GPIO_ENABLE_W1TC_REG registers become obvious when 10th bit in GPIO_FUNCn_OUT_SEL_CFG_REG is set and such an example "tells" MORE much faster than jumping through pages in TRM, where description of an operation is explained and pages where involved registers are described. Maybe, if some decides to put it in TRM, part where outputs are disabled, should be followed by routine of READING data bus. It is more logical to read data-bus when it is configured for reading (assuming some external device put some data on it), instead of writing data on it to show that data will not appear on external pads.

Now I am wondering, how close to that approach can be achieved on a simple ESP32 with LX6 core which does not have dedicated IO module implemented (ESP32S3 with LX7 core has such a module)?

I tried to use the same approach (with an assumption that all 8 data lines on external pads are each one after another, to avoid complex masking over scattered indices for the sake of speed). So I come to this working solution

Code: Select all

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

// 8 lines of data bus are assumed to be consecutive pads starting from DATA_BUS_LOWEST_PAD
// in this example lines: GPIO8 to GPIO15
// if you want to change that, change just starting pad number for DATA_BUS_LOWEST_PAD
// also take care not to overlap data-bus with pads required for external flash, PSRAM etc.  

#define DATA_BUS_LOWEST_PAD 8
#define SET_MASK (0x000000FF << DATA_BUS_LOWEST_PAD)
#define CLR_MASK (SET_MASK ^ 0xFFFFFFFF)

static __always_inline void  make_data_bus_in(void) { DPORT_REG_WRITE(GPIO_ENABLE_W1TC_REG, SET_MASK);}
static __always_inline void make_data_bus_out(void) { DPORT_REG_WRITE(GPIO_ENABLE_W1TS_REG, SET_MASK);}
static __always_inline void  write_db(uint8_t val) { DPORT_REG_WRITE(GPIO_OUT_REG, (_DPORT_REG_READ(GPIO_OUT_REG) & CLR_MASK) | (val << DATA_BUS_LOWEST_PAD));}
static __always_inline uint8_t read_db(void){ return (_DPORT_REG_READ(GPIO_IN_REG) >> DATA_BUS_LOWEST_PAD);}

void app_main(void)
{
//CONFIGURATION PART

   // unfortunately IO_MUX_GPIOx_REG are not positioned in memory map in ascending order
    // so there is no easy way to calculate their address, based on GPIO index (we would needed some remapping array) 
    // so user have to rename them manually if he changes DATA_BUS_LOWEST_PAD to some other value 
    // we set: MCU_SEL to 2 (it provides GPIO pad functionality),
    //         FUN_IE (this bit allows input from a pad to IO mux,
    //         FUN_WPD (pull down resistors to define data-bus state in the case no external peripheral write to it)

    DPORT_REG_WRITE(IO_MUX_GPIO8_REG, 0x00002280);
    DPORT_REG_WRITE(IO_MUX_GPIO9_REG, 0x00002280);
    DPORT_REG_WRITE(IO_MUX_GPIO10_REG, 0x00002280);
    DPORT_REG_WRITE(IO_MUX_GPIO11_REG, 0x00002280);
    DPORT_REG_WRITE(IO_MUX_GPIO12_REG, 0x00002280);
    DPORT_REG_WRITE(IO_MUX_GPIO13_REG, 0x00002280);
    DPORT_REG_WRITE(IO_MUX_GPIO14_REG, 0x00002280);
    DPORT_REG_WRITE(IO_MUX_GPIO15_REG, 0x00002280);

    //  GPIO_FUNCx_OUT_SEL_CFG_REG are consecutive in memory so we can use math to calc proper 
    //  register offset using GPIO index 
    //  we set forced OE (bit 10) and also set matrix function 0x100 (in bits 0 to 8) 

    DPORT_REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG + (DATA_BUS_LOWEST_PAD     ) * 4 ,  0x00000500);
    DPORT_REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG + (DATA_BUS_LOWEST_PAD + 1) * 4 ,  0x00000500);
    DPORT_REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG + (DATA_BUS_LOWEST_PAD + 2) * 4 ,  0x00000500);
    DPORT_REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG + (DATA_BUS_LOWEST_PAD + 3) * 4 ,  0x00000500);
    DPORT_REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG + (DATA_BUS_LOWEST_PAD + 4) * 4 ,  0x00000500);
    DPORT_REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG + (DATA_BUS_LOWEST_PAD + 5) * 4 ,  0x00000500);
    DPORT_REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG + (DATA_BUS_LOWEST_PAD + 6) * 4 ,  0x00000500);
    DPORT_REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG + (DATA_BUS_LOWEST_PAD + 7) * 4 ,  0x00000500);

// HERE IS THE USAGE PART:
// when you need to put byte(s)
    make_data_bus_out();
    write_db(0x00);
    write_db(0x01);
    ...

// when you need to read from some external peripheral       
    uint8_t value;
    make_data_bus_in();
    // trigger  OE on those external peripheral (for example some 8-bit latch or other MCU) to switch its output from tri-state and 
   // allows it on bus
   value = read_db();
   // use value
   // trigger another device     
   value = read_db();
   ...
}
The most critical part is write_db() function, because it needs to read actual GPIO_OUT_REG (you may have other pads beside those used for data bus also configured as GP outputs and you must not overwrite them by zeroes), masks it (to preserve those other pinouts) and then OR with new value. It is critical operation in multi-tasking environment because in the final compiled code it involves several assembler instructions which can not guaranty the integrity of whole operation.

So it would be much better approach (in the case if all 8 bits of data-bus are BYTE aligned within GPIO_OUT_REG) to use just a byte writing instruction into proper byte positioned memory location inside GPIO_OUT_REG. But unfortunately there is no C macro of assembler instruction S8I capable to do that thing. I am not sure how would C compiler compile the following

uint8_t value = 0xAA;
(*(volatile uint8_t *)(GPIO_OUT_REG + 1)) = value;

Would it be reading of 32 bit value, plus shifting, plus masking, plus rewriting or it would be just BYTE writing using S8I assembler instruction? How can I see assembly code generated by C compiler?

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

Re: Mimic 8 bit wide data-bus using dedicated gpio

Postby ESP_Sprite » Wed Oct 25, 2023 1:29 am

Your code should result in a S8I instruction. Note that I'm not sure if on all our chips all IO ranges have the byte strobes implemented... in other words, GPIO registers may not respond correctly to anything but 32-bit reads or writes.

Who is online

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