Add EVEN MORE comments.

The code:comment ratio in menelaus.scm is now 1:1.
This commit is contained in:
Phil Hagelberg 2020-03-20 19:38:34 -07:00
parent 382d557dda
commit 317dd9a83d
5 changed files with 126 additions and 30 deletions

View file

@ -32,4 +32,12 @@ usb_keyboard.s: usb_keyboard.h usb_keyboard.c
avr-gcc -std=gnu99 -S -D F_CPU=$(F_CPU)UL -mmcu=$(MCU) -c \
-o usb_keyboard.s usb_keyboard.c
.PHONY: build upload test clean count
udev: /etc/udev/rules.d/a-star.rules
/etc/udev/rules.d/a-star.rules:
echo "SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1ffb\", \
ATTRS{idProduct}==\"0101\", ENV{ID_MM_DEVICE_IGNORE}=\"1\"" > $@
echo "SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1ffb\", \
ATTRS{idProduct}==\"2300\", ENV{ID_MM_DEVICE_IGNORE}=\"1\"" >> $@
.PHONY: build upload test clean count udev

View file

@ -36,7 +36,12 @@ can be invoked by a key combo and sometimes a hard reset is
necessary. On the A-star Micro used in the Atreus kits, this is done
by shorting GND and RST twice in under a second, which causes the
onboard LED to pulse. The Keyboardio Atreus has a reset button you can
press with a pin to the bottom of the board.
press with a pin to the bottom of the board. On linux-based systems
you can monitor for the bootloader activation using `sudo dmesg --follow`.
Some linux-based systems will need a udev rule to grant permissions to
the USB device for uploading firmware. If you get permission denied on
`/dev/ttyACM0` or whatever it is, try running `sudo make udev`.
## Known bugs
@ -72,7 +77,8 @@ into Racket and simulates the GPIO functions with a test harness:
Copyright © 2014-2020 Phil Hagelberg and contributors
Released under the [GNU GPL version 3](https://www.gnu.org/licenses/gpl.html).
Released under the [GNU GPL version 3](https://www.gnu.org/licenses/gpl.html)
or any later version.
Uses [PJRC USB Keyboard library](http://www.pjrc.com/teensy/usb_keyboard.html)
which is Copyright © 2009 PJRC.COM, LLC and released under the MIT/X11 license.

View file

@ -97,7 +97,7 @@
(define (combo modifier keycode) (list (car modifier) keycode))
(define (uncombo keycode) (and (= 2 (length keycode)) (car (cdr keycode))))
;; we're treating these a little differently; they are not literal USB values
;; We're treating these a little differently; they are not literal USB values.
(define mod-ctrl (modify 1))
(define mod-shift (modify 2))
(define mod-alt (modify 3))

View file

@ -21,7 +21,7 @@
;; (sft key-5)
key-backtick)
;;;; layers
;;;; Layers
;; NB: the middle keys (ctrl and alt on the 42-key, also ~ and \ on the 44-key
;; variant) are physically in two separate columns, but electrically they are
@ -53,6 +53,7 @@
key-n key-m key-comma key-period key-slash
key-esc key-tab mod-super mod-shift key-backspace mod-alt
;; fn takes us to fn-layer below while it is held down
key-space fn key-quote key-left-bracket key-enter))
(define fn-layer
@ -65,6 +66,7 @@
key-dash key-equal (sft key-3) (sft key-dash) (sft key-equal) mod-ctrl
(sft key-8) key-1 key-2 key-3 (sft key-right-bracket)
;; set-layer 2 takes us to l2-layer below; doesn't need to be held
(set-layer 2) key-insert mod-super mod-shift key-backspace mod-alt
key-space fn key-e key-0 key-right-bracket))
@ -75,10 +77,13 @@
key-delete key-left key-down key-right key-page-down 0
key-down key-f4 key-f5 key-f6 key-f11
(set-layer 0) key-vol-up 0 0 reset mod-ctrl
;; the B key enters the bootloader
0 key-vol-up 0 0 reset mod-ctrl
;; the N key switches to hardware dvorak mode
(set-layer 4) key-f1 key-f2 key-f3 key-f12
0 key-vol-down mod-super mod-shift key-backspace mod-alt
;; tapping the fn key brings us back to the base layer
key-space (set-layer 0) key-printscreen key-scroll-lock key-pause))
(define hard-dvorak-layer
@ -109,3 +114,4 @@
(set! layers (vector base-layer fn-layer l2-layer
hard-dvorak-layer hard-dvorak-fn-layer))
(set! current-layer (vector-ref layers 0))

View file

@ -1,4 +1,18 @@
;;; menelaus.scm - a USB keyboard firmware for the Atreus.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; menelaus.scm
;; a USB keyboard firmware for the Atreus.
;; Copyright © 2014-2020 Phil Hagelberg
;; Released under the GNU General Public License version 3 or any later
;; version.
;; The point of a keyboard firmware is to translate physical key presses on
;; switches into USB keycodes that get sent to the host. This process takes
;; several phases:
;; Matrix scan -> Debounce -> Track press/release -> Layout lookup -> Send USB
;; Each phase is described in more detail below.
;; Note that there are a few unusual style choices made here because
;; it is written in a shared subset of Microscheme and Racket so that it
@ -7,9 +21,11 @@
;; For one example, we use `and' where `when' would be more idiomatic. We
;; are also missing the `cond' form.
;; In general when you see an -aux function, it is an internal function which
;; recursively steps thru a vector/list with the initial arguments calculated
;; by its non-aux equivalent.
;; When you see an -aux function, it is an internal function which recursively
;; steps thru a vector/list with the initial arguments calculated by its
;; non-aux equivalent. The -aux function is never called directly.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(include "keycodes.scm")
(include "layout.scm")
@ -33,7 +49,7 @@
;; isn't yet part of Microscheme:
;; https://github.com/ryansuchocki/microscheme/issues/32
;;;;;;;;;;;;;;;;;;; Utility
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Utility functions
(define (find-aux v x n max)
(let ((y (vector-ref v n)))
@ -61,14 +77,19 @@
;; Return a copy of lst with all elements equal to v removed.
(define (remove-all v lst) (remove-aux v lst (list) #t))
;;;;;;;;;;;;;;;;;;; The Matrix
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Matrix Scan
;; A scan is defined as a list containing the key positions which are currently
;; This phase is responsible for determining the current state of the key
;; matrix; that is, which keys are reading as down for a given instant.
;; It returns a scan list containing the key positions which are currently
;; pressed for a given pass thru the key matrix. We specifically do not attempt
;; to look up what the keys are mapped to yet; we have to do that later on after
;; identifying presses and releases, otherwise we run into layer-switching bugs.
;; Each element in the list is an integer representation of the key in question.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Which key in a layout vector is represented by the given row and column?
(define (offset-for row col)
(+ col (* row (length columns))))
@ -106,7 +127,10 @@
(scan-matrix (scan-column scan (car rows-left) columns)
(cdr rows-left)))))
;;;;;;;;;;;;;;;;;;; Debouncing
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Debounce
;; This phase is responsible for filtering out spurious keypresses detected
;; by the matrix scan due to physical properties of switching logic.
;; Electrical contacts do not switch cleanly from high to low voltage; there is
;; a short period of "bounce" while the signal settles into its new position.
@ -114,6 +138,8 @@
;; only considering the data we get trustworthy if we get the same value three
;; times in a row.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define debounce-passes 3)
(define (debounce-matrix-aux last-scan passes-left)
@ -127,7 +153,11 @@
(define (debounce-matrix)
(debounce-matrix-aux (list) debounce-passes))
;;;;;;;;;;;;;;;;;;; Press and release tracking
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Track press/release
;; This phase is responsible for comparing the current state of the keys to
;; the previous pass and interpreting which keys are newly pressed and which are
;; newly released.
;; If we didn't have layers, we'd be done now. But since we have layers, we
;; can't assume a 1:1 mapping between keys pressed and keycodes we should send.
@ -145,6 +175,8 @@
;; Because of this, it's necessary to track press and release on the level of
;; physical keys and only map it to keycodes when a new press is detected.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Which physical keys were pressed during the last scan?
(define last-keys-down (vector #f #f #f #f #f #f #f #f #f #f))
@ -182,11 +214,45 @@
(for-each remove-last-down (cdr p/r))
p/r))
;;;;;;;;;;;;;;;;;;; Generating Keycodes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Layout lookup
;; Given keys that have been pressed, turn those into keycodes for our USB
;; frame. Given keys that are released, update the press/release tracking
;; data to reflect them.
;; This phase is responsible for taking keys that have been pressed and turning
;; those into keycodes for our USB frame, and also for taking the keys that
;; have been released and removing from the USB frame and press/release
;; tracking data.
;; In order to release keys consistently across layer changes, it's necessary
;; to store which physical keys are responsible for which keycodes being sent.
;; A key being released means that we stop sending the keycode that was bound
;; to that key when it was pressed, not the keycode bound to that key in the
;; current layer!
;; Data is stored in two vectors: modifiers and keycodes-down. Modifiers is of
;; length 4 because there are only 4 modifiers; (we ignore that left-shift
;; and right-shift can be distinguished). The keycodes-down vector is of length
;; 6 because that is defined in the USB standard as the number of non-modifier
;; keycodes that a single USB frame can represent.
;; If you have more than ten fingers, I'm sorry; try a different firmware.
;; The layout is defined in layout.scm as a vector of layer vectors. Each layer
;; vector is simply a vector of elements, arranged one row after another
;; corresponding to the physical keys.
;; Most of these elements are integers; these correspond to normal USB keycodes
;; as defined in keycodes.scm.
;; Some elements are lists; these indicate modifier keys. A list of length 1 is
;; simply a single modifier key, while a list of length 2 is a modifier plus a
;; non-modifier simultaneously. This is how we can define a ! key despite there
;; being no USB keycode for the ! character; it is defined as a combo of shift
;; and 1.
;; Finally some elements are Scheme procedures (aka functions). These procedures
;; get called with #t when they are first pressed and with #f when released.
;; These are mostly used for layer switching but could be used for anything.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Vectors to store keycodes for the USB frame we are preparing to send.
(define modifiers (vector 0 0 0 0))
@ -254,18 +320,14 @@
(release-modifier modifier-slot key 0)
#f)))))
;;;;;;;;;;;;;;;;;;; SHOWTIME
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Send USB
;; Prepare the GPIO pins and initialize the USB connection.
(define (init)
(set! current-layer (vector-ref layers 0))
(for-each-vector output row-pins)
(for-each-vector high row-pins)
(for-each-vector input column-pins)
(for-each-vector high column-pins) ; activate pullup resistors
;; This phase is responsible for the initialization, the main loop, and
;; actually sending the USB frame to the host once it has been calculated.
(call-c-func "usb_init")
(pause 200))
;; Not much left to do here; just tying up loose ends bringing it all together.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Take press/release data and set USB keycodes and modifiers.
(define (set-usb-frame press/release)
@ -283,10 +345,24 @@
;; Scan the matrix, determine the appropriate keycodes, and send them.
(define (loop)
;; Microscheme doesn't have garbage collection; it has you preallocate
;; everything you can, and then run your code that might allocate more memory
;; inside this `free!' macro. When you enter this macro, the heap pointer gets
;; saved, and when you leave, it gets set back to the point it was previously,
;; effectively garbage-collecting any allocations which happened inside the
;; macro in one fell swoop. Primitive, but effective.
(free! (let ((keys-scanned (debounce-matrix)))
(set-usb-frame (press/release-for keys-scanned))
(apply usb-send (cons modifiers (vector->list keycodes-down)))))
(loop))
(init)
;; Prepare the GPIO pins.
(for-each-vector output row-pins)
(for-each-vector high row-pins)
(for-each-vector input column-pins)
(for-each-vector high column-pins) ; activate pullup resistors
;; Initialize the USB connection and go!
(call-c-func "usb_init")
(pause 200)
(loop)