Skip to content

Introduction

  • SparkFun Qwiic Pocket Development Board - ESP32-C6
    SKU: DEV-22925


    SparkFun Qwiic Pocket Development Board - ESP32-C6

  • The SparkFun Qwiic Pocket Development Board - ESP32-C6features the ESP32-C6 Mini-1 module from espressif. The ESP32-C6 SoC is built around a RISC-V single-core processor with 4 MB flash memory and supports 2.4 GHz WiFi 6, Bluetooth® 5 (with Low Energy), Zigbee and Thread 802.15.4 wireless protocols. The module uses an integrated PCB antenna.
    The ESP32-C6 includes on-chip serial-to-UART conversion which helps reduce the number of components on the development board so we designed this Qwiic Dev Board to cram as much as we could into the Qwiic system's 1"x1" form factor. It also includes a USB-C connector, 2-pin JST connector for a single-cell LiPo battery along with a charging circuit for a connected battery, a Qwiic connector, and it also breaks out eight GPIO pins (including one UART).

    Purchase from SparkFun

Required Materials

To follow along with this guide you will need a USB-C cable to connect the dev board to your computer:

You may also want to get a Qwiic cable or kit to connect the Qwiic Dev Board - ESP32-C6 to other Qwiic devices:

Optional Materials

The Qwiic Dev Board - ESP32-C6 includes a 2-pin JST connector and integrated charging circuit for an attached single-cell LiPo battery. Below are a few options for batteries we recommend for battery-powered applications:

If you prefer a soldered connection or want to modify the solder jumpers on this board, you may need some of the products listed below:

Smart Watch Example Materials

This guide also includes a demo using the Qwiic Pocket Development Board - ESP32-C6 with three Qwiic breakouts to create a DIY "smart watch" (sans the wristband) complete with display, pedometer, and motion controls. If you'd like to follow along and build this example, you'll need the following Qwiic breakouts:

You'll also need three Qwiic cables of your choice of length and a set of standoffs to assemble the smart watch. We recommend either the 50mm or 100mm flexible cables as shown below:

Suggested Reading

We designed this board for integration into SparkFun's Qwiic connect system. Click on the banner below to learn more about the SparkFun Qwiic Connect System.

Before getting started with this Hookup Guide, you may want to read through the tutorials below if you are not familiar with the concepts covered in them or want a refresher. If you are using either of the Qwiic Shields linked above, we recommend reading through their respective Hookup Guides before continuing with this tutorial:

Hardware Overview

Let's take a closer look at the ESP32-C6 module and other hardware on the Qwiic Pocket Development Board.

ESP32-C6 Mini-1 Module

The ESP32-C6 Mini-1 module from espressif features a 32-bit RISC-V single-core processor with an integrated wireless stack. The wireless stack is compatible with 2.4 GHz WiFi 6, Bluetooth® 5.3, Zigbee and Thread (802.15.4) and uses an on-board PCB antenna.

Highlighting the ESP32-C6 Module.

This development board uses the Mini version of the C6 module which has slightly less computing power in exchange for greater power efficiency. This makes this module perfect for battery-powered applications. The module features a wide range of peripheral options including SPI, UART, LPUART, I2C, I2S, LED PWM, USB Serial/JTAG controller, ADC and more. Many of these peripherals can be mapped to any GPIO pin though some are tied to specific pins. Refer to the datasheet for a complete overview of the ESP32-C6-MINI-1.

The ESP32-C6 has 4 MB Flash memory along with 512 KB SRAM (high power)/ 16 KB SRAM (low power). The module uses pin strapping to configure boot mode parameters. The board defaults to standard mode (GPIO 9 internal pull-up, all other strapping pins floating) but it can be set to other parameters by performing the following pin strapping:

  • SDIO Sampling and Driving Clock Edge - MTMS & MTDI
  • Chip Boot Mode - GPIO8 & GPIO9
  • ROM Code Printing to UART - GPIO8
  • JTAG Signal Source - GPIO15

Power Components

USB-C Connector

The USB-C connector on the board acts as the primary serial interface for the ESP32-C6 module as well as a power input. It connects directly to the ESP32-C6's USB serial converter. The 5V USB input voltage is regulated down to 3.3V through a voltage regulator with a max current of 500mA@3.3V.

Highlighting the USB-C connector.

JST Connector & Battery Charger

The board has a 2-pin JST connector to connect a single-cell Lithium Ion (LiPo) battery for battery-powered applications. It also has an MCP73831 battery charger to charge an attached battery. The charge rate is set to 214mA@3.3V. The MCP73831 receives power from the V_USB line so it only is powered when 5V is provided either over USB or the V_USB PTH pin. If applying voltage directly to the V_USB pin make sure it does not exceed 5.5V.

