intermediate checkin: working on timer

was about to start handling the scheduling for cycle counted events the
same way as for real time scheduled events, but i think i'll switch over
to using `<util/atomic.h>`, if i can
partial-rewrite
Ben Blazak 2013-05-16 21:52:18 -07:00
parent 758e95b17e
commit 7371362576
11 changed files with 510 additions and 244 deletions

View File

@ -164,12 +164,14 @@ void * list__pop_node_next(list__list_t * list, void * node) {
}
void list__free(list__list_t * list) {
void * node;
while (list->head) {
node = list->head;
list->head = N(list->head)->next;
free(node);
if (list) {
void * node;
while (list->head) {
node = list->head;
list->head = N(list->head)->next;
free(node);
}
free(list);
}
free(list);
}

View File

@ -7,7 +7,7 @@
/** description
* Timer interface
*
* Prefix: `timer__`
* Prefixes: `timer__`, `timer___`
*/
@ -18,10 +18,19 @@
uint8_t timer__init (void);
uint16_t timer__get_cycles (void);
uint16_t timer__get_milliseconds (void);
uint8_t timer__schedule ( uint16_t milliseconds,
void(*function)(void) );
uint8_t timer__schedule__cycles ( uint16_t cycles,
void(*function)(void) );
uint8_t timer__schedule__milliseconds ( uint16_t milliseconds,
void(*function)(void) );
// ----------------------------------------------------------------------------
// private
void timer___increment_cycles (void);
// ----------------------------------------------------------------------------
@ -51,6 +60,17 @@ uint8_t timer__schedule ( uint16_t milliseconds,
* - Should be called exactly once by `main()` before entering the run loop.
*/
// === timer__get_cycles() ===
/** functions/timer__get_cycles/description
* Return the number of cycles since the timer was initialized (mod 2^16)
*
* Returns:
* - success: The number of cycles since the timer was initialized (mod 2^16)
*
* Usage notes:
* - See the documentation for `timer__get_milliseconds()`
*/
// === timer__get_milliseconds() ===
/** functions/timer__get_milliseconds/description
* Return the number of milliseconds since the timer was initialized (mod 2^16)
@ -122,8 +142,28 @@ uint8_t timer__schedule ( uint16_t milliseconds,
* except within the first 255 milliseconds of the timer being initialized.
*/
// === timer__schedule() ===
/** functions/timer__schedule/description
// === timer__schedule__cycles() ===
/** functions/timer__schedule__cycles/description
* Schedule `function` to run in the given number of cycles
*
* Arguments:
* - `cycles`: The number of cycles to wait
* - `function`: A pointer to the function to run
*
* Returns:
* - success: `0`
* - failure: [other]
*
* Usage notes:
* - If possible, prefer scheduling using this function over scheduling using
* `timer__schedule__milliseconds()`. Functions run by this scheduler don't
* have to be quite as careful about finishing quickly, repeating too soon,
* or modifying shared variables, since they will not be executing inside an
* interrupt vector.
*/
// === timer__schedule__milliseconds() ===
/** functions/timer__schedule__milliseconds/description
* Schedule `function` to run in the given number of milliseconds
*
* Arguments:
@ -141,7 +181,23 @@ uint8_t timer__schedule ( uint16_t milliseconds,
* Usage notes:
* - If a function needs a longer wait time than is possible with a 16-bit
* millisecond resolution counter, it can repeatedly schedule itself to run
* in, say, 1 minute, increment a counter each time, and then only execute
* its body code after, say, 5 calls (for a 5 minute delay).
* in, say, 1 minute (= 1000*60 milliseconds), increment a counter each time,
* and then only execute its body code after, say, 5 calls (for a 5 minute
* delay).
*/
// ----------------------------------------------------------------------------
// private
// === timer___increment_cycles() ===
/** functions/timer___increment_cycles/description
* Increment the counter for the number of cycles, and perform scheduled tasks
*
* Meant to be used only by `main()`
*
* Notes:
* - The corresponding real-time function (dealing with milliseconds instead of
* cycles) will be in an interrupt vector, and not explicitly called anywhere
* in the code.
*/

View File

@ -1,114 +0,0 @@
/* ----------------------------------------------------------------------------
* Copyright (c) 2013 Ben Blazak <benblazak.dev@gmail.com>
* Released under The MIT License (see "doc/licenses/MIT.md")
* Project located at <https://github.com/benblazak/ergodox-firmware>
* ------------------------------------------------------------------------- */
/** description
* Implements the timer functions defined in "../timer.h" for the ATMega32U4
*
* See the accompanying '.md' file for further documentation.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include "../../../firmware/lib/data-types/list.h"
// ----------------------------------------------------------------------------
#if F_CPU != 16000000
#error "Expecting different CPU frequency"
#endif
// ----------------------------------------------------------------------------
typedef struct {
list__node_t _private;
uint16_t milliseconds;
void(*function)(void);
} event_t;
// ----------------------------------------------------------------------------
static volatile uint16_t _milliseconds;
// use `_to_schedule__lock` to make sure that only one function at a time is
// accessing `_to_schedule`, even if that function is preempted in the middle
// of its execution by an interrupt
static list__list_t * _to_schedule;
static bool _to_schedule__lock;
// for use *only* in `ISR(TIMER0_COMPA_vect)` (after initialization)
static list__list_t * _scheduled;
// ----------------------------------------------------------------------------
uint8_t timer__init(void) {
_scheduled = list__new();
_to_schedule = list__new();
if (!_scheduled || !_to_schedule)
return 1; // error
OCR0A = 250; // (ticks per millisecond)
TCCR0A = 0b00000010; // (configure Timer/Counter 0)
TCCR0B = 0b00000011; // (configure Timer/Counter 0)
TIMSK0 = 0b00000010; // (enable interrupt vector)
return 0; // success
}
uint16_t timer__get_milliseconds(void) {
return _milliseconds;
}
uint8_t timer__schedule(uint16_t milliseconds, void(*function)(void)) {
if (!function) return 0; // success: there is no function to add
if (_to_schedule__lock) return 1; // error
_to_schedule__lock = true;
event_t * event = list__insert(_to_schedule, -1, malloc(sizeof(event_t)));
if (!event) {
_to_schedule__lock = false;
return 2; // error
}
event->milliseconds = milliseconds;
event->function = function;
_to_schedule__lock = false;
return 0; // success
}
// ----------------------------------------------------------------------------
ISR(TIMER0_COMPA_vect) {
_milliseconds++;
if (!_to_schedule__lock) {
_to_schedule__lock = true;
// pop events from `_to_schedule` and insert them into `_scheduled`
// until there are no more events in `_to_schedule`
while ( list__insert( _scheduled, -1,
list__pop_index(_to_schedule, 0) ) );
_to_schedule__lock = false;
}
for (event_t * event = _scheduled->head; event;) {
if (event->milliseconds == 0) {
(*event->function)();
event = list__pop_node_next(_scheduled, event);
} else {
event->milliseconds--;
event = event->_private.next;
}
}
}

122
firmware/lib/timer/common.c Normal file
View File

@ -0,0 +1,122 @@
/* ----------------------------------------------------------------------------
* Copyright (c) 2013 Ben Blazak <benblazak.dev@gmail.com>
* Released under The MIT License (see "doc/licenses/MIT.md")
* Project located at <https://github.com/benblazak/ergodox-firmware>
* ------------------------------------------------------------------------- */
/** description
* Implements the non device specific functionality of
* ".../firmware/lib/timer", and all the functions defined in "./common.h"
*/
#include "./common.h"
// ----------------------------------------------------------------------------
uint8_t timer__init(void) {
_cycles_scheduled = list__new();
_milliseconds_scheduled = list__new();
_milliseconds_to_schedule = list__new();
if ( !_cycles_scheduled ||
!_milliseconds_scheduled ||
!_milliseconds_to_schedule ) {
list__free(_cycles_scheduled);
list__free(_milliseconds_scheduled);
list__free(_milliseconds_to_schedule);
return 1; // error
}
return _device__init();
}
uint16_t timer__get_cycles(void) {
return _cycles;
}
uint16_t timer__get_milliseconds(void) {
return _milliseconds;
}
uint8_t timer__schedule_cycles(uint16_t cycles, void(*function)(void)) {
// - there is the possibility that this function could be called by an
// event executing within an interrupt vector. if that happens, and the
// interrupt originally interrupted this same function, it wouldn't be
// safe to operate on `_cycles_scheduled`
if (lock) return 1; // error
lock = true;
uint8_t ret = _event_list__append(_cycles_scheduled, cycles, function);
lock = false;
return ret;
}
uint8_t timer__schedule_milliseconds( uint16_t milliseconds,
void(*function)(void) ) {
// - even though the only other place `...to_schedule` should be referenced
// is within an interrupt vector (which this function can't interrupt),
// we have to be careful: what if this function is interrupted, and an
// event executes that calls this function? this function would
// essentially be interrupting itself. we must avoid that.
if (_milliseconds_to_schedule_lock) return 1; // error
_milliseconds_to_schedule_lock = true;
uint8_t ret = _event_list__append( _milliseconds_to_schedule,
milliseconds,
function );
_milliseconds_to_schedule_lock = false;
return ret;
}
// ----------------------------------------------------------------------------
void timer___increment_cycles (void) {
_cycles++;
_event_list__update(_cycles_scheduled);
}
// ----------------------------------------------------------------------------
uint8_t _event_list__append( list__list_t * list,
uint16_t ticks,
void(*function)(void) ) {
if (!function) return 0; // success: there is no function to add
_event_t * event = list__insert(list, -1, malloc(sizeof(_event_t)) );
if (!event) return 1; // error
event->ticks = ticks;
event->function = function;
return 0; // success
}
void _event_list__update(list__list_t * list) {
for (_event_t * event = list->head; event;) {
if (event->ticks == 0) {
(*event->function)();
event = list__pop_node_next(list, event);
} else {
event->ticks--;
event = event->_private.next;
}
}
}
void _event_list__move(list__list_t * to, list__list_t * from) {
if (from->length == 0) return; // nothing to move
((_event_t *)to->tail)->_private.next = from->head;
to->tail = from->tail;
to->length += from->length;
from->head = NULL;
from->tail = NULL;
from->length = 0;
}

218
firmware/lib/timer/common.h Normal file
View File

@ -0,0 +1,218 @@
/* ----------------------------------------------------------------------------
* Copyright (c) 2013 Ben Blazak <benblazak.dev@gmail.com>
* Released under The MIT License (see "doc/licenses/MIT.md")
* Project located at <https://github.com/benblazak/ergodox-firmware>
* ------------------------------------------------------------------------- */
/** description
* A central place for all `#include`s and common definitions relevant to this
* timer library
*
* Prefix: `_`
*/
#ifndef ERGODOX_FIRMWARE__LIB__TIMER__COMMON__H
#define ERGODOX_FIRMWARE__LIB__TIMER__COMMON__H
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "../../../firmware/lib/data-types/list.h"
// ----------------------------------------------------------------------------
typedef struct {
list__node_t _private; // "subclass" `list__node_t`
uint16_t ticks; // cycles or milliseconds
void(*function)(void);
} _event_t;
// ----------------------------------------------------------------------------
uint16_t _cycles;
list__list_t * _cycles_scheduled;
list__list_t * _cycles_to_schedule;
list__list_t * _cycles_to_schedule_lock;
volatile uint16_t _milliseconds;
list__list_t * _milliseconds_scheduled;
list__list_t * _milliseconds_to_schedule;
bool _milliseconds_to_schedule_lock;
// ----------------------------------------------------------------------------
uint8_t _device__init (void);
uint8_t _event_list__append ( list__list_t * list,
uint16_t ticks,
void(*function)(void) );
void _event_list__update (list__list_t * list);
void _event_list__move (list__list_t * to, list__list_t * from);
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
#endif // ERGODOX_FIRMWARE__LIB__TIMER__COMMON__H
// ============================================================================
// === Documentation ==========================================================
// ============================================================================
// ----------------------------------------------------------------------------
// typedefs -------------------------------------------------------------------
// ----------------------------------------------------------------------------
// === _event_t ===
/** typedefs/_event_t/description
* A struct to represent a scheduled event
*
* Members:
* - `_private`: The C way of "subclassing" another type
* - `ticks`: The number of cycles or milliseconds in which the given event
* should run
* - `function`: A pointer to the function to run
*/
// ----------------------------------------------------------------------------
// variables ------------------------------------------------------------------
// ----------------------------------------------------------------------------
// === _cycles ===
/** variables/_cycles/description
* The number of scan cycles since the timer was initialized (mod 2^16)
*/
// === _cycles_scheduled ===
/** variables/_cycles_scheduled/description
* A list of scheduled events, to be run in a given number of cycles
*
* Meant to be used only by `timer___increment_cycles()` (after initialization)
*/
// === _cycles_to_schedule ===
//
/** variables/_cycles_to_schedule/description
* A list of events to schedule
*
* Meant to be used only by `timer__schedule_cycles()` and
* `timer___increment_cycles()` (after initialization)
*
* `timer___increment_cycles()` will move the events in this list to
* `_cycles_scheduled` the next time it runs and the latter list is not locked.
*
* The reason for having both `_cycles_scheduled` and `_cycles_to_schedule` is
* that this allows us to only reference `_cycles_scheduled` from place, which
* in turn allows us not to have to worry about whether that function will have
* access to it when it runs.
*/
// === _cycles_to_schedule_lock ===
//
/** variables/_milliseconds_to_schedule_lock/description
* A lock for `_milliseconds_to_schedule`
*
* To make sure that if `timer__schedule_milliseconds()` is interrupted by the
* interrupt vector that may touch `_milliseconds_to_schedule`, the interrupt
* leaves the variable alone until the function is done with it.
*/
// === _milliseconds ===
/** variables/_milliseconds/description
* The number of milliseconds since the timer was initialized (mod 2^16)
*/
// === _milliseconds_scheduled ===
/** variables/_milliseconds_scheduled/description
* A list of scheduled events, to be run in a given number of milliseconds
*
* Meant to be used only in the interrupt vector responsible for updating
* `_milliseconds` (after initialization)
*/
// === _milliseconds_to_schedule ===
/** variables/_milliseconds_to_schedule/description
* A list of events to schedule
*
* Meant to be used only by `timer__schedule_milliseconds()` and the interrupt
* vector that updates `_milliseconds` (after initialization)
*
* The responsible interrupt vector will move the events in this list to
* `_milliseconds_scheduled` the next time it runs and the latter list is not
* locked.
*
* The reason for having both `_milliseconds_scheduled` and
* `_milliseconds_to_schedule` is that this way only the events that have not
* yet been scheduled are effected if `timer__schedule_milliseconds()` is
* interrupted by the interrupt vector. In this case, time for these
* unscheduled events will not start being counted until they are scheduled,
* hopefully on the interrupt vector's next run. If, on the other hand, there
* were only one list and the function was interrupted, every event in the list
* would be inaccessible to the interrupt vector, and we would not be able to
* count time for *any* of them during that run.
*/
// === _milliseconds_to_schedule_lock ===
/** variables/_milliseconds_to_schedule_lock/description
* A lock for `_milliseconds_to_schedule`
*
* To make sure that if `timer__schedule_milliseconds()` is interrupted by the
* interrupt vector that may touch `_milliseconds_to_schedule`, the interrupt
* leaves the variable alone until the function is done with it.
*/
// ----------------------------------------------------------------------------
// functions ------------------------------------------------------------------
// ----------------------------------------------------------------------------
// === _device__init() ===
/** functions/_device__init/description
* Initialize the device specific portions of the timer
*
* Meant to be called only by `timer__init()`
*
* Returns:
* - success: `0`
* - failure: [other]
*/
// === _event_list__append() ===
/** functions/_event_list__append/description
* Append an event with the given attributes to `list`
*
* Arguments:
* - `list`: The list to add the new event to
* - `ticks`: The number of ticks to assign to the event
* - `function`: A pointer to the function to assign to the event
*
* Returns:
* - success: `0`
* - failure: [other]
*/
// === _event_list__update() ===
/** functions/_event_list__update/description
* Decrement the `ticks` of every event in `list` by `1`, and run any event
* with `ticks == 0`
*
* Arguments:
* - `list`: The list to update
*/
// === _event_list__move() ===
/** functions/_event_list__move/description
* Move all events in `from` to `to`, and clear `to`
*
* Arguments:
* - `to`: The list into which to move all elements
* - `from`: The list to move all elements from
*/

View File

@ -0,0 +1,45 @@
/* ----------------------------------------------------------------------------
* Copyright (c) 2013 Ben Blazak <benblazak.dev@gmail.com>
* Released under The MIT License (see "doc/licenses/MIT.md")
* Project located at <https://github.com/benblazak/ergodox-firmware>
* ------------------------------------------------------------------------- */
/** description
* Timer interface for device functions
*
* Prefix: `timer___device__`
*/
#ifndef ERGODOX_FIRMWARE__LIB__TIMER__DEVICE__H
#define ERGODOX_FIRMWARE__LIB__TIMER__DEVICE__H
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// private
void timer___device__init (void);
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
#endif // ERGODOX_FIRMWARE__LIB__TIMER__DEVICE__H
// ============================================================================
// === documentation ==========================================================
// ============================================================================
// ----------------------------------------------------------------------------
// functions ------------------------------------------------------------------
// ----------------------------------------------------------------------------
// === timer___device__init() ===
/** functions/timer___device__init/description
* Initialize the device specific portions of the timer
*/

View File

@ -0,0 +1,50 @@
/* ----------------------------------------------------------------------------
* Copyright (c) 2013 Ben Blazak <benblazak.dev@gmail.com>
* Released under The MIT License (see "doc/licenses/MIT.md")
* Project located at <https://github.com/benblazak/ergodox-firmware>
* ------------------------------------------------------------------------- */
/** description
* Implements the device specific functionality of ".../firmware/lib/timer" for
* the ATMega32U4
*
* See the accompanying '.md' file for further documentation.
*/
#include <avr/interrupt.h>
#include <avr/io.h>
#include "../common.h"
// ----------------------------------------------------------------------------
#if F_CPU != 16000000
#error "Expecting different CPU frequency"
#endif
// ----------------------------------------------------------------------------
uint8_t _device__init(void) {
OCR0A = 250; // (ticks per millisecond)
TCCR0A = 0b00000010; // (configure Timer/Counter 0)
TCCR0B = 0b00000011; // (configure Timer/Counter 0)
TIMSK0 = 0b00000010; // (enable interrupt vector)
return 0; // success
}
// ----------------------------------------------------------------------------
ISR(TIMER0_COMPA_vect) {
_milliseconds++;
// if possible, move all events from `...to_schedule` into `...scheduled`
// - interrupts are disabled here, so it's safe to operate on the list
// without locking it, as long as it's not already locked
if (!_milliseconds_to_schedule_lock)
_event_list__move(_milliseconds_scheduled, _milliseconds_to_schedule);
_event_list__update(_milliseconds_scheduled);
}

View File

@ -11,5 +11,6 @@
#
SRC += $(wildcard $(CURDIR)/$(MCU).c)
SRC += $(wildcard $(CURDIR)/*.c)
SRC += $(wildcard $(CURDIR)/device/$(MCU).c)

View File

@ -54,18 +54,6 @@ uint8_t col;
bool update_leds = true;
// --- for `main__timer__` functions ---
typedef struct {
list__node_t _private;
uint16_t cycles;
void(*function)(void);
} event_t;
static uint16_t _cycles;
static list__list_t * _scheduled;
// ----------------------------------------------------------------------------
// --- main() loop ------------------------------------------------------------
@ -95,7 +83,6 @@ int main(void) {
kb__led__delay__usb_init(); // give the OS time to load drivers, etc.
timer__init();
main__timer__init();
kb__led__state__ready();
@ -142,48 +129,9 @@ int main(void) {
#undef off
}
// take care of `main__timer__` stuff
_cycles++;
for (event_t * event = _scheduled->head; event;) {
if (event->cycles == 0) {
(*event->function)();
event = list__pop_node_next(_scheduled, event);
} else {
event->cycles--;
event = event->_private.next;
}
}
timer___increment_cycles();
}
return 0;
}
// ----------------------------------------------------------------------------
// --- `main__timer__` functions ----------------------------------------------
// ----------------------------------------------------------------------------
uint8_t main__timer__init(void) {
_scheduled = list__new();
if (!_scheduled) return 1; // error
return 0; // success
}
uint16_t main__timer__cycles(void) {
return _cycles;
}
uint8_t main__timer__schedule(uint16_t cycles, void(*function)(void)) {
if (!function) return 0; // success: there is no function to add
event_t * event = list__insert(_scheduled, -1, malloc(sizeof(event_t)));
if (!event) return 1; // error
event->cycles = cycles;
event->function = function;
return 0; // success
}

View File

@ -11,17 +11,6 @@
*
* Certain variables are declared here so that other functions can see (and
* perhaps modify) them, to accomplish things that may be difficult otherwise.
*
* The `main__timer__` functions implement a sort of pseudo-timer, counting the
* number of scan cycles instead of a set amount of real time. They are here
* because there isn't really a better place than `main()` to do that. If you
* need a real timer, take a look in ".../firmware/lib/timer". If not, this is
* probably a better set of functions to work with, since lower timer
* resolution and not having to deal with an interrupt vector means lower
* overhead and less overall to worry about.
*
* See ".../firmware/lib/timer.h" for more information on using timers in
* general.
*/
@ -44,12 +33,6 @@ extern uint8_t main__col;
extern bool main__update_leds;
// ----------------------------------------------------------------------------
uint8_t main__timer__init (void);
uint16_t main__timer__cycles (void);
uint8_t main__timer__schedule (uint16_t cycles, void(*function)(void));
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
@ -96,52 +79,7 @@ uint8_t main__timer__schedule (uint16_t cycles, void(*function)(void));
* This is for taking over control the LEDs temporarily, as one may want to
* do when in a special mode, etc. If you want to change the meaning of the
* LEDs under normal use, the correct place to do that is in the layout file,
* where the `kb__led__logical_*()` functions are defined (see the
* where the `kb__led__logical_...()` functions are defined (see the
* documentation in that and related files for more information).
*/
// ----------------------------------------------------------------------------
// functions ------------------------------------------------------------------
// ----------------------------------------------------------------------------
// === main__timer__init() ===
/** functions/main__timer__init/description
* Initialize the "timer"
*
* Returns:
* - success: `0`
* - failure: [other]
*
* Notes:
* - Should be called by `main()` exactly once before entering the run loop.
*/
// === main__timer__cycles() ===
/** functions/main__timer__cycles/description
* Return the number of cycles since the timer was initialized (mod 2^16)
*
* Returns:
* - success: The number of cycles since the timer was initialized (mod 2^16)
*/
// === main__timer__schedule() ===
/** functions/main__timer__schedule/description
* Schedule `function` to run in the given number of cycles
*
* Arguments:
* - `cycles`: The number of cycles to wait
* - `function`: A pointer to the function to run
*
* Returns:
* - success: `0`
* - failure: [other]
*
* Notes:
* - If possible, prefer scheduling using this function over scheduling using
* `timer__schedule()`. Functions run by this scheduler don't have to be
* quite as careful about finishing quickly, repeating too soon, or modifying
* shared variables, since they will be executing inside the `main()` loop
* instead of inside an interrupt vector.
*/