725 lines
28 KiB
C
725 lines
28 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
|
|
*
|
|
*
|
|
* 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.
|
|
*
|
|
*
|
|
* TODO:
|
|
* - i was thinking before that the calling function need not ignore layer
|
|
* shift keys, or any other keys. now i think that layer keys (or at least
|
|
* layer shift keys) really should be ignored. not doing so may lead to all
|
|
* sorts of fun problems. for example, if the "begin/end recording" key is
|
|
* not on layer 0 (which it probably won't be), the last keys pressed (but
|
|
* not released) will most likely be layer shift keys -- but since these keys
|
|
* were not released before we stopped recording, there would be no record of
|
|
* their release, and the macro would therefore push that layer onto the
|
|
* layer stack, and never pop it off.
|
|
*
|
|
* - need to write something like:
|
|
* - `kb__layout__exec_key_layer()`
|
|
* - `kb__layout__exec_key()` could just look up the current layer
|
|
* (falling through for transparent keys), and then call
|
|
* `kb__layout__exec_key_layer()`. this would obviate the need for a
|
|
* separate `static get_layer(void)` function, since the
|
|
* functionality would essentially be separated out anyway.
|
|
*/
|
|
|
|
|
|
#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
|
|
*
|
|
* Warnings:
|
|
* - This implementation of macros doesn't leave any room for error checking:
|
|
* we must be very careful not to corrupt the data. Also need to be very
|
|
* careful that any pointer into the EEMEM that's supposed to be pointing to
|
|
* the beginning of a macro (especially a non-initial macro) actually does
|
|
* point to one. Otherwise, behavior is undefined.
|
|
*
|
|
* 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_CONTINUED`
|
|
* - byte 1: `length`: the total number of bytes used by this
|
|
* macro, including the bytes for `type` and `length`
|
|
* - byte 2...: (optional) a continuation of the data section of
|
|
* the previous macro
|
|
* - 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_CONTINUED`
|
|
* - `TYPE_END`
|
|
*/
|
|
#define TYPE_DELETED 0x00
|
|
#define TYPE_VALID_MACRO 0x01
|
|
#define TYPE_CONTINUED 0x02
|
|
#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)
|
|
*/
|
|
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.
|
|
*/
|
|
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
|
|
*
|
|
* 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.
|
|
*/
|
|
uint8_t write_key_action(void * to, key_action_t * k) {
|
|
uint8_t ret; // for function return codes (to test for errors)
|
|
|
|
// - we need to leave room after this macro (and therefore after this
|
|
// key-action) for the `type == TYPE_END` byte
|
|
if (to > EEMEM_END-4)
|
|
return 0; // 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++) {
|
|
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 ;
|
|
ret = eeprom__write(to++, byte);
|
|
if (ret) 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`: 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).
|
|
*/
|
|
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`)
|
|
*/
|
|
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.
|
|
*/
|
|
void * find_next_nondeleted(void * start) {
|
|
for ( uint8_t type = eeprom__read(start);
|
|
type == TYPE_DELETED || type == TYPE_CONTINUED;
|
|
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
|
|
* - `end_macro` set to the new last macro
|
|
* - `new_end_macro` set to `0`
|
|
*
|
|
* 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.
|
|
*
|
|
* Implementation notes:
|
|
* - 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).
|
|
* - 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, this way, 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.
|
|
*
|
|
* TODO:
|
|
* - I feel like this still needs to be read over a bit more; maybe after I've
|
|
* started writing the public functions and have a better idea of exactly
|
|
* what it should do.
|
|
*/
|
|
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 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
|
|
|
|
// set `next` to a value that works when we enter the loop
|
|
// - on the first iteration, `find_next_nondeleted(next)` will return
|
|
// quickly, so this doesn't waste much time
|
|
// - since writes to the EEPROM are delayed, we could just set `next =
|
|
// to_overwrite`; but it's nicer to write things so they would work even
|
|
// if writes were immediate.
|
|
next = find_next_nondeleted(to_overwrite);
|
|
|
|
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
|
|
|
|
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;
|
|
to_overwrite += length;
|
|
to_compress += length;
|
|
}
|
|
|
|
if (next < end_macro) {
|
|
ret = eeprom__write(to_overwrite, TYPE_END);
|
|
if (ret) goto out;
|
|
}
|
|
|
|
ret = eeprom__write(type_location, type);
|
|
if (ret) goto out;
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// public functions -----------------------------------------------------------
|
|
|
|
uint8_t eeprom_macro__init(void) {
|
|
// TODO
|
|
return 0;
|
|
}
|
|
|
|
uint8_t eeprom_macro__record_init( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
// TODO
|
|
return 0;
|
|
}
|
|
|
|
uint8_t eeprom_macro__record_action( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
// TODO
|
|
return 0;
|
|
}
|
|
|
|
uint8_t eeprom_macro__record_finalize(void) {
|
|
// TODO
|
|
return 0;
|
|
}
|
|
|
|
uint8_t eeprom_macro__play( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
// TODO
|
|
return 0;
|
|
}
|
|
|
|
bool eeprom_macro__exists( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
// TODO
|
|
return false;
|
|
}
|
|
|
|
void eeprom_macro__clear( bool pressed,
|
|
uint8_t layer,
|
|
uint8_t row,
|
|
uint8_t column ) {
|
|
// TODO
|
|
}
|
|
|
|
void eeprom_macro__clear_all(void) {
|
|
// TODO
|
|
}
|
|
|