Highlighting the JST connector and charge IC.

Pinout & Qwiic Connector

PTH Pins

The Qwiic Dev Board routes eight GPIO pins to a pair of 0.1"-spaced PTH headers. One side has 3.3V and GND pins along with I/O pins 2, 3, 4, and 5. The other side has through-holes for V_USB and VBatt as well as I/O pins 16 (TX), 17 (RX), 18, and 19.

Highlighting the PTH Pinout.

Qwiic Connector

There's a Qwiic connector on the board tied to the ESP32-C6's Low Power I2C bus (I/O pins 6 and 7) for easy integration into SparkFun's Qwiic ecosystem. The Qwiic connector provides connections for SDA, SCL, 3.3V, and Ground.

Highlighting the Qwiic connector.

Buttons

There are two buttons on the board labeled RESET and BOOT. The RESET button is tied to the ESP32-C6's Enable (EN) pin and resets the module when pressed. The BOOT button puts the ESP32-C6 into bootloader mode when held down during power on or reset.

Highlighting the Boot and Reset buttons.

LEDs

The board has three LEDs labeled PWR, STAT, and CHG. The red Power (PWR) LED indicates whenever the 3.3V circuit is powered. The blue Status (STAT) LED is tied to IO23 on the ESP32. The yellow Charge (CHG) LED indicates whenever the MCP73831 is charging a connected LiPo battery.

Highlighting the ESP32-C6 Module.

Solder Jumpers

There are four solder jumpers on the Qwiic Dev Board labeled CHG, I2C, SHLD, and PWR. The table below outlines the jumpers' labels, default state, function, and any notes regarding their use:

Highlighting the ESP32-C6 Module.

Label Default State Function Notes
CHG CLOSED Completes Charge LED circuit Open to disable Charge LED
I2C CLOSED Pulls the SDA/SCL lines to 3.3V through a pair of 2.2kΩ resistors Three-way jumper. Open completely to disable pullups
SHLD CLOSED Ties the USB-C shield pin to the ground plane Open to isolate USB-C shield pin from the board's ground plane
PWR CLOSED Completes the Power LED circuit Open to disable the Power LED

Board Dimensions

The Qwiic Dev Board - ESP32-C6 matches the 1" x 1" (22.6mm x 22.6mm) Qwiic form factor and has two mounting holes that fit a 4-40 screw.

Board Dimensions.

Hardware Assembly

Basic USB Assembly

Basic assembly of the Qwiic Pocket Development Board only requires a USB-C cable connecting the board to a computer. Just plug the cable into the USB-C connector like the photo below shows and move on to the Software Setup section.

USB connected to Qwiic Pocket Dev Board.

Note

Your computer may not recognize the board as a known USB device if you have not installed the espressif boards package in Arduino and/or installed the espressif IDF.

Battery Assembly

If you prefer a battery-powered application, plug a single-cell LiPo battery into the 2-pin JST connector on the underside of the board like the photo below. Remember, the MCP73831 only charges the battery when V_USB has voltage present either from the USB-C connector or through the V_USB PTH pin.

Battery connected to Qwiic Pocket Dev Board.

For tips on the proper use of a LiPo battery and the 2-pin JST connector, please read through our Single Cell LiPo Battery Care tutorial.

Software Setup

Attention

If this is your first time using Arduino, please read through our tutorial on installing the Arduino IDE. If you have not installed an Arduino library before, we recommend you check out our installation guide.

With the Qwiic Pocket Dev Board connected to our computer, it's time to set up the boards package in Arduino.

Installing espressif Arduino Boards

Important

As of this board's release, espressif is currently working on an update to v3.0.0 for their boards manager with support for the Qwiic Pocket Development Board - ESP32-C6. This release is in alpha phase at the moment. Open the preferences menu by navigating to File > Preferences. Look at the bottom of the Preferences menu for "Additional boards manager URLS" and then copy this JSON link into that field: https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json

Click "Ok" and then open the Boards Manager tool, search for "espressif ESP32" and install the latest alpha release (3.0.0-alpha2 or later). This install process may take some time but when it completes, you can select "SparkFun ESP32-C6 Qwiic Pocket" as the board to upload code to the Qwiic Pocket Development Board. We'll keep an eye out for the full release of v3.0.0 and update this page once it is out.

