TVZ2022 / Mbed OS Pavetic_BozicnePjesme

Dependencies:   Pavetic_BuzzerLib Pavetic_MusicLib

main.cpp

Committer:
dpavetic
Date:
2022-11-27
Revision:
0:e9d0eb34d345

File content as of revision 0:e9d0eb34d345:

// Program koji simulira božične lampice //

// Gumb - Mijenja melodiju na klik (1...n) - Hardverski interrupt
// Potenciometar - Mjerenje vrijednosti na ulazu (0.0-1.0) - Potrebno za
// mjerenje vremena promjene pjesme Zvučnik - Reprodukcija melodije Zuta LED -
// Signalizacija da traje melodija jingle bells Crvena LED - Signalizacija da
// traje melodija We wish you a merry christmas Zelena LED - Signalizacija da
// traje melodija Silent night

// Ticker - Automatsko mijenjanje melodije u vremenu ovisnom o potenciometru
// (samo u modu MODE_AUTO)
//  --> 0V = 5s
//  --> 1V = 25s
//  --> f(x) = 5 + 20*x [s] - Skaliranje funkcije pomaknute za 5s (min =
// 5s, max = 25s)
// Timer #1 - Mjerenje vremena trajanja melodije
// Timer #2 - Debounce efekt za gumb

// Opis rada programa:
// Postoje 3 moda rada:
//      --> MODE_OFF - Sustav je ugašen - Buzzer ugašen kao i sve LEDice
//      --> MODE_SONG - Repetetivno ponavljanje odabrane melodije - Buzzer
// svira, te svijetli jedna od pripadajucih LEDica za trenutnu melodiju
//      --> MODE_AUTO - Automatsko mijenjanje melodije u vremenskom
//razmaku odabranom putem potenciometra - Buzzer svira, te svijetli jedna od
//pripadajucih LEDica za trenutnu melodiju
//
// Postoje 3 melodije: Jingle bells, We wish you, Silent night. Ovisno o
// trenutnoj melodiji, svijetli pripadajuca LEDica.
//
// Na početku programa postavljen je mod MODE_OFF te je sustav ugašen.
// Pritiskom na gumb u modu MODE_OFF (prvi pritisak gumba), mijenja se mod u
// MODE_SONG te počinje svirati prva od ponudenih melodija. Nakon sto melodija
// zavrsi, ukoliko nije došlo do promjene melodije ili prekida, ponovno se
// započinje sa reprodukcijom iste melodije. Ukoliko tijekom reprodukcije dođe
// do promjene moda ili melodije, trenutna melodija prekida sa reprodukcijom te
// počinje reprodukcija sljedeće melodije, ili se sustav gasi ukoliko je došlo
// do promjene moda u MODE_OFF.
//
// Program je u modu MODE_SONG te svira prva pjesma i upaljena je zuta LEDica.
// Drugim pritiskom gumba, mode ostaje isti, no mijenja se melodija te svira
// druga melodija i svijetli crvena LEDica. Trećim pritiskom gumba, mod i dalje
// ostaje isti i svira treca melodija uz upaljenu zelenu LEDicu.
//
// Cetvrtim pritiskom gumba mijenja se mod u MODE_OFF te se resetira ciklus
// melodija i započinje reprodukcija prve melodije. Melodija se reproducira
// ovisno o vremenu izračunatom iz vrijednosti na potenciometru prema određenoj
// formuli koja je navedena na vrhu. Nakon isteka vremena, mijenja se melodija i
// započinje reprodukcija sljedeće melodije, te tako u krug sa svim melodijama.
//  - Vrijednost potenciometra očitava se cijelo vrijeme na zasebnom
// Threadu.
//  - Vrijeme reprodukcije melodije u modu MODE_OFF izračunava se kod
// dolaska u taj mode.
//
// Petim pritiskom gumba, prekida se reprodukcija melodije te se mod mijenja u
// MODE_OFF i program nastavlja ispočetka.

#include "DigitalOut.h"
#include "Music.h"
#include "PinNameAliases.h"
#include "PinNames.h"
#include "ThisThread.h"
#include "Thread.h"
#include "Buzzer.h"
#include "mbed.h"
#include "mbed_rtc_time.h"
#include "mbed_thread.h"
#include "rtos.h"
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstring>

#define SONG_COUNT 3

#define MODE_OFF 0
#define MODE_SONG 1
#define MODE_AUTO 2

#define JINGLE_BELLS 1
#define WISH 2
#define SILENT_NIGHT 3

DigitalOut jingleBellsLed(PA_10); // Yellow
DigitalOut wishLed(PB_5);         // Red
DigitalOut silentNightLed(PA_9);  // Green

// -- START Sounds and music -- //
Timer songMeasure; // Measuring how long did song play
// PwmOut buzzer(D5);
Buzzer buzzer(D5);

void playMusic(int array[], float rithm[], int size);
// -- END Sounds and music -- //

// -- START Mode changing -- //
InterruptIn button(D7); // Button for changing melodies
Timer debounce;         // Debounce timer for the button
void onButtonPress();
// -- END Mode changing -- //

// -- START Main variables -- //
int currentMode = MODE_OFF;       // Mode that is currently selected
int currentMelody = JINGLE_BELLS; // Melody that is currently being played
int songStartedOnMode = -1;       // Variable ensuring mode change is detected
int songStartedOnMelody = -1;     // Vriable ensuring melody change is detected
// -- END Main variables -- //

