hero shot

Week 4 - Embedded programming

This week, Toni gave us a clarifying tutorial on embedded devices and programming. For our group work with Stanley and Shahmeer, our task was to demonstrate and compare the toolchains and development workflows for different embedded architectures. The main programming language options are:

  • MicroPython – A version of Python optimized for microcontrollers. It's less efficient than C/C++ but easier to use as a high-level language. Python is real-time and interactive, making it easier to test and retrieve data instantly. For example, if a temperature sensor is connected to a microcontroller, you can immediately read its value in Thonny without needing to compile or upload code.
  • C/C++ – Results in faster programs because it is a compiled language, whereas Python is interpreted. It's more difficult than Python and not real-time, but it has excellent documentation and many online examples. In contrast to the above example, with Arduino, you must write code to read the temperature, compile it, and upload it to the microcontroller for execution.

Considering my final project, it seems that a C++-based microcontroller is the better choice. My final project is a fast-paced game, requiring the embedded software to be as responsive as possible. If a Python-based microcontroller were too slow for my needs, switching to C++ later would mean rewriting the entire code.

To determine which microcontroller to use, I started with one of the most popular choices: the Raspberry Pi Pico. It features the RP2040 chip, a dual-core Arm Cortex-M0+ processor running at 133 MHz, with 264kB internal RAM and support for up to 16MB of off-chip flash memory. The RP2040 itself costs less than €1, while the Raspberry Pi Pico board is available for under €10, offering impressive capabilities for its price.

In my final project, I plan to have the following outputs from the microcontroller: fan speed control, baseball bat servo motor control, target angle servo control and several 8-segment displays. On the input side, I plan to include four simple inputs: start button, two game buttons and one sensor from the target.

The Pi Pico provides 30 GPIO pins, four of which can be used as analog inputs. This should be sufficient for my application.

To check for better alternatives, I asked ChatGPT: "Which microcontroller is best for a simple pinball-like game with four buttons, 12 8-segment displays, and two servo motors, where the game score needs to be saved in non-volatile memory?" the ESP32 was recommended over the Raspberry Pi Pico. The main advantages mentioned were: Built-in WiFi & Bluetooth (which I might not need), better support for TM1637 and MAX7219 (which allow multiple 8-segment displays to be controlled with fewer data lines, making wiring easier) and support for EEPROM emulation and SD cards (good for storing game scores).

Despite this, I first attempted to simulate the basic game performance on wokwi.com using the Pi Pico. My initial goal was simply to display a number that increases with each button press. Eventually, I plan to replace the button with a sensor from the target in my game project. I found examples similar to what I was trying to achieve, but for some reason, the following code did not work. This is likely what ChatGPT was referring to earlier - using a TM1637-based display with a Pi Pico is not entirely straightforward. Troubleshooting this was difficult due to the slowness of the Wokwi online simulator.

The unworking Wokwi simulation circuit:

Unworking code for Pi Pico:
		
			//almost the same as https://wokwi.com/projects/312696687190606401
			//Copyright (C) 2021, Uri Shaked. Released under the MIT License.
			#include "pitches.h"
			#include "TM1637Display.h" //required for the TM1637 seven segment display
			#include  //needed for OLED and Graphical LCD Displays (I2C/SPI)

			#define CLK 10  // Clock pin
			#define DIO 9   // Data pin

			TM1637Display display(CLK, DIO);

			#define SPEAKER_PIN 8  

			#define gameStart 0
			#define gameEnd 1
			#define gamePlaying 2

			volatile int gameStatus = gameStart;

			const uint8_t start_button = 4;
			const uint8_t button1 = 5;
			const uint8_t button2 = 6;
			const uint8_t score_button = 7;
			uint8_t MyScore = 1;

			const int buttonTones[] = {
			NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4,
			NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5 
			};

			void setup() {
			Serial.begin(9600);
			pinMode(start_button, INPUT_PULLUP);
			pinMode(button1, INPUT_PULLUP);
			pinMode(button2, INPUT_PULLUP);
			pinMode(score_button, INPUT_PULLUP);
			pinMode(SPEAKER_PIN, OUTPUT);
			}

			// Display the score on the 7-segment display
			void ShowScore() {
			display.showNumberDecEx(MyScore, 0b01000000, true);
			}

			void loop() {
			ShowScore();

			if (digitalRead(score_button) == LOW) {
			MyScore += 1;
			int pitch = buttonTones[MyScore % 8];
			tone(SPEAKER_PIN, pitch, 200);
			delay(300);
			}

			delay(10);
			}
		
			

