Week 04 - Embedded Programming

This week we have the following tasks to complete:

  • Demonstrate and compare the toolchains and development workflows for available embedded architectures.
  • Review the datasheet of your microcontroller.
  • Write a program for a microcontroller.
  • Simulate its operation, enabling interaction with local input/output devices and communication via remote wired or wireless connections.
  • Optional: Test the program on a development board.
  • Optional: Experiment with different programming languages and development environments.

Preparing for the Week

In preparation for this week, I switched from VS Codium to VS Code to facilitate the installation of PlatformIO. o program ESP32 microcontrollers from Espressif, the ESP-IDF framework is required. I followed the (official setup guide for the ESP-IDF extension in VS Code )[https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/get-started/vscode-setup.html].

After the installation, I executed the following command in the command palette and followed the setup wizard as outlined in the extension documentation

    ESP-IDF: Configure ESP-IDF Extension

Arduino Workshop by Ferdi (link following)

Selecting a Microcrontroller

For my final project, I will require a wireless communication module. ESP32 microcontrollers are ideal for this purpose, as they offer integrated Wi-Fi and Bluetooth capabilities. Our lab provides the Xiao ESP32C3 and Xiao ESP32S3, so I will use one of these models for my initial experiments.

For the final project, I will likely use an ESP32 without a development board, rather than a Xiao board. However, this decision will be made in a future phase of the project.

In my final project I need rotary encoders as a input device, ARGB LED's as a output device, components that require pwm signals and some webinterface interaction. So in the following I look up in some of these areas.

Working with Rotary Encoders

I started by researching rotary encoders to gain a better understanding of their functionality. I found an interesting project called StepperTester, which demonstrates stepper motor control using a rotary encoder.

The program operates in three modes:

  1. Adjust the RPM for mode 2.
  2. Rotate the stepper motor by one step when the rotary encoder is turned.
  3. Control the stepper motor's RPM using the rotary encoder.

The mode is selected by pressing the encoder button, which is built into the rotary encoder. This functionality is implemented using interrupts.

The simulation requires a stepper driver and a display to function correctly. The display shows the selected mode and the configured value.

Controlling WS2812B LEDs

For controlling WS2812B LEDs, libraries such as FastLED, NeoPixelBus, and NeoPixel are commonly used. I found an insightful benchmark comparing the performance of these three libraries. I tried both the FastLED library and the Neopixel library.

To work with rotary encoders in my own projects, I explored the ai-esp32-rotary-encoder and the Encoder libraries. My implementation was based on their example files for better understanding.

Simulating ESP32C3 in Wokwi

I attempted to adapt a Wokwi projectfor the Xiao ESP32-C3. The results are shown below:

wokwi 01

wokwi 02

#include <Adafruit_NeoPixel.h> // library for NeoPixels
//#include <Encoder.h> // library for rotary encoder
#include <AiEsp32RotaryEncoder.h>
#include <Arduino.h>
#define ROTARY_ENCODER_A_PIN D10
#define ROTARY_ENCODER_B_PIN D9
#define ENCODER_BTN D8
#define ROTARY_ENCODER_VCC_PIN -1
#define ROTARY_ENCODER_STEPS 2

#define PIN_NEO_PIXEL D6  // Arduino pin that connects to NeoPixel
#define NUM_PIXELS 4 // The number of LEDs (pixels) on NeoPixel
//#define ENCODER_BTN 18 // rotary encoder button pin

AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ENCODER_BTN, ROTARY_ENCODER_VCC_PIN, ROTARY_ENCODER_STEPS);

Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800); // set the NeoPixel ring initialization
//Encoder myEnc(22, 23); // initialize the rotary encoder

///////////////////////////////---------------------------////////////////////////////////
void rotary_onButtonClick()
{
    static unsigned long lastTimePressed = 0; // Soft debouncing
    if (millis() - lastTimePressed < 500)
    {
            return;
    }
    lastTimePressed = millis();
    Serial.print("button pressed ");
    Serial.print(millis());
    Serial.println(" milliseconds after restart");
}

void rotary_loop()
{
    //dont print anything unless value changed
    if (rotaryEncoder.encoderChanged())
    {
            Serial.print("Value: ");
            Serial.println(rotaryEncoder.readEncoder());
    }
    if (rotaryEncoder.isEncoderButtonClicked())
    {
            rotary_onButtonClick();
    }
}


void IRAM_ATTR readEncoderISR()
{
    rotaryEncoder.readEncoder_ISR();
}
///////////////////////////////---------------------------////////////////////////////////


byte color_blue_RGB[3] = {0, 0, 255}; // blue color in RGB color space
byte color_red_RGB[3] = {255, 0, 0}; // red color in RGB color space
byte color_blend_RGB[3]; // variable to store blended RGB color

byte color_blue_HSV[3] = {170, 255, 255}; // blue color in HSV color space
byte color_red_HSV[3] = {0, 255, 255}; // red color in HSV color space
byte color_blend_HSV[3]; // variable to store blended HSV color


