working mostly on lib/eeprom; hopefully done :)
parent
0a9f4ec61d
commit
c24adb542c
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* Project located at <https://github.com/benblazak/ergodox-firmware>
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
// TODO: remove `timer__schedule_milliseconds()`
|
||||
/** description
|
||||
* Timer interface
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue