powerpic
Replacement board for a Casio CA-53W
/** @file buzzer.c
*
* This library implements functionality of the piezo buzzer on the watch.
*/
#include <xc.h>
#include <math.h>
#include "drivers/pwm.h"
#include "lib/isr.h"
#include "lib/logging.h"
#include "lib/buzzer.h"
#undef LOG_TAG
#define LOG_TAG "lib.buzzer"
static volatile unsigned int tone_duration = 0;
static volatile unsigned int played_duration = 0;
static void buzzer_isr (void);
void
buzzer_init (void)
{
// Initialize our pwm driver. This configures it for our buzzer.
pwm_init();
// Register ISR
// Should this be done only when we are playing a tone?
isr_register(4, _PIR4_TMR2IF_MASK, &buzzer_isr);
}
void
buzzer_tone (unsigned int frequency, unsigned char volume, unsigned int duration)
{
pwm_freq_set(frequency);
pwm_duty_set(volume/2);
pwm_enable();
// This delay loop blocks for the given duration. It would be preferrable
// to use an interrupt. Perhaps use the pwm timer interrupt? Would have to
// service it at 1/4 the frequency, but it might be better than sitting
// around twiddling our cycles.
//
int delay_count = 0;
while (delay_count++ < duration)
{
__delay_us(990);
}
pwm_disable();
}
void
buzzer_tone2 (unsigned int frequency, unsigned char volume, unsigned int duration)
{
pwm_freq_set(frequency);
pwm_duty_set(volume/2);
// Duration of 1 interrupt in ms
float timer_duration = 1.0F/((frequency / 3.0F) / 1000);
// Figure out how many interrupt we need to delay
tone_duration = (unsigned int)lroundf(duration / timer_duration);
played_duration = 0;
LOG_DEBUG("Timer Duration: %f", timer_duration);
LOG_DEBUG("Tone Duration: %i", tone_duration);
// enable interrupt
PIE4bits.TMR2IE = 1;
// start tone
pwm_enable();
}
#define IS_ALPHA(c) (('A' <= (c) && (c) <= 'Z') || ('a' <= (c) && (c) <= 'z'))
#define IS_NUM(c) ('0' <= (c) && (c) <= '9')
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
const int Notes[] = { NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7,
NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7 };
void
buzzer_play_rtttl(char * rtttl_str)
{
unsigned char song_title[10];
unsigned int char_index = 0;
// Get first 10 characters of title
while (rtttl_str[char_index] != ':')
{
if (char_index < sizeof(song_title))
{
song_title[char_index] = rtttl_str[char_index];
}
char_index++;
}
char_index++; // Skip colon
// Get note defaults
unsigned char song_def_duration = 4;
unsigned char song_def_octave = 6;
unsigned int song_def_bpm = 63;
while (rtttl_str[char_index] != ':')
{
switch (rtttl_str[char_index])
{
case 'd':
char_index += 2; // Consume 'b='
// Set duration
song_def_duration = 0;
while (IS_NUM(rtttl_str[char_index]))
{
song_def_duration = (song_def_duration * 10) \
+ (rtttl_str[char_index++] - 48);
}
break;
case 'o':
char_index += 2; // Consume 'o='
// Set octave
song_def_octave = rtttl_str[char_index++] - 48;
break;
case 'b':
char_index += 2; // Consume 'b='
// Set bpm
song_def_bpm = 0;
while (IS_NUM(rtttl_str[char_index]))
{
song_def_bpm = (song_def_bpm * 10) \
+ (rtttl_str[char_index++] - 48);
}
break;
default:
// Unrecognized command, we'll skip it in a few loops.
char_index++;
break;
}
}
char_index++; // Skip colon
// Print info
LOG_INFO("Now Playing: %s", song_title);
LOG_DEBUG("in octave: %u", song_def_octave);
LOG_DEBUG("note len: %u", song_def_duration);
LOG_DEBUG("BPM: %u", song_def_bpm);
// Needed values
unsigned int note_length_ms = (unsigned)((60000U/song_def_bpm)*4);
unsigned char note_duration;
unsigned char note_pitch;
unsigned char note_sharp;
unsigned char note_octave;
// Play notes until end of string is reached
while (rtttl_str[char_index])
{
// Check if duration is present
if (IS_NUM(rtttl_str[char_index]))
{
// Get duration of note
note_duration = rtttl_str[char_index++] - 48;
if (IS_NUM(rtttl_str[char_index]))
{
note_duration = (note_duration * 10) \
+ (rtttl_str[char_index++] - 48);
}
}
else
{
// Go with default duration
note_duration = song_def_duration;
}
// Get the pitch
// note_pitch = rtttl_str[char_index++];
switch (rtttl_str[char_index++])
{
case 'c':
case 'C':
note_pitch = 0;
case 'd':
case 'D':
note_pitch = 2;
break;
case 'e':
case 'E':
note_pitch = 4;
break;
case 'f':
case 'F':
note_pitch = 5;
break;
case 'g':
case 'G':
note_pitch = 7;
break;
case 'a':
case 'A':
note_pitch = 9;
break;
case 'b':
case 'h':
case 'B':
case 'H':
note_pitch = 11;
break;
default:
note_pitch = 50;
break;
}
// Check for sharps
if (rtttl_str[char_index] == '#')
{
note_sharp = '#';
note_pitch++;
char_index++;
}
else
{
note_sharp = ' ';
}
// Check for period for special-duration
if (rtttl_str[char_index] == '.')
{
// Increase duration by 50%
note_duration += note_duration / 2;
char_index++; // Consume '.'
}
// Check for octave specifier
if (IS_NUM(rtttl_str[char_index]))
{
note_octave = rtttl_str[char_index++] - 48;
}
else
{
// Go with default octave
note_octave = song_def_octave;
}
// Skip comma
if (rtttl_str[char_index] == ',')
{
char_index++; // Consume ','
}
// Play note
if (note_pitch < 13)
{
unsigned int note_freq = (unsigned)((Notes[note_pitch] >> (7 - note_octave)) + 1);
unsigned int pause_duration = (unsigned)((note_length_ms / note_duration) >> 4);
unsigned int tone_duration = (unsigned)((note_length_ms / note_duration) - pause_duration);
LOG_DEBUG("Playing note: %uhz for %ums, pause %ums", note_freq, tone_duration, pause_duration);
buzzer_tone(note_freq, 25, tone_duration);
int delay_count = 0;
while (delay_count++ < pause_duration)
{
__delay_us(990);
}
}
else
{
// Rest
LOG_DEBUG("Resting for %ums", (note_length_ms / note_duration));
int delay_count = 0;
while (delay_count++ < (note_length_ms / note_duration))
{
__delay_us(990);
}
}
}
}
static void
buzzer_isr (void)
{
// This gets called at ~1/3 the PWM frequency
if (tone_duration <= played_duration)
{
// stop tone
// pwm_disable();
PWM4CONbits.PWM4EN = 0;
// Disable interrupt
PIE4bits.TMR2IE = 0;
}
else
{
played_duration++;
}
// Clear interrupt flag
PIR4bits.TMR2IF = 0;
}
// EOF //