int encoder_value_old = 0; // previous value of rotary encoder
int encoder_value_new = 0; // current value of rotary encoder
int percentage_value = 50; // percentage value to fill the ring: 0 - 100%


int mode = 0; // 0 = white, 1 = blue to red RGB, 2 = blue to red HSV


// function to blend between two colors
void blend_colors (byte color_start[3], byte color_end[3], byte color_blended[3], float percentage /*0-100*/) {
  for (int i = 0; i < 3; i ++) {
    // linear interpolation between two values
    color_blended[i] = round((float)color_start[i] + (((float)color_end[i] - (float)color_start[i]) * (percentage / 100.0)));
  }
}

void setup() {
  Serial.begin(115200);
  NeoPixel.begin();  // initialize the NeoPixel strip object
  pinMode(ENCODER_BTN, INPUT_PULLUP); // set pin for rotary encoder button


    //we must initialize rotary encoder
    rotaryEncoder.begin();
    rotaryEncoder.setup(readEncoderISR);
    //set boundaries and if values should cycle or not
    //in this example we will set possible values between 0 and 1000;
    bool circleValues = false;
    rotaryEncoder.setBoundaries(-100, 100, circleValues); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)

    /*Rotary acceleration introduced 25.2.2021.
   * in case range to select is huge, for example - select a value between 0 and 1000 and we want 785
   * without accelerateion you need long time to get to that number
   * Using acceleration, faster you turn, faster will the value raise.
   * For fine tuning slow down.
   */
    //rotaryEncoder.disableAcceleration(); //acceleration is now enabled by default - disable if you dont need it
    rotaryEncoder.setAcceleration(250); //or set the value - larger number = more accelearation; 0 or 1 means disabled acceleration
}

void loop() {


Serial.print(percentage_value);
  // read the new value of rotary encoder
  //encoder_value_new = myEnc.read(); // read the encoder value
  //in loop call your custom function which will process rotary encoder values
    encoder_value_new = rotaryEncoder.readEncoder();
    delay(50); //or do whatever you need to do...
  if (encoder_value_new != encoder_value_old) { // if the old value does not equal the new value
    percentage_value = constrain(percentage_value - (encoder_value_new - encoder_value_old), 0, 100); // set the percentage value between 0-100%
    encoder_value_old = encoder_value_new; // update the old encoder value
  }


  if (digitalRead(ENCODER_BTN) == LOW) {
    // switch the mode
    mode++;
    if (mode > 2) {
      mode = 0;
    }
    delay(500); // add a small delay
  }

  NeoPixel.clear();  // set all pixel colors to 'off'. It only takes effect if pixels.show() is called

  // go over every single pixel on the neopixel ring
  for (int pixel = 0; pixel < NUM_PIXELS; pixel++) {  // for each pixel
    if ((pixel * (100 / (NUM_PIXELS - 1))) < percentage_value) { // fill pixels based on the rotary encoder value
      if (mode == 0) { // draw white pixels
        // set pixels to WHITE color
        NeoPixel.setPixelColor(pixel, NeoPixel.Color(255, 255, 255));  // set all pixels to white color

      } else if (mode == 1) { // blend between blue and red in RGB
        // blend two colors in RGB color space
        blend_colors(color_blue_RGB, color_red_RGB, color_blend_RGB, pixel * (100.0 / (NUM_PIXELS - 1)));
        // set the pixel to the blended color
        NeoPixel.setPixelColor(pixel, NeoPixel.Color(color_blend_RGB[0], color_blend_RGB[1], color_blend_RGB[2]));

      } else if (mode == 2) { // blend between blue and red in HSV
        // blend two colors in HSV color space
        blend_colors(color_blue_HSV, color_red_HSV, color_blend_HSV, pixel * (100.0 / (NUM_PIXELS - 1)));
        // set the pixel to the blended color
        NeoPixel.setPixelColor(pixel, NeoPixel.ColorHSV((unsigned int)color_blend_HSV[0] * 256, color_blend_HSV[1], color_blend_HSV[2]));

      }
    }
  }

  //NeoPixel.setBrightness(50);  // don´t forget to uncomment this line for real Arduino to make it less bright
  NeoPixel.show(); // show all the set pixels on neopixel ring
}

This setup integrates a rotary encoder with WS2812B LEDs.

Wokwi enables code simulation for various microcontrollers, including the Arduino Uno, Xiao ESP32-C3, and Raspberry Pi Pico. However, it does not simulate actual electrical circuits—e.g., resistors are not required in Wokwi but are necessary in real-world applications.

Additional Experiments

I also tested the Arduino IDE by programming an Arduino Nano with the blink example:

Arduino IDE

Arduino 1

Since breadboards are not allowed, I performed a quick soldering job, which, despite its imperfections, worked successfully:

Arduino 2

Another workflow for programming microcontrollers is using PlatformIO within VS Code. As a demonstration, I programmed an Attiny84V soldered onto a binary clock used in workshops at Unikat, the origin of our FabLab.


To create this page, I used ChatGPT to check my syntax and grammar.

Copyright 2025 < Benedikt Feit > - Creative Commons Attribution Non Commercial

Source code hosted at gitlab.fabcloud.org