Can I program a an ESP32C6 with the esp-idf?

This article is part of a series.

So to go along with the server I want to have an object that talks to it. Which means one with a stable WiFi chip. Very popular right now is espressif’s ESP32-C6

The ESP32-C6 SoC (System on Chip) supports Wi-Fi 6 in 2.4 GHz band, Bluetooth 5, Zigbee 3.0 and Thread1.3. It consists of a high-performance (HP) 32-bit RISC-V processor, an low-power (LP) 32-bit RISC-V processor,wireless baseband and MAC (Wi-Fi, Bluetooth LE, and 802.15.4), RF module, and numerous peripherals. Wi-Fi,Bluetooth and 802.15.4 coexist with each other and share the same antenna.

I was interested in trying out a RISC-V chip since I hadn’t played with one of those before.

Seeed Studio’s XIAO ESP32C6 won out as the dev board, honestly because todbot collects boards that have the same form factor as Adafruit’s QT Py and he had an extra. Also its small size is perfect for my target project.

Other ESP32-C6 dev boards include (info links, not purchase links):

About the Board

The XIAO ESP3232C6 board is tiny, but it packs A LOT in.

block diagram showing location of leds, buttons and antenna mount

pin diagram

Of the two led’s on the board one is the classic “built in led” to blink hello world to, and the other is hooked into the battery management system. Hopefully I’ll get back to that later.

The red light behavior for the XIAO ESP32C6 is as follows:

When no battery is connected:
The red light turns on when the Type-C cable is connected and turns off after 30 seconds.
When a battery is connected and the Type-C cable is plugged in for charging:
The red light flashes.
When the battery is fully charged via the Type-C connection:
The red light turns off.

The two buttons are the reset button and the boot button.

While the boot button is tiny I’m hoping later in the project I should be able to find a toothpick or something to temporarily hijack it for hello worlding a button.

Board Proof of Life

It’s fairly rare these days to get a dud board, but it’s best to make sure the board actually works with familiar tooling before launching into unfamiliar tooling.

Seeed actually provides step by step instructions with screen shots on how to get things set up in the Arduino IDE, an environment I know well.

One of the things I do with the example blink sketch is change the the blink patter to be uneven.

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);  // turn the LED on (HIGH is the voltage level)
  delay(500);                      // wait for a second
  digitalWrite(LED_BUILTIN, LOW);   // turn the LED off by making the voltage LOW
  delay(2000);                      // wait for a second
}

On the Xiao, this code will have the LED be ON for longer than OFF because the LED’s current is being sinked by the pin, rather than sourced from the pin. This is more the norm in production electronics vs hobby electronics more.

Install Official Tools

Espressif wraps its SDK in a pretty robust set of tools (Integrated Development Framework), for which they’ve written very explicit install instructions:

Here’s a summary, but I suggest reading the full page, making sure its actually about the target you’re looking for. (i.e. the url says esp32c6 NOT simply esp32. esp32 is actually a different family than esp32c6 and its tooling won’t work!)

## required
brew install cmake ninja dfu-util
## recommended
brew install ccache

Had:

New:

Note, ccache posted a fairly ominous warning… maybe I will start using containers more…

To install symlinks for compilers that will automatically use
ccache, prepend this directory to your PATH:
  /opt/homebrew/opt/ccache/libexec

If this is an upgrade and you have previously added the symlinks to
your PATH, you may need to modify it to the path specified above so
it points to the current version.

NOTE: ccache can prevent some software from compiling.
ALSO NOTE: The brew command, by design, will never use ccache.

So espressif wants you to put their tooling at the root of the user, but does provide instruction on how to map to somewhere else.

Also, there is a VSCode plugin that will do this all for you, but I find VSCode tooling very aggressive and prefer custom scripts that don’t start automagically doing things I didn’t ask to be done. I also don’t enjoy vendor lock in. I also find doing these things myself helps with trouble shooting later. YMMV.

mkdir -p ~/esp 
cd ~/esp
git clone -b v5.5.1 --recursive https://github.com/espressif/esp-idf.git
## Install the tools
cd ~/esp/esp-idf
./install.sh esp32c6 #note NOT esp32, esp32c6 <---- !!!!!