The espressif ESP32 Arduino Boards package includes the Qwiic Pocket Development Board - ESP32-C6. Install it by opening the Boards Manager tab on the left side of the IDE, then search for "espressif ESP32" and install the latest version of the ESP32 boards package as the screenshot below shows. This assumes the use of Arduino 2.x. Legacy users (Arduino 1.8x and previous) can find the Boards Manager tool in File/Tools/Boards Manager.

ESP32 Boards manager install.

espressif IDF

Users who prefer to use espressif's development toolkit, espressif IDF, can get started by following their instructions here and ESP32-C6 specific documentation here.

Arduino Examples

Now that we've installed the espressif boards package in Arduino, it's time to upload our first sketch to make sure everything is working properly.

This basic example makes sure the board package installed correctly and the board accepts programming properly to blink the blue STAT LED on the board every second. Open the example in Arduino by navigating to File > Examples > Basics > 01-Blink.

USB CDC On Boot Settings

Take note of the option labeled "USB CDC on Boot" when selecting the Board from the Tools menu. This option sets the serial outputs and defines their label for use in code. The SparkFun variants default to Enable USB CDC on boot which sets both Serial and Serial0 as available serial ports. In this configuration, Serial corresponds to the direct USB/Serial converter on the chip (and the USB-C interface) and Serial0 corresponds to the UART0 bus (default pins are 16 and 17).

With either setting, Serial1 is available and refers to the UART1 bus (default pins are 4 and 5).

Select the board (SparkFun ESP32-C6 Qwiic Pocket) and Port and click "Upload". After uploading you should see the STAT LED on the board blinking every second.

Smart Watch Assembly

Building the smart watch with the Pocket Development Board and the three Qwiic breakouts requires a few assembly steps to build the smart watch stack and connect everything using standofs, screws, hookup wire, and Qwiic cables. This assembly also requires a bit of through-hole soldering so if you've never soldered before or would like a refresher, take a look at our How to Solder: Through-Hole Soldering tutorial.

Note

Some users may want to skip ahead and program the Qwiic Pocket Development Board before assembling the smart watch. If you are one of these people, skip ahead to the Software Setup and Smart Watch Example sections to upload the code before building your smart watch stack.

Start by soldering a short length of wire (roughly one inch) to pin 4 on the Pocket Dev Board and then solder the other end to INT1 on the BMA400 Breakout:

Smart watch interrupt wire soldered.

Next, stack the BMA400 Breakout on top of the Qwiic Pocket Dev Board using two standoffs and two nuts to secure them to the Qwiic Pocket Dev Board; then connect the two with a Qwiic cable like the photo below show:

BMA400 stacked on top of the Qwiic Pocket Development Board.

Now stack the RTC Breakout on top of those two with a Qwiic cable between the BMA400 and RTC breakouts and finally add the Qwiic OLED on top with a Qwiic cable between this and the RTC breakout. You may want to connect the Qwiic cable to the OLED before securing it into place depending on which connector you opt for as one is a bit of a tough reach once the board is attached. Secure everything in place while being careful not to damage any of the components since the standoffs and other hardware come very close to them.

With the boards stacked and connected together you can plug the battery in at this point, but can also wait until after programming the Pocket Development Board as well. The final assembly should look like the photo below:

Completed smart watch assembly.

Smart Watch Example

Smart Watch Example Libraries

The smart watch demo requires three Arduino libraries for the three Qwiic breakouts used in it. Install the libraries through Arduino's Library Manager Tool by searching for the following: "Micro OLED Breakout", "SparkFun Qwiic RTC RV1805", and "SparkFun BMA400". Users who prefer to install them manually can download ZIP folders of the libraries by clicking the buttons below:

SparkFun Micro OLED Arduino Library (ZIP)

SparkFun RV-1805 Arduino Library (ZIP)

SparkFun BMA400 Arduino Library (ZIP)

Upload Demo Code

With the Qwiic Pocket Dev Board smart watch assembled, board definitions and libraries installed, we can upload the smart watch code to the Qwiic Pocket Development board. Copy the code below into a blank sketch or you can download it from the GitHub repository.

Smart Watch Demo
// Inlcude libraries needed
#include <Wire.h>
#include "SparkFun_BMA400_Arduino_Library.h"
#include <SparkFun_Qwiic_OLED.h>
#include <SparkFun_RV1805.h>
#include <WiFi.h>
#include "time.h"

// WiFi SSID and password, change these for your WiFi network!
const char* ssid = "your-ssid";
const char* password = "your-password";

// Create RTC object
RV1805 rtc;

// Create OLED object
QwiicMicroOLED oled;

