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

Individual Assignments


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.

PiezoScheme
PiezoWiring

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.

BuzzerFullWiring

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.

PotentioWiring

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.

TransBuzzPote
 /*
  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.

UartCom1
UartCom2

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.