I’m trying to control the state of a gpio pin on my xiao-esp32c6 module which is based on an esp32c6 chip. I’ve attached an LED to pin D3 of my xiao module. I can control the LED using the standard Arduino digitialWrite statements. I’d like to learn how to control the same pin using GPIO bit-banging. I followed the advice given here (https://www.youtube.com/watch?v=QrJf6CF_g_8&t=597s). I found information stating that my xiao module pin D3 is connected to esp32c6 GPIO21. Here is the code:
// Code to test GPIO pin control via register bit-banging.
// I connect an LED to pin D3 on the XIAO-ESP323C6 module.
// Pin D3 maps to GPIO21 (IO# 21) on the ESP32C6 module
#include <stdint.h>
#define GPIO_OUT_W1TS_REG 0x3FF44008
#define GPIO_OUT_W1TC_REG 0x3FF4400C
#define GPIO_ENABLE_REG 0x3FF44020
#define GPIO_PIN_ESP32 21
void setup() {
volatile uint32_t* gpio_enable_reg = (volatile uint32_t*) GPIO_ENABLE_REG;
// volatile uint32_t* gpio_out_w1ts_reg = (volatile uint32_t*) GPIO_OUT_W1TS_REG;
// volatile uint32_t* gpio_out_w1tc_reg = (volatile uint32_t*) GPIO_OUT_W1TC_REG;
*gpio_enable_reg = (1 << GPIO_PIN_ESP32); // Enable the gpio pin as an output
// *gpio_enable_reg = (1 << GPIO_PIN_ESP32); // Set the bit
// Insert a delay
// *gpio_out_w1tc_reg = (1 << GPIO_PIN_ESP32); // Clear the bit
}
void loop() {
}
I should add that the code compiles just fine but the processor keeps rebooting with the following error message:
Guru Meditation Error: Core 0 panic’ed (Store access fault). Exception was unhandled.
Any advice will be much appreciated.
Thanks!
Hi there,
I like it, OLD school…
(Store Access Fault—)
The register addresses 0x3FF44008
, etc., are for the ESP32 / ESP32S / ESP32-C3, not for the ESP32-C6.
The Boot loop , You set it to boot laoder mode to stop it (com port may change) reflash with a known good blink sketch
Why It Crashes:
On the ESP32C6:
- Register addresses are different
- You cannot directly reuse old ESP32 memory-mapped register tricks from ESP32/ESP32-S/C3
- The C6 is based on RISC-V, and the GPIO peripheral base address changed
AL , has this code that is similar
#define GPIO_OUT_W1TS_REG 0x60004008
#define GPIO_OUT_W1TC_REG 0x6000400C
#define GPIO_ENABLE_W1TS_REG 0x60004024
#define GPIO_PIN 21
void setup() {
volatile uint32_t* gpio_enable = (volatile uint32_t*)GPIO_ENABLE_W1TS_REG;
volatile uint32_t* gpio_set = (volatile uint32_t*)GPIO_OUT_W1TS_REG;
volatile uint32_t* gpio_clear = (volatile uint32_t*)GPIO_OUT_W1TC_REG;
*gpio_enable = (1 << GPIO_PIN); // Enable output
*gpio_set = (1 << GPIO_PIN); // Set HIGH
delay(1000);
*gpio_clear = (1 << GPIO_PIN); // Set LOW
}
void loop() {}
The ESP32C6 is not register-compatible with the ESP32/ESP32S2/S3. Any direct memory access code from those chips must be revalidated against the new RISC-V register map.
Your right to learn bit-banging — but you’ve got to adjust for the silicon your on. Or use ESP-IDF macros if you want portable low-level access. 
HTH
GL
PJ 
1 Like
Thank you, PJ! I’ve made some changes to my code such that it is now compiling and running without crashing the processor. However, I’m not seeing the GPIO pin change state as hoped. Please let me know if you see any obvious problems with my new code as follows:
// Code to test GPIO pin control via register bit-banging.
// I connect an LED to pin D3 on the XIAO-ESP323C6 module.
// Pin D3 maps to GPIO21 (IO# 21) on the ESP32C6 module
#include <stdint.h>
#define GPIO_OUT_REG 0x60091004
#define GPIO_OUT_W1TS_REG 0x60091008
#define GPIO_OUT_W1TC_REG 0x6009100C
#define GPIO_ENABLE_W1TS_REG 0x60091024
#define GPIO_ENABLE_W1TC_REG 0x60091028
#define GPIO_IN_REG 0x6009103C
#define GPIO_PIN_ESP32 21
volatile uint32_t* gpio_enable_w1ts_reg = (volatile uint32_t*) GPIO_ENABLE_W1TS_REG;
volatile uint32_t* gpio_enable_w1tc_reg = (volatile uint32_t*) GPIO_ENABLE_W1TC_REG;
volatile uint32_t* gpio_out_w1ts_reg = (volatile uint32_t*) GPIO_OUT_W1TS_REG;
volatile uint32_t* gpio_out_w1tc_reg = (volatile uint32_t*) GPIO_OUT_W1TC_REG;
volatile uint32_t* gpio_out_reg = (volatile uint32_t*) GPIO_OUT_REG;
void setup() {
*gpio_enable_w1ts_reg = (1 << GPIO_PIN_ESP32);
*gpio_enable_w1tc_reg = (1 << GPIO_PIN_ESP32);
Serial.begin(115200);
}
void loop() {
*gpio_out_w1ts_reg = (1 << GPIO_PIN_ESP32); // Set the bit
printf(“Post bit set : GPIO_OUT_REG = %ld\n”,*gpio_out_reg);
delay(2000);
*gpio_out_w1tc_reg = (1 << GPIO_PIN_ESP32); // Clear the bit
printf(“Post bit clear: GPIO_OUT_REG = %ld\n”,*gpio_out_reg);
}
***Here are a few lines resulting from the printf statements I added:
11:26:27.504 → Post bit set : GPIO_OUT_REG = 2097152
11:26:29.531 → Post bit clear: GPIO_OUT_REG = 0
11:26:29.531 → Post bit set : GPIO_OUT_REG = 2097152
11:26:31.524 → Post bit clear: GPIO_OUT_REG = 0
Thanks again!
1 Like
I’m not certain that I’m using the proper base address for the GPIO registers. In the esp32c6 technical manual I see the following:
7 IO MUX and GPIO Matrix (GPIO, IO MUX) GoBack
GPIO_FUNCn_OUT_SEL may be modified by the hardware. For such reason, it’s recommended to reconfigure
these registers when the GPIO is free from the control of ETM task channel.
GPIO has eight event channels, and the ETM events that each event channel can generate are:
• GPIO_EVT_CHx_RISE_EDGE: Indicates that the output signal of the corresponding GPIO filter (see Figure
7-1) has a rising edge;
• GPIO_EVT_CHx_FALL_EDGE: Indicates that the output signal of the corresponding GPIO filter (see
Figure 7-1) has a falling edge;
• GPIO_EVT_CHx_ANY_EDGE: Indicates that the output signal of the corresponding GPIO filter (see Figure
7-1) is reversed.
The specific configuration of the event channel is as follows:
• Set GPIO_EXT_ETM_CHx_EVENT_EN to enable event channel x (0 ~ 7).
• Configure GPIO_EXT_ETM_CHx_EVENT_SEL to y (0 ~ 30), i.e., select one from the 31 GPIOs.
Note:
One GPIO can be selected by one or more event channels.
In specific applications, GPIO ETM events can be used to trigger GPIO ETM tasks. For example, event channel
0 selects GPIO0, GPIO1 selects task channel 0, and the GPIO_EVT_CH0_RISE_EDGE event is used to trigger
the GPIO_TASK_CH0_TOGGLE task. When a square wave signal is input to the chip through GPIO0, the chip
outputs a square wave signal with a frequency divided by 2 through GPIO1.
7.15 Register Summary
7.15.1 GPIO Matrix Register Summary
The addresses in this section are relative to GPIO base address provided in Table 5-2 in Chapter 5 System and
Memory.
Then, in Table 5.2, I find the following entry:
GPIO Matrix 0x6009_1000
Does this seem like the correct base address for the set of GPIO registers?
Thanks again!
Hi there,
Sure, No problem…
- Pin 21 (D3) is not actually configured as an output
*gpio_enable_w1ts_reg = (1 << GPIO_PIN_ESP32); // Set output enable
*gpio_enable_w1tc_reg = (1 << GPIO_PIN_ESP32); // Clear output enable
This second line disables the output right after enabling it.
It’s cancelling itself out.
Fix: Remove the gpio_enable_w1tc_reg
line entirely.
While direct register writes do work, GPIO21 still needs to be mapped for output mode, either via:
- a proper
pinMode(21, OUTPUT);
, or
- the IO_MUX setup for GPIO21 via register if going full low-level
- it’s good codeong practice
But since this is bit-banging, adding just:
pinMode(21, OUTPUT);
before doing raw writes ensures the IOMUX is correctly set for output. You don’t lose low-level control by calling it once in setup()
.
make sure the LED is wired correctly for a Xiao
GPIO21 ---[resistor]--> LED (anode) --> GND
You can use the on board LED also mapped to GPIO 15
HTH
GL
PJ 
1 Like
The output of my print statements in hex format follow:
11:53:35.228 → Post bit clear: GPIO_OUT_REG = 0
11:53:35.228 → Post bit set : GPIO_OUT_REG = 200000
My code is now working thanks to your advice to add the pinMode command. Thank you very much!
Hi there,
So I got this and it works…
#include <stdint.h>
#define GPIO_OUT_REG 0x60091004
#define GPIO_OUT_W1TS_REG 0x60091008
#define GPIO_OUT_W1TC_REG 0x6009100C
#define GPIO_ENABLE_W1TS_REG 0x60091024
#define GPIO_IN_REG 0x6009103C
#define GPIO_PIN_ESP32 21
volatile uint32_t* gpio_enable_w1ts_reg = (volatile uint32_t*) GPIO_ENABLE_W1TS_REG;
volatile uint32_t* gpio_out_w1ts_reg = (volatile uint32_t*) GPIO_OUT_W1TS_REG;
volatile uint32_t* gpio_out_w1tc_reg = (volatile uint32_t*) GPIO_OUT_W1TC_REG;
volatile uint32_t* gpio_out_reg = (volatile uint32_t*) GPIO_OUT_REG;
void setup() {
Serial.begin(115200);
pinMode(GPIO_PIN_ESP32, OUTPUT); // Important: sets the IO_MUX correctly
*gpio_enable_w1ts_reg = (1 << GPIO_PIN_ESP32); // Set GPIO21 as output
}
void loop() {
*gpio_out_w1ts_reg = (1 << GPIO_PIN_ESP32); // Set high
Serial.printf("Post bit set : GPIO_OUT_REG = %ld\n", *gpio_out_reg);
delay(2000);
*gpio_out_w1tc_reg = (1 << GPIO_PIN_ESP32); // Set low
Serial.printf("Post bit clear: GPIO_OUT_REG = %ld\n", *gpio_out_reg);
delay(2000);
}
Serial output, now…
Post bit set : GPIO_OUT_REG = 2097152
Post bit clear: GPIO_OUT_REG = 0
Post bit set : GPIO_OUT_REG = 2097152
Post bit clear: GPIO_OUT_REG = 0
Post bit set : GPIO_OUT_REG = 2097152
HTH
GL
PJ 
1 Like
GPIO_OUT_REG = 2097152 → Binary: 0b0010 0000 0000 0000 0000 0000
= Bit 21 is HIGH (2²¹ = 2097152) 
GPIO_OUT_REG = 0 → All bits LOW → pin is LOW 
This confirms:
-
You’re setting bit 21 (GPIO21 → D3) → HIGH
-
You’re clearing it → LOW
-
This is exactly what you’d expect from correctly toggling GPIO21 using W1TS/W1TC (write-one-to-set / write-one-to-clear) registers on the ESP32-C6.
-
The printed values directly match the bitmask of 1 << 21
.
EDIT:
Mark this as the Solution so others can FInd it. 
1 Like