// Create sensor object
BMA400 accelerometer;

// Pin used for button input
int buttonPin = 9;

// Pin used for measuring battery voltage
int batteryPin = 1;

// Pin used for interrupt detection
int interruptPin = 4;

// Flag to know when interrupts occur
volatile bool interruptOccurred = false;

// Flag to know when user input is requested
bool userInput = true;

// Screen index
uint8_t screenIndex = 0;

// Enum to define various screen indices
typedef enum
{
    SCREEN_CLOCK,
    SCREEN_STEP_COUNTER,
    SCREEN_BATTERY_VOLTAGE,
    SCREEN_SYNC_RTC,
    SCREEN_MAX
} screen_index_t;

// Screen size properties
uint8_t midX = 0;
uint8_t midY = 0;
uint8_t clockRadius = 0;
uint8_t rowHeight = 0;

// Screen display flags
volatile bool updateScreen = true;
volatile bool screenOn = true;

// Screen update timer
uint32_t lastUpdateTime = 0;
uint32_t updateInterval = 1000/25;

// Sleep timer to automatically enter deep sleep after inactivity
uint32_t sleepTimerStart = 0;
uint32_t sleepTimerPeriod = 60000;

// Standard time zone relative to GMT (not daylight savings!)
int8_t timeZone = -7;

void setup()
{
    // Start serial
    Serial.begin(115200);
    Serial.println("Watch demo begin!");

    // Configure button pin as input with pullup
    pinMode(buttonPin, INPUT_PULLUP);

    // Configure battery pin as input
    pinMode(batteryPin, INPUT);

    // The ESP32-C6 has an attenuator on the ADC, which defaults to 11dB. This causes the resolution to be reduced
    analogSetAttenuation(ADC_0db);

    // Begin I2C driver
    Wire.begin(6, 7);

    // Begin RTC
    while(!rtc.begin())
    {
        Serial.println("Error: RTC not connected, check wiring and I2C address!");
        delay(1000);
    }
    Serial.println("RTC connected!");

    // Manually set time (uncomment if needed, set current time and upload, then comment out and upload again)
    // rtc.setTime(0,second,minute,hour,day,month,year,weekday);

    // Begin OLED
    while(!oled.begin())
    {
        Serial.println("Error: OLED not connected, check wiring and I2C address!");
        delay(1000);
    }
    Serial.println("OLED connected!");

    // Set screen size parameters
    midX = oled.getWidth() / 2;
    midY = oled.getHeight() / 2;
    clockRadius = min(midX, midY) - 2;
    rowHeight = oled.getHeight() / 3;

    // Begin accelerometer
    while(accelerometer.beginI2C() != BMA400_OK)
    {
        Serial.println("Error: BMA400 not connected, check wiring and I2C address!");
        delay(1000);
    }
    Serial.println("BMA400 connected!");

    // Set up tap detection
    bma400_tap_conf tapConfig =
    {
        .axes_sel = BMA400_TAP_Z_AXIS_EN, // Which axes to evaluate for interrupts (X/Y/Z in any combination)
        .sensitivity = BMA400_TAP_SENSITIVITY_7, // Sensitivity threshold, up to 7 (lower is more sensitive)
        .tics_th = BMA400_TICS_TH_6_DATA_SAMPLES, // Max time between top/bottom peaks of a single tap
        .quiet = BMA400_QUIET_60_DATA_SAMPLES, // Minimum time between taps to trigger interrupt
        .quiet_dt = BMA400_QUIET_DT_4_DATA_SAMPLES, // Minimum time between 2 taps to trigger double tap interrupt
        .int_chan = BMA400_INT_CHANNEL_1 // Which pin to use for interrupts
    };
    accelerometer.setTapInterrupt(&tapConfig);

    // Set up generic 1 interrupt, used to detect when device is rotated away from user
    bma400_gen_int_conf genConfig =
    {
        .gen_int_thres = 125, // 8mg resolution (eg. gen_int_thres=5 results in 40mg)
        .gen_int_dur = 100, // 10ms resolution (eg. gen_int_dur=5 results in 50ms)
        .axes_sel = BMA400_AXIS_XYZ_EN, // Which axes to evaluate for interrupts (X/Y/Z in any combination)
        .data_src = BMA400_DATA_SRC_ACCEL_FILT_LP, // Which filter to use (must be 100Hz, datasheet recommends filter 2)
        .criterion_sel = BMA400_ACTIVITY_INT, // Trigger interrupts when active or inactive
        .evaluate_axes = BMA400_ANY_AXES_INT, // Logical combining of axes for interrupt condition (OR/AND)
        .ref_update = BMA400_UPDATE_MANUAL, // Whether to automatically update reference values
        .hysteresis = BMA400_HYST_96_MG, // Hysteresis acceleration for noise rejection
        .int_thres_ref_x = (uint16_t) -360, // Raw 12-bit acceleration value
        .int_thres_ref_y = 0, // Raw 12-bit acceleration value
        .int_thres_ref_z = 360, // Raw 12-bit acceleration value (at 4g range (default), 512 = 1g)
        .int_chan = BMA400_INT_CHANNEL_1 // Which pin to use for interrupts
    };
    accelerometer.setGeneric1Interrupt(&genConfig);

    // Set up generic 2 interrupt, used to detect when device is rotated towards user
    genConfig.gen_int_thres = 50;
    genConfig.gen_int_dur = 50;
    genConfig.criterion_sel = BMA400_INACTIVITY_INT;
    genConfig.evaluate_axes = BMA400_ALL_AXES_INT;
    accelerometer.setGeneric2Interrupt(&genConfig);

    // Enable step counting
    bma400_step_int_conf stepConfig = {.int_chan = BMA400_INT_CHANNEL_1};
    accelerometer.setStepCounterInterrupt(&stepConfig);

    // Set INT 1 pin as push pull active high
    accelerometer.setInterruptPinMode(BMA400_INT_CHANNEL_1, BMA400_INT_PUSH_PULL_ACTIVE_1);

    // Enable interrupts from accelerometer
    accelerometer.enableInterrupt(BMA400_STEP_COUNTER_INT_EN, true);
    accelerometer.enableInterrupt(BMA400_SINGLE_TAP_INT_EN, true);
    accelerometer.enableInterrupt(BMA400_DOUBLE_TAP_INT_EN, true);
    accelerometer.enableInterrupt(BMA400_GEN1_INT_EN, true);
    accelerometer.enableInterrupt(BMA400_GEN2_INT_EN, true);
    accelerometer.enableInterrupt(BMA400_LATCH_INT_EN, true);

    // Attach interrupt handler
    pinMode(interruptPin, INPUT);
    attachInterrupt(digitalPinToInterrupt(interruptPin), bma400InterruptHandler, RISING);

    // Reset sleep timer
    sleepTimerStart = millis();
}

