Week 6 : Embedded Programming
Summary
This week I worked with the RP2040 microcontroller and the board we made in week 4. I wrote a program to generate a sound with a buzzer and control it using a potentiometer. I also tested UART communication with a communication program between my board and a raspberry Pi Pico. My board transmits to the raspberry what octave it should be, and the raspberry produces a sound modulated by a potentiometer.
Assignments
Group Assignment
- Browse through the data sheet for your microcontroller compare the performance and development workflows for other architectures.
Individual Assignments
- Write a program for a microcontroller development board that you made, to interact and communicate.
- Extra credit: use different languages &/or development environments, and connect external components to the board.
RP 2040 Data sheet
This part was done in group and is accessible on the group page.
Sound Generation
All development here was carried out in C using the Arduino IDE.
Testing Buzzer
This week I wanted to try my hand at generating sound with my microcontroller. The development kit we received contains a piezoelectric buzzer that generates a sound at the frequency of the injected signal.
I’m looking in the piezoelectric buzzer datasheet. They show the recommended connections for using the buzzer, which I do on my test board quite easily.
The voltage source here will be 5V, but we can normally go up to a maximum of 30V. This will influence the sound amplitude. I’m not looking for maximum sound power here. In the video below, I’ve connected the buzzer directly. Indeed, it works, but if you want to increase the input voltage, you’ll have to use the assembly shown above.
I then connect the assembly to my development board. Simply send a PWM signal to the pin, configure the output, and you’re done. To generate the PWM signal with Arduino, a simpler function is used here, the “tone()” function, dedicated to sound signal generation.
Here I use a test code playing “Happy Birthday”.
// The speaker will play the tune to Happy Birthday continuously
// Author: Tony DiCola
// License: MIT (https://opensource.org/licenses/MIT)
#include <Arduino.h>
#ifdef USE_TINYUSB
// For Serial when selecting TinyUSB. Can't include in the core because Arduino IDE
// will not link in libraries called from the core. Instead, add the header to all
// the standard libraries in the hope it will still catch some user cases where they
// use these libraries.
// See https://github.com/earlephilhower/arduino-pico/issues/167#issuecomment-848622174
#include <Adafruit_TinyUSB.h>
#endif
// pin_buzzer should be defined by the supported variant e.g CPlay Bluefruit or CLUE.
// Otherwise please define the pin you would like to use for tone output
#ifndef PIN_BUZZER
#define PIN_BUZZER 15
#endif
uint8_t const pin_buzzer = PIN_BUZZER;
// A few music note frequencies as defined in this tone example:
// https://www.arduino.cc/en/Tutorial/toneMelody
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
// Define note durations. You only need to adjust the whole note
// time and other notes will be subdivided from it directly.
#define WHOLE 2200 // Length of time in milliseconds of a whole note (i.e. a full bar).
#define HALF WHOLE/2
#define QUARTER HALF/2
#define EIGHTH QUARTER/2
#define EIGHTH_TRIPLE QUARTER/3
#define SIXTEENTH EIGHTH/2
// Play a note of the specified frequency and for the specified duration.
// Hold is an optional bool that specifies if this note should be held a
// little longer, i.e. for eighth notes that are tied together.
// While waiting for a note to play the waitBreath delay function is used
// so breath detection and pixel animation continues to run. No tones
// will play if the slide switch is in the -/off position or all the
// candles have been blown out.
void playNote(int frequency, int duration, bool hold = false, bool measure = true) {
(void) measure;
if (hold) {
// For a note that's held play it a little longer than the specified duration
// so it blends into the next tone (but there's still a small delay to
// hear the next note).
tone(pin_buzzer, frequency, duration + duration / 32);
} else {
// For a note that isn't held just play it for the specified duration.
tone(pin_buzzer, frequency, duration);
}
delay(duration + duration / 16);
}
// Song to play when the candles are blown out.
void celebrateSong() {
// Play a little charge melody, from:
// https://en.wikipedia.org/wiki/Charge_(fanfare)
// Note the explicit boolean parameters in particular the measure=false
// at the end. This means the notes will play without any breath measurement
// logic. Without this false value playNote will try to keep waiting for candles
// to blow out during the celebration song!
playNote(NOTE_G4, EIGHTH_TRIPLE, true, false);
playNote(NOTE_C5, EIGHTH_TRIPLE, true, false);
playNote(NOTE_E5, EIGHTH_TRIPLE, false, false);
playNote(NOTE_G5, EIGHTH, true, false);
playNote(NOTE_E5, SIXTEENTH, false);
playNote(NOTE_G5, HALF, false);
}
void setup() {
// Initialize serial output and Circuit Playground library.
Serial.begin(115200);
pinMode(pin_buzzer, OUTPUT);
digitalWrite(pin_buzzer, LOW);
}
void loop() {
// Play happy birthday tune, from:
// http://www.irish-folk-songs.com/happy-birthday-tin-whistle-sheet-music.html#.WXFJMtPytBw
// Inside each playNote call it will play a note and drive the NeoPixel animation
// and check for a breath against the sound sensor. Once all the candles are blown out
// the playNote calls will stop playing music.
playNote(NOTE_D4, EIGHTH, true);
playNote(NOTE_D4, EIGHTH);
playNote(NOTE_E4, QUARTER); // Bar 1
playNote(NOTE_D4, QUARTER);
playNote(NOTE_G4, QUARTER);
playNote(NOTE_FS4, HALF); // Bar 2
playNote(NOTE_D4, EIGHTH, true);
playNote(NOTE_D4, EIGHTH);
playNote(NOTE_E4, QUARTER); // Bar 3
playNote(NOTE_D4, QUARTER);
playNote(NOTE_A4, QUARTER);
playNote(NOTE_G4, HALF); // Bar 4
playNote(NOTE_D4, EIGHTH, true);
playNote(NOTE_D4, EIGHTH);
playNote(NOTE_D5, QUARTER); // Bar 5
playNote(NOTE_B4, QUARTER);
playNote(NOTE_G4, QUARTER);
playNote(NOTE_FS4, QUARTER); // Bar 6
playNote(NOTE_E4, QUARTER);
playNote(NOTE_C5, EIGHTH, true);
playNote(NOTE_C5, EIGHTH);
playNote(NOTE_B4, QUARTER); // Bar 7
playNote(NOTE_G4, QUARTER);
playNote(NOTE_A4, QUARTER);
playNote(NOTE_G4, HALF); // Bar 8
celebrateSong();
// One second pause before repeating the loop and playing
delay(1000);
}
Adding Control
Now that I know how to generate sound, I want to have some control over the tone. To do this, I use a simple potentiometer. The idea here is to be able to tune a constant note with the value of the potentiometer.
To do this, connect the potentiometer between ground and a constant voltage, and the third pin will send the potentiometer position information to the microcontroller.
First, I test the potentiometer alone with a test code.
/*
AnalogReadSerial
Reads an analog input on pin 0, prints the result to the Serial Monitor.
Graphical representation is available using Serial Plotter (Tools > Serial Plotter menu).
Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground.
This example code is in the public domain.
https://www.arduino.cc/en/Tutorial/BuiltInExamples/AnalogReadSerial
*/
// the setup routine runs once when you press reset:
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
}
// the loop routine runs over and over again forever:
void loop() {
// read the input on analog pin 0:
int sensorValue = analogRead(26);
// print out the value you read:
Serial.println(sensorValue);
delay(1); // delay in between reads for stability
}
The problem I’m having at the moment is that I don’t have enough pins available to handle two signals (potentio input and buzzer output). So first, I run the test on the Raspberry Pi Pico I have available. The advantage is that an RP2040 is also used here.
I create a code that allows me, quite simply, to change the frequency sent to the buzzer from the value of the potentiometer.
/*
Sound Generation
Control the frequency of a buzzer with an analogue input (eg : potentiometer).
Teo Serra
2024
FabAcademy 2024 Week 6
*/
//Define PINs
#ifndef PIN_ANALOG
#define PIN_ANALOG 26
#endif
#ifndef PIN_BUZZER
#define PIN_BUZZER 15
#endif
uint8_t const pin_buzzer = PIN_BUZZER;
//Define note frequency
#define NOTE_C4 262
//Define temp
#define TEMPO 2200
void setup() {
// Initialize serial output.
Serial.begin(115200);
pinMode(pin_buzzer, OUTPUT);
digitalWrite(pin_buzzer, LOW);
}
void loop() {
int analogValue = analogRead(PIN_ANALOG);
Serial.println(analogValue);
tone(pin_buzzer, NOTE_C4 + round((analogValue - 510)), TEMPO/8);
delay(TEMPO/16);
}
The result is already satisfactory.
Adding UART Communication & Octave Switching
I want to go a step further, and above all respect the assignment, by using my development card again. I’d like to add my development card to the previous assembly and use the button integrated in it.
The idea is to have both microcontrollers communicate via a UART connection and protocol.
The raspberry Pi Pico has a sound generation code, very similar to the previous “Sound Generation” code with the difference that here, the set of frequencies is more discrete, being assigned to notes. And will also receive octave change information from the development board and Xiao Seeed by UART communication.
/*
Project: Potentiometer-based Buzzer Control and UART Communication
Author: Teo Serra
Date: 2024
Device: Raspberry Pi Pico
Description:
This code reads the value from a potentiometer connected to the Raspberry Pi Pico.
Depending on the octave set by the user (1, 2 ou 3), it controls the buzzer
to play either first, second or third octave notes.
The potentiometer value selects notes from a predefined array of frequencies.
FabAcademy 2024, Week 6
*/
#include <Arduino.h>
// Define potentiometer pin
const int potentiometerPin = 26; // Potentiometer connected to pin 26
// Define buzzer pin
const int buzzerPin = 15; // Buzzer pin
// Define notes frequencies (with semi-tones)
const int notes[] = {262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523}; // C4, C#4, D4, D#4, E4, F4, F#4, G4, G#4, A4, A#4, B4, C5
// Set the octave ('c' for continuous, 'n' for notes)
int octave = 1;
int newOctave = 1; // Variable to store the new octave received from Seeed Xiao
void setup() {
pinMode(buzzerPin, OUTPUT); // Set buzzer pin as output
Serial1.begin(9600); // Initialize serial communication
}
void loop() {
int potValue = analogRead(potentiometerPin); // Read potentiometer value
// Notes : map potentiometer value to note index
int noteIndex = map(potValue, 0, 1023, 0, 12); // Map potentiometer value to note index
int frequency = octave * notes[noteIndex]; // Get frequency of the note multiplied by octave value
tone(buzzerPin, frequency); // Play the note
// Read the choice from the serial port
if (Serial1.available() > 0) {
newOctave = Serial1.read(); // Read the new octave choice
if (newOctave == 1 || newOctave == 2 || newOctave == 3) { // Check if there's a change in mode
octave = newOctave; // Update the octave
}
}
delay(50); // Adjust delay for responsiveness
}
The Xiao Seeed (development board) has a code that reads the activation of the built-in button, and sends the information to the Raspberrry Pi Pico. A luminous indication via LED change is also coded to let you know if the button has been activated.
/*
Project: Potentiometer-based Buzzer Control and UART Communication
Author: Teo Serra
Date: 2024
Device: Xiao Seeed & Development board
Description:
This code reads a button push and set the octave status.
Then an UART communication is used to send the octave to the other device.
Leds are used to show at wich octave we are.
FabAcademy 2024, Week 6
*/
#include <Arduino.h>
// Define LED pins
const int ledPins[] = {17, 16, 25}; // Example LED pins, adjust as needed
// Define UART pins
const int uartTxPin = 28; // TX pin
const int uartRxPin = 29; // RX pin
// Define button pin
const int buttonPin = 27; // Button pin
// Set initial LED state
bool ledState[] = {false, false, false};
// Set the octave
int octave = 1;
void setup() {
for (int i = 0; i < 3; i++) {
pinMode(ledPins[i], OUTPUT); // Set LED pins as output
}
pinMode(buttonPin, INPUT); // Set button pin as input with pull-up resistor
Serial1.setRX(uartRxPin);
Serial1.setTX(uartTxPin);
Serial1.begin(9600); // Initialize serial communication
}
void loop() {
// Check if the button is pressed
if (digitalRead(buttonPin) == HIGH) {
// Toggle octave
if (octave == 1) {
octave = 2; // Change octave from 1 to 2
} else if (octave == 2) {
octave = 3; // Change octave from 2 to 3
} else if (octave == 3) {
octave = 1; // Change octave from 3 to 1
}
// Send octave via UART to Raspberry Pi Pico
Serial1.write(octave);
}
// Update LEDs based on octave
for (int i = 0; i < 3; i++) {
if (i == octave-1){
ledState[i] = true;
} else {
ledState[i] = false;
}
}
// Update LEDs
for (int i = 0; i < 3; i++) {
digitalWrite(ledPins[i], ledState[i]);
}
delay(500); // Adjust delay as needed
}
Wiring is fairly straightforward. On the Raspberry Pi Pico side, it’s exactly the same as in the previous section. And on the Xiao Seeed, the button is integrated into the development board. Communication between the two microcontrollers is via wiring of the Tx and Rx pins and ground. The Tx pin of one must be connected to the Rx of the other, and vice versa. In effect, the Tx pin is the transmit pin, and the Rx pin the receive pin.
UART communication is working properly.
Other Language
Python
These tests were carried out later, during week 9, using MicroPython and Visual Studio Code with a Raspberry Pi Pico.
Using Thonny has also been tried, and documented during week 8.