working mostly on lib/eeprom; hopefully done :)

partial-rewrite
Ben Blazak 2013-06-13 16:14:42 -07:00
parent 0a9f4ec61d
commit c24adb542c
7 changed files with 295 additions and 66 deletions

View File

@ -134,7 +134,7 @@
(http://mathforum.org/library/drmath/view/52343.html)
`(-1)%5` in python returns `4` (just like it should)
* [Is it safe to free `void *`?]
* [Is it safe to free a `void *`?]
(http://stackoverflow.com/a/2182522/2360353)
Yes. The memory manager keeps track of the size of allocations - and the
pointer you pass to `free()` is cast to `void *` before deallocation anyway.

View File

@ -14,6 +14,12 @@
* functions provided by `<avr/eeprom.h>`, and should be preferred for those
* operations. There are other things provided by that header that may be
* useful however, and it's likely that both will be needed.
*
* Implementation notes:
* - Writes generated by calls to `eeprom__write()` and `eeprom__copy()` should
* collectively execute in the order in which the calls were performed (i.e.
* all writes should be sequential, in the expected order, regardless of the
* function which generated them).
*/
@ -27,8 +33,9 @@
// ----------------------------------------------------------------------------
uint8_t eeprom__read (uint8_t * address);
void eeprom__write (uint8_t * address, uint8_t data);
uint8_t eeprom__read (uint8_t * from);
uint8_t eeprom__write (uint8_t * to, uint8_t data);
uint8_t eeprom__copy (uint8_t * to, uint8_t * from, uint8_t length);
// ----------------------------------------------------------------------------
@ -47,41 +54,67 @@ void eeprom__write (uint8_t * address, uint8_t data);
* Read and return the data at `address` in the EEPROM memory space
*
* Arguments:
* - `address: The address of (i.e. a pointer to) the location to operate on
* - `from: The address of (i.e. a pointer to) the location to read from
*/
// === eeprom__write() ===
/** functions/eeprom__write/description
* Schedule a write to `address` in the EEPROM memory space
* Schedule a regular 1 byte write to the EEPROM memory space
*
* Arguments:
* - `address: The address of (i.e. a pointer to) the location to operate on
* - `to`: The address of (i.e. a pointer to) the location to write to
* - `data`: The data to write
*
* Returns:
* - success: `0`
* - failure: [other]
*
* Notes:
*
* - Writes are scheduled (i.e. buffered) because writing to EEPROMs takes an
* enormous (relative to a microprocessor clock cycle) amount of time.
* - Due to the technology used, EEPROM bytes, when cleared, have a logical
* value of `1`. Interesting stuff, but I didn't read about it thoroughly
* enough to give my own explanation here.
*
*
* Implementation notes:
*
* - If possible, this function should only modify when necessary; that is,
* when the data to be written is different than the data that's already
* there. This requires more processor time (to read the current value and
* compare), but it's better for the EEPROM (which has a limited write life),
* and will allow the operation to complete *much* more quickly in the event
* that the data has not changed.
*
* - If possible, writing `0xFF` should clear the memory (without writing
* anything), and writing to a location currently set to `0xFF` should write
* without clearing first.
*
* - If possible, this function should "schedule" writes to the EEPROM; that
* is, it should keep track of what needs to be written and write it more or
* less as soon as possible, but return quickly after it's called without
* waiting for the write (or even previous writes) to finish.
* - Undefined behavior will result if
* - `to` is not a valid address
* - This function should only modify when necessary; that is, when the data to
* be written is different than the data that's already there. This requires
* more processor time (to read the current value and compare), but it's
* better for the EEPROM (which has a limited write life), and will allow the
* operation to complete *much* more quickly in the event that the data has
* not changed.
* - Writing `0xFF` should clear the memory (without writing anything), and
* writing to a location currently set to `0xFF` should write without
* clearing first.
*/
// === eeprom__copy() ===
/** functions/eeprom__copy/description
* Copy data from one location in the EEPROM memory space to another
*
* Arguments:
* - `to: The address of (i.e. a pointer to) the location to start writing to
* - `from`: The address of (i.e. a pointer to) the location to start copying
* from
* - `length`: The number of bytes to sequentially copy
*
* Returns:
* - success: `0`
* - failure: [other]
*
* Implementation notes:
* - Undefined behavior will result if
* - `to` is not a valid address.
* - `from` is not a valid address.
* - Any address in either the block you're copying from
* (`from`..`from+length-1`) or the block you're copying to
* (`to`..`to+length-1`) is invalid.
* - The block you're copying from overlaps with the block you're copying
* to.
* - The direction in which the block is copied (beginning with `to` and
* incrementing, or beginning with `to + length - 1` and decrementing) is
* undefined.
*/

View File

@ -6,63 +6,125 @@
/** description
* Implements the EEPROM interface defined in "../eeprom.h" for the ATMega32U4
*
* These functions (and most of the comments) are taken more or less straight
* from the data sheet, section 5.3
*
* Assumptions
* - Voltage will never fall below the specified minimum for the clock
* frequency being used.
* - The address passed as `address` to any of the functions is valid.
* - A write to flash memory (PROGMEM) will never be in progress when any of
* these functions are called.
*
* Warnings:
* - These functions are *not* reentrant (i.e. they must never be called within
* an interrupt unless all calls to these functions are protected by having
* interrupts disabled for the duration of the call).
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <util/atomic.h>
#include <avr/io.h>
#include "../../../firmware/lib/data-types/list.h"
#include "../../../firmware/lib/timer.h"
#include "../eeprom.h"
// ----------------------------------------------------------------------------
/** functions/eeprom__read/description
* Implementation notes:
/** types/write_t/description
* To hold values for a "write" to the EEPROM
*
* - If a write is in progress when this function is called, this function will
* busy wait until the write has been completed. This may take quite some
* time (up to 3.4 ms if a write has just been started), so you should be
* careful about when this function is called.
* Struct members:
* - `action`: A field indicating what type of action to perform. Valid values
* defined by `enum action`
* - `to`: The address in EEPROM memory space to write to
* - `value`:
* - `action == ACTION_WRITE`: The data to write
* - `action == ACTION_COPY`: The number of bytes left to copy
*
* Implementation notes:
* - Since the ATMega32U4 only has 1024 bytes of EEPROM, it's safe to restrict
* `to` to 10 bits.
*/
uint8_t eeprom__read(uint8_t * address) {
while (EECR & (1<<EEPE)); // wait for previous write to complete
EEAR = (uint16_t) address; // set up address register
EECR |= (1<<EERE); // start EEPROM read (then halt, 4 clock cycles)
return EEDR; // return the value in the data register
}
typedef struct {
list__node_t _super;
uint8_t action : 6;
uint16_t to : 10; // since we only have 1024 bytes
uint8_t value;
} write_t;
/** functions/eeprom__write/description
* Implementation notes:
/** types/copy_t/description
* To hold the extra values needed for a "copy" to/from the EEPROM
*
* Struct members:
* - `from`: The address in the EEPROM memory space to copy from
*/
typedef struct {
list__node_t _super;
uint16_t from;
} copy_t;
/** macros/(enum) action/description
* Valid values for `write_t.action`, determining the type of action to perform
* for an entry in `to_write`
*
* Members:
* - `ACTION_WRITE`
* - `ACTION_COPY`
*/
enum action {
ACTION_WRITE,
ACTION_COPY,
};
// ----------------------------------------------------------------------------
/** variables/status/description
* Flags for keeping track of the status of these functions (since write
* operations will be buffered)
*
* Members:
* - `writing`: Indicates that writes are queued and/or being performed
* - If this is `true`, `write_queued()` is either running or scheduled to
* run, and should not be rescheduled by any function other than itself.
* - If this is `false`, no writes are queued or being performed, and
* `write_queued()` must be scheduled to run externally before writes
* will commence.
*/
struct {
bool writing : 1;
} status;
// TODO: i should probably pick one or the other as a standard way of declaring
// lists; either as pointers, and then pass that, or as variables, and pass a
// reference to them
static list__list_t to_write;
static list__list_t to_copy;
// ----------------------------------------------------------------------------
/** functions/write/description
* Write `data` to `address` in EEPROM memory space as soon as possible
*
* Arguments:
* - `to: The address of the location to write to
* - `data`: The data to write
*
* Notes:
* - This function is static (and `eeprom__write()`, the public function, is
* written below) because writes are really slow, and we're going to buffer
* them.
*
* Implementation notes:
* - This function (and most of the comments) were taken more or less straight
* from the data sheet, section 5.3
* - If another write is in progress when this function is called, this
* function will busy wait until the first write has been completed. This
* may take quite some time (up to 3.4 ms if a write has just been started),
* so you should be careful about when this function is called.
*
* so we must be careful about when this function is called.
* - This function starts the write to the EEPROM, but returns long before it
* has been completed.
*
* TODO: this should *schedule* writes
* Assumptions:
* - The address passed as `to` is valid.
* - Voltage will never fall below the specified minimum for the clock
* frequency being used.
* - A write to flash memory (PROGMEM) will never be in progress when this
* function is called.
*/
void eeprom__write(uint8_t * address, uint8_t data) {
static void write(uint16_t to, uint8_t data) {
// - if a write is in progress, this will also wait until it's finished
uint8_t old_data = eeprom__read(address);
uint8_t old_data = eeprom__read( (uint8_t *) to );
if (data == old_data) {
// do nothing
@ -81,8 +143,8 @@ void eeprom__write(uint8_t * address, uint8_t data) {
EECR &= ~(1<<EEPM0); // clear
}
EEAR = (uint16_t) address; // set up address register
EEDR = data; // set up data register
EEAR = to; // set up address register
EEDR = data; // set up data register
// - interrupts must be disabled between these two operations, or else
// "EEPROM Master Programming Enable" may time out (since it's cleared by
@ -93,3 +155,130 @@ void eeprom__write(uint8_t * address, uint8_t data) {
}
}
/** functions/write_queued/description
* Write (or copy) the next byte of data as dictated by our queue(s), and
* schedule the write of the next byte if necessary
*/
static void write_queued(void) {
#define next_write ((write_t *) to_write.head)
#define next_copy ((copy_t *) to_copy.head)
// if there's nothing to write
// - checking for this here will cause this function to be called an extra
// time before it stops rescheduling itself; but it also allows us not to
// make assumptions about the state of `to_write` when this function is
// called
if (to_write.length == 0) {
status.writing = 0;
return;
}
if (next_write->action == ACTION_WRITE) {
// write 1 byte
write( next_write->to, next_write->value );
// prepare for the next
free( list__pop_index(&to_write, 0) );
} else if (next_write->action == ACTION_COPY && to_copy.length) {
// if we're done with the current copy
// - checking for this here requires an extra iteration between a copy
// being finished and the next operation being started; but it also
// allows us not to make assumptions about the state of `to_copy`
// when this function is called
if (next_write->value == 0) {
free( list__pop_index(&to_write, 0) );
free( list__pop_index(&to_copy, 0) );
}
// copy 1 byte
write( next_write->to, eeprom__read( (uint8_t *) next_copy->from ) );
// prepare for the next
++(next_write->to);
++(next_copy->from);
--(next_write->value);
} else {
// if we get here, there was an invalid node: remove it
free( list__pop_index(&to_write, 0) );
}
// - `(3.5/OPT__DEBOUNCE_TIME)+1` gives us the number of cycles we need to
// wait until the next write, where `3.5` is the maximum number of
// milliseconds an EEPROM write can take, and `OPT__DEBOUNCE_TIME` is the
// minimum number of milliseconds a scan can take. Note that if the
// division produces an integer, the `+1` is strictly unnecessary, since
// no truncation will be performed; but if we're that close to needing to
// wait an extra cycle we may as well wait anyway, just to be safe.
timer__schedule_cycles( (3.5/OPT__DEBOUNCE_TIME)+1, &write_queued );
#undef next_write
#undef next_copy
}
// ----------------------------------------------------------------------------
/** functions/eeprom__read/description
* Implementation notes:
* - This function (and most of the comments) were taken more or less straight
* from the data sheet, section 5.3
* - If a write is in progress when this function is called, this function will
* busy wait until the write has been completed. This may take quite some
* time (up to 3.4 ms if a write has just been started), so you should be
* careful about when this function is called.
*
* Assumptions:
* - The address passed as `address` is valid.
*/
uint8_t eeprom__read(uint8_t * from) {
while (EECR & (1<<EEPE)); // wait for previous write to complete
EEAR = (uint16_t) from; // set up address register
EECR |= (1<<EERE); // start EEPROM read (then halt, 4 clock cycles)
return EEDR; // return the value in the data register
}
uint8_t eeprom__write(uint8_t * address, uint8_t data) {
write_t * write = malloc(sizeof(write_t));
if (!write) return 1; // error
write->action = ACTION_WRITE;
write->to = (uint16_t) address;
write->value = data;
list__insert(&to_write, -1, write);
if (! status.writing) {
timer__schedule_cycles( 0, &write_queued );
status.writing = true;
}
return 0; // success
}
uint8_t eeprom__copy(uint8_t * to, uint8_t * from, uint8_t length) {
write_t * write = malloc(sizeof(write_t));
copy_t * copy = malloc(sizeof(copy_t));
if (!(write && copy)) {
free(write);
free(copy);
return 1; // error
}
write->action = ACTION_COPY;
write->to = (uint16_t) to;
write->value = length;
copy->from = (uint16_t) from;
list__insert(&to_write, -1, write);
list__insert(&to_copy, -1, copy);
if (! status.writing) {
timer__schedule_cycles( 0, &write_queued );
status.writing = true;
}
return 0; // success
}

View File

@ -11,5 +11,8 @@
#
# TODO: add `call`s like this to other files that depend on things implicitly
$(call include_options_once,lib/timer)
SRC += $(wildcard $(CURDIR)/$(MCU).c)

View File

@ -25,7 +25,7 @@
#include <avr/eeprom.h>
#include "../../../../firmware/keyboard.h"
#include "../../../../firmware/lib/eeprom.h"
#include "../eeprom-macro.h"
#include "../eeprom-macro.h" // TODO: add includes like this to all implementation files
// ----------------------------------------------------------------------------
@ -161,11 +161,11 @@ struct log_header {
uint8_t run_length;
};
enum {
MACRO,
LOG_ATOMIC_WRITE,
LOG_ATOMIC_COPY,
HEADER_NULL = 0xFF
enum type {
TYPE_MACRO,
TYPE_LOG_ATOMIC_WRITE,
TYPE_LOG_ATOMIC_COPY,
TYPE_HEADER_NULL = 0xFF
};
struct macro_action {

View File

@ -4,6 +4,7 @@
* Project located at <https://github.com/benblazak/ergodox-firmware>
* ------------------------------------------------------------------------- */
// TODO: remove `timer__schedule_milliseconds()`
/** description
* Timer interface
*

View File

@ -11,6 +11,9 @@
i have makefiles set up, etc.
- write a mediumly thorough note about my workflow (vim, ...)
- add a note in the docs somewhere that SDA and SCL need to have a pull-up
resistor on them, or `mcp23018__init()` will hang (or something like that)
## Dependencies
- the gnu avr toolchain
- python 3