void loop()
{
    // Check if we need to enter deep sleep
    if((digitalRead(buttonPin) == LOW) || (millis() > (sleepTimerStart + sleepTimerPeriod)))
    {
        enterDeepSleep();
    }

    // Check whether interrupt triggered from accelerometer
    if(interruptOccurred)
    {
        // Reset interrupt flag
        interruptOccurred = false;

        // Reset sleep timer
        sleepTimerStart = millis();

        Serial.print("Interrupt occured: ");

        // Check what the interrupt source was
        uint16_t interruptStatus = 0;
        accelerometer.getInterruptStatus(&interruptStatus);
        if(interruptStatus & BMA400_ASSERTED_S_TAP_INT)
        {
            // Single tap, increment screen index if screen is on
            if(screenOn)
            {
                Serial.println("Single tap");
                screenIndex++;
                updateScreen = true;
            }
        }
        else if(interruptStatus & BMA400_ASSERTED_D_TAP_INT)
        {
            // Double tap used for input within a screen index, if applicable.
            // This always follows a single tap, so decrement screen index and
            // set input flag, if screen is on
            if(screenOn)
            {
                Serial.println("Double tap");
                screenIndex--;
                updateScreen = true;
                userInput = true;
            }
        }
        else if(interruptStatus & BMA400_ASSERTED_GEN1_INT)
        {
            // Generic 1 indicates the device was rotated away from the user, in
            // which case we'll turn the screen off
            Serial.println("Generic 1");
            screenOn = false;
            oled.erase();
            oled.display();

            // The generic interrupts trigger constantly. Disable generic 1, and
            // enable generic 2
            accelerometer.enableInterrupt(BMA400_GEN1_INT_EN, false);
            accelerometer.enableInterrupt(BMA400_GEN2_INT_EN, true);
        }
        else if(interruptStatus & BMA400_ASSERTED_GEN2_INT)
        {
            // Generic 2 indicates the device was rotated towards the user, in
            // which case we'll turn the screen on
            Serial.println("Generic 2");
            screenOn = true;
            updateScreen = true;

            // The generic interrupts trigger constantly. Disable generic 2, and
            // enable generic 1
            accelerometer.enableInterrupt(BMA400_GEN1_INT_EN, true);
            accelerometer.enableInterrupt(BMA400_GEN2_INT_EN, false);
        }
        else if(interruptStatus & BMA400_ASSERTED_STEP_INT)
        {
            // Step counter incremented, update the screen to give live count
            Serial.println("Step");
            updateScreen = true;
        }
        else
        {
            // Unknown source, just print and ignore it
            Serial.println("Unknown interrupt");
            Serial.println(interruptStatus, HEX);
        }
    }

    // Figure out what to display on the screen, if anything
    if(screenOn)
    {
        screenIndex %= SCREEN_MAX;
        switch(screenIndex)
        {
            case SCREEN_CLOCK:
                displayClock();
                break;
            case SCREEN_STEP_COUNTER:
                displayStepCounter();
                break;
            case SCREEN_BATTERY_VOLTAGE:
                displayBatteryVoltage();
                break;
            case SCREEN_SYNC_RTC:
                displaySyncRTC();
                break;
            default:
                // Shouldn't get here, but just in case
                screenIndex = 0;
                break;
        }
    }

    // Reset flags
    userInput = false;
}

