ESP32 In MicroPython: Using Hardware Registers
Written by Harry Fairhead & Mike James   
Monday, 19 June 2023
Article Index
ESP32 In MicroPython: Using Hardware Registers
Blinky
Sine Wave Generator

Blinky Revisited

Now we can re-write Blinky yet again, but this time using direct access to the GPIO registers.

from machine import mem32,Pin
from time import sleep_ms 
led = Pin(2,mode=Pin.OUT)
GPIOSet = 0x3FF44008
GPIOClear = 0x3FF4400C
mask = 1<<2
while True:
    mem32[GPIOSet] = mask
    sleep_ms(500)
    mem32[GPIOClear] = mask
    sleep_ms(500)

This program uses the standard MicroPython class to set the GPIO line to output. If you think that this is cheating, it is an exercise in setting the line correctly using the GPIO control register, but if you do this you risk getting out of sync with MicroPython’s internal state. That is, if you set a GPIO line to output don’t expect MicroPython to know anything about it.

To toggle GPIO2 we make use of the set and clear registers and a mask that has bit 2 set to 1. Notice that 1<<n is a bit pattern with bit n set to 1. Alternatively you could use:

mask = 0x02

Once we have the mask, the loop simply stores it in the set and clear register alternately. Notice that as only bit 2 is a 1 this only changes the state of GPIO2.

This raises the question of how fast is this direct manipulation of the GPIO line’s state? Using the same optimizations we used in Chapter 4 gives:

from machine import mem32,Pin
from time import sleep_ms 
@micropython.native
def blink():
    GPIOSet = 0x3FF44008
    GPIOClear = 0x3FF4400C
    mask = 1<<2
    while True:
        mem32[GPIOSet] = mask
        mem32[GPIOClear] = mask
led = Pin(2,mode=Pin.OUT)
blink()

If you try this out you will find that the pulses are slightly faster at 1.8µs compared to 2.7µs.

pulse1

This example is a demonstration rather than being useful, but there are some very useful functions we can write using our knowledge of how the GPIO lines are controlled. For example, MicroPython is limited to controlling a single GPIO line at a time, but the hardware can change or read multiple GPIO lines in a single register operation. This was introduced in Chapter 4 but without explanation.

For example:

def gpio_get():
    return mem32[0x3FF44004]

Here the get function simply reads the GPIO_OUT register which has a single bit for the output state of each GPIO line. Notice that GPIO lines that are set to output reflect their last written-to state – this is not a way of reading the line’s current state.

A set function simply writes the mask to the GPIO_OUT_W1TS_REG register:

def gpio_set(mask):
	mem32[0x3FF44008] = mask

A clear function is just as easy and writes to the GPIO_OUT_W1TC_REG register:

def gpio_clear(mask):
 	mem32[0x3FF4400C] = mask

As before, only the set bits in the mask are affected.

Example 1 - Simultaneous Setting of GPIO Lines

You use these two functions to set or clear any GPIO lines, but you often want to select a set of bits and set or clear them in one operation. For example, if you want to change two or more GPIO lines in phase, i.e. all high or all low, then you can use clear and set.

For example;

gpio_set(0x3)
gpio_clear(0x3)

sets the bottom 2 bits and so it toggles the GPIO0 and GPIO1. Both turn on and off at exactly the same time.

Now consider how you do the same thing but setting GPIO0 high when GPIO1 is low?

The best you can do is:

gpio_set(0x1)
gpio_clear(0x2)
gpio_set(0x2)
gpio_clear(0x1)

and, while this does set the GPIO lines correctly, the changes don’t happen at the same time.

What we need is a function that will set any group of GPIO lines to 0 or 1 at the same time:

def gpio_setgroup(value, mask):
    reg = machine.mem32[0x3FF44004]
    reg =  reg & ~mask
    value = value & mask
    reg = reg | value
    machine.mem32[0x3FF44004] = reg

The mask gives the GPIO lines that need to be changed, i.e. it determines the group and the value gives the state they are to be set to.

For example, if mask is 0111 and value is 0100 and the low four bits of the register are 1010 then reg & ~mask is 1000, value & mask is 0100 and finally reg | value is 1100. You can see that bits 0 to 3 have been set to 100 and bit 4 has been unchanged.

As demonstrated in Chapter 4, the value, mask function can be used to set GPIO lines simultaneously:

from machine import Pin
import machine
def gpio_setgroup(value, mask):
    machine.mem32[0x3FF44004] = 
     machine.mem32[0x3FF44004] & ~mask | value & mask
pin = Pin(2, Pin.OUT)
pin = Pin(4, Pin.OUT)
value1 = 1 << 2 | 0 << 4
value2 = 0 << 2 | 1 << 4
mask = 1 << 2 | 1 << 4
while True:
    gpio_setgroup(value1, mask)
    gpio_setgroup(value2, mask)


As we are changing the same pins each time, we only need a single mask. The value, however, changes each time. If you run this program you will see an almost perfect pair of out-of-phase 27µs pulses:

pulse2

 



Last Updated ( Tuesday, 20 June 2023 )