K210: How to set & read pins in parallel? (per port) [undocumented terrible support is kendryte defunct]

In the SDK (freertos & regular) there are just methods to set and read single pins, one at a time.
There must be a way to access IOs in parallel like on any other microcontroller. It seems to me this is done with the LCD driver. So it may just be undocumented.
Why?

This functionality is absolutely crucial if you want to use a button matrix.

While we’re at it, it is usual to do these things in the background run by a hardware timer & counter along with DMA? This is very basic stuff if you look at STM32 and so on.
It’s kind of ridiculous that this isn’t properly documented, all the performance in the world won’t help you if you can’t even do basic stuff. I’ve got several maix bit, and a maixduino from crowdfunding, decide to finally use them in a project because I already have them here but might end up having to go back to getting cortex-m7s because the documentation is so lackluster.

PS: is http://kendryte.com/ defunct? what’s up?

1 Like

There is a lack of documentation, but you should be able to find everything if you read (and understand) the headers files carefully.
K210 gpiohs peripheral is very flexible, memory mapped ( at GPIOHS_BASE_ADDR, 0x38001000U ) and can be accessed directly.
The key to understand the gpiohs functionality is the gpiohs register structure (from gpiohs.h):

typedef union _gpiohs_u32
{
    /* 32x1 bit mode */
    uint32_t u32[1];
    /* 16x2 bit mode */
    uint16_t u16[2];
    /* 8x4 bit mode */
    uint8_t u8[4];
    /* 1 bit mode */
    gpiohs_bits_t bits;
} __attribute__((packed, aligned(4))) gpiohs_u32_t;

typedef struct _gpiohs
{
    /* Address offset 0x00, Input Values */
    gpiohs_u32_t input_val;
    /* Address offset 0x04, Input enable */
    gpiohs_u32_t input_en;
    /* Address offset 0x08, Output enable */
    gpiohs_u32_t output_en;
    /* Address offset 0x0c, Onput Values */
    gpiohs_u32_t output_val;
    /* Address offset 0x10, Internal Pull-Ups enable */
    gpiohs_u32_t pullup_en;
    /* Address offset 0x14, Drive Strength */
    gpiohs_u32_t drive;
    /* Address offset 0x18, Rise interrupt enable */
    gpiohs_u32_t rise_ie;
    /* Address offset 0x1c, Rise interrupt pending */
    gpiohs_u32_t rise_ip;
    /* Address offset 0x20, Fall interrupt enable */
    gpiohs_u32_t fall_ie;
    /* Address offset 0x24, Fall interrupt pending */
    gpiohs_u32_t fall_ip;
    /* Address offset 0x28, High interrupt enable */
    gpiohs_u32_t high_ie;
    /* Address offset 0x2c, High interrupt pending */
    gpiohs_u32_t high_ip;
    /* Address offset 0x30, Low interrupt enable */
    gpiohs_u32_t low_ie;
    /* Address offset 0x34, Low interrupt pending */
    gpiohs_u32_t low_ip;
    /* Address offset 0x38, HW I/O Function enable */
    gpiohs_u32_t iof_en;
    /* Address offset 0x3c, HW I/O Function select */
    gpiohs_u32_t iof_sel;
    /* Address offset 0x40, Output XOR (invert) */
    gpiohs_u32_t output_xor;
} __attribute__((packed, aligned(4))) gpiohs_t;

Inputs and/or outputs can be acctually accessed as one 32-bit parallel port.
The SDK drivers do not have this functionality, but it is quite easy to access the port directly.
Here is an example of implementing the 8-bit parallel port:

You can assign any combination of phisical pins (K210 pads) to 8 consecutive gpiohs inputs/outputs (I’m using the FreeRTOS SDK here, but it can be done also using the standalone SDK):

// First declare the gpio peripheral memmory mapped area:
static volatile gpiohs_t* const gpiohs = (volatile gpiohs_t*)GPIOHS_BASE_ADDR;

handle_t gpiohs_handle = io_open("/dev/gpio0");
// our pin mapping
static uint8_t port_pins[8] = { 17, 18, 20, 21, 21, 32, 33, 27 };
// first_gpio is the bit-position in the 32-bit gpiohs port, it can be 0 ~ 24
static uint8_t first_gpio = 0;

// Initialize the used pins
void port_out_init(uint8_t *pins)
{
    // Use standard SDK functions to assign pins to gpiohs via fpioa
    // and set the output mode
    for (int i=0; i<8; i++) {
        fpioa_set_function(pins[i], FUNC_GPIOHS0+first_gpio+i);
        gpio_set_drive_mode(gpiohs_handle, FUNC_GPIOHS0+first_gpio+i, GPIO_DM_OUTPUT);
    }
}

// Set our 8-bit port to some value
void port_out_set(uint8_t val)
{
    // get current gpiohs out state (we want to keep other pins state unchanged)
    uint32_t gpio_state = *gpiohs->output_val.u32;
    // clear bits on our 8-bit port location
    gpio_state &= ~(0x000000FF << first_gpio);
    // set the new value
    gpio_state |= (val << first_gpio);
    // apply the new value to the gpiohs output
    *gpiohs->output_val.u32 = gpio_state;
}

If you want (and can, depending of other pin usage) to use the first 8 consecutive gpiohs bits, you can omit first_gpio and all shifting operations…

Similar functions can be implemented for input port.
Of course you can use any port width (from 1-bit to up to 32-bits), not only 8.

Accessing gpiohs this way is very fast (basically you only need one instruction to set/read zhe port, so theoretical pin change frequency is ~CPU frequency). In real life application you can easilly achive pin change frequency of 10~100 MHz.

1 Like