diff --git a/firmware/lib/layout/eeprom-macro.h b/firmware/lib/layout/eeprom-macro.h index 2632765..cca386e 100644 --- a/firmware/lib/layout/eeprom-macro.h +++ b/firmware/lib/layout/eeprom-macro.h @@ -237,8 +237,8 @@ void eeprom_macro__clear_all (void); * - `column`: The column of the key action * * Returns: - * - `true`: if a macro with the given UID exists - * - `false`: if a macro with the given UID does not exist + * - `true`: if a macro remapping the given key-action exists + * - `false`: if a macro remapping the given key-action does not exist */ // === eeprom_macro__clear() === diff --git a/firmware/lib/layout/eeprom-macro/atmega32u4.c b/firmware/lib/layout/eeprom-macro/atmega32u4.c index 98cd6d5..c7e0bd6 100644 --- a/firmware/lib/layout/eeprom-macro/atmega32u4.c +++ b/firmware/lib/layout/eeprom-macro/atmega32u4.c @@ -20,11 +20,10 @@ * primarily little endian: in avr-gcc, multi-byte data types are allocated * with the least significant byte occupying the lowest address. Protocols, * data formats (including UTF-8), and such are primarily big endian. I like - * little endianness better -- it feels more mathematically consistent to me - * -- but after writing a bit of code, it seems that big endian - * serializations are easier to work with, at least in C. For that reason, - * this code organizes bytes in a big endian manner whenever it has a choice - * between the two. + * little endianness better -- it just feels nicer to me -- but after writing + * a bit of code, it seems that big endian serializations are easier to work + * with, at least in C. For that reason, this code organizes bytes in a big + * endian manner whenever it has a choice between the two. * * - For a long time, I was going to try to make this library robust in the * event of power loss, but in the end I decided not to. This feature is @@ -52,6 +51,10 @@ #error "OPT__EEPROM__EEPROM_MACRO__END must not be greater than 1023" #endif +#if EEMEM_END - EEMEM_START < 300 + #warn "Only a small space has been allocated for macros" +#endif + // ---------------------------------------------------------------------------- // macros --------------------------------------------------------------------- @@ -81,20 +84,97 @@ * - `EEMEM_MACROS_END` * - `EEMEM_END`: The address of the last byte of our block of EEMEM * + * * EEMEM sections: + * * - START_ADDRESS: * - byte 0: MSB of `EEMEM_START` * - byte 1: LSB of `EEMEM_START` + * * - END_ADDRESS: * - byte 0: MSB of `EEMEM_END` * - byte 1: LSB of `EEMEM_END` + * * - VERSION: - * - This byte will all be set to `VERSION` as the last step of - * initializing our portion of the EEPROM. - * - Upon initialization, if this value is not equal to the current - * `VERSION`, our portion of the EEPROM should be reinitialized. - * - MACROS: - * - byte 0..`(EEMEM_END - EEMEM_VERSION_END - 1)`: TODO + * - byte 0: + * - This byte will all be set to `VERSION` as the last step of + * initializing our portion of the EEPROM. + * - Upon initialization, if this value is not equal to the current + * `VERSION`, our portion of the EEPROM should be reinitialized. + * + * - MACROS: byte 0..`(EEMEM_END - EEMEM_VERSION_END - 1)`: + * - This section will contain a series of zero or more macros, each with + * the following format: + * - byte 0: `type == TYPE_DELETED` + * - byte 1: `length`: the total number of bytes used by this + * macro, including the bytes for `type` and `length` + * - byte 2...: (optional) undefined + * - byte 0: `type == TYPE_VALID_MACRO` + * - byte 1: `length`: the total number of bytes used by this + * macro, including the bytes for `type` and `length` + * - `key-action` 0: the key-action which this macro remaps + * - See below for a description of the bytes making up a + * key-action + * - `key-action` 1...: (optional) the key-actions to which + * `key-action` 0 is remapped + * - byte 0: `type == TYPE_END` + * - byte 1...: (optional) undefined + * + * - The last key-action in this series will have `type == TYPE_END`. + * + * - A key-action is a variable length encoding of the information in a + * `key_action_t`, with the following format: + * + * byte 0 + * .----------------------------------------------. + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * |----------------------------------------------| + * | continued | pressed | layer | row | column | + * '----------------------------------------------' + * + * byte 1..3 (optional) + * .----------------------------------------------. + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * |----------------------------------------------| + * | continued | n/a | layer | row | column | + * '----------------------------------------------' + * + * - `continued`: + * - `1`: The next byte is part of this key-action + * - `0`: The next byte is not part of this key-action (i.e. this + * is the last byte in this key-action) + * + * - `layer`, `row`, `column`: + * - In the first byte of this key-action, these fields contain the + * two most significant bits of their respective values such that + * these bits are nonzero in *any* of `layer`, `row`, or + * `column`. In subsequent bytes of this key-action, these + * fields contain the pair of bits to the right of the pair of + * bits in the previous key-action byte (the next less + * significant pair of bits). If `layer`, `row`, and `column` + * all equal `0`, then these three fields will all equal `0`, and + * there will only be 1 byte written for this key-action. + * + * - Example of an encoded key-action (where `*` means "undefined"): + * + * --- as a key_action_t --- + * pressed = true + * layer = 0 b 00 00 01 00 + * row = 0 b 00 01 10 01 + * column = 0 b 00 10 00 11 + * | '- least significant pair of bits + * '- most significant pair of bits + * + * --- in EEMEM --- + * byte 0 = 0 b 1 1 00 01 10 + * byte 1 = 0 b 1 * 01 10 00 + * byte 2 = 0 b 0 * 00 01 11 + * | | | | '- column bit pair + * | | | '- row bit pair + * | | '- layer bit pair + * | '- pressed / n/a + * '- continued + * * * Notes: * - `START_ADDRESS` and `END_ADDRESS` are written as part of our effort to @@ -105,14 +185,14 @@ * of the EEPROM have the same values for each. */ #define EEMEM_START ((void *)OPT__EEPROM__EEPROM_MACRO__START) -#define EEMEM_START_ADDRESS_START EEMEM_START +#define EEMEM_START_ADDRESS_START EEMEM_START + 0 #define EEMEM_START_ADDRESS_END EEMEM_START_ADDRESS_START + 1 #define EEMEM_END_ADDRESS_START EEMEM_START_ADDRESS_END + 1 #define EEMEM_END_ADDRESS_END EEMEM_END_ADDRESS_START + 1 #define EEMEM_VERSION_START EEMEM_END_ADDRESS_END + 1 #define EEMEM_VERSION_END EEMEM_VERSION_START + 0 #define EEMEM_MACROS_START EEMEM_VERSION_END + 1 -#define EEMEM_MACROS_END EEMEM_END +#define EEMEM_MACROS_END EEMEM_END - 0 #define EEMEM_END ((void *)OPT__EEPROM__EEPROM_MACRO__END) /** macros/(group) type/description @@ -131,7 +211,18 @@ // types ---------------------------------------------------------------------- /** types/key_action_t/description - * TODO + * To hold everything needed to represent a single key-action (the press or + * release of a specific key on a specific layer of the layout matrix). + * + * Struct members: + * - `pressed`: Whether the key is pressed (`true`) or not (`false`) + * - `layer`: The layer of the key, in the layout matrix + * - `row`: The row of the key, in the layout matrix + * - `column`: The column of the key, in the layout matrix + * + * Notes: + * - Since these fields together can reference any key (on any layer) + * unambiguously, a `key_action_t` may also serve as a UID for a key. */ typedef struct { bool pressed; @@ -143,23 +234,29 @@ typedef struct { // ---------------------------------------------------------------------------- // local functions ------------------------------------------------------------ -// - if we go back to little endian, need to modify the comments at the top of -// the file, as well as the documented order of the bytes for the stored -// start and end addresses of the EEPROM -// -// - sizes (in bytes) (with optimizations on): -// -// function frame stack -// read big 154 3 10 -// read little 200 2 10 -// write big 160 0 6 -// write little 172 0 5 - -/** - * TODO +/** functions/read_key_action/description + * Read and return the key-action beginning at `*from` in the EEPROM, and + * advance `*from` to one byte past the key-action. + * + * Arguments: + * - `from`: A pointer to a pointer to the location in EEPROM from which to + * begin reading + * + * Returns: + * - success: The key-action, as a `key_action_t` + * + * Notes: + * - See the documentation for "(group) EEMEM layout" above for a description + * of the layout of key-actions in EEMEM. */ -key_action_t read_key_action_big_endian(void * from) { - uint8_t byte = eeprom__read(from++); +key_action_t read_key_action(void ** from) { + uint8_t byte; + + // handle the first byte + // - since this byte (and no others) stores the value of `k.pressed` + // - also, this allows us to avoid `|=` in favor of `=` for this byte + + byte = eeprom__read((*from)++); key_action_t k = { .pressed = byte >> 6 & 0b01, @@ -168,13 +265,21 @@ key_action_t read_key_action_big_endian(void * from) { .column = byte >> 0 & 0b11, }; - while (byte >> 7) { - byte = eeprom__read(from++); + // handle all subsequent bytes + // - we assume the stream is valid. especially, we do not check to make + // sure that the key-action is no more than 4 bytes long. + while (byte >> 7) { + byte = eeprom__read((*from)++); + + // shift up (make more significant) the bits we have so far, to make + // room for the bits we just read k.layer <<= 2; k.row <<= 2; k.column <<= 2; + // logical or the bits we just read into the lowest (least significant) + // positions k.layer |= byte >> 4 & 0b11; k.row |= byte >> 2 & 0b11; k.column |= byte >> 0 & 0b11; @@ -182,46 +287,54 @@ key_action_t read_key_action_big_endian(void * from) { return k; // success } -key_action_t read_key_action_little_endian(void * from) { - uint8_t byte = eeprom__read(from++); - key_action_t k = { - .pressed = ( byte & 0x40 ), - .layer = ( byte & 0x30 ) << 2, - .row = ( byte & 0x0C ) << 4, - .column = ( byte & 0x03 ) << 6, - }; - - uint8_t i = 0; - - for (; byte>>7; i++) { - byte = eeprom__read(from++); - - k.layer >>= 2; - k.row >>= 2; - k.column >>= 2; - - k.layer |= ( byte & 0x30 ) << 2; - k.row |= ( byte & 0x0C ) << 4; - k.column |= ( byte & 0x03 ) << 6; - } - - for (; i<4; i++) { - k.layer >>= 2; - k.row >>= 2; - k.column >>= 2; - } - - return k; // success -} - -/** - * TODO +/** functions/write_key_action/description + * Write the given information to a key-action beginning at `*to` in the + * EEPROM, and advance `*to` to one byte past the newly written key-action. + * + * Arguments: + * - `to`: A pointer to a pointer to the location in EEPROM at which to begin + * writing + * - `k`: The key-action to write + * + * Returns: + * - success: `0` + * - failure: [other] + * + * Notes: + * - See the documentation for "(group) EEMEM layout" above for a description + * of the layout of key-actions in EEMEM. + * + * Implementation notes: + * - We handle the `layer`, `row`, and `column` variables (inside `k`) as being + * made up of 4 pairs of bits. + * - We deal with these bits beginning with the high (most significant) pair, + * and shifting left (towards the most significant end of the byte) to + * discard bit pairs we're done with. + * - This method seemed faster (i.e. generated less assembly code) when I + * was testing than leaving the `layer`, `row`, and `column` bytes as + * they were and using a variable mask (as in `k.layer & 0b11 << i*2`). + * It's probably worthwhile to note that I was looking at the assembly + * (though not closely) and function size with optimizations turned on. */ -uint8_t write_key_action_big_endian(void * to, key_action_t k) { - if (to > EEMEM_END-3) +uint8_t write_key_action(void ** to, key_action_t k) { + + // - we need to leave room after this macro (and therefore after this + // key-action) for the `TYPE_END` byte + if ((*to) > EEMEM_END-4) return 1; // error: might not be enough space + // ignore the bits we don't need to write + // - if the leading two bits of all three variables are `0b00`, we don't + // need to write a key-action byte containing that pair of bits + // - the maximum number of pairs of bits we can ignore is 3; the last pair + // (the least significant) must be written to the EEPROM regardless of + // its value + // - we set `i` here (and make it global to the function) because we need + // to make sure to *consider writing* exactly 4 pairs of bits. some may + // be skipped, some or all may be written, but the total of both must be + // 4. + uint8_t i = 0; for (; i<3 && !((k.layer|k.row|k.column) & 0xC0); i++) { @@ -230,6 +343,11 @@ uint8_t write_key_action_big_endian(void * to, key_action_t k) { k.column <<= 2; } + // write key-action bytes for all bit pairs that weren't ignored + // - the first byte contains the value of `k.pressed` + // - all bytes except the last one written (containing the least + // significant bits) have their first bit set to `1` + uint8_t byte = (k.pressed ? 1 : 0) << 6; for (; i<4; i++) { @@ -237,8 +355,9 @@ uint8_t write_key_action_big_endian(void * to, key_action_t k) { | ( k.layer & 0xC0 ) >> 2 | ( k.row & 0xC0 ) >> 4 | ( k.column & 0xC0 ) >> 6 ; - eeprom__write(to++, byte); + eeprom__write((*to)++, byte); byte = 0; + k.layer <<= 2; k.row <<= 2; k.column <<= 2; @@ -246,36 +365,6 @@ uint8_t write_key_action_big_endian(void * to, key_action_t k) { return 0; // success } -uint8_t write_key_action_little_endian(void * to, key_action_t k) { - if (to > EEMEM_END-3) - return 1; // error: might not be enough space - - uint8_t byte = ( k.pressed ? 1 : 0 ) << 6 - | ( k.layer & 0b11 ) << 4 - | ( k.row & 0b11 ) << 2 - | ( k.column & 0b11 ) << 0 ; - - k.layer >>= 2; - k.row >>= 2; - k.column >>= 2; - - while (k.layer|k.row|k.column) { - byte |= 1 << 7; - eeprom__write(to++, byte); - - byte = ( k.layer & 0b11 ) << 4 - | ( k.row & 0b11 ) << 2 - | ( k.column & 0b11 ) << 0 ; - - k.layer >>= 2; - k.row >>= 2; - k.column >>= 2; - } - - eeprom__write(to, byte); - - return 0; // success -} // TODO: rewriting (yet again) - stopped here #if 0