// Displays time an analog clock style
void displayClock()
{
    // Check if we need to update the screen. Uses a timer to limit refresh rate
    if(!updateScreen)
    {
        if(millis() >= (lastUpdateTime + updateInterval))
        {
            lastUpdateTime += updateInterval;
        }
        else
        {
            return;
        }
    }
    updateScreen = false;

    // Get current time from RTC
    rtc.updateTime();
    int timeMilli = millis() % 1000;
    int timeSecs = rtc.getSeconds();
    int timeMins = rtc.getMinutes();
    int timeHours = rtc.getHours() % 12;

    // Clear OLED
    oled.erase();

    // Draw circle to define clock face
    oled.circle(midX, midY, clockRadius);

    // Draw a solid circle to indicate 1 second intervals
    int x = (clockRadius+1) * sin(timeMilli * 2*PI/1000);
    int y = (clockRadius+1) *-cos(timeMilli * 2*PI/1000);
    oled.circleFill(midX + x, midY + y, 2);

    // Draw second hand
    x = clockRadius * sin(timeSecs * 2*PI/60);
    y = clockRadius *-cos(timeSecs * 2*PI/60);
    oled.line(midX, midY, midX + x, midY + y);

    // Draw minute hand
    x = clockRadius*.75 * sin(timeMins * 2*PI/60);
    y = clockRadius*.75 *-cos(timeMins * 2*PI/60);
    oled.line(midX, midY, midX + x, midY + y);

    // Draw hour hand
    x = clockRadius*.5 * sin(timeHours * 2*PI/12);
    y = clockRadius*.5 *-cos(timeHours * 2*PI/12);
    oled.line(midX, midY, midX + x, midY + y);

    // Display on the OLED
    oled.display();
}

// Displays step count
void displayStepCounter()
{
    // Check if screen needs to be updated
    if(!updateScreen)
        return;
    updateScreen = false;

    // Create text buffer
    char displayStr[10] = {0};
    uint8_t numChars = 0;

    // Get step count and activity type from accelerometer
    uint32_t stepCount = 0;
    uint8_t activityType = 0;
    accelerometer.getStepCount(&stepCount, &activityType);

    // Clear screen
    oled.erase();

    // Print header
    numChars = sprintf(displayStr, "Steps");
    printRow(0, displayStr, numChars);

    // Print number of steps
    numChars = sprintf(displayStr, "%li", stepCount);
    printRow(1, displayStr, numChars);

    // Print activity type
    switch(activityType)
    {
        case BMA400_RUN_ACT:
            numChars = sprintf(displayStr, "Running");
            break;
        case BMA400_WALK_ACT:
            numChars = sprintf(displayStr, "Walking");
            break;
        case BMA400_STILL_ACT:
            numChars = sprintf(displayStr, "Standing");
            break;
        default:
            numChars = sprintf(displayStr, "Unknown");
            break;
    }
    printRow(2, displayStr, numChars);

    // Display on OLED
    oled.display();
}