// -- START Auto changing transitions -- //
AnalogIn potenciometer(
    A0); // Analog input for potenciometer - detecting song change speed

Ticker changeSongTicker; // Ticker for changing song every x time - depends on
                         // measuredVoltage
Thread analogMeasureThread(osPriorityNormal, 300, nullptr, nullptr);
float measuredVoltage = 0; // Currently measured voltage on potenciometer - For
                           // determining time between transitions
void readValue();
// -- END Auto changing transitions -- //

// -- START Common functions -- //
void changeMode();
void changeSong();
void toggleLeds(int song);
// -- END Common functions -- //

int main() {
  debounce.start();
  button.mode(PullUp); // Required - otherwise button does not work properly
  button.rise(&onButtonPress);

  analogMeasureThread.start(readValue);

  while (true) {
    toggleLeds(currentMelody);

    // If MODE_OFF, turn off buzzer and skip the code
    if (currentMode == MODE_OFF) {
      buzzer.silence();
      continue;
    }

    songStartedOnMode = currentMode;
    songStartedOnMelody = currentMelody;

    switch (currentMelody) {
    case JINGLE_BELLS: {
      int size = (unsigned int)sizeof jingle_bells_melody /
                 sizeof jingle_bells_melody[0];
      playMusic(jingle_bells_melody, jingle_bells_note_durations, size);
      break;
    }

    case WISH: {
      int size = (unsigned int)sizeof wish_melody / sizeof wish_melody[0];
      playMusic(wish_melody, wish_note_durations, size);
      break;
    }

    case SILENT_NIGHT: {
      int size = (unsigned int)sizeof silent_night_melody /
                 sizeof silent_night_melody[0];
      playMusic(silent_night_melody, silent_night_note_durations, size);
      break;
    }
    }
  }
}

void playMusic(int array[], float rithm[], int size) {
  songMeasure.start();

  int i = 0;
  for (i = 0; i < size; i++) {
    // Stop executing if mode or song change in the meantime
    // Otherwise song would remain playing after mode or song is changed
    if (currentMode != songStartedOnMode ||
        currentMelody != songStartedOnMelody)
      break;
    printf("Entering iteration %d for song %d. Measured voltage: %f\n", i,
           currentMelody, measuredVoltage);
    buzzer.buzz(array[i], rithm[i]);
  }

  // Printing how long did song played in seconds
  int elapsedTime_sec =
      duration_cast<std::chrono::seconds>(songMeasure.elapsed_time()).count();
  printf("Song played for %d seconds\n", elapsedTime_sec);
  songMeasure.stop();
  songMeasure.reset();

  // Pause for 1 second after song has finished WITHOUT interruption - Silent
  // transition Otherwise, move straight to the next song
  buzzer.silence();
  if (i == size)
    ThisThread::sleep_for(1s);
}

// Callback executed when button is pressed - initiated by the Interrupt.
// Uses debounce effect of 200ms and if button really pressed following code is
// executed.
// 1. If any song is currently playing in MODE_SONG AND it is not last song,
// change song.
// 2. Otherwise, if any other mode, or it is last song - Change mode.
void onButtonPress() {
  if (debounce.elapsed_time() > 200ms) {
    if (currentMode == MODE_SONG && currentMelody < SONG_COUNT)
      changeSong();
    else
      changeMode();
    debounce.reset();
  }
}

void changeMode() {
  // Reset melody because mode has changed - Initial melody should be played
  // (Melody cycle reset)
  currentMelody = JINGLE_BELLS;

  // Temp var used to prevent multiple mode changes at once because new mode
  // could be unknown.
  int newMode = currentMode + 1; // Increase temp mode variable
  switch (newMode) {
  case MODE_AUTO: {
    // Calculate time base on potenciometer reading and attach Ticker
    // 0V = 5s
    // 1V = 25s
    // f(x) = 5 + 20*x [s] - Skaliranje funkcije pomaknute za 5s (min = 5s,
    // max = 25s)
    int changeTime_sec = 5 + (20 * measuredVoltage);
    changeSongTicker.attach_us(&changeSong, changeTime_sec * 1000000);
    break;
  }

  case MODE_SONG:
  case MODE_OFF:
    // Detach timer to prevent song change after time expires
    changeSongTicker.detach();
    break;

  default:
    // Unrecognised mode - Turn off + detach timer to prevent song change
    changeSongTicker.detach();
    newMode = MODE_OFF;
  }

  // Change source of truth mode
  currentMode = newMode;
}

// Changes current song.
// If current melody is the last melody, reset the cyle of the songs and
// play the first song.
void changeSong() {
  int newMelody = currentMelody + 1;
  if (newMelody > SILENT_NIGHT)
    newMelody = 1;
  currentMelody = newMelody;
}

// Constantly reads the analog input value in separate thread.
void readValue() {
  while (true) {
    measuredVoltage = potenciometer.read();
  }
}

// Toggles LEDs based on the current song.
// If MODE_OFF, turn off all LEDs.
// Only 1 LED can be turned on at the same time.
void toggleLeds(int song) {
  if (currentMode == MODE_OFF) {
    jingleBellsLed.write(false);
    wishLed.write(false);
    silentNightLed.write(false);
    return;
  }

  jingleBellsLed.write(song == JINGLE_BELLS);
  wishLed.write(song == WISH);
  silentNightLed.write(song == SILENT_NIGHT);
}