Created
September 4, 2020 15:13
-
-
Save owennewo/150fc6f9385c43a8ab7a750b20da8952 to your computer and use it in GitHub Desktop.
SimpleFOC Music Player
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Arduino.h> | |
#include <SimpleFOC.h> | |
/* | |
This works best on an esp32 but have tested on atmega328p too | |
Be careful to set voltage appropriately. Might want to turn down power_supply to 6v too. | |
*/ | |
#ifdef ESP32 | |
BLDCMotor motor = BLDCMotor(5, 18, 19, 7, 4); | |
#else | |
BLDCMotor motor = BLDCMotor(9, 10, 11, 7); | |
// BLDCMotor motor = BLDCMotor(3, 5, 6, 7); | |
#endif | |
float tone_voltage = 1.0; // as this openloop we need to give another voltage to achieve amplitude/vibration reliably | |
float tone_amplitude = 0.25 * PI; // controls volume (too high it'll be distorted) | |
float octave_offset = 0; // increasing/decreasing offset can sometimes sound louder/clearer | |
// calculates note duration in milliseconds | |
long noteDuration(int bpm, int tone_duration, boolean dotted) { | |
long durationMillis = (60 * 1000L / bpm) * (4.0/tone_duration); | |
if (dotted) { | |
durationMillis += durationMillis/2; | |
} | |
return durationMillis; | |
} | |
// calculates frequency of tone in Hz | |
float noteFrequency(char tone_note, int tone_octave, boolean tone_sharp) { | |
int twelth_index = 0; | |
switch(tone_note) | |
{ | |
case 'c': | |
twelth_index = 1; | |
break; | |
case 'd': | |
twelth_index = 3; | |
break; | |
case 'e': | |
twelth_index = 5; | |
break; | |
case 'f': | |
twelth_index = 6; | |
break; | |
case 'g': | |
twelth_index = 8; | |
break; | |
case 'a': | |
twelth_index = 10; | |
break; | |
case 'b': | |
twelth_index = 12; | |
break; | |
case 'p': | |
default: | |
return 0; // a pause, no frequency | |
} | |
if (tone_sharp) twelth_index++; | |
int note_index = tone_octave * 12 + twelth_index; | |
int a4_index = 58; | |
// formula from https://pages.mtu.edu/~suits/NoteFreqCalcs.html | |
float freq = 440 * pow(1.059463094359, note_index - a4_index); | |
return freq; | |
} | |
// plays a tone of given frequency, duration and amplitude (volume) | |
void playTone(float freq, long duration_msec, float amplitude, float voltage) | |
{ | |
// float period_usec = 1000000.0 / freq; | |
float period_msec = 1000.0 / freq; | |
// long start = _micros(); | |
long start = millis(); | |
long diff = 0; | |
long count = 0; | |
while (diff < duration_msec) | |
{ | |
long now = millis(); | |
diff = now - start; | |
float angle = sin((float) diff/period_msec) * amplitude; | |
motor.setPhaseVoltage(voltage, angle); | |
count ++; | |
} | |
motor.setPhaseVoltage(0, angle); | |
} | |
// plays a nokia rtttl ringtone - spec: http://merwin.bespin.org/t4a/specs/nokia_rtttl.txt | |
// example format of rttl is: rttl:"Simpsons:d=4,o=5,b=160:32p,c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g"; | |
void playRTTTL(char* rtttl, float amplitude, float voltage, int octave_offset) { | |
// defaults | |
int octave = 6; | |
int bpm = 63; | |
int duration = 4; | |
// read three main sections which are separated by colons | |
char* name = strtok(rtttl, ":"); | |
char* controls = strtok(NULL, ":"); | |
char* tones = strtok(NULL, ":"); | |
Serial.print(F("Playing: "));Serial.println(name); | |
// read and iterate through control tokens. There are a maximum of 3 (default bpm, duration, octave) | |
char *control_token = strtok(controls,","); | |
while (control_token) | |
{ | |
// avoid nested use of strtok | |
char *save = NULL; | |
char* control_key = strtok_r(control_token,"=", &save); | |
int control_value = atoi(strtok_r(NULL,"=", &save)); | |
switch (control_key[0]) { | |
case 'o': | |
octave = control_value; | |
break; | |
case 'd': | |
duration = control_value; | |
break; | |
case 'b': | |
bpm = control_value; | |
break; | |
} | |
control_token = strtok(NULL,","); | |
} | |
// read and iterate through the notes | |
char *tone_token = strtok(tones,","); | |
while (tone_token) | |
{ | |
boolean tone_dotted = false; | |
boolean tone_sharp = false; | |
char tone_note = 0; | |
int tone_duration = 0; | |
int tone_octave = octave; | |
int size = strlen(tone_token); | |
for (int index = 0; index < size; index++) { | |
char current = tone_token[index]; | |
if (current == '.') { | |
tone_dotted = true; | |
} else { | |
if (isDigit(current)) { | |
// either a duration or an octave depending on position | |
int value = atoi(¤t); | |
if (tone_note == 0) { | |
tone_duration = 10 * tone_duration + value; | |
} else { | |
tone_octave = value; | |
} | |
} else if (current=='#') { | |
tone_sharp = true; | |
} else { | |
tone_note = current; | |
} | |
} | |
} | |
if (tone_duration == 0) tone_duration = duration; | |
float tone_freq = noteFrequency(tone_note, tone_octave + octave_offset, tone_sharp); | |
long tone_millis = noteDuration(bpm, tone_duration, tone_dotted); | |
Serial.print(F(" ")); Serial.print(tone_note); Serial.print(tone_octave + octave_offset); | |
// Lets play that note! | |
playTone(tone_freq, tone_millis, amplitude,voltage); | |
tone_token = strtok(NULL,","); | |
} | |
Serial.println(); | |
Serial.println(F("Complete.")); | |
delay(1000); | |
} | |
// This is reads instructions from serial in. It is not rtttl. Example format: '6cdec cdec efgp efgp +gagf-ec +gagf-ec c5g6cp c5g6-c' for Frere Jaque | |
void playNotesFromSerial() { | |
static int octave = 6; | |
static int duration = 400; | |
// a string to hold incoming data | |
static String received_chars; | |
while (Serial.available()) { | |
char inChar = (char)Serial.read(); | |
if (isDigit(inChar)) { | |
octave = atoi(&inChar); | |
Serial.print(F("Octave is now: ")); Serial.println(octave); | |
} else if (isWhitespace(inChar)) { | |
// ignore | |
} else if (inChar == '-') { | |
Serial.println(F("Half Speed")); | |
duration *=2; // slow it down | |
} else if (inChar == '+') { | |
Serial.println(F("Double Speed")); | |
duration /=2; // speed it up | |
} else if (isAlpha(inChar)) { | |
char note = inChar; | |
float freq = noteFrequency(note, octave + octave_offset, false); | |
Serial.print(F("Playing: ")); Serial.print(note); Serial.println(octave); | |
playTone(freq, duration, tone_amplitude, tone_voltage); | |
} else { | |
Serial.println(F("bad character, try pasting with mouse not keyboard!")); | |
} | |
} | |
} | |
// these tunes are in rtttl (ringtone) format. Declared here for limited RAM | |
#define CROATIA "croatia:d=64,o=6,b=160:4f#,p,4f#,p,4f#.,p,8e,p,8e,p,8d,p,4d,p,2a5,p,8g5,p,8f#5,p,8g5,p,8a5,p,2b5,p,8a5,p,8g5,p,8f#5,p,8g5,p,2a5,p,4f#,p,4f#,p,4f#.,p,8e,p,8e,p,8d,p,4d,p,2a5,p,8g5,p,8f#5,p,8g5,p,8a5,p,2b5,p,8c#,p,8c#,p,4e,p,2d,p,4c#,p,4c#,p,4c#.,p,8b5,p,4c#,p,8c#,p,8d,p,4e.,p,8c#,p,8e,p,8e,p,8e,p,8e,p,4e,p,4d,p,4c#,p,4b5,p,2a5,p,4f#,p,4f#,p,4f#.,p,8e,p,8e,p,8d,p,4d,p,2a5,p,8g5,p,8f#5,p,8g5,p,8a5,2b5,p,8c#,p,p,8c#,p,4e,p,2d,2d,2p" | |
#define MARSEILLAI "marseillai:d=4,o=5,b=160:2p.,16d,8d.,16d,g,g,a,a,d.6,8b,8g,16p,16g,8b.,16g,e,2c6,8a.,16f#,2g,p,8g.,16a,b,b,b,8c.6,16b,b,a,p,8a.,16b,c6,c6,c6,8d.6,16c6,2b,p,8d.6,16d6,d6,8b.,16g,d6,8b.,16g,2d,8p.,16d,8d.,16f#,2a,c6,8a.,16f#,a,g,2f,e,8g.,16g,g,8f#.,16g,2a.,8p,8a,a#.,8a#,8a#,8a#,8c6,8d6,2a.,8a#,8a,g.,8g,8g,8a#,8a,8g,g,8f#,2p,16d6,2d.6,16d6,8b.,16g,2a.,8p.,16d6,2d.6,16d6,8b.,16g,2a,8p,d,2g,p,a,2b,2p,2c6,d6,e6,2a,8p,e6,2d.6,16b,8c.6,16a,2g." | |
#define SIMPSONS "Simpsons:d=4,o=5,b=160:32p,c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g" | |
void setup() { | |
Serial.begin(115200); | |
while(!Serial) {;} | |
delay(2000); | |
Serial.print(F("#########################\n Simple FOC Music Player \n#########################\n")); | |
#ifdef ESP32 | |
Serial.println(F("\nYou have a 12 bit DAC. Good choice! Prepare for the Highest Fidelity BLDC has to offer!\n")); | |
octave_offset += 1; | |
#else | |
Serial.println(F("\nYou have a 8 bits DAC. Old school and retro! I won't be able to play high notes, sorry!\n")); | |
octave_offset -= 1; | |
#endif | |
motor.voltage_power_supply = 12; | |
motor.foc_modulation = FOCModulationType::SinePWM; | |
motor.init(); | |
// This directive if/else is necessary because of limited space on atmega328p. otherwise 328p SRAM unstable | |
#ifdef ESP32 | |
char croatia[] = CROATIA; | |
char marseillai[] = MARSEILLAI; | |
char simpsons[] = SIMPSONS; | |
playRTTTL(croatia, tone_amplitude, tone_voltage, octave_offset); | |
playRTTTL(marseillai, tone_amplitude, tone_voltage, octave_offset); | |
playRTTTL(simpsons, tone_amplitude, tone_voltage, octave_offset); | |
#else | |
playRTTTL(CROATIA, tone_amplitude, tone_voltage, octave_offset); | |
playRTTTL(MARSEILLAI, tone_amplitude, tone_voltage, octave_offset); | |
playRTTTL(SIMPSONS, tone_amplitude, tone_voltage, octave_offset); | |
#endif | |
Serial.print(F("\nYour turn!!\nType/paste: '6cdec cdec efgp efgp +gagf-ec +gagf-ec c5g6cp c5g6-c' for Frere Jaque (this is not rtttl format)\n")); | |
} | |
void loop() { | |
playNotesFromSerial(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment