Week 3
Embedded Programming
Part 1: Comparing Embedded Architectures
Goal: demonstrate and compare the toolchains and development workflows for alternative embedded architectures.
This is my first time exploring electronics or embedded programming, so I'll start from the ground up and learn about + compare some of the different architectures available. After some quick googling and ChatGPT, here's a summary of what I found:
Toolchains - this is all of the software tools used together to develop, compile, build, and install the code onto the embedded systems (like a microcontroller or a processor). Toolchains allow developers to go from code in high level language like C++ into the actual machine code that can be executed on the relevant hardware. This includes the compiler, debugging tools, build system, etc.
Embedded architectures - this is the overall design + structure of the different hardware and software components, which determines how the processor or microcontroller is interacting with things like memory. Some important parts of an embedded architecture includes the processor type, memory, word size, peripherals, instruction set, and the operating system / bare metal. The processor type is at the core of this architecture, and could be something like a microcontroller or microprocessor. The key difference is that microprocessors support computing operatations and handle processing tasks (but needs external memory and peripherals), whereas microcontrollers are full basic computing units (with it's own internal RAM/ROM). I found this guide particularly helpful for comparing the two.
Next, let's compare the toolchains and development workflows for alternative embedded architectures--here's the tl;dr on what I looked into.
ARM vs AVR: these are two prominent microcontroller architectures (source).
- AVR microcontrollers have the benefits of low power consumption and a simple architecture that is easier to use for beginners--but they have limited processing power and are less flexible due to limited peripherals.
- ARM microcontrollers is more popular due to more powerful processing capabilities, more variants, and a larger surrounding ecosystem of tools and libraries. However, they also have higher power requirements and a higher learning curve.
ESP32C3:
- Part of the open source RISC-V architecture, RISC-V 32-bit single-core processor (source)
- Built in Wi-Fi and Bluetooth, great for networked applications
- High clock speed (160 MHz) + low power consumption, great for IoT and wearables
- Simple development flow for Xiao ESP32C3 (tutorial here): download the Arduino IDE, add the ESP32 board package, and select the XIAO_ESP32C3 board. Connect to the XIAO ESP32C3 with a USB-C cable
- Can also use ESP-IDF (Espressif IoT Development Framework) for development (tutorial here)
RP2040:
- Dual-core ARM Cortex-M0+ processor, 133 MHz clock speed
- Memory - 264KB of SRAM, 2MB of QSPI flash
- No built in WiFi / Blutooth
- Peripherals: GPIO, UART, I2C, SPI, PWM, ADC
- General-purpose microcontroller, good for real-time control systems, processing sensor data, robotics
- Programmable I/O for creating custom communication protocols and dual-core processing that is helpful for real-time tasks
SAMD21:
- Single-core ARM Cortex-M0+ (32-bit), 48 MHz clock speed
- Memory - 32KB of SRAM, 256KB of flash
- No built-in wireless/bluetooth
- Peripherals: GPIO, UART, I2C, SPI, PWM, ADC, built in USB port
- Lower power consumption, so good for energy efficient applications
Here's a great table I had ChatGPT put together for me :)
Part 2: Write and Simulate a Program
Goal: write a program for a microcontroller, and simulate its operation, to interact (with local input &/or output devices) and communicate (with remote wired or wireless connections) its operation.
I used Wokwi for this part of the assignment, building a simple circuit with LEDs and a button. There are 3 different modes (lights off, all lights on, and sequential blinking), and pressing the button switches between these three modes.
The code for this is shown below:
// Define pins
#define RED_LED_PIN D2
#define GREEN_LED_PIN D3
#define BLUE_LED_PIN D4
#define BUTTON_PIN D5
// Variables for the button and mode
int mode = 0;
int lastButtonState = HIGH;
int currentButtonState = HIGH;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50; // Debounce delay in milliseconds
// Variables for non-blocking timing
unsigned long previousMillis = 0; // last time an LED was updated
const long interval = 500; // interval to blink the LEDs
int currentLED = 0; // Tracks which LED is currently active (0 = red, 1 = green, 2 = blue)
void setup() {
Serial.begin(115200);
pinMode(RED_LED_PIN, OUTPUT);
pinMode(GREEN_LED_PIN, OUTPUT);
pinMode(BLUE_LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.println("Press the button to switch modes.");
}
void loop() {
// Read the button state
int reading = digitalRead(BUTTON_PIN);
// Reset debounce timer if button state has changed
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
// Only act if the button state is LOW (pressed) and debounce has passed
if ((millis() - lastDebounceTime) > debounceDelay && reading == LOW) {
// Change the mode when button is pressed
mode++;
if (mode > 2) {
mode = 0;
}
Serial.print("Mode changed to: ");
Serial.println(mode);
// Reset LED states when changing mode
digitalWrite(RED_LED_PIN, LOW);
digitalWrite(GREEN_LED_PIN, LOW);
digitalWrite(BLUE_LED_PIN, LOW);
// Wait for button release before allowing next press
while (digitalRead(BUTTON_PIN) == LOW) {
delay(10);
}
}
// Update the last button state
lastButtonState = reading;
// Call the appropriate function based on the current mode
switch (mode) {
case 0:
allLEDsOff();
break;
case 1:
allLEDsOn();
break;
case 2:
nonBlockingSequentialBlink();
break;
}
}
// Mode 0: Turn all LEDs off
void allLEDsOff() {
digitalWrite(RED_LED_PIN, LOW);
digitalWrite(GREEN_LED_PIN, LOW);
digitalWrite(BLUE_LED_PIN, LOW);
}
// Mode 1: Turn all LEDs on
void allLEDsOn() {
digitalWrite(RED_LED_PIN, HIGH);
digitalWrite(GREEN_LED_PIN, HIGH);
digitalWrite(BLUE_LED_PIN, HIGH);
}
// Mode 2: Non-blocking sequential blinking of LEDs
void nonBlockingSequentialBlink() {
unsigned long currentMillis = millis();
// Check if it's time to change the LED state based on the interval
if (currentMillis - previousMillis >= interval) {
// Save the last time you updated the LEDs
previousMillis = currentMillis;
// Turn off all LEDs
digitalWrite(RED_LED_PIN, LOW);
digitalWrite(GREEN_LED_PIN, LOW);
digitalWrite(BLUE_LED_PIN, LOW);
// Cycle through the LEDs (0 = red, 1 = green, 2 = blue)
if (currentLED == 0) {
digitalWrite(RED_LED_PIN, HIGH);
} else if (currentLED == 1) {
digitalWrite(GREEN_LED_PIN, HIGH);
} else if (currentLED == 2) {
digitalWrite(BLUE_LED_PIN, HIGH);
}
// Update the current LED to the next one
currentLED++;
if (currentLED > 2) {
currentLED = 0;
}
}
}