/** * PWM tone generator library * * Author: Jan Dvořák z Vozerovic * E-mail: dvorkaman@gmail.com * Web: dvorkaman.asp2.cz * Created: 2014 */ /* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __dvorkaman_PWMTone_base_c__ #define __dvorkaman_PWMTone_base_c__ #include "libPWMTone_base.h" #include "libSystemTime_base.h" #include "libPWM_base.h" #include "libUtils_base.h" #include /** * PDC */ /// /// PWMs tone pdc initialize. /// /// The PWM PDC. /// The PWM pin. /// The first tone distance. /// The full tone distance. void pwmTone_pdcInitialize(libPWMPDC* pwmPDC, libPWMpin* pwmPin, uint16_t firstToneDistance, uint16_t fullToneDistance) { pwmPDC->pwmPin = pwmPin; pwmPDC->firstToneDistance = firstToneDistance; pwmPDC->fullToneDistance = fullToneDistance; pwmPDC->mode = 0; } /// /// PWM tone_pdc stop. /// /// The PWM PDC. void pwmTone_pdcStop(libPWMPDC* pwmPDC) { pwmPDC->mode = 0; pwmTone_playTone(pwmPDC->pwmPin, PWM_NOTE_NONE); } /// /// PWM tone_pdc set distance. /// /// The PWM PDC. /// The distance. void pwmTone_pdcSetDistance(libPWMPDC* pwmPDC, uint16_t distance) { // no tone if (distance > pwmPDC->firstToneDistance && pwmPDC->mode != 0){ pwmTone_pdcStop(pwmPDC); return; } // full tone if (distance <= pwmPDC->fullToneDistance && distance > 0 && pwmPDC->mode != 3) { pwmPDC->mode = 3; pwmTone_playTone(pwmPDC->pwmPin, PWM_TONE_PDC); return; } if (distance > pwmPDC->firstToneDistance || distance <= pwmPDC->fullToneDistance) { return; } // Beeping tone - in the middle of the distance pwmPDC->silenceDuration = map(distance, pwmPDC->fullToneDistance, pwmPDC->firstToneDistance, PWM_TONE_PDC_MIN_PAUSE, PWM_TONE_PDC_MAX_PAUSE); if (pwmPDC->mode == 0 || pwmPDC->mode == 3) { // only if changing state pwmPDC->mode = 2; // silence pwmPDC->nextChange.s = 0; pwmPDC->nextChange.ms = 0; } } /// /// PWM the tone_pdc loop. /// /// The PWM PDC. void pwmTone_pdcLoop(libPWMPDC* pwmPDC) { systemTime now, st; if (pwmPDC->mode == 0 || pwmPDC->mode == 3) { return; } // tone loop now = systemTime_getTime(); if (systemTime_compareTimes(now, pwmPDC->nextChange) < 0) { return; } if (pwmPDC->mode == 1) { // Silence st = systemTime_build(now.s, now.ms + pwmPDC->silenceDuration); pwmPDC->mode = 2; pwmTone_playTone(pwmPDC->pwmPin, PWM_NOTE_NONE); pwmPDC->nextChange = st; } else { // Tone st = systemTime_build(now.s, now.ms + PWM_TONE_PDC_TONE_DURATION); pwmPDC->mode = 1; pwmTone_playTone(pwmPDC->pwmPin, PWM_TONE_PDC); pwmPDC->nextChange = st; } } /** * Siren */ void pwmTone_sirenInitialize(libPWMsiren* pwmSiren, libPWMpin* pwmPin) { pwmSiren->pwmPin = pwmPin; pwmSiren->lowTone = PWM_TONE_SIREN_LOW_TONE; pwmSiren->highTone = PWM_TONE_SIREN_HIGH_TONE; pwmSiren->toneIncrement = PWM_TONE_SIREN_TONE_INCREMENT; pwmSiren->incrementEach = PWM_TONE_SIREN_TONE_INCREMENT_EACH; // ms pwmSiren->toneDuration = PWM_TONE_SIREN_TONE_DURATION; // ms - low and hight tone pwmSiren->mode = 0; } void pwmTone_sirenStart(libPWMsiren* pwmSiren) { systemTime t = systemTime_getTime(); pwmSiren->mode = 1; // increment pwmSiren->tone = pwmSiren->lowTone; pwmSiren->nextNoteTime.s = t.s; pwmSiren->nextNoteTime.ms = t.ms; pwmTone_sirenPlay(pwmSiren); } void pwmTone_sirenPlay(libPWMsiren* pwmSiren) { uint16_t duration; if (pwmSiren->mode == 0) { return; } if (systemTime_compareTimes(systemTime_getTime(), pwmSiren->nextNoteTime) < 0) { return; } // Tone increment, check bounds if (pwmSiren->mode == 1) { pwmSiren->tone += pwmSiren->toneIncrement; } else { pwmSiren->tone -= pwmSiren->toneIncrement; } if (pwmSiren->tone < pwmSiren->lowTone) { pwmSiren->tone = pwmSiren->lowTone; } if (pwmSiren->tone > pwmSiren->highTone) { pwmSiren->tone = pwmSiren->highTone; } // Play pwmTone_playTone(pwmSiren->pwmPin, pwmSiren->tone); // Set Next note time duration = pwmSiren->incrementEach; if (pwmSiren->tone == pwmSiren->lowTone || pwmSiren->tone == pwmSiren->highTone) { duration = pwmSiren->toneDuration; if (pwmSiren->mode == 1) { pwmSiren->mode = 2; } else { pwmSiren->mode = 1; } } pwmSiren->nextNoteTime = systemTime_build(pwmSiren->nextNoteTime.s, pwmSiren->nextNoteTime.ms + duration); } void pwmTone_sirenStop(libPWMsiren* pwmSiren) { pwmSiren->mode = 0; pwmTone_playTone(pwmSiren->pwmPin, PWM_NOTE_NONE); } /** * Beep */ void pwmTone_beepInitialize(libPWMbeep* pwmBeep, libPWMpin* pwmPin) { pwmBeep->pwmPin = pwmPin; pwmBeep->tone = PWM_TONE_BEEP_TONE; pwmBeep->toneDuration = PWM_TONE_BEEP_TONE_DURATION; pwmBeep->silenceDuration = PWM_TONE_BEEP_SILENCE_DURATION; pwmBeep->mode = 0; } void pwmTone_beepStart(libPWMbeep* pwmBeep) { pwmBeep->mode = 2; // next will be tone pwmBeep->lastNoteTime.s = 0; pwmBeep->lastNoteTime.ms = 0; pwmTone_beepPlay(pwmBeep); } void pwmTone_beepPlay(libPWMbeep* pwmBeep) { systemTime t = systemTime_getTime(); uint32_t diff = systemTime_getTimeDiffInMs(pwmBeep->lastNoteTime, t); if (pwmBeep->mode == 0) { return; } // Tone after silence if (pwmBeep->mode == 2 && diff >= pwmBeep->silenceDuration) { pwmTone_playTone(pwmBeep->pwmPin, pwmBeep->tone); pwmBeep->mode = 1; pwmBeep->lastNoteTime = t; return; } // Silence after tone if (pwmBeep->mode == 1 && diff >= pwmBeep->toneDuration) { pwmTone_playTone(pwmBeep->pwmPin, PWM_NOTE_NONE); pwmBeep->mode = 2; pwmBeep->lastNoteTime = t; return; } } void pwmTone_beepStop(libPWMbeep* pwmBeep) { pwmBeep->mode = 0; pwmTone_playTone(pwmBeep->pwmPin, PWM_NOTE_NONE); } /** * Horn */ void pwmTone_hornStart(libPWMpin* pwmPin) { pwmTone_playTone(pwmPin, PWM_TONE_HORN); } void pwmTone_hornStop(libPWMpin* pwmPin) { pwmTone_playTone(pwmPin, PWM_NOTE_NONE); } /** * Initialize melody */ void pwmTone_melodyInitialize(libPWMmelody* pwmTone, libPWMpin* pwmPin, uint8_t notesCount, uint16_t* notes) { pwmTone_melodyInitializeVolumes(pwmTone, pwmPin, notesCount, notes, NULL, NULL); } void pwmTone_melodyInitializeDurations(libPWMmelody* pwmTone, libPWMpin* pwmPin, uint8_t notesCount, uint16_t* notes, uint16_t* durations) { pwmTone_melodyInitializeVolumes(pwmTone, pwmPin, notesCount, notes, durations, NULL); } void pwmTone_melodyInitializeVolumes(libPWMmelody* pwmTone, libPWMpin* pwmPin, uint8_t notesCount, uint16_t* notes, uint16_t* durations, uint8_t* volumes) { pwmTone->pwmPin = pwmPin; pwmTone->notesCount = notesCount; pwmTone->notes = notes; pwmTone->durations = durations; pwmTone->volumes = volumes; // clear flags pwmTone_melodyStop(pwmTone); } /** * Stop melody */ void pwmTone_melodyStop(libPWMmelody* pwmTone) { pwmTone->nextNoteIndex = 0; pwmTone_playToneVolume(pwmTone->pwmPin, PWM_NOTE_NONE, 0); } /** * Get if melody is finished */ bool pwmTone_melodyFinished(libPWMmelody* pwmTone) { return pwmTone->nextNoteIndex >= pwmTone->notesCount ? TRUE : FALSE; } /** * Play melody main loop */ void pwmTone_melodyPlay(libPWMmelody* pwmTone) { systemTime now = systemTime_getTime(); systemTime nextBase; uint8_t volume; uint16_t duration; // Play next note (or the first one) if (pwmTone->nextNoteIndex == 0 || systemTime_compareTimes(now, pwmTone->nextNoteTime) >= 0) // when now >= next note time { // end condition if (pwmTone_melodyFinished(pwmTone) == TRUE) { pwmTone_playToneVolume(pwmTone->pwmPin, PWM_NOTE_NONE, 0); pwmTone->nextNoteTime = systemTime_build(nextBase.s + 1000, nextBase.ms); return; } // play note volume = pwmTone->volumes != NULL ? pwmTone->volumes[pwmTone->nextNoteIndex] : 100; // 100 if not defined duration = pwmTone->durations != NULL ? pwmTone->durations[pwmTone->nextNoteIndex] : PWM_TONE_DEFAULT_DURATION; pwmTone_playToneVolume(pwmTone->pwmPin, pwmTone->notes[pwmTone->nextNoteIndex], volume); if (pwmTone->nextNoteIndex == 0) { nextBase = now; } // first note else { nextBase = pwmTone->nextNoteTime; } pwmTone->nextNoteTime = systemTime_build(pwmTone->nextNoteTime.s, pwmTone->nextNoteTime.ms + duration); pwmTone->nextNoteIndex++; } } /** * Play tone, volume is 0..100 */ void pwmTone_playToneVolume(libPWMpin* pwmPin, uint16_t tone, uint8_t volume) { // Set Frequency, prescaler to generate given frequency on the entire TIM pwm_setPeriodFrequency(pwmPin->tim, PWM_PERIOD_BUZZER, tone); // clear volume for empty sound if (tone == PWM_NOTE_NONE) { volume = 0; } // Set volume - force to set CCRx value to generate proper pulses pwm_writePin(pwmPin, (PWM_PERIOD_BUZZER >> 1) * volume / 100); } /** * Play tone (volume 100 by default) */ void pwmTone_playTone(libPWMpin* pwmPin, uint16_t tone) { pwmTone_playToneVolume(pwmPin, tone, 100); } /** * Get note duration for given tempo in MS. e.g. 1/4 in 120 => pwmTone_getDuration(120, 1, 4) */ uint16_t pwmTone_getDuration(uint8_t tempo, uint8_t count, uint8_t length) { uint32_t v = 60000 * count * PWM_TONE_DEFAULT_BEATS / length / tempo; // in ms return (uint16_t) v; } /** * Load melody from Nokia string, notes can be space separated, [d-] [dNO] [d#NO] [d.NO] duration, note, octave; - for silence, # or . for sharp (+semitone) */ void pwmTone_loadMelodyFromNokiaString(libPWMmelody* pwmTone, char* string, uint8_t maximalNotesCount, uint8_t tempo) { uint16_t startIndex = 0; uint16_t note, duration; uint8_t noteIndex = 0; pwmTone->notesCount = 0; while (pwmTone_nokiaStringParseNextNote(string, &startIndex, tempo, ¬e, &duration)) { // convert note, save pwmTone->notes[noteIndex] = note; if (pwmTone->durations != NULL) { pwmTone->durations[noteIndex] = duration; } if (pwmTone->volumes != NULL) { pwmTone->volumes[noteIndex] = 100; } pwmTone->notesCount = noteIndex + 1; // increment noteIndex++; if (noteIndex >= maximalNotesCount) { break; } } } /** * Parse next note for nokia string */ bool pwmTone_nokiaStringParseNextNote(char* string, uint16_t* startIndex, uint8_t tempo, uint16_t* note, uint16_t* duration) { uint8_t state = 0; char ch; char toneName = '\0'; uint16_t toneDuration = 0; uint8_t octave = 0; bool sharp = FALSE; while (1) { ch = *(string + *startIndex); (*startIndex)++; // space if (ch == ' ') { if (state == 0) { continue; } return FALSE; // invalid string } if (ch == '\0') { return FALSE; } // - get duration if (state == 0) { toneDuration = ch - '0'; state = 1; continue; } // - sharp if (state == 1) { if (ch == '#' || ch == '.') { sharp = TRUE; state = 2; continue; } sharp = FALSE; state = 2; // skip #, keep eval } // - note if (state == 2) { toneName = ch; if (ch == '-') { break; } // done state = 3; continue; } // octave if (state == 3) { octave = ch - '0'; break; } } // check range if (toneName != '-' && (octave < 1 || octave > 8)) { return FALSE; } if (toneDuration < 1 || toneDuration > 9) { return FALSE; } if (toneName != '-' && (toneName < 'a' || toneName > 'h')) { return FALSE; } // set values *duration = pwmTone_getDuration(tempo, 1, toneDuration / 2); // 2 => whole, 4 => half, 8 => quarter if (toneName != '-') { *note = pwmTone_getTone(toneName, sharp, octave); } else { *note = PWM_NOTE_NONE; } return TRUE; } /** * Build tone from note name and octave */ uint16_t pwmTone_getTone(char noteName, bool sharp, uint8_t octave) { uint16_t tone; // get offset switch (noteName) { case 'c': case 'C': tone = 0; break; case 'd': case 'D': tone = 2; break; case 'e': case 'E': tone = 4; break; case 'f': case 'F': tone = 5; break; case 'g': case 'G': tone = 7; break; case 'a': case 'A': tone = 9; break; case 'h': case 'H': case 'b': case 'B': tone = 11; break; } // Sharp if (sharp) { tone = tone + 1; } // Octave tone = tone + (12 * octave); // Convert to frequency return (uint16_t)(16.375f * exp(0.0577f * tone) + 0.5f); // 0.5 for round } #endif