This is an old revision of the document!
We will be using the ESP32-C6 Sparrow board as the main development board for the lab assignments.
Also, for the most of the labs, we will be using the Visual Studio Code and Platformio environment, which you can download from here
After downloading and installing the PlatformIO extension, create a new project using any ESP32-C6 board. After project creation, you will need to edit the platformio.ini file and replace it with the following:
; PlatformIO Project Configuration File ; ; Build options: build flags, source filter ; Upload options: custom upload port, speed and extra flags ; Library options: dependencies, extra library storages ; Advanced options: extra scripting ; ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html [env:esp32-c6-sparrow] platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip board = esp32-c6-devkitm-1 framework = arduino ; use SPIFFS for on-board files board_build.filesystem = spiffs build_flags = -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1 -D ESP32_C6_env monitor_speed = 115200 lib_deps = adafruit/Adafruit NeoPixel@^1.11.0 adafruit/Adafruit GFX Library@^1.11.9 adafruit/Adafruit SSD1306@^2.5.10 adafruit/Adafruit BME680 Library dantudose/LTR308 library@^1.0 https://github.com/sparkfun/SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library h2zero/NimBLE-Arduino@^2.1.0
The board has a Neopixel attached on GPIO3 of the ESP32-C6 processor. Let's test if the board is working properly by turning on the LED. Use the code below and paste it in your main.c project file (you will have to rename it to main.cpp):
#include <Arduino.h> #include <Adafruit_NeoPixel.h> // --- config --- #define LED_PIN 3 // your NeoPixel data pin #define NUM_PIXELS 1 // change if you have more #define BRIGHTNESS 30 // 0..255 (keep modest if powered from USB) // Most WS2812/NeoPixel strips are GRB @ 800 kHz: #define PIXEL_TYPE (NEO_GRB + NEO_KHZ800) Adafruit_NeoPixel strip(NUM_PIXELS, LED_PIN, PIXEL_TYPE); static void solid(uint32_t c, uint16_t ms) { for (uint16_t i = 0; i < NUM_PIXELS; i++) strip.setPixelColor(i, c); strip.show(); delay(ms); } void setup() { strip.begin(); strip.setBrightness(BRIGHTNESS); strip.show(); // all off // Quick RGB sanity check (each color ~300 ms) solid(strip.Color(255, 0, 0), 300); // Red solid(strip.Color( 0, 255, 0), 300); // Green solid(strip.Color( 0, 0, 255), 300); // Blue solid(strip.Color( 0, 0, 0), 200); // Off } void loop() { // Smooth rainbow using HSV -> RGB with gamma correction static uint16_t hue = 0; // 0..65535 uint32_t c = strip.gamma32(strip.ColorHSV(hue)); for (uint16_t i = 0; i < NUM_PIXELS; i++) strip.setPixelColor(i, c); strip.show(); hue += 256; // step size (smaller = slower) delay(20); // frame rate (~50 FPS) }
Now, let's do a quick sensor bring-up for the BME680 (temperature, humidity, pressure and gas). The sensor is connected on the I2C bus. Use the code below to read values over the serial terminal.
#include <Arduino.h> #include <Wire.h> #include <Adafruit_BME680.h> // Change this if you want altitude computed for your location #define SEALEVEL_HPA (1013.25f) // Try both common I2C addresses Adafruit_BME680 bme; // use the default constructor bool beginBME680() { // SDA on pin 21 and SCL on pin 22 Wire.begin(21, 22); // Try 0x76 first if (bme.begin(0x76, &Wire)) { Serial.println("[BME680] Found at 0x76"); return true; } // Then 0x77 if (bme.begin(0x77, &Wire)) { Serial.println("[BME680] Found at 0x77"); return true; } Serial.println("[BME680] Sensor not found at 0x76 or 0x77. Check wiring/power."); return false; } void setupBME680() { // Oversampling & filter settings tuned for ~1 Hz updates bme.setTemperatureOversampling(BME680_OS_8X); bme.setHumidityOversampling(BME680_OS_2X); bme.setPressureOversampling(BME680_OS_4X); bme.setIIRFilterSize(BME680_FILTER_SIZE_3); // Enable gas heater: 320°C for 150 ms (typical example) bme.setGasHeater(320, 150); } void setup() { Serial.begin(115200); while (!Serial) { delay(100); } Serial.println("\n[BOOT] BME680 serial demo (1 Hz)"); if (!beginBME680()) { // Stay here so you can read the error while (true) { delay(1000); } } setupBME680(); } void loop() { // Trigger a reading and wait for completion if (!bme.performReading()) { Serial.println("[BME680] Failed to perform reading!"); delay(1000); return; } // Values from the Adafruit_BME680 library: float temperatureC = bme.temperature; // °C float pressureHpa = bme.pressure / 100.0f; // Pa -> hPa float humidityPct = bme.humidity; // % float gasOhms = bme.gas_resistance; // Ω float altitudeM = bme.readAltitude(SEALEVEL_HPA);// meters (approx.) // Print nicely Serial.print("T: "); Serial.print(temperatureC, 2); Serial.print(" °C | "); Serial.print("RH: "); Serial.print(humidityPct, 1); Serial.print(" % | "); Serial.print("P: "); Serial.print(pressureHpa, 2); Serial.print(" hPa | "); Serial.print("Gas: ");Serial.print(gasOhms, 0); Serial.print(" Ω | "); Serial.print("Alt: ");Serial.print(altitudeM, 1); Serial.println(" m"); // 1 Hz update delay(1000); }
Now let's make sure we can connect to WiFi. Load this WiFi scanner code:
#include <Arduino.h> #include <WiFi.h> // Arduino-ESP32 WiFi (works on ESP32-C6 with Arduino 3.x) static const char* authModeToStr(wifi_auth_mode_t m) { switch (m) { case WIFI_AUTH_OPEN: return "OPEN"; case WIFI_AUTH_WEP: return "WEP"; case WIFI_AUTH_WPA_PSK: return "WPA_PSK"; case WIFI_AUTH_WPA2_PSK: return "WPA2_PSK"; case WIFI_AUTH_WPA_WPA2_PSK: return "WPA/WPA2_PSK"; case WIFI_AUTH_WPA2_ENTERPRISE:return "WPA2_ENT"; case WIFI_AUTH_WPA3_PSK: return "WPA3_PSK"; case WIFI_AUTH_WPA2_WPA3_PSK: return "WPA2/WPA3"; case WIFI_AUTH_WAPI_PSK: return "WAPI_PSK"; case WIFI_AUTH_OWE: return "OWE"; default: return "UNKNOWN"; } } void setup() { Serial.begin(115200); delay(300); // Scan as a station, disconnected WiFi.mode(WIFI_STA); WiFi.persistent(false); WiFi.disconnect(true, true); WiFi.setSleep(false); Serial.println("\nWiFi scanner ready."); } void loop() { Serial.println("\n--- Scanning... ---"); // Synchronous scan; set 2nd arg to true to include hidden SSIDs int n = WiFi.scanNetworks(/*async=*/false, /*show_hidden=*/true); if (n <= 0) { Serial.println("No networks found."); } else { Serial.printf("Found %d network(s):\n", n); for (int i = 0; i < n; ++i) { String ssid = WiFi.SSID(i); String bssid = WiFi.BSSIDstr(i); int32_t rssi = WiFi.RSSI(i); int32_t ch = WiFi.channel(i); auto auth = (wifi_auth_mode_t)WiFi.encryptionType(i); bool hidden = (ssid.length() == 0); // no API needed if (hidden) ssid = "(hidden)"; Serial.printf("%2d) %-32s BSSID:%s CH:%2ld RSSI:%4ld dBm AUTH:%s\n", i + 1, ssid.c_str(), bssid.c_str(), (long)ch, (long)rssi, authModeToStr(auth) ); } } WiFi.scanDelete(); // free RAM delay(5000); }
Build, Upload and Monitor the results. You should be able to see a periodic scan of the available WiFi networks in your proximity.
Build the example below, which advertises the board on BLE. Install on your phone an app that scans nearby Bluetooth devices, such as nRF Connect. Check if your device is in the list.
#include <Arduino.h> #include <NimBLEDevice.h> static const char* DEVICE_NAME = "ESP32-C6 Demo"; static NimBLEUUID SERVICE_UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); static NimBLEUUID CHAR_UUID ("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"); NimBLEServer* gServer = nullptr; NimBLEService* gService = nullptr; NimBLECharacteristic* gChar = nullptr; void startBLE() { NimBLEDevice::init(DEVICE_NAME); gServer = NimBLEDevice::createServer(); gService = gServer->createService(SERVICE_UUID); gChar = gService->createCharacteristic( CHAR_UUID, NIMBLE_PROPERTY::READ ); gChar->setValue("Hello from ESP32-C6!"); gService->start(); NimBLEAdvertising* adv = NimBLEDevice::getAdvertising(); // Advertise our service UUID adv->addServiceUUID(SERVICE_UUID); // (v2.x) Build advertising + scan-response payloads explicitly NimBLEAdvertisementData advData; advData.setFlags(0x06); // LE General Discoverable + BR/EDR Not Supported NimBLEAdvertisementData scanData; scanData.setName(DEVICE_NAME); // put the name in scan response // you can also add manufacturer data here if you want: // std::string mfg = "\x34\x12C6"; scanData.setManufacturerData(mfg); adv->setAdvertisementData(advData); adv->setScanResponseData(scanData); // Appearance is still supported adv->setAppearance(0x0200); // Generic Tag NimBLEDevice::startAdvertising(); } void setup() { Serial.begin(115200); while (!Serial) { delay(10); } startBLE(); Serial.println("Advertising as ESP32-C6 Demo. Open nRF Connect -> Scan."); } void loop() { delay(1000); }