diff --git a/firmware/keyboard.h b/firmware/keyboard.h index 396cfcf7..c34cc2e8 100644 --- a/firmware/keyboard.h +++ b/firmware/keyboard.h @@ -1,5 +1,5 @@ /* ---------------------------------------------------------------------------- - * Copyright (c) 2012 Ben Blazak + * Copyright (c) 2012, 2014 Ben Blazak * Released under The MIT License (see "doc/licenses/MIT.md") * Project located at * ------------------------------------------------------------------------- */ @@ -50,13 +50,20 @@ void kb__led__all_set (float percent); // ------- void kb__led__state__power_on (void); void kb__led__state__ready (void); +void kb__led__delay__error (void); void kb__led__delay__usb_init (void); // layout void kb__led__logical_on (char led); void kb__led__logical_off (char led); // ------- -void kb__layout__exec_key (bool pressed, uint8_t row, uint8_t column); +void kb__layout__exec_key ( bool pressed, + uint8_t row, + uint8_t column ); +void kb__layout__exec_key_layer ( bool pressed, + uint8_t layer, + uint8_t row, + uint8_t column ); // ---------------------------------------------------------------------------- @@ -217,6 +224,12 @@ void kb__layout__exec_key (bool pressed, uint8_t row, uint8_t column); * keystrokes. */ + +// === kb__led__delay__error() === +/** functions/kb__led__delay__error/description + * Signal a generic error + */ + // === kb__led__delay__usb_init() === /** functions/kb__led__delay__usb_init/description * Delay for a total of ~1 second, to allow the host to load drivers and such. @@ -273,3 +286,22 @@ void kb__layout__exec_key (bool pressed, uint8_t row, uint8_t column); * etc. from `main()`. */ +// === kb__layout__exec_key_layer === +/** functions/kb__layout__exec_key_layer/description + * Perform the appropriate actions for a "press" or "release" of the key at the + * given position, on the given layer. + * + * Arguments: + * - `pressed`: + * - `true`: Indicates that the key to be "executed" has been pressed + * - `false`: Indicates that the key to be "executed" has been released + * - `layer`: The layer of the key to be "executed" + * - `row`: The row of the key to be "executed" + * - `column`: The column of the key to be "executed" + * + * Notes: + * - If the implementation does not support layers, the `layer` argument should + * be ignored, and this function will be equivalent to + * `kb__layout__exec_key()`. + */ + diff --git a/firmware/keyboard/ergodox/controller.c b/firmware/keyboard/ergodox/controller.c index 63a68607..ac511b8c 100644 --- a/firmware/keyboard/ergodox/controller.c +++ b/firmware/keyboard/ergodox/controller.c @@ -23,7 +23,8 @@ uint8_t kb__init(void) { if (mcp23018__init()) // must be second return 2; - eeprom_macro__init(); + if (eeprom_macro__init()) + return 3; return 0; // success } diff --git a/firmware/keyboard/ergodox/controller/mcp23018.c b/firmware/keyboard/ergodox/controller/mcp23018.c index 12d8676e..52ac6c06 100644 --- a/firmware/keyboard/ergodox/controller/mcp23018.c +++ b/firmware/keyboard/ergodox/controller/mcp23018.c @@ -23,10 +23,6 @@ #error "Expecting different keyboard dimensions" #endif -#if ( OPT__MCP23018__DRIVE_ROWS && OPT__MCP23018__DRIVE_COLUMNS ) \ - || !( OPT__MCP23018__DRIVE_ROWS || OPT__MCP23018__DRIVE_COLUMNS ) - #error "MCP23018 pin drive direction incorrectly set" -#endif /** macros/(group) pin drive direction/description * Select which set of pins (rows or columns) will drive (alternate between * hi-Z and drive low), and which will be inputs (hi-Z) @@ -49,6 +45,10 @@ * - If the diode cathode is towards the circular solder pad, set * `OPT__MCP23018__DRIVE_ROWS` to `1` */ +#if ( OPT__MCP23018__DRIVE_ROWS && OPT__MCP23018__DRIVE_COLUMNS ) \ + || !( OPT__MCP23018__DRIVE_ROWS || OPT__MCP23018__DRIVE_COLUMNS ) + #error "MCP23018 pin drive direction incorrectly set" +#endif // ---------------------------------------------------------------------------- diff --git a/firmware/keyboard/ergodox/controller/teensy-2-0.c b/firmware/keyboard/ergodox/controller/teensy-2-0.c index 3c4c369f..5190617e 100644 --- a/firmware/keyboard/ergodox/controller/teensy-2-0.c +++ b/firmware/keyboard/ergodox/controller/teensy-2-0.c @@ -28,10 +28,6 @@ #error "Expecting different keyboard dimensions" #endif -#if ( OPT__TEENSY__DRIVE_ROWS && OPT__TEENSY__DRIVE_COLUMNS ) \ - || !( OPT__TEENSY__DRIVE_ROWS || OPT__TEENSY__DRIVE_COLUMNS ) - #error "Teensy pin drive direction incorrectly set" -#endif /** macros/(group) pin drive direction/description * Select which set of pins (rows or columns) will drive (alternate between * hi-Z and drive low), and which will be inputs (hi-Z) @@ -54,6 +50,10 @@ * - If the diode cathode is towards the circular solder pad, set * `OPT__TEENSY__DRIVE_ROWS` to `1` */ +#if ( OPT__TEENSY__DRIVE_ROWS && OPT__TEENSY__DRIVE_COLUMNS ) \ + || !( OPT__TEENSY__DRIVE_ROWS || OPT__TEENSY__DRIVE_COLUMNS ) + #error "Teensy pin drive direction incorrectly set" +#endif // ---------------------------------------------------------------------------- diff --git a/firmware/keyboard/ergodox/layout/fragments/includes.part.h b/firmware/keyboard/ergodox/layout/fragments/includes.part.h index f80080e4..8cf49234 100644 --- a/firmware/keyboard/ergodox/layout/fragments/includes.part.h +++ b/firmware/keyboard/ergodox/layout/fragments/includes.part.h @@ -20,6 +20,7 @@ #include "../../../../../firmware/lib/timer.h" #include "../../../../../firmware/lib/usb.h" #include "../../../../../firmware/lib/usb/usage-page/keyboard.h" +#include "../../../../../firmware/lib/layout/eeprom-macro.h" #include "../../../../../firmware/lib/layout/key-functions.h" #include "../../../../../firmware/lib/layout/layer-stack.h" #include "../../../../../firmware/keyboard.h" diff --git a/firmware/keyboard/ergodox/layout/fragments/matrix-control.part.h b/firmware/keyboard/ergodox/layout/fragments/matrix-control.part.h index 688e4223..a635b08e 100644 --- a/firmware/keyboard/ergodox/layout/fragments/matrix-control.part.h +++ b/firmware/keyboard/ergodox/layout/fragments/matrix-control.part.h @@ -12,55 +12,63 @@ */ +/** functions/kb__layout__exec_key/description + * Assumptions: + * - All arguments are valid. + * + * Implementation notes: + * - The default layer is layer 0. + * - This function is only responsible for layer resolution (which includes the + * handling of transparent keys). Everything else, it passes to + * `kb__layout__exec_key_layer()`. + */ void kb__layout__exec_key(bool pressed, uint8_t row, uint8_t column) { - // if we press a key, we need to keep track of the layer it was pressed on, - // so we can release it on the same layer - // - if the release is transparent, search through the layer stack for a - // non-transparent release in the same position, as normal + // - to keep track of the layer a key was pressed on, so we can release on + // the same layer static uint8_t pressed_layer[OPT__KB__ROWS][OPT__KB__COLUMNS]; - void (*function)(void); uint8_t layer; + void (*function)(void); - // - add 1 to the stack size because we spend the first iteration checking - // to see if we need to release on a previously stored layer - // - add 1 to the stack size in order to peek out of bounds on the last - // iteration (if we get that far), so that layer 0 is our default (see - // the documentation for ".../firmware/lib/layout/layer-stack.h") - for (uint8_t i=0; i < layer_stack__size()+1+1; i++) { // i = offset+1 - if (i == 0) - if (!pressed) - layer = pressed_layer[row][column]; - else - continue; - else - layer = layer_stack__peek(i-1); + // handle the case that a key is released, and the layer it was pressed on + // has a non-transparent release function in the given location + if (! pressed) { + layer = pressed_layer[row][column]; function = (void (*)(void)) - pgm_read_word( &( layout[ layer ] - [ row ] - [ column ] - [ (pressed) ? 0 : 1 ] ) ); - - if (function == &KF(transp)) - function = NULL; + pgm_read_word( &( layout[ layer ] + [ row ] + [ column ] + [ !pressed ] ) ); - if (function) { - if (pressed) - pressed_layer[row][column] = layer; + if (function != &KF(transp)) { + kb__layout__exec_key_layer( pressed, layer, row, column ); + return; + } + } - flags.tick_keypresses = (pressed) ? true : false; // set default + // otherwise, search through the layer stack for a layer with a + // non-transparent key-function in the given location - (*function)(); + // - altogether, unless we find a non-transparent key-function earlier, we + // want to peek at offsets `0` through `layer_stack__size()`. this will + // cause us to peek out of bounds on the last iteration, so that layer 0 + // will be the default (see the documentation for + // ".../lib/layout/layer-stack") + for (uint8_t i=0; i <= layer_stack__size(); i++) { + layer = layer_stack__peek(i); + function = (void (*)(void)) + pgm_read_word( &( layout[ layer ] + [ row ] + [ column ] + [ !pressed ] ) ); - // TODO: *always* tick keypresses - // TODO: instead of this, set a flag for the type of key pressed, - // and any functions that execute can check it, and conditionally - // reschedule themselves to run later, if they so desire - if (flags.tick_keypresses) - timer___tick_keypresses(); + if (function != &KF(transp)) { + if (pressed) + pressed_layer[row][column] = layer; + kb__layout__exec_key_layer( pressed, layer, row, column ); return; } } @@ -68,3 +76,40 @@ void kb__layout__exec_key(bool pressed, uint8_t row, uint8_t column) { // if we get here, there was a transparent key in layer 0; do nothing } +/** functions/kb__layout__exec_key_layer/description + * Assumptions: + * - All arguments are valid. + * + * TODO: + * - take care of the recording and such of macros :) + * - need to ignore layer-shift keys when recording + */ +void kb__layout__exec_key_layer( bool pressed, + uint8_t layer, + uint8_t row, + uint8_t column ) { + + void (*function)(void) = (void (*)(void)) + pgm_read_word( &( layout[ layer ] + [ row ] + [ column ] + [ !pressed ] ) ); + if (! function) return; + + // set default values + // - the key-function will not be able to see the values set previously + // - any function scheduled to run will be able to see the values set + // by the immediately preceding function; but that may change in the + // future, so it shouldn't be relied on. if functions need to + // communicate with each other, they should share a file-local or global + // variable. + flags[0].key_type.sticky = false; + flags[0].key_type.layer_shift = false; + flags[0].key_type.layer_lock = false; + + (*function)(); + + if (pressed) + timer___tick_keypresses(); +} + diff --git a/firmware/keyboard/ergodox/layout/fragments/variables.part.h b/firmware/keyboard/ergodox/layout/fragments/variables.part.h index 4d95ae2a..31bd1f8e 100644 --- a/firmware/keyboard/ergodox/layout/fragments/variables.part.h +++ b/firmware/keyboard/ergodox/layout/fragments/variables.part.h @@ -20,17 +20,34 @@ static layout_t layout PROGMEM; /** variables/flags/description * A collection of flags pertaining to the operation of `...exec_key()` * + * Notes: + * - These should be set within key-functions, but only read inside + * `...exec_key()`. The ability to read them outside that function should + * not be counted on. + * * Struct members: - * - `tick_keypresses`: A predicate indicating whether or not to "tick" - * keypresses on this run of the function (see the documentation in - * ".../firmware/lib/timer.h" for more precisely what this means) - * - This is useful for defining things like sticky keys, if, e.g., you - * want to make it so that you can press more than one and have none of - * them release until the press of the next normal key. + * - `key_type`: To indicate the type of key most recently pressed + * - More than one type flag may be set (e.g. a key may be both a + * layer-shift key and a sticky key). + * - `key_type.sticky` + * - `key_type.layer_shift` + * - `key_type.layer_lock` + * + * Terms: + * - A "sticky key" is one which, once pressed, remains pressed in software + * (whether or not the user holds it down) ... TODO + * + * TODO: + * - finish terms + * - change other code (in "./matrix-control.part.h") to actually use the fact + * that we have 2 of these now (so that there is an "old" version, and a + * version to update) */ static struct { - bool tick_keypresses : 1; -} flags = { - .tick_keypresses = true, -}; + struct { + bool sticky : 1; + bool layer_shift : 1; + bool layer_lock : 1; + } key_type; +} flags[2]; diff --git a/firmware/keyboard/ergodox/layout/qwerty--ben.c b/firmware/keyboard/ergodox/layout/qwerty--ben.c index 09ddf48e..7afd35b0 100644 --- a/firmware/keyboard/ergodox/layout/qwerty--ben.c +++ b/firmware/keyboard/ergodox/layout/qwerty--ben.c @@ -108,7 +108,7 @@ shL2kcap, z, x, c, v, b, lpupo2l2, transp, transp, transp, // right hand ..... ......... ......... ......... ......... ......... ......... F12, F6, F7, F8, F9, F10, power, - lpo2l2, nop, undersc, lessThan, grtrThan, dollar, volumeU, + lpo2l2, caret, undersc, lessThan, grtrThan, dollar, volumeU, bkslash, 1, parenL, parenR, equal, volumeD, lpupo3l3, asterisk, 2, 3, 4, 5, mute, transp, transp, transp, transp, transp, diff --git a/firmware/keyboard/ergodox/layout/test.c b/firmware/keyboard/ergodox/layout/test.c index e6b5e341..cc9f9d2e 100644 --- a/firmware/keyboard/ergodox/layout/test.c +++ b/firmware/keyboard/ergodox/layout/test.c @@ -8,9 +8,6 @@ * A layout for testing newly build boards * * Implements the "layout" section of '.../firmware/keyboard.h' - * - * TODO: - * - update, if i change the semantics `kb__layout__exec_key()` */ diff --git a/firmware/keyboard/ergodox/led.c b/firmware/keyboard/ergodox/led.c index 1a68830e..33e9da5e 100644 --- a/firmware/keyboard/ergodox/led.c +++ b/firmware/keyboard/ergodox/led.c @@ -1,5 +1,5 @@ /* ---------------------------------------------------------------------------- - * Copyright (c) 2013 Ben Blazak + * Copyright (c) 2013, 2014 Ben Blazak * Released under The MIT License (see "doc/licenses/MIT.md") * Project located at * ------------------------------------------------------------------------- */ @@ -18,13 +18,24 @@ // ---------------------------------------------------------------------------- -#ifndef OPT__LED_BRIGHTNESS - #error "OPT__LED_BRIGHTNESS not defined" -#endif /** macros/OPT__LED_BRIGHTNESS/description * A percentage of maximum brightness, with '1' being greatest and '0' being * not quite off */ +#ifndef OPT__LED_BRIGHTNESS + #error "OPT__LED_BRIGHTNESS not defined" +#endif + +// ---------------------------------------------------------------------------- + +/** types/struct led_state/description + * For holding the state of the LEDs (if we ever need to save/restore them) + */ +struct led_state { + bool led_1 : 1; + bool led_2 : 1; + bool led_3 : 1; +}; // ---------------------------------------------------------------------------- @@ -100,6 +111,39 @@ void kb__led__state__ready(void) { kb__led__all_set( OPT__LED_BRIGHTNESS ); } +void kb__led__delay__error(void) { + // make sure LED states have stabilized + // - i'm not quite sure how long this takes; i would think it'd be nearly + // instant, but a bit of testing seemed to show that even 5ms isn't quite + // long enough; 10ms seems to work; at least we can afford the time here + _delay_ms(10); + + struct led_state state = { + .led_1 = kb__led__read(1), + .led_2 = kb__led__read(2), + .led_3 = kb__led__read(3), + }; + + kb__led__all_off(); + _delay_ms(167); + kb__led__all_on(); + _delay_ms(167); + kb__led__all_off(); + _delay_ms(167); + kb__led__all_on(); + _delay_ms(167); + kb__led__all_off(); + _delay_ms(167); + kb__led__all_on(); + _delay_ms(167); + kb__led__all_off(); + _delay_ms(167); + + if (state.led_1) kb__led__on(1); + if (state.led_2) kb__led__on(2); + if (state.led_3) kb__led__on(3); +} + void kb__led__delay__usb_init(void) { // need to delay for a total of ~1 second kb__led__set( 1, OPT__LED_BRIGHTNESS ); diff --git a/firmware/keyboard/ergodox/options.mk b/firmware/keyboard/ergodox/options.mk index 38ab5b78..7345ce97 100644 --- a/firmware/keyboard/ergodox/options.mk +++ b/firmware/keyboard/ergodox/options.mk @@ -21,7 +21,7 @@ F_CPU := 16000000 # processor speed, in Hz; max value is 16000000 (16MHz); must match # initialization in source -KEYBOARD_LAYOUT := qwerty--kinesis-mod +KEYBOARD_LAYOUT := qwerty--ben # default layout for this keyboard KEYBOARD_LAYOUTS := \ @@ -50,7 +50,3 @@ SRC += $(wildcard $(CURDIR)/layout/$(KEYBOARD_LAYOUT)*.c) CFLAGS += -include $(wildcard $(CURDIR)/options.h) -$(CURDIR)/layout/qwerty-kinesis-mod.o: $(wildcard $(CURDIR)/layout/common/*) -$(CURDIR)/layout/dvorak-kinesis-mod.o: $(wildcard $(CURDIR)/layout/common/*) -$(CURDIR)/layout/colemak-symbol-mod.o: $(wildcard $(CURDIR)/layout/common/*) - diff --git a/firmware/lib/layout/eeprom-macro.h b/firmware/lib/layout/eeprom-macro.h index 305f697f..45ef3c81 100644 --- a/firmware/lib/layout/eeprom-macro.h +++ b/firmware/lib/layout/eeprom-macro.h @@ -1,5 +1,5 @@ /* ---------------------------------------------------------------------------- - * Copyright (c) 2013 Ben Blazak + * Copyright (c) 2013, 2014 Ben Blazak * Released under The MIT License (see "doc/licenses/MIT.md") * Project located at * ------------------------------------------------------------------------- */ @@ -25,19 +25,19 @@ * - A "key" is a pair of actions, one for when the key is pressed and another * for when it is released. We specify keys by their `layer`, `row`, and * `column`. - * - A "key action" is a single action. We specify key actions by their + * - A "key-action" is a single action. We specify key-actions by their * `pressed` value (whether the action corresponds to a press (`true`) or * release (`false`)) and the key they belong to. * - A "macro" is a collection of data that lives in persistent memory, and * specifies possibly many actions to perform in the place of a single, * usually different, action. For the purposes of this library, macros live - * in the EEPROM, and contain a key action who's original behavior we wish - * to mask, and a list of key actions that should be sequentially performed + * in the EEPROM, and contain a key-action who's original behavior we wish + * to mask, and a list of key-actions that should be sequentially performed * instead. * - A "keystroke" is a full press then release of a key. Keystrokes may * overlap each other. - * - To "remap" a key action is to assign a macro to it (masking, not - * replacing, what the key action originally did). + * - To "remap" a key-action is to assign a macro to it (masking, not + * replacing, what the key-action originally did). * - The "EEPROM" is an "Electronically Erasable Programmable Read Only * Memory". It is where this library stores persistent data. * - "EEMEM" is "EEprom MEMory" (i.e. another way of referring to the memory of @@ -67,12 +67,12 @@ * one might wish to do when, for example, quickly swapping the positions of * two letter keys. * - With sufficient trickiness, we could probably do away with having `layer` - * in the key actions that make up the body of macros (most of this + * in the key-actions that make up the body of macros (most of this * trickiness being in the logic for how users record macros and assign them - * to key actions). I could imagine there being situations where this turned + * to key-actions). I could imagine there being situations where this turned * out to be useful... but I feel like much more often it would just be a bit * confusing. It would also be inconsistent a little, having two different - * representations of a key action. And it wouldn't actually save us that + * representations of a key-action. And it wouldn't actually save us that * much EEPROM. */ @@ -99,10 +99,11 @@ uint8_t eeprom_macro__init (void); uint8_t eeprom_macro__record_init ( ARGS ); uint8_t eeprom_macro__record_action ( ARGS ); uint8_t eeprom_macro__record_finalize (void); +uint8_t eeprom_macro__record_cancel (void); uint8_t eeprom_macro__play ( ARGS ); bool eeprom_macro__exists ( ARGS ); -void eeprom_macro__clear ( ARGS ); -void eeprom_macro__clear_all (void); +uint8_t eeprom_macro__clear ( ARGS ); +uint8_t eeprom_macro__clear_all (void); #undef ARGS @@ -158,66 +159,91 @@ void eeprom_macro__clear_all (void); * Prepare to record a new macro * * Arguments: - * - (group) The key action to remap - * - `pressed`: Whether the key action is a press (`true`) or a release + * - (group) The key-action to remap + * - `pressed`: Whether the key-action is a press (`true`) or a release * (`false`) - * - `layer`: The layer of the key action - * - `row`: The row of the key action - * - `column`: The column of the key action + * - `layer`: The layer of the key-action + * - `row`: The row of the key-action + * - `column`: The column of the key-action * * Returns: * - success: `0` - * - failure: [other] (not enough memory left to record) + * - failure: [other] * * Notes: * - Only one macro may be recorded at a time. If another macro is being * recorded when this function is called (i.e. this function has been called * once already, and `...finalize()` has not been called yet), the old macro * should be thrown away, and this new one prepared for. + * - If a macro remapping the given key-action already exists, it should be + * deleted. */ // === eeprom_macro__record_action() === /** functions/eeprom_macro__record_action/description - * Record the next key action of the current macro + * Record the next key-action of the current macro * * Arguments: - * - (group) The key action to record - * - `pressed`: Whether the key action is a press (`true`) or a release + * - (group) The key-action to record + * - `pressed`: Whether the key-action is a press (`true`) or a release * (`false`) - * - `layer`: The layer of the key action - * - `row`: The row of the key action - * - `column`: The column of the key action + * - `layer`: The layer of the key-action + * - `row`: The row of the key-action + * - `column`: The column of the key-action * * Returns: * - success: `0` - * - failure: [other] (not enough memory left to record) + * - failure: [other] + * + * Notes: + * - If this function fails, the macro currently being written may be canceled + * (thrown away). */ // === eeprom_macro__record_finalize() === /** functions/eeprom_macro__record_finalize/description * Finalize the recording of the current macro * - * Returns + * Returns: * - success: `0` * - failure: [other] * * Notes: * - Before this function is called, the macro (even though parts of it may be * written) should not be readable, or referenced anywhere in the EEPROM. + * + * Notes: + * - If this function fails, the macro currently being written may be canceled + * (thrown away). + */ + +// === eeprom_macro__record_cancel() === +/** functions/eeprom_macro__record_cancel/description + * Cancel the recording of the current macro + * + * Returns: + * - success: `0` + * - failure: [other] + * + * Notes: + * - Depending on the implementation, this function may not be necessary due to + * the behavior of `eeprom_macro__record_init()` and + * `eeprom_macro__record_finalize()`. In that case this function should + * simply do nothing. */ // === eeprom_macro__play() === /** functions/eeprom_macro__play/description - * Play back recorded key actions for the macro assigned to the specified key + * Play back recorded key-actions for the macro assigned to the specified key * action * * Arguments: - * - (group) The key action to search for - * - `pressed`: Whether the key action is a press (`true`) or a release + * - (group) The key-action to search for + * - `pressed`: Whether the key-action is a press (`true`) or a release * (`false`) - * - `layer`: The layer of the key action - * - `row`: The row of the key action - * - `column`: The column of the key action + * - `layer`: The layer of the key-action + * - `row`: The row of the key-action + * - `column`: The column of the key-action * * Returns: * - success: `0` (macro successfully played) @@ -226,15 +252,15 @@ void eeprom_macro__clear_all (void); // === eeprom_macro__exists() === /** functions/eeprom_macro__exists/description - * Predicate indicating whether the specified key action has been remapped + * Predicate indicating whether the specified key-action has been remapped * * Arguments: - * - (group) The key action to search for - * - `pressed`: Whether the key action is a press (`true`) or a release + * - (group) The key-action to search for + * - `pressed`: Whether the key-action is a press (`true`) or a release * (`false`) - * - `layer`: The layer of the key action - * - `row`: The row of the key action - * - `column`: The column of the key action + * - `layer`: The layer of the key-action + * - `row`: The row of the key-action + * - `column`: The column of the key-action * * Returns: * - `true`: if a macro remapping the given key-action exists @@ -243,15 +269,19 @@ void eeprom_macro__clear_all (void); // === eeprom_macro__clear() === /** functions/eeprom_macro__clear/description - * Clear (delete) the macro assigned to the given key action + * Clear (delete) the macro assigned to the given key-action * * Arguments: - * - (group) The key action to un-remap - * - `pressed`: Whether the key action is a press (`true`) or a release + * - (group) The key-action to un-remap + * - `pressed`: Whether the key-action is a press (`true`) or a release * (`false`) - * - `layer`: The layer of the key action - * - `row`: The row of the key action - * - `column`: The column of the key action + * - `layer`: The layer of the key-action + * - `row`: The row of the key-action + * - `column`: The column of the key-action + * + * Returns: + * - success: `0` + * - failure: [other] */ // === eeprom_macro__clear_all() === @@ -261,7 +291,11 @@ void eeprom_macro__clear_all (void); * Notes: * - For the purposes of this function, "clearing" the EEPROM means to put it * in such a state that none of the functions declared here will be able to - * find a macro for any key action. This does not necessarily imply that the + * find a macro for any key-action. This does not necessarily imply that the * EEPROM is in a fully known state. + * + * Returns: + * - success: `0` + * - failure: [other] */ diff --git a/firmware/lib/layout/eeprom-macro/atmega32u4.c b/firmware/lib/layout/eeprom-macro/atmega32u4.c index d10ae1fb..74b53453 100644 --- a/firmware/lib/layout/eeprom-macro/atmega32u4.c +++ b/firmware/lib/layout/eeprom-macro/atmega32u4.c @@ -9,12 +9,36 @@ * 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 our choices for default values, and such. + * 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 @@ -27,14 +51,13 @@ * * - 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 + * 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 #include #include "../../../../firmware/keyboard.h" #include "../../../../firmware/lib/eeprom.h" @@ -84,6 +107,21 @@ * - `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: * @@ -91,13 +129,19 @@ * - 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 all be set to `VERSION` as the last step of + * - 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. @@ -120,7 +164,7 @@ * - byte 0: `type == TYPE_END` * - byte 1...: (optional) undefined * - * - The last key-action in this series will have `type == TYPE_END`. + * - 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: @@ -178,15 +222,6 @@ * | | '- layer bit pair * | '- pressed / 1 * '- continued - * - * - * 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. */ #define EEMEM_START ((void *)OPT__EEPROM__EEPROM_MACRO__START) #define EEMEM_START_ADDRESS_START (EEMEM_START + 0) @@ -235,75 +270,100 @@ typedef struct { 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 and return the key-action beginning at `*from` in the EEPROM, and - * advance `*from` to one byte past the key-action. + * Read the key-action beginning at `from` in the EEPROM * * Arguments: - * - `from`: A pointer to a pointer to the location in EEPROM from which to - * begin reading + * - `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 key-action, as a `key_action_t` + * - 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. */ -key_action_t read_key_action(void ** from) { +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` + // - 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)++); + byte = eeprom__read(from++); + uint8_t read = 1; - key_action_t k = { - .pressed = byte >> 6 & 0b01, - .layer = byte >> 4 & 0b11, - .row = byte >> 2 & 0b11, - .column = byte >> 0 & 0b11, - }; + 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)++); + 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; + 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; + k->layer |= byte >> 4 & 0b11; + k->row |= byte >> 2 & 0b11; + k->column |= byte >> 0 & 0b11; } - return k; // success + return read; // success } /** 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. + * 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 a pointer to the location in EEPROM at which to begin - * writing - * - `k`: The key-action to write + * - `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: `0` - * - failure: [other] + * - 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 @@ -321,13 +381,7 @@ key_action_t read_key_action(void ** from) { * 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) { - - // - 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 1; // error: might not be enough space - +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 @@ -341,232 +395,449 @@ uint8_t write_key_action(void ** to, key_action_t k) { uint8_t i = 0; - for (; i<3 && !((k.layer|k.row|k.column) & 0xC0); i++) { - k.layer <<= 2; - k.row <<= 2; - k.column <<= 2; + 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 + // - 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 ? 1 : 0) << 6; + uint8_t byte = k->pressed << 6; for (; i<4; i++) { - byte = byte | ( i<3 ? 1 : 0 ) << 7 - | ( k.layer & 0xC0 ) >> 2 - | ( k.row & 0xC0 ) >> 4 - | ( k.column & 0xC0 ) >> 6 ; - eeprom__write((*to)++, byte); + 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; + k->layer <<= 2; + k->row <<= 2; + k->column <<= 2; } - return 0; // success + return written; // success } -// TODO: rewriting (yet again) - stopped here -#if 0 - -// ---------------------------------------------------------------------------- -// 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. -// -// - 255 bytes (so, on average, about 100 keystrokes = 200 key actions) should -// be enough for a macro, i think. `length` can be 1 byte, and count the -// total number of bytes (including `type` and `length`, and anything else) -// -// - 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. -// - `kb__led__delay__error()` -// - "delay" because it should probably flash a few times, or -// something, and i feel like it'd be better overall to not continue -// accepting input while that's happening. -// -// - how should we handle key-functions, in both their SRAM and EEMEM forms? -// it would be very convenient if we could compare the passed key-function -// with EEMEM key-functions without having to decode the EEMEM ones (only -// read them out - and maybe not even read out them out entirely). perhaps -// there should be different functions for converting from a `uint8_t a[4]` -// to a `key_action_t`, and vice versa. or perhaps `key_action_t`s should -// really be `uint8_t a[4]`s, with a different encoding than those -// representing EEMEM key-actions (which would be written to EEMEM eliding -// zero bytes). need to think about it some more. -// -// ---------------------------------------------------------------------------- -// ---------------------------------------------------------------------------- - -// variables in SRAM ---------------------------------------------------------- - -static void * current_macro; -uint8_t current_macro_length; - -// ---------------------------------------------------------------------------- -// local functions ------------------------------------------------------------ - -/** functions/find_uid/description - * Find the macro with the given `uid` +/** functions/find_key_action/description + * Find the macro remapping the given key-action (if it exists). * * Arguments: - * - `uid`: The UID of the macro we're trying to find + * - `k`: A pointer to the key-action to search for * * Returns: - * - success: The EEMEM address of the beginning of the macro - * - failure: `NULL` + * - success: The EEMEM address of the desired macro + * - failure: `0` * * Notes: - * - `NULL` is `#define`ed to `((void *)0)`, which for the EEPROM is a valid - * memory address; but because `struct eeprom` does not place - * `eeprom.macros.data` first in memory, `NULL` is guaranteed to be before - * the beginning of that array, and is therefore usable as a signal that the - * macro we're looking for does not exist. + * - 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: - * - Using `memcmp()` to compare two structs is bad practice in general (what - * if there's uninitialized padding in the struct?); but I'm doing it here - * because we're already trusting the binary layout of the struct when we - * store and retrieve it from EEMEM, and it saves writing another function - * just for that. - * - It should not be strictly necessary for us to check whether we're - * iterating over the bounds of `eeprom.macros.data`, since the list of - * macros is supposed to be terminated in a well defined way. But we may as - * well, just to be safer :) + * - 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_uid(eeprom_macro__uid_t uid) { - // if `eeprom.table.data` indicates that the macro does not exist - if ( (eeprom.table.data[uid.row][uid.column] >> uid.layer) & 1 ) - return NULL; - - // otherwise the macro may exist: we must search for it - for ( uint8_t * p = &eeprom.macros.data[0]; - p < &eeprom.macros.data[MACROS_LENGTH-3]; ) { +static void * find_key_action(key_action_t * k) { + void * current = EEMEM_MACROS_START; - header_t header; - eeprom__block_read(&header, p, sizeof(header)); + for ( uint8_t type = eeprom__read(current); + type != TYPE_END; + current += eeprom__read(current+1), type = eeprom__read(current) ) { - switch (header.type) { - case HEADER_TYPE_VALID: - if ( ! memcmp(&uid, &header.uid, sizeof(uid)) ) - return p; + if (type == TYPE_VALID_MACRO) { - // (do not break) + key_action_t k_current; + read_key_action(current+2, &k_current); - case HEADER_TYPE_DELETED: - p += sizeof(header_t) + header.length * sizeof(action_t); - break; + if ( k->pressed == k_current.pressed + && k->layer == k_current.layer + && k->row == k_current.row + && k->column == k_current.column ) { - // `HEADER_TYPE_END` or invalid value - default: - // (no more macros to search) - return NULL; + return current; + } } } - // macro does not exist - return NULL; + return 0; // key-action not found } /** functions/find_next_deleted/description - * Find the first deleted macro at or after the macro at the given position + * Find the first deleted macro at or after the given macro. * * Arguments: - * - `start`: The address (in EEMEM) of the first byte of the header of the - * macro at which to begin searching + * - `start`: The EEMEM address of the macro at which to begin searching * * Returns: - * - success: The address (in EEMEM) of the of the beginning of the first - * deleted macro found at or after `start` - * - failure: `NULL` (no deleted macros found) + * - 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 * p = start; - p < &eeprom.macros.data[MACROS_LENGTH-3]; ) { + for ( uint8_t type = eeprom__read(start); + type != TYPE_END; + start += eeprom__read(start+1), type = eeprom__read(start) ) { - uint8_t type = eeprom__read(p); - uint8_t length = eeprom__read(p+1); + if (type == TYPE_DELETED) + return start; + } - switch (type) { - case HEADER_TYPE_VALID: - p += sizeof(header_t) + length * sizeof(action_t); - break; + return 0; // no deleted macro found +} - case HEADER_TYPE_DELETED: - return p; +/** 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) ); - // `HEADER_TYPE_END` or invalid value - default: - // (no more macros to search) - return NULL; + 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; } - // no deleted macros found - return NULL; + return 0; // success: compression finished + +out: + end_macro = type_location; + new_end_macro = 0; + return 2; // write failed; data lost } -/** functions/compress/description - * Compress `macros.data` +// ---------------------------------------------------------------------------- +// 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 * - * Shift all macros towards index `0`, overwriting the areas previously - * occupied by deleted macros. + * 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 void compress(void) { return; - // TODO: this whole thing... just starting. +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) - uint8_t * current_deleted = find_next_deleted(&eeprom.macros.data[0]); + 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; - uint8_t * next_deleted = find_next_deleted(current_deleted); - if (! next_deleted) next_deleted = macros_free_begin; +out: + new_end_macro = 0; + return 1; } -#endif // 0 +/** 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) { - // TODO - return 0; + #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 ) { - // TODO - return 0; + + 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 ) { - // TODO - return 0; + + 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) { - // TODO + 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; } @@ -574,7 +845,27 @@ uint8_t eeprom_macro__play( bool pressed, uint8_t layer, uint8_t row, uint8_t column ) { - // TODO + + 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; } @@ -582,18 +873,59 @@ bool eeprom_macro__exists( bool pressed, uint8_t layer, uint8_t row, uint8_t column ) { - // TODO - return false; + + key_action_t k = { + .pressed = pressed, + .layer = layer, + .row = row, + .column = column, + }; + + return find_key_action(&k); } -void eeprom_macro__clear( bool pressed, - uint8_t layer, - uint8_t row, - uint8_t column ) { - // TODO +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); } -void eeprom_macro__clear_all(void) { - // TODO +/** 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; } diff --git a/firmware/lib/timer.h b/firmware/lib/timer.h index b371ef71..ee7341eb 100644 --- a/firmware/lib/timer.h +++ b/firmware/lib/timer.h @@ -190,7 +190,7 @@ void timer___tick_keypresses (void); // === timer___tick_keypresses() === /** functions/timer___tick_keypresses/description - * Increment the counter for the number of keypresses, and perform scheduled + * Increment the counter for the number of key-presses, and perform scheduled * tasks * * Meant to be used only by `kb__layout__exec_key()` diff --git a/firmware/lib/twi/atmega32u4.c b/firmware/lib/twi/atmega32u4.c index 10bfaa01..3e92d68a 100644 --- a/firmware/lib/twi/atmega32u4.c +++ b/firmware/lib/twi/atmega32u4.c @@ -28,18 +28,15 @@ // ---------------------------------------------------------------------------- -#if OPT__TWI__FREQUENCY > 400000 - #error "OPT__TWI__FREQUENCY must be <= 400000" -#endif /** macros/OPT__TWI__FREQUENCY/description * Implementation notes: * - The max speed for the ATmega32U4 is 400kHz (datasheet sec. 20.1) * - The max speed for the MCP23017 is 1.7MHz (datasheet pg. 1) * - The max speed for the MCP23018 is 3.4MHz (datasheet pg. 1) - * - * TODO: do these implementation notes belong in the keyboard "options.h", - * where they're set? */ +#if OPT__TWI__FREQUENCY > 400000 + #error "OPT__TWI__FREQUENCY must be <= 400000" +#endif // ---------------------------------------------------------------------------- diff --git a/firmware/lib/usb.h b/firmware/lib/usb.h index 95c76e90..9acde00d 100644 --- a/firmware/lib/usb.h +++ b/firmware/lib/usb.h @@ -24,8 +24,8 @@ // --- general --- -void usb__init (void); -bool usb__is_configured (void); +uint8_t usb__init (void); +bool usb__is_configured (void); // --- keyboard --- @@ -53,6 +53,10 @@ uint8_t usb__kb__send_report (void); /** functions/usb__init/description * Initialize USB for this device * + * Returns: + * - success: `0` + * - failure: [other] + * * Notes: * - Should be called exactly once by `main()`, and nothing else USB related * should be done until `usb__configured` is `true` diff --git a/firmware/lib/usb/atmega32u4/general.c b/firmware/lib/usb/atmega32u4/general.c index e0c15a21..f35f3e01 100644 --- a/firmware/lib/usb/atmega32u4/general.c +++ b/firmware/lib/usb/atmega32u4/general.c @@ -16,7 +16,7 @@ // ---------------------------------------------------------------------------- -void usb__init(void) { usb_init(); } +uint8_t usb__init(void) { usb_init(); return 0; } bool usb__is_configured(void) { return usb_configured(); } diff --git a/firmware/main.c b/firmware/main.c index 8ffd4583..e518a6fd 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -1,5 +1,5 @@ /* ---------------------------------------------------------------------------- - * Copyright (c) 2013 Ben Blazak + * Copyright (c) 2013, 2014 Ben Blazak * Released under The MIT License (see "doc/licenses/MIT.md") * Project located at * ------------------------------------------------------------------------- */ @@ -28,6 +28,9 @@ * * Notes: * - Cherry MX bounce time <= 5ms (at 16 in/sec actuation speed) (spec) + * - From experience (after having this issue brought to my attention by + * Thanatermesis), waiting longer can sometimes fix the issue of having some + * keys unexpectedly seem to double-tap. */ #ifndef OPT__DEBOUNCE_TIME #error "OPT__DEBOUNCE_TIME not defined" @@ -70,17 +73,25 @@ int main(void) { static uint8_t time_scan_started; - kb__init(); // initialize hardware (besides USB and timer) + { + // initialize everything; signal an error (at the end) if one occurs - kb__led__state__power_on(); + bool error; - usb__init(); - while (!usb__is_configured()); - kb__led__delay__usb_init(); // give the OS time to load drivers, etc. + error = kb__init(); // initialize hardware (besides USB and timer) - timer__init(); + kb__led__state__power_on(); - kb__led__state__ready(); + error |= usb__init(); + while (!usb__is_configured()); + kb__led__delay__usb_init(); // give the OS time to load drivers, etc. + + error |= timer__init(); + + kb__led__state__ready(); + + if (error) kb__led__delay__error(); + } time_scan_started // on the first iteration, scan immediately = timer__get_milliseconds() - OPT__DEBOUNCE_TIME; diff --git a/firmware/makefile b/firmware/makefile index a9144131..ffc4a7c4 100644 --- a/firmware/makefile +++ b/firmware/makefile @@ -165,7 +165,7 @@ clean-all-files: clean-object-files clean-target-files clean-object-files: -@rm -vf $(OBJ) \ - $(OBJ:%=%.dep) + $(OBJ:%=%.dep) clean-target-files: -@rm -vf $(TARGET).eep \ diff --git a/doc/references.md b/firmware/references.md similarity index 98% rename from doc/references.md rename to firmware/references.md index e0514b74..5b71e56b 100644 --- a/doc/references.md +++ b/firmware/references.md @@ -145,7 +145,11 @@ * [Why do we need C Unions?] (http://stackoverflow.com/questions/252552/why-do-we-need-c-unions) - Some good examples of what Unions are good for. + Some examples of what unions are good for. + +* [Using and Abusing Unions] + (http://critical.eschertech.com/2010/03/12/using-and-abusing-unions/) + A good discussion on when to use unions and when not to. * [C preprocessor, recursive macros] (http://stackoverflow.com/questions/5641836/c-preprocessor-recursive-macros) @@ -191,6 +195,11 @@ (http://stackoverflow.com/questions/6702161/pointer-comparisons-in-c-are-they-signed-or-unsigned) Pointer comparisons in C are interesting... Have to be careful with them. +* [Saturating Addition in C] + (http://stackoverflow.com/a/3431717) + Saturated addition is kind of difficult in C. This is my favorite of the + methods I saw. + ### C++ Stuff * [Google C++ Style Guide] @@ -723,7 +732,7 @@ ------------------------------------------------------------------------------- -Copyright © 2012, 2013 Ben Blazak +Copyright © 2012, 2013, 2014 Ben Blazak Released under The MIT License (see "doc/licenses/MIT.md") Project located at diff --git a/layout-gen/.gitignore b/layout-gen/.gitignore new file mode 100644 index 00000000..daf9c8fe --- /dev/null +++ b/layout-gen/.gitignore @@ -0,0 +1,3 @@ +build/ +cache/ + diff --git a/layout-gen/readme.md b/layout-gen/readme.md new file mode 100644 index 00000000..f66c800b --- /dev/null +++ b/layout-gen/readme.md @@ -0,0 +1,6 @@ +This might eventually become a layout generator :) . Until then, it's an +experiment. + +## TODO: +- update readme with useful information (once we've gotten anywhere) + diff --git a/layout-gen/references.md b/layout-gen/references.md new file mode 100644 index 00000000..b3878778 --- /dev/null +++ b/layout-gen/references.md @@ -0,0 +1,83 @@ +## Elm Stuff + +* [Official Documentation] + (http://elm-lang.org) + + * [Standard Libaries] + (http://library.elm-lang.org/catalog/elm-lang-Elm/0.12.3/) + * [Examples] + (http://elm-lang.org/Examples.elm) + * [Syntax: a Quick Tour] + (http://elm-lang.org/learn/Syntax.elm) + * [Types] + (http://elm-lang.org/learn/Getting-started-with-Types.elm) + * [Changes in Elm 0.10] + (http://elm-lang.org/blog/announce/0.10.elm) + +* [What does the “Just” syntax mean in Haskell?] + (http://stackoverflow.com/questions/18808258/what-does-the-just-syntax-mean-in-haskell/18809252#18809252) + Haskell is very similar to Elm in some ways, so I think this applies. + + + +## Web Stuff + +### JavaScript + +* [How to include a JavaScript file in another JavaScript file?] + (http://stackoverflow.com/questions/950087/how-to-include-a-javascript-file-in-another-javascript-file) + Not as simple as one might wish... But still not that bad. + +* [JavaScript 101] + (http://learn.jquery.com/javascript-101/) + JavaScript tutorial on the jQuery site :) + +* [How Good C# Habits can Encourage Bad JavaScript Habits: Part 1] + (http://appendto.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/) + Actually didn't read this all the way through... :) but good information + + * Referenced [on stackoverflow] + (http://stackoverflow.com/a/5947280) + +* [Set default value of javascript object attributes] + (http://stackoverflow.com/questions/6600868/set-default-value-of-javascript-object-attributes) + +* [A Plain English Guide to JavaScript Prototypes] + (http://sporto.github.io/blog/2013/02/22/a-plain-english-guide-to-javascript-prototypes/) + About the JavaScript object model. I guess objects can have defaults? + +* [Do You Really Need jQuery?] + (http://www.sitepoint.com/do-you-really-need-jquery/) + For this project I can probably get away without it... + + +#### Libraries + +* [Raphaël] + (http://raphaeljs.com) + Cool graphics library :) + + +## HTML + +* [W3C Documentation] + (http://www.w3schools.com/html/html5_intro.asp) + + +## CSS + +* [github: CSS file for the website of Ethan Schoonover] + (https://github.com/altercation/ethanschoonover.com/blob/master/resources/css/style.css) + Ethan Schoonover is the creator of the Solarized color palette. + + + + + + +------------------------------------------------------------------------------- + +Copyright © 2014 Ben Blazak +Released under The MIT License (see "doc/licenses/MIT.md") +Project located at + diff --git a/layout-gen/test-elm/Boards.elm b/layout-gen/test-elm/Boards.elm new file mode 100644 index 00000000..092fc94c --- /dev/null +++ b/layout-gen/test-elm/Boards.elm @@ -0,0 +1,214 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2014 Ben Blazak +-- Released under The MIT License (see "doc/licenses/MIT.md") +-- Project located at +------------------------------------------------------------------------------- + +{-| Keyboard definitions +-} + + +module Boards where + + +------------------------------------------------------------------------------- +-- values +------------------------------------------------------------------------------- + + +{-| A list of the keyboards defined +-} +all : [Board] +all = [ ergodox ] + + +------------------------------------------------------------------------------- +-- types and default values +------------------------------------------------------------------------------- + + +{-| A type for keys in our keyboard + +Fields: +- `name`: The name of the key (so we can reference it) +- `size`: The width of the key (in multiples of the standard 1x1 key, which + takes up 1 grid block) +- `position`: The position of the center of the key on the grid, with `(0,0)` + being the top left of the board, positive x extending right, and positive y + extending down +- `rotation`: The rotation of the key (in degrees) +- `value`: A name corresponding to the assigned keycode for this key +- `configuration`: The configuration of the board to which this key belongs + - 0: This key belongs to all configurations +-} +type Key = { name: String + , size: Float + , position: (Float,Float) + , rotation: Float + , value: String + , configuration: Int + } + +defaultKey : Key +defaultKey = { name = "" + , size = 1 + , position = (0,0) + , rotation = 0 + , value = "" + , configuration = 0 + } + + +{-| A type for keyboards (`Keyboard` is already used, in Elm) + +Fields: +- `name`: The display name +- `size`: The horizontal and vertical size of the board, in grid squares +- `keys`: A list of keys, making up the keyboard +-} +type Board = { name: String + , size: (Float,Float) + , keys: [Key] + } + + +------------------------------------------------------------------------------- +-- board definitions +------------------------------------------------------------------------------- + + +{-| Definition for the ErgoDox Keyboard + +From the firmware source: + + /* left hand, spatial positions */ + k50,k51,k52,k53,k54,k55,k56, + k40,k41,k42,k43,k44,k45,k46, + k30,k31,k32,k33,k34,k35, + k20,k21,k22,k23,k24,k25,k26, + k10,k11,k12,k13,k14, + k05,k06, + k15,k16,k04, + k03,k02,k01, + + /* right hand, spatial positions * + k57,k58,k59,k5A,k5B,k5C,k5D, + k47,k48,k49,k4A,k4B,k4C,k4D, + k38,k39,k3A,k3B,k3C,k3D, + k27,k28,k29,k2A,k2B,k2C,k2D, + k19,k1A,k1B,k1C,k1D, + k07,k08, + k09,k17,k18, + k0C,k0B,k0A ) +-} + + +ergodox : Board +ergodox = { name = "ErgoDox", size = (18,8), keys = [ + + -- left hand, roughly from upper left to bottom right + + { defaultKey | name <- "k50", position <- (0.25,0), size <- 1.5 } + , { defaultKey | name <- "k51", position <- (1.5,0) } + , { defaultKey | name <- "k52", position <- (2.5,0) } + , { defaultKey | name <- "k53", position <- (3.5,0) } + , { defaultKey | name <- "k54", position <- (4.5,0) } + , { defaultKey | name <- "k55", position <- (5.5,0) } + , { defaultKey | name <- "k56", position <- (6.5,0) } + + , { defaultKey | name <- "k40", position <- (0.25,1), size <- 1.5 } + , { defaultKey | name <- "k41", position <- (1.5,1) } + , { defaultKey | name <- "k42", position <- (2.5,1) } + , { defaultKey | name <- "k43", position <- (3.5,1) } + , { defaultKey | name <- "k44", position <- (4.5,1) } + , { defaultKey | name <- "k45", position <- (5.5,1) } + , { defaultKey | name <- "k46", position <- (6.5,1.25), size <- 1.5, rotation <- 90 } + + , { defaultKey | name <- "k30", position <- (0.25,2), size <- 1.5 } + , { defaultKey | name <- "k31", position <- (1.5,2) } + , { defaultKey | name <- "k32", position <- (2.5,2) } + , { defaultKey | name <- "k33", position <- (3.5,2) } + , { defaultKey | name <- "k34", position <- (4.5,2) } + , { defaultKey | name <- "k35", position <- (5.5,2) } + + , { defaultKey | name <- "k20", position <- (0.25,3), size <- 1.5 } + , { defaultKey | name <- "k21", position <- (1.5,3) } + , { defaultKey | name <- "k22", position <- (2.5,3) } + , { defaultKey | name <- "k23", position <- (3.5,3) } + , { defaultKey | name <- "k24", position <- (4.5,3) } + , { defaultKey | name <- "k25", position <- (5.5,3) } + , { defaultKey | name <- "k26", position <- (6.5,2.75), size <- 1.5, rotation <- 90 } + + , { defaultKey | name <- "k10", position <- (0.5,4) } + , { defaultKey | name <- "k11", position <- (1.5,4) } + , { defaultKey | name <- "k12", position <- (2.5,4) } + , { defaultKey | name <- "k13", position <- (3.5,4) } + , { defaultKey | name <- "k14", position <- (4.5,4) } + + , { defaultKey | name <- "k05", position <- (6.5,5) } + , { defaultKey | name <- "k06", position <- (7.5,5) } + + , { defaultKey | name <- "k15", position <- (5.5,6), configuration <- 2 } + , { defaultKey | name <- "k16", position <- (6.5,6), configuration <- 2 } + , { defaultKey | name <- "k04", position <- (7.5,6) } + + , { defaultKey | name <- "k03", position <- (5.5,6.5), size <- 2, rotation <- 90, configuration <- 1 } + , { defaultKey | name <- "k02", position <- (6.5,6.5), size <- 2, rotation <- 90, configuration <- 1 } + , { defaultKey | name <- "k03", position <- (5.5,7), configuration <- 2 } + , { defaultKey | name <- "k02", position <- (6.5,7), configuration <- 2 } + , { defaultKey | name <- "k01", position <- (7.5,7) } + + -- right hand, roughly from upper left to bottom right + + , { defaultKey | name <- "k57", position <- (10.5,0) } + , { defaultKey | name <- "k58", position <- (11.5,0) } + , { defaultKey | name <- "k59", position <- (12.5,0) } + , { defaultKey | name <- "k5A", position <- (13.5,0) } + , { defaultKey | name <- "k5B", position <- (14.5,0) } + , { defaultKey | name <- "k5C", position <- (15.5,0) } + , { defaultKey | name <- "k5D", position <- (16.75,0), size <- 1.5 } + + , { defaultKey | name <- "k47", position <- (10.5,1.25), size <- 1.5, rotation <- -90 } + , { defaultKey | name <- "k48", position <- (11.5,1) } + , { defaultKey | name <- "k49", position <- (12.5,1) } + , { defaultKey | name <- "k4A", position <- (13.5,1) } + , { defaultKey | name <- "k4B", position <- (14.5,1) } + , { defaultKey | name <- "k4C", position <- (15.5,1) } + , { defaultKey | name <- "k4D", position <- (16.75,1), size <- 1.5 } + + , { defaultKey | name <- "k38", position <- (11.5,2) } + , { defaultKey | name <- "k39", position <- (12.5,2) } + , { defaultKey | name <- "k3A", position <- (13.5,2) } + , { defaultKey | name <- "k3B", position <- (14.5,2) } + , { defaultKey | name <- "k3C", position <- (15.5,2) } + , { defaultKey | name <- "k3D", position <- (16.75,2), size <- 1.5 } + + , { defaultKey | name <- "k27", position <- (10.5,2.75), size <- 1.5, rotation <- -90 } + , { defaultKey | name <- "k28", position <- (11.5,3) } + , { defaultKey | name <- "k29", position <- (12.5,3) } + , { defaultKey | name <- "k2A", position <- (13.5,3) } + , { defaultKey | name <- "k2B", position <- (14.5,3) } + , { defaultKey | name <- "k2C", position <- (15.5,3) } + , { defaultKey | name <- "k2D", position <- (16.75,3), size <- 1.5 } + + , { defaultKey | name <- "k19", position <- (12.5,4) } + , { defaultKey | name <- "k1A", position <- (13.5,4) } + , { defaultKey | name <- "k1B", position <- (14.5,4) } + , { defaultKey | name <- "k1C", position <- (15.5,4) } + , { defaultKey | name <- "k1D", position <- (16.5,4) } + + , { defaultKey | name <- "k07", position <- (9.5,5) } + , { defaultKey | name <- "k08", position <- (10.5,5) } + + , { defaultKey | name <- "k09", position <- (9.5,6) } + , { defaultKey | name <- "k17", position <- (10.5,6), configuration <- 2 } + , { defaultKey | name <- "k18", position <- (11.5,6), configuration <- 2 } + + , { defaultKey | name <- "k0C", position <- (9.5,7) } + , { defaultKey | name <- "k0B", position <- (10.5,6.5), size <- 2, rotation <- -90, configuration <- 1 } + , { defaultKey | name <- "k0A", position <- (11.5,6.5), size <- 2, rotation <- -90, configuration <- 1 } + , { defaultKey | name <- "k0B", position <- (10.5,7), configuration <- 2 } + , { defaultKey | name <- "k0A", position <- (11.5,7), configuration <- 2 } + + ]} + diff --git a/layout-gen/test-elm/main.elm b/layout-gen/test-elm/main.elm new file mode 100644 index 00000000..0533e06d --- /dev/null +++ b/layout-gen/test-elm/main.elm @@ -0,0 +1,123 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2014 Ben Blazak +-- Released under The MIT License (see "doc/licenses/MIT.md") +-- Project located at +------------------------------------------------------------------------------- + +{-| A web-based UI for generating keyboard layouts for the firmware +-} + + +import Window +import Dict + +import Boards + + +------------------------------------------------------------------------------- +-- values and signals +------------------------------------------------------------------------------- + +{-| The number of columns and rows in our grid + +Notes: +- The list gives the aspect ratio (of width to height) +- The number gives the scale of the aspect ratio, relative to the window size +-} +[columns, rows] = map ((*) 5) [4,3] + + +{-| The edge length of a square in our grid +-} +edgeSignal : Signal Float +edgeSignal = + let function = \(width,height) -> min (toFloat width / columns) + (toFloat height / rows) + in lift function Window.dimensions + + +------------------------------------------------------------------------------- +-- groups of forms +------------------------------------------------------------------------------- + +background : (Int, Int) -> Float -> [Form] +background (width,height) edge = + let (w,h) = (toFloat width, toFloat height) + + ruleLines = True + lineStyle = dashed lightBlue + ruleLineStyle = dotted charcoal + + -- x and y represent the horizontal and vertical scaling factors + line (x,y) style offset = + segment (-x*w/2,-y*h/2) (x*w/2,y*h/2) + |> traced style + |> move (y*offset*edge, x*offset*edge) + + lines (x,y) = + let dimension = if x == 0 then w else h + in map (\n -> line (x,y) lineStyle (toFloat n/2) + |> alpha (1 / (toFloat (mod (n+1) 2 + 1) * 3))) + [floor (-dimension/edge)*2 .. floor (dimension/edge)*2] + + vLines = lines (0,1) + hLines = lines (1,0) + + rLines = + let boxLine (x,y) offset = line (x,y) ruleLineStyle offset + in [ boxLine (0,1) (-columns/2), boxLine (1,0) (-rows/2) +-- , boxLine (0,1) 0, boxLine (1,0) 0 + , boxLine (0,1) (columns/2), boxLine (1,0) (rows/2) + ] + + in hLines ++ vLines ++ if ruleLines then rLines else [] + + +keyboardTestDraw : Float -> [Form] +keyboardTestDraw edge = + let lineStyle = + let temp = solid lightBlue + in { temp | width <- 5, cap <- Round, join <- Smooth } + + -- grab just 1 Board, and use only 1 configuration + keyboard = head <| filter (\n -> n.name == "ErgoDox") Boards.all + configuration = 1 + + -- s = scaling factor (from 1 square of the grid) + -- p = position of the center of the rectangle on the grid + formKey k = + let (sizeX, sizeY) = (k.size*edge, edge) + (posX, posY) = ((fst k.position)*edge, (snd k.position)*edge) + in map (move (posX, posY) . rotate (degrees k.rotation)) + [ rect sizeX sizeY |> outlined lineStyle + , toForm <| plainText k.name + ] + + -- convert positions to be relative to center of the page + keys = + let convertPosition (x,y) = ( x - (fst keyboard.size) / 2, + -y + (snd keyboard.size) / 2 ) + in map (\n -> { n | position <- convertPosition n.position }) + keyboard.keys + |> filter ( \n -> n.configuration == 0 + || n.configuration == configuration ) + + in foldl1 (++) (map formKey keys) + + +------------------------------------------------------------------------------- +-- putting it all together +------------------------------------------------------------------------------- + +scene : (Int, Int) -> Float -> Element +scene (width,height) edge = + let backgroundColor = lightGray + in layers [ collage width height [] |> color backgroundColor + , collage width height (background (width,height) edge) + , collage width height (keyboardTestDraw edge) + ] + + +main : Signal Element +main = scene <~ Window.dimensions ~ edgeSignal + diff --git a/layout-gen/test-js/index.html b/layout-gen/test-js/index.html new file mode 100644 index 00000000..7ec95d83 --- /dev/null +++ b/layout-gen/test-js/index.html @@ -0,0 +1,16 @@ + + + + + +layout-gen + + + + + + + + + + diff --git a/layout-gen/test-js/main.js b/layout-gen/test-js/main.js new file mode 100644 index 00000000..18976f7f --- /dev/null +++ b/layout-gen/test-js/main.js @@ -0,0 +1,362 @@ +// ---------------------------------------------------------------------------- +// colors +// ---------------------------------------------------------------------------- + +/* + * Notes: + * - From + * + * Usage: + * ``` + * light dark + * --------------------------- --------------------------- + * base03 ........................... background ................ + * base02 ........................... background highlights ..... + * base01 optional emphasized content comments .................. + * base00 body text ................. ........................... + * base0 ........................... body text ................. + * base1 comments .................. optional emphasized content + * base2 background highlights ..... ........................... + * base3 background ................ ........................... + * ``` + */ +var solarized = { + base03 : '#002b36', + base02 : '#073642', + base01 : '#586e75', + base00 : '#657b83', + base0 : '#839496', + base1 : '#93a1a1', + base2 : '#eee8d5', + base3 : '#fdf6e3', + yellow : '#b58900', + orange : '#cb4b16', + red : '#dc322f', + magenta : '#d33682', + violet : '#6c71c4', + blue : '#268bd2', + cyan : '#2aa198', + green : '#859900', +}; + + +/* + * Notes: + * - Names taken from Elm: + * + * - Real names from Tango: + * + * ``` + * yellow : butter + * orange : orange + * brown : chocolate + * green : chameleon + * blue : sky blue + * purple : plum + * red : scarlet red + * gray : aluminum (light) + * charcoal : aluminum (dark) + * ``` + */ +var tango = { + lightYellow : '#fce94f', yellow : '#edd400', darkYellow : '#c4a000', + lightOrange : '#fcaf3e', orange : '#f57900', darkOrange : '#ce5c00', + lightBrown : '#e9b96e', brown : '#c17d11', darkBrown : '#8f5902', + lightGreen : '#8ae234', green : '#73d216', darkGreen : '#4e9a06', + lightBlue : '#729fcf', blue : '#3465a4', darkBlue : '#204a87', + lightPurple : '#ad7fa8', purple : '#75507b', darkPurple : '#5c3566', + lightRed : '#ef2929', red : '#cc0000', darkRed : '#a40000', + lightGray : '#eeeeec', gray : '#d3d7cf', darkGray : '#babdb6', + lightCharcoal : '#888a85', charcoal : '#555753', darkCharcoal : '#2e3436', +}; + + + +// ---------------------------------------------------------------------------- +// keyboards +// ---------------------------------------------------------------------------- + + +/* + * Usage: + * ``` + * var keyboards = { + * id: { // keyboard name + * size: [1,1], // the number of 1x keys it would take to cover the board + * keys: { + * id: { // configuration name + * id: { // key name + * size: 1, // 1 = 1x key, 2 = 2x key, etc. + * position: [0,0], // of the top left of the key + * rotation: 0, // in degrees + * value: 'transp', // name of the assigned key-function + * }, + * // ... more keys + * }, + * // ... more configurations + * // - `all`: keys belong to all configurations + * // - [other]: keys belong to the named configuration; at least one + * // other configuration must be specified; a configuration may be + * // empty (then only the `all` keys are in that configuration) + * }, + * }, + * // ... more keyboards + * }; + * ``` + */ +var keyboards = {}; + + +/* + * Notes: + * - From the firmware source: + * ``` + * // left hand, spatial positions + * k50,k51,k52,k53,k54,k55,k56, + * k40,k41,k42,k43,k44,k45,k46, + * k30,k31,k32,k33,k34,k35, + * k20,k21,k22,k23,k24,k25,k26, + * k10,k11,k12,k13,k14, + * k05,k06, + * k15,k16,k04, + * k03,k02,k01, + * + * // right hand, spatial positions + * k57,k58,k59,k5A,k5B,k5C,k5D, + * k47,k48,k49,k4A,k4B,k4C,k4D, + * k38,k39,k3A,k3B,k3C,k3D, + * k27,k28,k29,k2A,k2B,k2C,k2D, + * k19,k1A,k1B,k1C,k1D, + * k07,k08, + * k09,k17,k18, + * k0C,k0B,k0A ) + * ``` + */ +keyboards['ErgoDox'] = { + size: [18,8], + keys: { + all: { + + // left hand, roughly from upper left to bottom right + + k50: { position: [0,0], size: 1.5 }, + k51: { position: [1.5,0] }, + k52: { position: [2.5,0] }, + k53: { position: [3.5,0] }, + k54: { position: [4.5,0] }, + k55: { position: [5.5,0] }, + k56: { position: [6.5,0] }, + + k40: { position: [0,1], size: 1.5 }, + k41: { position: [1.5,1] }, + k42: { position: [2.5,1] }, + k43: { position: [3.5,1] }, + k44: { position: [4.5,1] }, + k45: { position: [5.5,1] }, + k46: { position: [6.25,1.25], size: 1.5, rotation: -90 }, + + k30: { position: [0,2], size: 1.5 }, + k31: { position: [1.5,2] }, + k32: { position: [2.5,2] }, + k33: { position: [3.5,2] }, + k34: { position: [4.5,2] }, + k35: { position: [5.5,2] }, + + k20: { position: [0,3], size: 1.5 }, + k21: { position: [1.5,3] }, + k22: { position: [2.5,3] }, + k23: { position: [3.5,3] }, + k24: { position: [4.5,3] }, + k25: { position: [5.5,3] }, + k26: { position: [6.25,2.75], size: 1.5, rotation: -90 }, + + k10: { position: [0.5,4] }, + k11: { position: [1.5,4] }, + k12: { position: [2.5,4] }, + k13: { position: [3.5,4] }, + k14: { position: [4.5,4] }, + + k05: { position: [6.5,5] }, + k06: { position: [7.5,5] }, + + k04: { position: [7.5,6] }, + + k01: { position: [7.5,7] }, + + // right hand, roughly from upper left to bottom right + + k57: { position: [10.5,0] }, + k58: { position: [11.5,0] }, + k59: { position: [12.5,0] }, + k5A: { position: [13.5,0] }, + k5B: { position: [14.5,0] }, + k5C: { position: [15.5,0] }, + k5D: { position: [16.5,0], size: 1.5 }, + + k47: { position: [10.25,1.25], size: 1.5, rotation: 90 }, + k48: { position: [11.5,1] }, + k49: { position: [12.5,1] }, + k4A: { position: [13.5,1] }, + k4B: { position: [14.5,1] }, + k4C: { position: [15.5,1] }, + k4D: { position: [16.5,1], size: 1.5 }, + + k38: { position: [11.5,2] }, + k39: { position: [12.5,2] }, + k3A: { position: [13.5,2] }, + k3B: { position: [14.5,2] }, + k3C: { position: [15.5,2] }, + k3D: { position: [16.5,2], size: 1.5 }, + + k27: { position: [10.25,2.75], size: 1.5, rotation: 90 }, + k28: { position: [11.5,3] }, + k29: { position: [12.5,3] }, + k2A: { position: [13.5,3] }, + k2B: { position: [14.5,3] }, + k2C: { position: [15.5,3] }, + k2D: { position: [16.5,3], size: 1.5 }, + + k19: { position: [12.5,4] }, + k1A: { position: [13.5,4] }, + k1B: { position: [14.5,4] }, + k1C: { position: [15.5,4] }, + k1D: { position: [16.5,4] }, + + k07: { position: [9.5,5] }, + k08: { position: [10.5,5] }, + + k09: { position: [9.5,6] }, + + k0C: { position: [9.5,7] }, + }, + + 'Long Thumbs': { + k03: { position: [5,6.5], size: 2, rotation: -90 }, + k02: { position: [6,6.5], size: 2, rotation: -90 }, + + k0B: { position: [10,6.5], size: 2, rotation: 90 }, + k0A: { position: [11,6.5], size: 2, rotation: 90 }, + }, + + 'Split Thumbs': { + k15: { position: [5.5,6] }, + k16: { position: [6.5,6] }, + + k03: { position: [5.5,7] }, + k02: { position: [6.5,7] }, + + k17: { position: [10.5,6] }, + k18: { position: [11.5,6] }, + + k0B: { position: [10.5,7] }, + k0A: { position: [11.5,7] }, + }, + + }, +}; + + + +// ---------------------------------------------------------------------------- +// test: functions and event bindings +// ---------------------------------------------------------------------------- + +// TODO: +// - need to actually plan the organization of the key data... +// - how shall we handle switching between different layers? +// - we should probably do all the styling in css... + +var drawKey = function(key) { + var size = key.size || 1; + var position = key.position || [0,0]; + var rotation = key.rotation || 0; + + key.raphael = {}; + + key.raphael.rect = + paper.rect( position[0]*keysize, + position[1]*keysize, + size*keysize, + keysize ) + .attr({ 'fill': tango.lightBlue, + 'stroke': tango.lightGray }) + .transform('r'+rotation); + + key.raphael.text = + paper.text( position[0]*keysize + size*keysize/2, + position[1]*keysize + keysize/2, + key.value ) + .attr({ 'font-family': 'monospace' }) + .transform( 'r' + rotation + + 's' + Math.min( size*8/key.value.length, 8/3) ); + + key.raphael.button = + paper.rect( position[0]*keysize, + position[1]*keysize, + size*keysize, + keysize ) + .attr({ 'fill': '#fff', + 'stroke': 'none', + 'opacity': 0 }) + .transform('r'+rotation) + .mouseover(function() { + key.raphael.button.attr({ 'opacity': .1 }); + }) + .mouseout(function() { + key.raphael.button.attr({ 'opacity': 0 }); + }) + .mouseup(function() { + key.raphael.rect.attr({ 'fill': tango.lightOrange }); + }); +} + +var updateKey = function(key) { + var size = key.size || 1; + var rotation = key.rotation || 0; + + key.raphael.text + .attr({'text': key.value}) + .transform( 'r' + rotation + + 's' + Math.min( size*8/key.value.length, 8/3) ); +} + + +// main() (kind of) ----------------------------------------------------------- + +window.onload = function() { + // style + document.body.style.background = tango.lightGray; + + // setup + keyboard = keyboards['ErgoDox']; + configuration = 'Long Thumbs'; + + keysize = 990/keyboard['size'][0]; + + paper = Raphael( 0, 0, keyboard.size[0]*keysize+10, + keyboard.size[1]*keysize+10 ); + + // draw bounding box + paper.rect( 0, 0, + keyboard.size[0]*keysize+10, + keyboard.size[1]*keysize+10, + 5 ) + .attr('stroke', tango.lightBlue); + + // draw keys + for (var id in keyboard.keys[configuration]) { + var key = keyboard.keys[configuration][id]; + key.position[0] += 5/keysize; + key.position[1] += 5/keysize; + key.value = id; + drawKey(key); + } + for (var id in keyboard.keys.all) { + var key = keyboard.keys['all'][id]; + key.position[0] += 5/keysize; + key.position[1] += 5/keysize; + key.value = id; + drawKey(key); + } +} + diff --git a/readme.md b/readme.md index 023fbee3..768c0d62 100644 --- a/readme.md +++ b/readme.md @@ -28,9 +28,8 @@ Notes: (written by "hasu") to the ErgoDox! Status: -* School is starting again late August 2013. It looks to be a busy semester, - so I may not have that much time to work on it. I wasn't able to get nearly - the amount done this summer that I had hoped, other things taking precedence, - but I still plan to finish everything I've already talked about: macro - recording, (attempt) USB features, layout examples, and other documentation. +* (updated 2014-04-12) School and chores and life in general still taking most + of my time. I still plan to finish everything I've already talked about + though: macro recording, (attempt) USB features, layout examples, and other + documentation.