~laumann/teensy-keyboard

Firmware sources for Teensy 2.0 powered Dactyl keyboard
loader: remove unneccesary parenthesis
loader: minor changes to usbopen()

refs

master
browse  log 

clone

read-only
https://git.sr.ht/~laumann/teensy-keyboard
read/write
git@git.sr.ht:~laumann/teensy-keyboard

You can also use your local clone with git send-email.

#Dactyl keyboard with Teensy 2.0

This repository holds the sources for a Dactyl-shaped keyboard firmware, running on Teensy 2.0.

#Getting started

Have a look at https://www.pjrc.com/teensy/first_use.html.

#Gentoo tooling

On Gentoo, we have crossdev to help us out. To get an AVR cross-compiler toolchain, it's as simple as:

$ crossdev -t avr

It also turns out we need dev-libs/libusb-compat (for <usb.h>).

$ emerge dev-libs/libusb-compat

#Teensy loader CLI

There is a graphical utility for flashing the Teensy, but I much prefer a command-line tool, and luckily there is one we can just use: https://github.com/PaulStoffregen/teensy_loader_cli.

Building the tool requires the <usb.h> header, but that should be all that's needed to run make successfully.

To flash the sample slow blinking program on the Teensy 2.0, plug it in, press the push button so the light stops blinking, then run:

$ ./teensy_loader_cli --mcu=TEENSY2 -v -w blink_slow_Teensy2.hex

That should be all there is to it.

#Blinky

The blinky program on the https://www.pjrc.com/teensy/gcc.html page (it's provided as a zip file) provides a larger program that blinks morse code.

With avr-gcc version 13, the blinky sources do not compile out of the box. In usb_debug_only.c there is a collection of static global variables that are meant to go in read-only memory (by the __attribute__((progmem)) attribute). The error looks something like this:

error: variable ‘xyz’ must be const in order to be put into read-only section by means of ‘__attribute__((progmem))’

Adding const to these variables lets the program compile. The default settings in the Makefile target Teensy 2.0 so no further changes are necessary.

To flash blinky.hex onto our Teensy 2.0 do:

$ teensy_loader_cli --mcu=TEENSY2 -w -v blinky.hex

And off we go!

#Why not QMK (or TMK)?

There's QMK and TMK firmware libraries to make firmware programming super-easy. QMK even has a keyboards/handwired/dactyl folder that can be used as a handy reference.

Why not use it? First of all, QMK seems big. It's a driver for making managing keyboard firmware (including layout) as painless as possible, but I want to experience the pain.

During setup, QMK requests a lot of additional tools that aren't really needed, and suggests downgrading avr-gcc from version 13 to 8.

If I was in the business of making lots of keyboards with many different boards I would probably consider it. But I'm not. And I want to get into the weeds of programming the firmware, including setting up the compilation and flashing pipeline.

#Teensy 2.0 programming

The Teensy 2.0 board comes with this diagram:

The thing to understand about programming the board is that each pin is a member of a port. Each port can be viewed as a unsigned 8-bit number. Each port has three registers: PINx, DDRx, and PORTx, where x ∈ {B,C,D,E,F} is the port letter. The PINx register allows reading a specific pin (so it cannot be assigned to), the DDRx register is used to configure the direction (0=Input, 1=Ouput), and PORTx either sets the output low (0) or high (1) (when DDRx = 1) or configures the input (when DDRx = 0, 0=Normal, 1=Pullup Resistor)

Here is the full list of all registers on Teensy 2.0:

Port PIN DDR PORT Comment
B 0x3 0x4 0x5  
C 0x6 0x7 0x8 Only C6, C7
D 0x9 0xA 0xB  
E 0xC 0xD 0xE Only E2, E6
F 0xF 0x10 0x11  

The LED is controlled on pin D6. From the blinky sources (and the documentation), we can control the LED by configuring D6 as an output pin, and set it high.

#include <avr/io.h>
DDRD |= (1<<6);  // configure as output
PORTD &= (1<<6); // set high

DDRD and PORTD both expand to use the macro _SFR_IO8(). With -mmcu=atmega32u4 the above expands to:

(*(volatile uint8_t *)((0xA + __SFR_OFFSET)) |= (1<<6);
(*(volatile uint8_t *)((0xB + __SFR_OFFSET)) |= (1<<6);

We can think of the registers as memory locations that can be read or written to (simply by casting to volatile uint8_t * and dereferencing).

The __SFR_OFFSET is either 0x00 or 0x20 - for us it will always be 0x20.

Reference: https://www.pjrc.com/teensy/pins.html

#Intel HEX

https://www.pjrc.com/tech/8051/pm2_docs/intel-hex.html