// Displays battery voltage
void displayBatteryVoltage()
{
    // Check if screen needs to be updated
    if(!updateScreen)
        return;
    updateScreen = false;

    // Create text buffer
    char displayStr[10] = {0};
    uint8_t numChars = 0;

    // Clear screen
    oled.erase();

    // Print header
    numChars = sprintf(displayStr, "Battery");
    printRow(0, displayStr, numChars);
    numChars = sprintf(displayStr, "Voltage");
    printRow(1, displayStr, numChars);

    // Measure battery voltage, averaged over several samples
    uint8_t numSamples = 100;
    uint32_t batteryRaw = 0;
    for(uint8_t i = 0; i < numSamples; i++)
    {
        batteryRaw += analogRead(batteryPin);
    }
    float voltage = 3.3 / 4095 * batteryRaw / numSamples;

    // Measured voltage is less than true battery voltage, partly because of the
    // voltage divider circuit, and partly because the ADC has a non-zero input
    // current. This scaler was experimentally found to correct these effects,
    // though the nominal value may differ between boards
    voltage *= 1.688;

    // Print battery voltage
    numChars = sprintf(displayStr, "%.3fV", voltage);
    printRow(2, displayStr, numChars);

    // Display on OLED
    oled.display();
}

void displaySyncRTC()
{
    // Check if screen needs to be updated
    if(!updateScreen)
        return;
    updateScreen = false;

    // Create text buffer
    char displayStr[10] = {0};
    uint8_t numChars = 0;

    // Clear screen
    oled.erase();

    // Print RTC sync message
    numChars = sprintf(displayStr, "Sync RTC");
    printRow(0, displayStr, numChars);
    numChars = sprintf(displayStr, "with NTP?");
    printRow(1, displayStr, numChars);

    // Display on OLED
    oled.display();

    // Check if user requested to synchronize RTC
    if(userInput)
    {
        // Clear screen
        oled.erase();

        // Print RTC sync message
        numChars = sprintf(displayStr, "Syncing");
        printRow(0, displayStr, numChars);
        numChars = sprintf(displayStr, "RTC...");
        printRow(1, displayStr, numChars);

        // Display on OLED
        oled.display();

        // Start connecting to WiFi network
        WiFi.begin(ssid, password);
        while(WiFi.status() != WL_CONNECTED)
        {
            Serial.println("Connecting to WiFi...");
            delay(1000);
        }

        // Sync with an NTP server
        configTime(timeZone * 3600, 3600, "pool.ntp.org");

        // Disconnect WiFi
        WiFi.disconnect(true);
        WiFi.mode(WIFI_OFF);

        // Get updated time
        struct tm timeinfo;
        getLocalTime(&timeinfo);

        // Set RTC to current time
        uint8_t time[TIME_ARRAY_LENGTH];
        time[TIME_HUNDREDTHS] = rtc.DECtoBCD(millis());
        time[TIME_SECONDS] = rtc.DECtoBCD(timeinfo.tm_sec);
        time[TIME_MINUTES] = rtc.DECtoBCD(timeinfo.tm_min);
        time[TIME_HOURS] = rtc.DECtoBCD(timeinfo.tm_hour);
        time[TIME_DATE] = rtc.DECtoBCD(timeinfo.tm_mday);
        time[TIME_MONTH] = rtc.DECtoBCD(timeinfo.tm_mon);
        time[TIME_YEAR] = rtc.DECtoBCD(timeinfo.tm_year);
        time[TIME_DAY] = rtc.DECtoBCD(timeinfo.tm_wday);
        rtc.setTime(time, TIME_ARRAY_LENGTH);

        // Clear screen
        oled.erase();

        // Print RTC sync message
        numChars = sprintf(displayStr, "RTC");
        printRow(0, displayStr, numChars);
        numChars = sprintf(displayStr, "Synced!");
        printRow(1, displayStr, numChars);

        // Display on OLED
        oled.display();
    }
}

// Helper function to display text on rows 0-2
void printRow(uint8_t row, char* text, uint8_t numChars)
{
    // Set cursor location so text is centered
    oled.setCursor(midX - 5 * numChars / 2, rowHeight * row);

    // Print text at cursor location
    oled.print(text);
}

// Helper function to minimuze total current consumption and enter deep sleep
void enterDeepSleep()
{
    // Turn off display
    oled.erase();
    oled.display();
    oled.displayPower(false);

    // Minimize current consumption of RTC
    rtc.enableLowPower();
    rtc.enableSleep();
    rtc.writeRegister(RV1805_CTRL1, 0x77);

    // Disable accelerometer interrupts, except double tap
    accelerometer.enableInterrupt(BMA400_STEP_COUNTER_INT_EN, false);
    accelerometer.enableInterrupt(BMA400_SINGLE_TAP_INT_EN, false);
    // accelerometer.enableInterrupt(BMA400_DOUBLE_TAP_INT_EN, false);
    accelerometer.enableInterrupt(BMA400_GEN1_INT_EN, false);
    accelerometer.enableInterrupt(BMA400_GEN2_INT_EN, false);

    // Could have had a new interrupt fire, read status to clear old interrupts
    uint16_t interruptStatus = 0;
    accelerometer.getInterruptStatus(&interruptStatus);

    // Set interrupt pin as wakeup source
    esp_deep_sleep_enable_gpio_wakeup(BIT(interruptPin), ESP_GPIO_WAKEUP_GPIO_HIGH);

    // Enter deep sleep. This never returns, instead the code restarts entirely
    esp_deep_sleep_start();
}