Hello World with Official Tools

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/linux-macos-setup.html#step-4-set-up-the-environment-variables

In the terminal window that will be the shell doing the building and running:

## Set up the environment variables
. $HOME/esp/esp-idf/export.sh
## Go to the dev folder
cd ~/esp
## pull out the example
cp -r $IDF_PATH/examples/get-started/hello_world .
## linux
# ls /dev/ | grep tty
## macOS 
ls /dev/ | grep cu. ## or ls /dev/cu.*

Mine was called /dev/cu.usbmodem2101, plug into a different port, it will change.

## SPECIFIC TO MINE
SOME_PORT=/dev/cu.usbmodem2101
cd ~/esp/hello_world
idf.py set-target esp32c6
# this creates sdkconfig, look at it
cat sdkconfig
## optional/not needed for hello world. THe below edits sdkconfig 
## with verification to help prevent errors
# idf.py menuconfig 
idf.py build
idf.py -p $SOME_PORT flash
# assumes 40 MHz, use idf.py menuconfig to change if needed
idf.py -p $SOME_PORT monitor
# CNTRL+] to leave

The hello world example doesn’t touch the GPIO which, so on to blink.

If in a totally new Terminal window

. $HOME/esp/esp-idf/export.sh
ls /dev/ | grep cu. ## or ls /dev/cu.*
SOME_PORT= dev.cu.someusbmodem

Pull out the blink example code and go…

## or where ever you want to store your files
cd ~/esp
cp -r $IDF_PATH/examples/get-started/blink .
## Set the GPIO_BLINK pin to 15 here
cd blink
# this is PER PROJECT, do it in the project folder. 
idf.py set-target esp32c6
## MUST change Example Configuration. It's not an LED strip, GPIO pin (needs to be 15), etc. 
idf.py menuconfig
idf.py build
## do it on one this time
idf.py -p $SOME_PORT flash monitor

Also went fairly smoothly and easily. Yet I don’t know that I’ve actually learned anything really about the SDK, about RISC-V…

Pulling it Apart

One thing I will say about the VSCode plugin is that it did help making a new project off of a base template and that is nice. I will look to see if that’s a feature of the idf.

Starting from the blink example I tore out a lot of code and deleted a lot of files.

Tore out the specialized config stuff, left in the logging

// Needed for logging to serial monitor
#include <stdio.h>
#include "esp_log.h"

// Needed for portTICK_PERIOD_MS
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// Needed for io pins
#include "driver/gpio.h"

#define BLINK_GPIO 15
#define BLINK_PERIOD 2000

// logging prefix
static const char *TAG = "simple_blink";

static uint8_t s_led_state = 0;

static void blink_led(void)
{
    /* Set the GPIO level according to the state (LOW or HIGH)*/
    gpio_set_level(BLINK_GPIO, s_led_state);
}

static void configure_led(void)
{
    ESP_LOGI(TAG, "Example configured to blink GPIO LED!");
    gpio_reset_pin(BLINK_GPIO);
    /* Set the GPIO as a push/pull output */
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
}

void app_main(void)
{
    /* Configure the peripheral according to the LED type */
    configure_led();

    while (1) {
        // pin HIGH is __LED OFF__ for the XIAO built in LED. 
        ESP_LOGI(TAG, "Turning the LED %s!", s_led_state == true ? "OFF":"ON");
        blink_led();
        /* Toggle the LED state */
        s_led_state = !s_led_state;
        // calculating the millis by hand based on the crystal speed is 
        // ANNNNNOOOOOOYYYYING. Keeping FreeRTOS. 
        vTaskDelay(BLINK_PERIOD / portTICK_PERIOD_MS);
    }
}

Links to the functions that are left:

To do this without the SDK, one has to do things like the make a vector table, etc. I found one example that did that for an older esp32 chip that was not a RISC-V.

I think I want to save that energy for once I’ve started writing is Swift, if at all this project.

Summary

Pretty smooth sailing. Next post, blinking with Swift.

This article is part of a series.