I decided to try with the ESP32. Again, I found a useful example on Wokwi.com, which I modified just slightly to get the bare minimum operation I was looking for. This is the Wokwi simulation result:

Working code for the ESP32:
		
			// Based on https://wokwi.com/projects/372153001566564353

			// Removed: playSequence(), gameOver(), checkUserSequence(), playLevelUpSound()
			// Simplified the code to function as a basic counter
			// Serves as a starting point for implementing my project

			// Copyright (C) 2023, Uri Shaked. Released under the MIT License.

			#include "pitches.h"

			/* Define pin numbers for LEDs, buttons and speaker: */
			const uint8_t buttonPins[] = {0, 1, 2, 3};
			const uint8_t ledPins[] = {8, 7, 6, 5};
			#define SPEAKER_PIN 10

			// These are connected to 74HC595 shift register (used to show game score):
			const int LATCH_PIN = 18;  // 74HC595 pin 12
			const int DATA_PIN = 19;   // 74HC595 pin 14
			const int CLOCK_PIN = 9;  // 74HC595 pin 11

			#define MAX_GAME_LENGTH 100

			const int gameTones[] = { NOTE_G3, NOTE_C4, NOTE_E4, NOTE_G5};

			/* Global variables - store the game state */
			uint8_t gameSequence[MAX_GAME_LENGTH] = {0};
			uint8_t gameIndex = 0;

			/**
				Set up the Arduino board and initialize Serial communication
			*/
			void setup() {
				Serial.begin(9600);
				for (byte i = 0; i < 4; i++) {
				pinMode(ledPins[i], OUTPUT);
				pinMode(buttonPins[i], INPUT_PULLUP);
				}
				pinMode(SPEAKER_PIN, OUTPUT);
				pinMode(LATCH_PIN, OUTPUT);
				pinMode(CLOCK_PIN, OUTPUT);
				pinMode(DATA_PIN, OUTPUT);
			}

			/* Digit table for the 7-segment display */
			const uint8_t digitTable[] = {
				0b11000000,
				0b11111001,
				0b10100100,
				0b10110000,
				0b10011001,
				0b10010010,
				0b10000010,
				0b11111000,
				0b10000000,
				0b10010000,
			};
			const uint8_t DASH = 0b10111111;

			void sendScore(uint8_t high, uint8_t low) {
				digitalWrite(LATCH_PIN, LOW);
				shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, low);
				shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, high);
				digitalWrite(LATCH_PIN, HIGH);
			}

			void displayScore() {
				int high = gameIndex % 100 / 10;
				int low = gameIndex % 10;
				sendScore(high ? digitTable[high] : 0xff, digitTable[low]);
			}


			void lightLedAndPlayTone(byte ledIndex) {
				digitalWrite(ledPins[ledIndex], HIGH);
				tone(SPEAKER_PIN, gameTones[ledIndex]);
				delay(300);
				digitalWrite(ledPins[ledIndex], LOW);
				noTone(SPEAKER_PIN);
			}


			/**
				Waits until the user presses one of the buttons,
				then lights up corresponding LED, plays sound and increases variable gameIndex
			*/
			void read_buttons() {
				for (byte i = 0; i < 4; i++) {  // Loop through all buttons
				if (digitalRead(buttonPins[i]) == LOW) {  // If any button is pressed
					lightLedAndPlayTone(i); // different functions are not yet defined for different buttons
					gameIndex += 1;  // Increase the score
					Serial.println("Score!");
					delay(200);  // Debounce delay to avoid multiple counts from one press
					return;  // Exit the function after the first button press
				}
				}
			}
	
	
	/**
		The main game loop, made this much simpler
	*/
	void loop() {
		displayScore();
		read_buttons();
	}