932 lines
35 KiB
C
932 lines
35 KiB
C
/* ----------------------------------------------------------------------------
|
|
* Copyright (c) 2013, 2014 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 eeprom-macro functionality defined in "../eeprom-macro.h" for
|
|
* the ATMega32U4
|
|
*
|
|
*
|
|
* Warnings:
|
|
*
|
|
* - A worst case `compress()` operation might take too much memory. Not sure
|
|
* what (if anything) to do about this right now.
|
|
* - Max EEPROM space for macros: 1024-5 = 1019 bytes
|
|
* - Min space for a macro: 5 bytes
|
|
* - Approximate space for a copy object in ".../lib/eeprom": 5 bytes
|
|
* - Worst case would be EEMEM filled with the smallest possible macros,
|
|
* alternating between valid and deleted. This would give us 1019/5/2 ~=
|
|
* 100 noncontiguous deleted macros, which would be about as many copy
|
|
* objects (plus a few write objects) in ".../lib/eeprom", so about 500
|
|
* bytes. SRAM is 2560 bytes (per the PJRC website). Because of the way
|
|
* ".../lib/eeprom" is written, much of this data would have to be
|
|
* contiguous.
|
|
* - At some point, I should probably consider changing how
|
|
* ".../lib/eeprom" (and the layer-stack code, and everything else that
|
|
* needs a variable amount of memory) manages its memory. Again, not
|
|
* quite sure how, at the moment. For common cases, the current solution
|
|
* might be sufficient.
|
|
* - If this turns out to be a problem, the easiest solution (at the
|
|
* expense of extra EEPROM wear in lower memory locations) would probably
|
|
* be to simply call `compress()` more often.
|
|
*
|
|
*
|
|
* Implementation notes:
|
|
*
|
|
* - The default state (the "erased" state) of this EEPROM is all `1`s, which
|
|
* makes setting a byte to `0xFF` easier and faster in hardware than zeroing
|
|
* it (and also causes less wear on the memory over time, I think). This is
|
|
* reflected in some of the choices for default values, and such.
|
|
*
|
|
* - GCC and AVR processors (and Intel processors, for that matter) are
|
|
* 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 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
|
|
* meant to be used for *temporary* macros -- so, with the risk of power loss
|
|
* during a critical time being fairly low, and the consequence of (detected)
|
|
* data corruption hopefully more of an annoyance than anything else, I
|
|
* decided the effort (and extra EEMEM usage) wasn't worth it.
|
|
*/
|
|
|
|
|
|
#include <stdint.h>
|
|
#include "../../../../firmware/keyboard.h"
|
|
#include "../../../../firmware/lib/eeprom.h"
|
|
#include "../eeprom-macro.h"
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// checks ---------------------------------------------------------------------
|
|
|
|
/** macros/OPT__EEPROM__EEPROM_MACRO__END/description
|
|
* Implementation notes:
|
|
* - The ATMega32U4 only has 1024 bytes of EEPROM (beginning with byte 0)
|
|
*/
|
|
#if OPT__EEPROM__EEPROM_MACRO__END > 1023
|
|
#error "OPT__EEPROM__EEPROM_MACRO__END must not be greater than 1023"
|
|
#endif
|
|
|
|
#if OPT__EEPROM__EEPROM_MACRO__END - OPT__EEPROM__EEPROM_MACRO__START < 300
|
|
#warn "Only a small space has been allocated for macros"
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// macros ---------------------------------------------------------------------
|
|
|
|
/** macros/VERSION/description
|
|
* The version number of the EEMEM layout
|
|
*
|
|
* Assignments:
|
|
* - 0x00: Reserved: EEPROM not yet initialized, or in inconsistent state
|
|
* - 0x01: First version
|
|
* - ... : (not yet assigned)
|
|
* - 0xFF: Reserved: EEPROM not yet initialized, or in inconsistent state
|
|
*/
|
|
#define VERSION 0x01
|
|
|
|
/** macros/(group) EEMEM layout/description
|
|
* To define the layout of our section of the EEPROM
|
|
*
|
|
* Members:
|
|
* - `EEMEM_START`: The address of the first byte of our block of EEMEM
|
|
* - `EEMEM_START_ADDRESS_START`
|
|
* - `EEMEM_START_ADDRESS_END`
|
|
* - `EEMEM_END_ADDRESS_START`
|
|
* - `EEMEM_END_ADDRESS_END`
|
|
* - `EEMEM_VERSION_START`
|
|
* - `EEMEM_VERSION_END`
|
|
* - `EEMEM_MACROS_START`
|
|
* - `EEMEM_MACROS_END`
|
|
* - `EEMEM_END`: The address of the last byte of our block of EEMEM
|
|
*
|
|
* Terms:
|
|
* - The "address" of a macro is the EEMEM address of the first byte of that
|
|
* macro.
|
|
* - The "header" of a macro is the part of the macro containing the macro's
|
|
* type and length.
|
|
* - The "data" of a macro is everything following the macro's header.
|
|
*
|
|
* Notes:
|
|
* - `START_ADDRESS` and `END_ADDRESS` are written as part of our effort to
|
|
* make sure that the assumptions in place when writing the data don't shift
|
|
* (undetected) by the time it gets read. Either of these values could
|
|
* change, legitimately, without `VERSION` being incremented, but it's
|
|
* important that any two builds of the firmware that deal with this section
|
|
* of the EEPROM have the same values for each.
|
|
*
|
|
*
|
|
* EEMEM sections:
|
|
*
|
|
* - START_ADDRESS:
|
|
* - byte 0: MSB of `EEMEM_START`
|
|
* - byte 1: LSB of `EEMEM_START`
|
|
*
|
|
* - Upon initialization, if this block does not have the expected value,
|
|
* our portion of the EEPROM should be reinitialized.
|
|
*
|
|
* - END_ADDRESS:
|
|
* - byte 0: MSB of `EEMEM_END`
|
|
* - byte 1: LSB of `EEMEM_END`
|
|
*
|
|
* - Upon initialization, if this block does not have the expected value,
|
|
* our portion of the EEPROM should be reinitialized.
|
|
*
|
|
* - VERSION:
|
|
* - byte 0:
|
|
* - This byte will 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`
|
|
* - byte 2...: (variable length, as described below)
|
|
* - `key-action` 0: the key-action which this macro remaps
|
|
* - byte ...: (optional) (variable length, as described below)
|
|
* - `key-action` 1...: the key-actions to which `key-action` 0
|
|
* is remapped
|
|
* - byte 0: `type == TYPE_END`
|
|
* - byte 1...: (optional) undefined
|
|
*
|
|
* - The last macro 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 | 1 | 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)
|
|
*
|
|
* - `pressed`:
|
|
* - This value is stored *only* in the first byte. In all
|
|
* subsequent bytes the bit should be set to `1`.
|
|
*
|
|
* - `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:
|
|
*
|
|
* --- as a key_action_t ---
|
|
* pressed = false
|
|
* 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 0 00 01 10
|
|
* byte 1 = 0 b 1 1 01 10 00
|
|
* byte 2 = 0 b 0 1 00 01 11
|
|
* | | | | '- column bit pair
|
|
* | | | '- row bit pair
|
|
* | | '- layer bit pair
|
|
* | '- pressed / 1
|
|
* '- continued
|
|
*/
|
|
#define EEMEM_START ((void *)OPT__EEPROM__EEPROM_MACRO__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 - 0)
|
|
#define EEMEM_END ((void *)OPT__EEPROM__EEPROM_MACRO__END)
|
|
|
|
/** macros/(group) type/description
|
|
* Aliases for valid values of the "type" field in `MACROS`
|
|
*
|
|
* Members:
|
|
* - `TYPE_DELETED`
|
|
* - `TYPE_VALID_MACRO`
|
|
* - `TYPE_END`
|
|
*/
|
|
#define TYPE_DELETED 0x00
|
|
#define TYPE_VALID_MACRO 0x01
|
|
#define TYPE_END 0xFF
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// types ----------------------------------------------------------------------
|
|
|
|
/** types/key_action_t/description
|
|
* 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;
|
|
uint8_t layer;
|
|
uint8_t row;
|
|
uint8_t column;
|
|
} key_action_t;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// variables ------------------------------------------------------------------
|
|
|
|
/** variables/end_macro/description
|
|
* The EEMEM address of the macro with `type == TYPE_END`
|
|
*/
|
|
void * end_macro;
|
|
|
|
/** variables/new_end_macro/description
|
|
* The EEMEM address of where to write the next byte of a macro in progress (or
|
|
* `0` if no macro is in progress)
|
|
*
|
|
* Mnemonic:
|
|
* - This macro will become the new `end_macro` when the macro currently being
|
|
* written is finalized.
|
|
*
|
|
* Note:
|
|
* - This variable should be the primary indicator of whether a macro is in
|
|
* progress or not.
|
|
*/
|
|
void * new_end_macro;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// local functions ------------------------------------------------------------
|
|
|
|
/** functions/read_key_action/description
|
|
* Read the key-action beginning at `from` in the EEPROM
|
|
*
|
|
* Arguments:
|
|
* - `from`: A pointer to the location in EEPROM from which to begin reading
|
|
* - `k`: A pointer to the variable in which to store the key-action
|
|
*
|
|
* Returns:
|
|
* - success: The number of bytes read
|
|
*
|
|
* Notes:
|
|
* - See the documentation for "(group) EEMEM layout" above for a description
|
|
* of the layout of key-actions in EEMEM.
|
|
*/
|
|
static uint8_t read_key_action(void * from, key_action_t * k) {
|
|
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++);
|
|
uint8_t read = 1;
|
|
|
|
k->pressed = byte >> 6 & 0b01;
|
|
k->layer = byte >> 4 & 0b11;
|
|
k->row = byte >> 2 & 0b11;
|
|
k->column = byte >> 0 & 0b11;
|
|
|
|
// 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++);
|
|
read++;
|
|
|
|
// 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;
|
|
}
|
|
|
|
return read; // success
|
|
}
|
|
|
|
/** functions/write_key_action/description
|
|
* Write the given information to a key-action beginning at `to` in the
|
|
* EEPROM, and return the number of bytes written.
|
|
*
|
|
* Arguments:
|
|
* - `to`: A pointer to the location in EEPROM at which to begin writing
|
|
* - `k`: A pointer to the key-action to write
|
|
* - `limit`: A pointer to the last address to which we are allowed to write
|
|
*
|
|
* Returns:
|
|
* - success: The number of bytes written
|
|
* - failure: `0`
|
|
*
|
|
* Warnings:
|
|
* - Writes are not atomic: if there are 4 bytes to be written, and the first
|
|
* three writes succeed, the 4th may still fail.
|
|
*
|
|
* 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.
|
|
*/
|
|
static uint8_t write_key_action(void * to, key_action_t * k, void * limit) {
|
|
// 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++) {
|
|
k->layer <<= 2;
|
|
k->row <<= 2;
|
|
k->column <<= 2;
|
|
}
|
|
|
|
uint8_t written = 4-i;
|
|
|
|
// write key-action bytes for all bit pairs that weren't ignored
|
|
// - the first byte contains the value of `k->pressed`; the same position
|
|
// is set to `1` in all subsequent bytes
|
|
// - all bytes except the last one written (containing the least
|
|
// significant bits) have their first bit set to `1`
|
|
|
|
uint8_t byte = k->pressed << 6;
|
|
|
|
for (; i<4; i++) {
|
|
byte = byte | ( i<3 ) << 7
|
|
| ( k->layer & 0xC0 ) >> 2
|
|
| ( k->row & 0xC0 ) >> 4
|
|
| ( k->column & 0xC0 ) >> 6 ;
|
|
|
|
if ( to > limit ) return 0; // out of bounds
|
|
if ( eeprom__write(to++, byte) ) return 0; // write failed
|
|
|
|
byte = 1 << 6;
|
|
|
|
k->layer <<= 2;
|
|
k->row <<= 2;
|
|
k->column <<= 2;
|
|
}
|
|
|
|
return written; // success
|
|
}
|
|
|
|
/** functions/find_key_action/description
|
|
* Find the macro remapping the given key-action (if it exists).
|
|
*
|
|
* Arguments:
|
|
* - `k`: A pointer to the key-action to search for
|
|
*
|
|
* Returns:
|
|
* - success: The EEMEM address of the desired macro
|
|
* - failure: `0`
|
|
*
|
|
* Notes:
|
|
* - The address `0` (or really `NULL`, which is `#define`ed to `((void *)0)`)
|
|
* is a valid address in the EEPROM; but because macros are not placed first
|
|
* in the EEPROM, we can still use it to signal nonexistence or failure.
|
|
* - See the documentation for "(group) EEMEM layout" above for a description
|
|
* of the layout of macros in EEMEM.
|
|
*
|
|
* Implementation notes:
|
|
* - It would be more efficient to convert the given key-action into the same
|
|
* binary representation as used in the EEPROM, once, and then compare that
|
|
* directly with the encoded key-action bytes read; but I don't think it'll
|
|
* have enough of an impact on performance to justify rewriting the
|
|
* ...key_action() functions, and it seems like this solution is a little bit
|
|
* cleaner (since it results in slightly fewer functions and keeps the
|
|
* representation of a key-function in SRAM consistent).
|
|
*/
|
|
static void * find_key_action(key_action_t * k) {
|
|
void * current = EEMEM_MACROS_START;
|
|
|
|
for ( uint8_t type = eeprom__read(current);
|
|
type != TYPE_END;
|
|
current += eeprom__read(current+1), type = eeprom__read(current) ) {
|
|
|
|
if (type == TYPE_VALID_MACRO) {
|
|
|
|
key_action_t k_current;
|
|
read_key_action(current+2, &k_current);
|
|
|
|
if ( k->pressed == k_current.pressed
|
|
&& k->layer == k_current.layer
|
|
&& k->row == k_current.row
|
|
&& k->column == k_current.column ) {
|
|
|
|
return current;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0; // key-action not found
|
|
}
|
|
|
|
/** functions/find_next_deleted/description
|
|
* Find the first deleted macro at or after the given macro.
|
|
*
|
|
* Arguments:
|
|
* - `start`: The EEMEM address of the macro at which to begin searching
|
|
*
|
|
* Returns:
|
|
* - success: The EEMEM address of the first deleted macro at or after `start`
|
|
* - failure: `0` (no deleted macros were found at or after `start`)
|
|
*/
|
|
static void * find_next_deleted(void * start) {
|
|
for ( uint8_t type = eeprom__read(start);
|
|
type != TYPE_END;
|
|
start += eeprom__read(start+1), type = eeprom__read(start) ) {
|
|
|
|
if (type == TYPE_DELETED)
|
|
return start;
|
|
}
|
|
|
|
return 0; // no deleted macro found
|
|
}
|
|
|
|
/** functions/find_next_nondeleted/description
|
|
* Find the first macro at or after the given macro that is not marked as
|
|
* deleted.
|
|
*
|
|
* Arguments:
|
|
* - `start`: The EEMEM address of the macro at which to begin searching
|
|
*
|
|
* Returns:
|
|
* - success: The EEMEM address of the first non-deleted macro at or after
|
|
* `start`
|
|
*
|
|
* Notes:
|
|
* - Since the sequence of macros must end with a `TYPE_END` macro (which is,
|
|
* of course, not a deleted macro), this function will always find a
|
|
* non-deleted macro at or after the one passed.
|
|
*/
|
|
static void * find_next_nondeleted(void * start) {
|
|
for ( uint8_t type = eeprom__read(start);
|
|
type == TYPE_DELETED;
|
|
start += eeprom__read(start+1), type = eeprom__read(start) );
|
|
|
|
return start;
|
|
}
|
|
|
|
/** functions/compress/description
|
|
* Remove any gaps in the EEPROM caused by deleted macros
|
|
*
|
|
* Returns:
|
|
* - success: `0`
|
|
* - failure:
|
|
* - `1`: write failed; data unchanged
|
|
* - `2`: write failed; data lost; any macro currently being written is
|
|
* cancelled
|
|
*
|
|
* Notes:
|
|
* - As a rough idea of the time it might take for a compress to be fully
|
|
* committed to EEMEM: 1024 bytes * 5 ms/byte = 5120 ms ~= 5 seconds.
|
|
* - It's important to keep in mind that nothing will be written to the EEPROM
|
|
* until after this function returns (since writes are done 1 byte per
|
|
* keyboard scan cycle, at the end of each scan cycle). But the code should
|
|
* not depend on that.
|
|
* - It's also important to remember that this function will not be interrupted
|
|
* by the recording of any new key-actions for an in-progress macro (though,
|
|
* key-actions may be queued for writing before all `compress()` writes have
|
|
* been completed).
|
|
*
|
|
* Implementation notes:
|
|
* - Before performing any copy operation, we invalidate the portion of the
|
|
* EEPROM we are going to modify by setting the first byte of it (which is,
|
|
* and will be, the beginning of a macro) to `TYPE_END`. This way, as long
|
|
* as writes to the EEPROM are atomic (or, as long as we don't lose power
|
|
* while writing one of these crucial `type` bytes) the EEPROM will always be
|
|
* in a consistent state.
|
|
* - If power is lost before all writes have been committed, the portion of
|
|
* the EEPROM that has not yet been compressed will remain invalidated
|
|
* (so data will be lost, but the list of macros will not be corrupted).
|
|
* - If the user tries to execute a macro before all writes have been
|
|
* committed, and the macro is in the portion of the EEPROM that has
|
|
* already been compressed, it will be found as normal. If the macro is
|
|
* in the portion of the EEPROM that is still being modified, it will
|
|
* temporarily appear not to exist.
|
|
* - In any case, no extra checks need to be performed, the possibility of
|
|
* data loss is kept very low, and the possibility of data corruption
|
|
* (which would, in this scheme, be undetected...) is (I think, for our
|
|
* purposes) vanishingly small.
|
|
*/
|
|
static uint8_t compress(void) {
|
|
uint8_t ret; // for function return codes (to test for errors)
|
|
|
|
void * to_overwrite; // the first byte with a value we don't need to keep
|
|
void * to_compress; // the first byte of the data we do need to keep
|
|
void * next; // the next macro after the data to keep (usually)
|
|
|
|
uint8_t type; // the type of the first macro in `to_compress`
|
|
void * type_location; // the final location of this `type` byte in EEMEM
|
|
|
|
to_overwrite = find_next_deleted(EEMEM_MACROS_START);
|
|
if (! to_overwrite) return 0; // success: nothing to compress
|
|
|
|
// - here `next` is the next macro to consider keeping
|
|
// - we could set `next = to_overwrite`, but then this would depend on
|
|
// writes being delayed
|
|
next = to_overwrite + eeprom__read(to_overwrite+1);
|
|
|
|
// invalidate the portion of the EEPROM we'll be working on
|
|
ret = eeprom__write(to_overwrite, TYPE_END);
|
|
if (ret) return 1; // write failed; data unchanged
|
|
|
|
while (next <= end_macro) {
|
|
to_compress = find_next_nondeleted(next);
|
|
|
|
// `next` will always be 1 byte beyond the data we wish to copy
|
|
// - since the EEPROM is only 2^10 bytes, and pointers are 16 bits, we
|
|
// don't have to worry about overflow
|
|
next = find_next_deleted(to_compress);
|
|
if (! next) next = new_end_macro;
|
|
if (! next) next = end_macro+1;
|
|
|
|
// save the `type` so we can write it last
|
|
type = eeprom__read(to_compress);
|
|
type_location = to_overwrite;
|
|
to_overwrite++;
|
|
to_compress++;
|
|
|
|
// copy the data in at most `UINT8_MAX` size chunks
|
|
// - because the `length` argument of `eeprom__write()` is a `uint8_t`
|
|
// - even though macros (individually) will be at most `UINT8_MAX`
|
|
// bytes long, the block of macros we need to save may be longer
|
|
for ( uint16_t length = next-to_compress;
|
|
length;
|
|
length = next-to_compress ) {
|
|
|
|
if (length > UINT8_MAX)
|
|
length = UINT8_MAX;
|
|
|
|
ret = eeprom__copy(to_overwrite, to_compress, length);
|
|
if (ret) goto out; // write failed; data lost
|
|
to_overwrite += length;
|
|
to_compress += length;
|
|
}
|
|
|
|
// invalidate the portion of the EEPROM we'll be working on next
|
|
// - no need to do this if there's nothing more to compress
|
|
if (next <= end_macro) {
|
|
ret = eeprom__write(to_overwrite, TYPE_END);
|
|
if (ret) goto out; // write failed; data lost
|
|
}
|
|
|
|
// lastly, write the `type` we saved earlier
|
|
// (revalidate the portion of the EEPROM we're done with)
|
|
ret = eeprom__write(type_location, type);
|
|
if (ret) goto out; // write failed; data lost
|
|
}
|
|
|
|
// update state variables
|
|
if (new_end_macro) {
|
|
end_macro -= (new_end_macro-to_overwrite);
|
|
new_end_macro = to_overwrite;
|
|
} else {
|
|
end_macro = to_overwrite-1;
|
|
}
|
|
|
|
return 0; // success: compression finished
|
|
|
|
out:
|
|
end_macro = type_location;
|
|
new_end_macro = 0;
|
|
return 2; // write failed; data lost
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// helper functions -----------------------------------------------------------
|
|
|
|
/** functions/write_key_action_for_new_macro/description
|
|
* Write the given key-action to the next empty space in the macros block of
|
|
* the EEPROM
|
|
*
|
|
* Arguments:
|
|
* - `k`: A pointer to the key-action to write
|
|
*
|
|
* Returns:
|
|
* - success: `0`
|
|
* - failure: [other]
|
|
*
|
|
* Assumptions:
|
|
* - A macro is currently in progress (i.e. `new_end_macro` is not `0`).
|
|
*
|
|
* Notes:
|
|
* - We make sure to leave 1 empty byte for the end macro.
|
|
* - We update the value of `new_end_macro` (either to indicate the bytes that
|
|
* were written, or to cancel the new macro if writing failed).
|
|
*/
|
|
static inline uint8_t write_key_action_for_new_macro(key_action_t * k) {
|
|
uint8_t ret; // for function return values
|
|
|
|
ret = write_key_action(new_end_macro, k, EEMEM_MACROS_END-1);
|
|
if (! ret) {
|
|
if ( compress() ) goto out; // compress failed (macro cancelled)
|
|
|
|
ret = write_key_action(new_end_macro, k, EEMEM_MACROS_END-1);
|
|
if (! ret) goto out; // write failed again, or not enough room
|
|
}
|
|
|
|
new_end_macro += ret;
|
|
return 0;
|
|
|
|
out:
|
|
new_end_macro = 0;
|
|
return 1;
|
|
}
|
|
|
|
/** functions/delete_macro_if_exists/description
|
|
* Deletes the macro remapping the given key-action, if it exists
|
|
*
|
|
* Arguments:
|
|
* - `k`: A pointer to the key-action to delete
|
|
*
|
|
* Returns:
|
|
* - success: `0`
|
|
* - failure: [other]
|
|
*/
|
|
static inline uint8_t delete_macro_if_exists(key_action_t * k) {
|
|
void * k_location = find_key_action(k);
|
|
|
|
if (k_location)
|
|
if ( eeprom__write(k_location, TYPE_DELETED) )
|
|
return 1; // write failed
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// public functions -----------------------------------------------------------
|
|
|
|
/** functions/eeprom_macro__init/description
|
|
* Implementation notes:
|
|
* - The initialization of static EEPROM values that this function is supposed
|
|
* to do when the EEPROM is not in a valid state (for this build of the
|
|
* firmware) is done in `eeprom_macro__clear_all()`.
|
|
*/
|
|
uint8_t eeprom_macro__init(void) {
|
|
#define TEST(address, offset, expected) \
|
|
if ( eeprom__read((address)+(offset)) != (expected) ) \
|
|
return eeprom_macro__clear_all()
|
|
|
|
TEST( EEMEM_START_ADDRESS_START, 0, (uint16_t)EEMEM_START >> 8 );
|
|
TEST( EEMEM_START_ADDRESS_START, 1, (uint16_t)EEMEM_START & 0xFF );
|
|
|
|
TEST( EEMEM_END_ADDRESS_START, 0, (uint16_t)EEMEM_END >> 8 );
|
|
TEST( EEMEM_END_ADDRESS_START, 1, (uint16_t)EEMEM_END & 0xFF );
|
|
|
|
TEST( EEMEM_VERSION_START, 0, VERSION );
|
|
|
|
#undef TEST
|
|
|
|
// find the end macro
|
|
void * current = EEMEM_MACROS_START;
|
|
for ( uint8_t type = eeprom__read(current);
|
|
type != TYPE_END;
|
|
current += eeprom__read(current+1), type = eeprom__read(current) );
|
|
|
|
end_macro = current;
|
|
new_end_macro = 0;
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
/** functions/eeprom_macro__record_init/description
|
|
* Implementation notes:
|
|
* - At minimum, for a normal macro, we will need a `type` byte, a `length`
|
|
* byte, and 3 key-actions (the key-action to remap, 1 press, and 1 release).
|
|
* Key-actions take a minimum of 1 byte, so our minimum macro will be 5
|
|
* bytes.
|
|
*/
|
|
uint8_t eeprom_macro__record_init( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
|
|
if (new_end_macro)
|
|
eeprom_macro__record_cancel();
|
|
|
|
if ( end_macro + 5 > EEMEM_MACROS_END )
|
|
return 1; // not enough room
|
|
|
|
key_action_t k = {
|
|
.pressed = pressed,
|
|
.layer = layer,
|
|
.row = row,
|
|
.column = column,
|
|
};
|
|
|
|
if ( delete_macro_if_exists(&k) ) return 1; // failure
|
|
|
|
new_end_macro = end_macro + 2;
|
|
|
|
return write_key_action_for_new_macro(&k);
|
|
}
|
|
|
|
/** functions/eeprom_macro__record_action/description
|
|
* Implementation notes:
|
|
* - Macros can only be `UINT8_MAX` bytes long in total. If we don't have at
|
|
* least 4 bytes left before exceeding that limit (since 4 bytes is the
|
|
* maximum length of a key-action), we simply stop recording actions. This
|
|
* is certainly not optimal behavior... but I think it'll end up being the
|
|
* least surprising, where our other options are to either finalize the
|
|
* macro, or return an error code.
|
|
* - If longer macros are desired, there are several ways one might modify
|
|
* the implementation to allow them. The simplest method would be to
|
|
* make `length` a 2 byte variable. That would reduce the number of
|
|
* small macros one could have, however. Alternately, one could steal 2
|
|
* bits from the `type` byte, which would save space, but make things
|
|
* more difficult to read. Another method would be to introduce a
|
|
* `TYPE_CONTINUED`, or something similar, where the data section of a
|
|
* macro of this type would continue the data section of the previous
|
|
* macro. That would make the logic of recording macros (and playing
|
|
* them back) a little more complicated though.
|
|
*/
|
|
uint8_t eeprom_macro__record_action( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
|
|
if (! new_end_macro)
|
|
return 1; // no macro in progress
|
|
|
|
if ( new_end_macro - end_macro > UINT8_MAX - 4 )
|
|
return 0; // macro too long, ignoring further actions
|
|
|
|
key_action_t k = {
|
|
.pressed = pressed,
|
|
.layer = layer,
|
|
.row = row,
|
|
.column = column,
|
|
};
|
|
|
|
return write_key_action_for_new_macro(&k);
|
|
}
|
|
|
|
uint8_t eeprom_macro__record_finalize(void) {
|
|
if ( eeprom__write( new_end_macro, TYPE_END ) ) goto out;
|
|
if ( eeprom__write( end_macro+1, new_end_macro - end_macro ) ) goto out;
|
|
if ( eeprom__write( end_macro, TYPE_VALID_MACRO ) ) goto out;
|
|
|
|
end_macro = new_end_macro;
|
|
new_end_macro = 0;
|
|
return 0;
|
|
|
|
out:
|
|
new_end_macro = 0;
|
|
return 1;
|
|
}
|
|
|
|
uint8_t eeprom_macro__record_cancel(void) {
|
|
new_end_macro = 0;
|
|
return 0;
|
|
}
|
|
|
|
uint8_t eeprom_macro__play( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
|
|
key_action_t k = {
|
|
.pressed = pressed,
|
|
.layer = layer,
|
|
.row = row,
|
|
.column = column,
|
|
};
|
|
|
|
void * k_location = find_key_action(&k);
|
|
if (! k_location) return 1; // macro does not exist
|
|
|
|
uint8_t length = eeprom__read(k_location+1);
|
|
length -= 2;
|
|
k_location += 2;
|
|
while (length) {
|
|
uint8_t read = read_key_action(k_location, &k);
|
|
kb__layout__exec_key_layer( k.pressed, k.layer, k.row, k.column );
|
|
length -= read;
|
|
k_location += read;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool eeprom_macro__exists( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
|
|
key_action_t k = {
|
|
.pressed = pressed,
|
|
.layer = layer,
|
|
.row = row,
|
|
.column = column,
|
|
};
|
|
|
|
return find_key_action(&k);
|
|
}
|
|
|
|
uint8_t eeprom_macro__clear( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
|
|
key_action_t k = {
|
|
.pressed = pressed,
|
|
.layer = layer,
|
|
.row = row,
|
|
.column = column,
|
|
};
|
|
|
|
return delete_macro_if_exists(&k);
|
|
}
|
|
|
|
/** functions/eeprom_macro__clear_all/description
|
|
* Implementation notes:
|
|
* - Since the `eeprom__...` functions only modify data when necessary, we
|
|
* don't need to worry here about excessive EEPROM wear when writing; so it's
|
|
* easier to initialize all static EEPROM values every time this function
|
|
* runs than to do most of these initializations as a special case in
|
|
* `eeprom_macro__init()`.
|
|
*/
|
|
uint8_t eeprom_macro__clear_all(void) {
|
|
#define WRITE(address, offset, value) \
|
|
if (eeprom__write( (address)+(offset), (value) )) return 1
|
|
|
|
WRITE( EEMEM_START_ADDRESS_START, 0, (uint16_t)EEMEM_START >> 8 );
|
|
WRITE( EEMEM_START_ADDRESS_START, 1, (uint16_t)EEMEM_START & 0xFF );
|
|
|
|
WRITE( EEMEM_END_ADDRESS_START, 0, (uint16_t)EEMEM_END >> 8 );
|
|
WRITE( EEMEM_END_ADDRESS_START, 1, (uint16_t)EEMEM_END & 0xFF );
|
|
|
|
WRITE( EEMEM_VERSION_START, 0, VERSION );
|
|
|
|
WRITE( EEMEM_MACROS_START, 0, TYPE_END );
|
|
|
|
#undef WRITE
|
|
|
|
end_macro = EEMEM_MACROS_START;
|
|
new_end_macro = 0;
|
|
|
|
return 0;
|
|
}
|
|
|