// ISR for accelerometer interrupts
void bma400InterruptHandler()
{
    // Set flag to be evaluated later
    interruptOccurred = true;
}

Now select the board (SparkFun ESP32-C6 Qwiic Pocket) and the Port and click the "Upload" button. Once the code finishes uploading, you should see a clock display on the Qwiic Micro OLED screen.

Code to Note

The code has a few settings users should take note of to get the most out of the smart watch demo. Let's take a closer look.

BMA400 Settings

The code sets several thresholds on the BMA400 for tap/double-tap detection, step counting, and tilt-thresholds to fairly generous values to make all of the motion actions easy to trigger but some may find these to be too easily triggered. The code sets the BMA400's tap detection sensitivity to the max value (7) so if you want to make it less sensitive adjust this line:

.sensitivity = BMA400_TAP_SENSITIVITY_7,

NTP Configuration

Syncing the RTC to a NTP (Network Time Protocol) server requires an internet connection. The code has placeholders for these values so you'll need to enter your network credentials in the section of code shown below:

const char* ssid = "your-ssid";
const char* password = "your-password";

Smart Watch Actions

The smart watch code includes a bunch of motion-triggered actions to change the OLED display so let's take a closer look at them to make sure we get the most out of our smart watch.

Deep Sleep Wake & Return

The code tells the circuit to enter deep sleep on two conditions: 60 seconds of inactivity or pressing the BOOT button on the bottom of the Qwiic Development Board. Double-tapping the watch wakes it from deep sleep and turns on the OLED to display the last screen viewed (default after upload is the analog clock).

Tilt Functions

The OLED display is off by default to help conserve battery power. It enables the display when you're "checking your watch" so try tilting it in "towards" you with the OLED screen upright (ribbon cable on the bottom) and the display should turn on like the photo below:

Smart Watch Clock display.

Tilt the screen "away" to turn the screen back off. Otherwise, screen should return to deep sleep after 60 seconds of inactivity (ie. no tilts or taps).

Tap Menu Options

When the OLED is active you can tap on the screen (or just trigger a tap action for the BMA400 like setting the watch down or a similar action) to cycle through the four display options: Clock, Step Counter, Battery Voltage, and Sync with NTP Server.

Analog Clock

This display is a simple analog clock interface without hour markings. It also features a nifty second counter signified by a dot travelling around the clock face.

Smart Watch Clock display.

Step Counter

The smart watch code also takes advantage of the BMA400's built-in step counting feature. It displays steps counted as well as the current action: Standing, Walking, and Running.

Smart Watch Pedometer display.

Battery Voltage

The third screen option shows the remaining battery voltage of a connected battery voltage to keep track of remaining charge on of an attached battery.

Smart Watch Battery voltage display.

Sync with NTP Server

The last display option lets you remotely sync the RTC with an NTP (assuming it has a WiFi connection) by double-tapping the display when "Sync with NTP Server" is displayed.

Smart Watch NTP Sync display.

Troubleshooting

USB CDC on Boot Settings

Take note of the option labeled "USB CDC on Boot" when selecting the Board from the Tools menu. This option sets the serial outputs and defines their label for use in code. The SparkFun variants default to Enable USB CDC on boot which sets both Serial and Serial0 as available serial ports. In this configuration, Serial corresponds to the direct USB/Serial converter on the chip (and the USB-C interface) and Serial0 corresponds to the UART0 bus (default pins are 16 and 17).

With either setting, Serial1 is available and refers to the UART1 bus (default pins are 4 and 5).

General Troubleshooting

Need Help?

If you need technical assistance or more information on a product that is not working as you expected, we recommend heading over to the SparkFun Technical Assistance page for some initial troubleshooting.

SparkFun Technical Assistance Page

If you can't find what you need there, the SparkFun Forums is a great place to search product forums and ask questions.

Account Registration Required

If this is your first visit to our forum, you'll need to create a Forum Account to post questions.

Resources:

Check out the resources below for more information about the Qwiic Pocket Development Board - ESP32-C6: