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

You can use MicroPython to directly access the hardware via registers. In this example we find out how to make waves - sine waves. This is an extract from Programming the ESP32 in MicroPython, the latest title in the I Programmer Library.

Programming the ESP32in MicroPython

By Harry Fairhead & Mike James


Buy from Amazon.



  1. The ESP32 – Before We Begin
  2. Getting Started
  3. Getting Started With The GPIO 
  4. Simple Output
  5. Some Electronics
  6. Simple Input
  7. Advanced Input – Interrupts
  8. Pulse Width Modulation
    PWM And The Duty Cycle
  9. Controlling Motors And Servos
  10. Getting Started With The SPI Bus
  11. Using Analog Sensors
    Analog Input
  12. Using The I2C Bus
    : I2C, HTU21D And Slow Reading 
  13. One-Wire Protocols
  14. The Serial Port
  15. Using WiFi
  16. Sockets
    Client Sockets
    SSL Client Sockets***NEW!
  17. Asyncio And Servers
  18. Direct To The Hardware
    Using Hardware Registers 


MicroPython provides classes and methods to let you access most of the major hardware features of the ESP32. They are very simple wrappers around the basic mechanism of working with the hardware – memory-mapped registers. Unfortunately at the time of writing there are many hardware features which are simply not exposed via MicroPython. In most cases it is possible to extend what you access using lower-level interactions with the hardware. This way you can stay in MicroPython while writing and reading the low-level, register-based hardware.

The obvious reason for knowing how to use memory-mapped registers is that if MicroPython doesn’t provide a function that does just what you want, you simply create it! Perhaps a better reason is just to know how things work. In this chapter we take a look at how the ESP32 presents its hardware for you to use and how to access it via basic software.


Some processors have special ways of connecting devices, but the ESP32’s processor uses the more common memory-mapping approach. In this, each external device is represented by a set of memory locations or “registers” that control it. Each bit in the register controls some aspect of the way the device behaves. Groups of bits also can be interpreted as short integers which set operating values or modes.

How do you access a register? MicroPython provides a number of ways of doing this but the simplest is to make use of the mem functions in the machine module:


Returns or sets a 32-bit value at the address


Returns or sets a 16-bit value at the address


Returns or sets an 8-bit value at the address

The only difficult part is in working out the address you need to use and the value that sets or resets the bits you need to modify. For example, if you look in the documentation you will find that the GPIO registers start at address 0x3FF4_4000. The registers are defined by their offset from this starting address or an absolute address.

So for example, the start of the table of GPIO registers is:

Name Description Address Access
GPIO_OUT_REG GPIO 0-31 output register 0x3FF44004



GPIO 0-31 output register_W1TS 0x3FF44008



GPIO 0-31 output register_W1TC 0x3FF4400C WO

This describes three registers which control the GPIOs in output mode. How the GPIO line gets into output mode is a matter of using other registers described later in the table. But if we assume that the GPIO line is fully configured in output mode then these three registers control the state of GPIO0 to GPIO31. There are three similar registers for GPIO32 to GPIO39.

The big problem in making use of this information is that the “Description” part of the table is cryptic and often incomplete. You almost have to know what sorts of things the register is used for before it makes any sense. The first register is simple – if you write a 1 to bit n then GPIOn will be set active, usually high voltage, and if you write a 0 to bit n then the line is deactivated, usually low voltage. The other two registers are slightly more difficult to understand due to the use of W1TS and W1TC – which stand for Write One To Set and Write One To Clear. Once you know this it is obvious that the first register is a bit-set register and the second a bit-clear register. That is, if you write a 1 to bit n using the W1TS register then GPIOn will be set active, but if you write it using the W1TC register, GPIOn will be deactivated.

You might wonder why we need three registers to control the GPIO lines? It is true that you don’t need anything beyond the first, but the other two make things easier. By writing a bit pattern to GPIO_OUT_REG you set or reset all of the GPIO lines depending on whether there is a 1 or a 0 at bit n. If you only want to change a subset of lines then you have to read the current state of the lines, notice whether GPIO_OUT_REG has read or write access, and then modify just the bits corresponding to the lines you want to change. This isn’t difficult, but you can avoid having to do this by using GPIO_OUT_W1TS_REG with a bit pattern that sets just the lines that correspond to a 1 or GPIO_OUT_W1TC_REG which resets the same lines.

This becomes easier to understand after an example.

Last Updated ( Tuesday, 20 June 2023 )