powerpic
Replacement board for a Casio CA-53W
/** @file alarm.h
*
* This library implements configurable alarms.
*/
#include <xc.h>
#include "drivers/rtcc.h"
#include "lib/isr.h"
#include "lib/events.h"
#define LOG_TAG "lib.alarm"
#include "lib/logging.h"
#include "lib/alarm.h"
/**
* This array holds the alarms that are currently registered.
*/
static datetime_t registered_alarms[ALARM_MAX_ALARMS] = {0};
/** Number of currently registered alarms. */
static unsigned char registered_alarms_count = 0;
/**
* This inserts an alarm at the given index. Shifts alarms around if needed.
*/
void alarm_insert(unsigned char index, datetime_t *alarm_dt);
/** Alarm interrupt service routine. */
static void alarm_isr (void);
void
alarm_init (void)
{
registered_alarms_count = 0;
// Set alarm mask to match HH:MM:SS
rtcc_alarm_mask_set(0b0110);
// Register alarm isr
isr_register(8, _PIR8_RTCCIF_MASK, &alarm_isr);
// Enable alarm interrupts
rtcc_alarm_interrupt_enable();
}
void
alarm_get (datetime_t *alarm_datetime)
{
*alarm_datetime = registered_alarms[0];
}
void
alarm_set_time (time_t *alarm_time, unsigned char event_data)
{
// Check if we have a free spot
//
if ((ALARM_MAX_ALARMS > registered_alarms_count))
{
// Our datetime object that represents this alarm.
datetime_t alarm_dt;
// Set the time of our alarm.
alarm_dt.time = *alarm_time;
// **NOTE**: if a 0 is in the second ones place, the alarm will not
// occur. We work around this by just setting 0 to 1. An unwanted effect
// of this hack is that e.g. 12:30:00 is considered the same as 12:30:01
// and one will not be registered if the other is. We could solve this
// by supporting more than one alarm event for a given time.
if (0 == (alarm_dt.time.second & 0x0F))
{
alarm_dt.time.second++;
}
// Populate the alarm with current date.
datetime_today(&alarm_dt.date);
// Figure out true date of alarm.
// NOTE: This will make all alarms sort correctly, but what happens
// if today is the last day of the month? We work around this currently
// by just matching the alarm time and not the date. It has yet to be
// determined how this effects midnight rollover.
//
// Check to see if time is before right now.
if (alarm_time->hour <= HOURS)
{
if (alarm_time->hour == HOURS)
{
if (alarm_time->minute <= MINUTES)
{
if (alarm_time->minute == MINUTES)
{
if (alarm_time->second <= SECONDS)
{
if (alarm_time->second == SECONDS)
{
// ALARM IS RIGHT NOW! PANIC!
// The caller probably wants it tomorrow?
alarm_dt.date.day += 1;
//Subject to change
}
else
{
// If the alarm second is less than the current
// second, we assume the alarm is for tomrrow.
alarm_dt.date.day += 1;
}
}
else
{
// The alarm is in less than a minute.
}
}
else
{
// If the alarm minute is less than the current minute, we
// assume the alarm is for tomrrow.
alarm_dt.date.day += 1;
}
}
else
{
// The alarm is in less than an hour.
}
}
else
{
// If the alarm hour is less than the current hour, we assume the
// alarm is for tomorrow.
alarm_dt.date.day += 1;
}
}
else
{
// If it is later than the current hour, we assume today.
}
// Set our datetime
alarm_set_datetime(&alarm_dt, event_data);
}
else
{
// Max alarms have been added
LOG_ERROR("Max alarms added: %.2i:%.2i:%.2i",
BCD2DEC(alarm_time->hour),
BCD2DEC(alarm_time->minute),
BCD2DEC(alarm_time->second)
);
}
}
void
alarm_set_datetime (datetime_t *alarm_datetime, unsigned char event_data)
{
// Check if we have a free spot
//
if ((ALARM_MAX_ALARMS > registered_alarms_count))
{
// TODO: Verify alarm is within bounds of time:
// - Hour 0-23
// - Minute 0-59
// - Seconds *0-59
// **NOTE**: if a 0 is in the second ones place, the alarm will not
// occur. We work around this by just setting 0 to 1. An unwanted effect
// of this hack is that e.g. 12:30:00 is considered the same as 12:30:01
// and one will not be registered if the other is. We could solve this
// by supporting more than one alarm event for a given time.
if (0 == (alarm_datetime->time.second & 0x0F))
{
alarm_datetime->time.second++;
}
// We store the event data in the weekday field
alarm_datetime->date.weekday = event_data;
int alarm_index = -1;
// Loop through the list of registered alarms and compare.
// Right now we only compare up to the day, so monthly alarms might now
// work correctly.
for (unsigned char i = 0; i < registered_alarms_count; i++)
{
if (alarm_datetime->date.day < registered_alarms[i].date.day)
{
// This is our alarm index
alarm_index = i;
break;
}
else if (alarm_datetime->date.day == registered_alarms[i].date.day)
{
// If the day is the same, compare the times.
if (alarm_datetime->time.hour < registered_alarms[i].time.hour)
{
// This is our alarm index
alarm_index = i;
break;
}
else if (alarm_datetime->time.hour == registered_alarms[i].time.hour)
{
// If the hour is the same, compare the minute.
if (alarm_datetime->time.minute < registered_alarms[i].time.minute)
{
// This is our alarm index
alarm_index = i;
break;
}
else if (alarm_datetime->time.minute == registered_alarms[i].time.minute)
{
// If the minute is the same, compare the seconds.
if (alarm_datetime->time.second < registered_alarms[i].time.second)
{
// This is our alarm index
alarm_index = i;
break;
}
else if (alarm_datetime->time.second == registered_alarms[i].time.second)
{
// An alarm with that time is already registered.
LOG_WARNING("Duplicate alarm");
return;
}
else
{
// Alarm is after this index
// Check if this is the last registered alarm.
if ((i+1) == registered_alarms_count)
{
// This is our alarm index
alarm_index = i+1;
break;
}
}
}
else
{
// Alarm is after this index
// Check if this is the last registered alarm.
if ((i+1) == registered_alarms_count)
{
// This is our alarm index
alarm_index = i+1;
break;
}
}
}
else
{
// Alarm is after this index
// Check if this is the last registered alarm.
if ((i+1) == registered_alarms_count)
{
// This is our alarm index
alarm_index = i+1;
break;
}
}
}
else
{
// Alarm is after this index
// Check if this is the last registered alarm.
if ((i+1) == registered_alarms_count)
{
// This is our alarm index
alarm_index = i+1;
break;
}
}
}
// Make sure our alarm index has been set in the spaghetti above.
if (-1 == alarm_index)
{
// Check if we have any alarms registered
if (0 == registered_alarms_count)
{
// Alarm is the only registered alarm
alarm_index = 0;
}
else
{
// We should never get here
LOG_ERROR("Couldn't register alarm!");
return;
}
}
// Increment the count of registered alarms.
registered_alarms_count++;
// Insert our alarm into the list.
alarm_insert((unsigned char)alarm_index, alarm_datetime);
LOG_INFO("Registered alarm (%i/%i) %.2i:%.2i:%.2i %.2i/%.2i x%.2X%.2X",
alarm_index+1,
registered_alarms_count,
BCD2DEC(alarm_datetime->time.hour),
BCD2DEC(alarm_datetime->time.minute),
BCD2DEC(alarm_datetime->time.second),
BCD2DEC(alarm_datetime->date.day),
BCD2DEC(alarm_datetime->date.month),
alarm_datetime->date.weekday,
ALARM_EVENT
);
}
else
{
// Max alarms have been added
LOG_ERROR("Max alarms added: %.2i:%.2i:%.2i",
BCD2DEC(alarm_datetime->time.hour),
BCD2DEC(alarm_datetime->time.minute),
BCD2DEC(alarm_datetime->time.second)
);
}
}
unsigned char
alarm_del_event (unsigned char event_data)
{
unsigned char deleted_alarms = 0;
// Loop through the registered_alarm list and remove alarms that have the
// specified event_data. The event_data is stored in the weekday field.
for (int i = registered_alarms_count-1; i >= 0; i--)
{
if (event_data == registered_alarms[i].date.weekday)
{
LOG_DEBUG("Removing alarm: (x%.2X) %.2i:%.2i",
registered_alarms[i].date.weekday,
BCD2DEC(registered_alarms[i].time.hour),
BCD2DEC(registered_alarms[i].time.minute)
);
// Remove alarm and shift all other up.
// registered_alarms[i] = (datetime_t){0};
deleted_alarms++;
registered_alarms_count--;
for (int j = i+1; j < registered_alarms_count; j++)
{
registered_alarms[j-1].time = registered_alarms[j].time;
registered_alarms[j-1].date = registered_alarms[j].date;
}
}
}
return deleted_alarms;
}
void
alarm_insert (unsigned char index, datetime_t *alarm_dt)
{
// Shift alarms behind
for (int i = registered_alarms_count; i >= index; i--)
{
registered_alarms[i+1].time = registered_alarms[i].time;
registered_alarms[i+1].date = registered_alarms[i].date;
}
// Insert alarm at the index
registered_alarms[index] = *alarm_dt;
// If the alarm is going to happen the soonest (index 0), then set the
// alarm registers.
if (0 == index)
{
// Disable alarm to change registers
rtcc_alarm_disable();
// Set Registers
// ALRMDAY = registered_alarms[0].date.day;
ALRMHR = registered_alarms[0].time.hour;
ALRMMIN = registered_alarms[0].time.minute;
ALRMSEC = registered_alarms[0].time.second;
// Enable alarm
rtcc_alarm_enable();
}
}
static void
alarm_isr (void)
{
// Alarm with index 0 has occurred. alarm is disabled.
rtcc_alarm_disable();
// Emit an alarm event with the specified data.
event_isr((unsigned)
EVENT_ID(
ALARM_EVENT,
registered_alarms[0].date.weekday)
);
// Shift alarms down
for (int i = 1; i < registered_alarms_count; i++)
{
registered_alarms[i-1].time = registered_alarms[i].time;
registered_alarms[i-1].date = registered_alarms[i].date;
}
// Decrement count of registered alarms
registered_alarms_count--;
// Check if we have any alarms registered
if (registered_alarms_count)
{
// Set alarm registers with index 0 values.
ALRMHR = registered_alarms[0].time.hour;
ALRMMIN = registered_alarms[0].time.minute;
ALRMSEC = registered_alarms[0].time.second;
rtcc_alarm_enable();
}
// Clear alarm interrupt flag
rtcc_alarm_interrupt_clear();
}
// EOF //