Firmware, TOC

main
Tom Smalley 2017-02-09 12:00:43 +00:00
parent 36594d6257
commit a45b40ebf1
2 changed files with 269 additions and 14 deletions

283
README.md
View File

@ -1,9 +1,41 @@
# Guide for designing a custom Topre keyboard
This is an incomplete guide for building a custom Topre keyboard: specifically
the PCB, plate, and firmware. It is an accumulation of information gained
through these projects:
[designing a custom Topre board](https://deskthority.net/workshop-f7/designing-a-custom-topre-board-t11734.html),
[Split HHKB - Realforce/TypeHeaven mod](https://deskthority.net/workshop-f7/another-custom-split-hand-topre-board-need-your-input-t14769.html).
## Circuitry
If anything is unclear or needs adding, let me know.
### Overview
Thanks to [hasu's research](https://github.com/tmk/tmk_keyboard/tree/master/keyboard/hhkb/doc)
for starting this all off.
**Table of contents**
1. [Circuitry](#circuitry)
1. [Basic schematic](#basic-schematic)
2. [Drain pin](#drain-pin)
3. [Practical considerations](#practical-considerations)
1. [Parasitic capacitance](#parasitic-capacitance)
2. [Other notes](#other-notes)
4. KiCad files
2. [Hardware (case/plate)](#hardware-caseplate)
3. [Firmware](#firmware)
1. [Basic read procedure](#basic-read-procedure)
2. [Normalisation](#normalisation)
3. [Calibration](#calibration)
1. [Overview](#overview)
2. [Calibration procedure](#calibration-procedure)
3. [A warning](#a-warning)
4. [Handling the depths](#handling-the-depths)
1. [Digital conversion](#digital-conversion)
2. [Analog](#analog)
5. [Example](#example)
# Circuitry
## Basic schematic
The method I use to sense key depression is rather simple. In tests that I
have done it works well provided some calibration is performed in the firmware
@ -20,9 +52,7 @@ following schematic:
![Schematic](schematic.png "Basic schematic")
Each read line is pulled to ground with an individual 22k resistor, and fed
into an analog multiplexer. Any unused inputs of the multiplexer should be
grounded to prevent additional sources of noise: this goes for any unused op
amp pins too. After selecting a read line on the multiplexer, the
into an analog multiplexer. After selecting a read line on the multiplexer, the
microcontroller strobes a column and a small voltage pulse can be seen on the
selected read line, larger pulses correspond to greater key depression.
@ -49,12 +79,10 @@ limiting resistor into a non inverting amplifier. The purpose of this is to
provide a clean signal boost back into the range of 0 - 3.3V. The gain is given
by `1 + R2 / R4` which in this case is around 200. It also serves to protect
the microcontroller from negative voltages which can happen when the strobe
line returns to ground. I found it important to use a very fast amplifier here,
opting for the OPA350A. Cheaper options proved to be too slow, turning the
voltage spike into more of a voltage mound. The output of the amplifier should
connect to an ADC pin of the microcontroller.
line returns to ground. The output of the amplifier should connect to an ADC
pin of the microcontroller.
### Drain pin
## Drain pin
With the selected read line forming an RC circuit we can see that the time for
it to relax to ground is simply governed by `5 * RC time constant`. The time
@ -76,12 +104,239 @@ resistor formula:
```
which gives `R ~ 950 Ohms` for our chosen values. Recalculating the relax time
now gives `5 * 950 Ohms * 1 nF ~ 5 us` - much faster.
now gives `5 * 950 Ohms * 1 nF ~ 5 us`. This would allow 1000 Hz polling for
even 100 key keyboards.
## Practical considerations
## Hardware (case/plate)
## Parasitic capacitance
When routing any analog lines (and to some extent the digital strobe lines)
care must be taken to reduce parasitic capacitance due to the low signal level.
The lines must be surrounded by a ground pour and have a ground plane on
surrounding layers. Crossing tracks should ideally occur with a ground plane
between them, but this requires a 4 layer PCB. 2 layers is perfectly fine, as
long as you ensure traces cross at right angles and do so as little as
possible. Basically just don't run the strobe lines (or other digital lines)
close to the analog lines where you can avoid it! Study the Topre PCBs to see
the careful routing of the matrix.
## Firmware
## Other notes
Any unused inputs of the multiplexer should be grounded to prevent additional
sources of noise: this goes for any unused op amp pins too. If they are not
connected, ground them.
I found it important to use a very fast amplifier, opting for the OPA350A.
Cheaper options proved to be too slow, turning the voltage spike into more of a
voltage mound, making reading unpredictable.
# Hardware (case/plate)
The stackup is fairly simple, and is determined by the housing dimensions:
```
1.2 mm thick steel plate
3/16 inch spacers
PCB
```
TODO: hole sizes drawings, warn about Realforce control key rotation, dealing
with clones, dome cutting.
# Firmware
You will need to modify a firmware quite substantially to get your keyboard
working. Your initial choice depends on your chosen microcontroller and
preferences in firmware. You need access to an ADC and some EEPROM memory in
order to store calibration values: at least 2 bytes per key, if the stored
values take 1 byte each (uint8_t).
## Basic read procedure
Global variables
```
relaxTime (5 * R * C, include drain pin if using)
state[num reads, num strobes] a structure containing:
depth (current depth of key)
pressed (whether the key is currently "pressed", see digital conversion)
```
Matrix scan
```
for each read:
select read line on multiplexer
delay for relaxTime
for each strobe:
value <- strobeRead(strobe)
depth <- normalise(strobe, read, value)
state->depth[read, strobe] <- depth
```
Read value on ADC
```
strobeRead(strobe):
static lastTime
wait until lastTime + relaxTime < current time
float drain pin
disable interrupts (could throw timing off)
set strobe high
value <- read on ADC
set strobe low
enable interrupts
ground drain pin
lastTime <- current time
return value
```
The values which come out of the strobeRead function are assumed to be 8 bit
unsigned integers, that is, 0 to 255. This directly correlates to the voltage
of the read line, and also the capacitance of the key. Thanks to Topre's method
of using a conical spring, the capacitance of the key is linear with key
depression. So the output of strobeRead can be taken as the key depth!
Of course it isn't quite so simple, because each key will have an offset value
from ground and a different peak value when the key is fully depressed. As
such, we need to be able to rescale each key to find the true depth. For
example, if a key is reading on average 34 when unpressed and 200 when pressed,
we must rescale this into the range [0, 255]. These values should be stored in
EEPROM for each key, and can be determined by the [calibration
procedure](#calibration).
## Normalisation
See [this wikipedia article](https://en.wikipedia.org/wiki/Feature_scaling#Rescaling).
In the example procedure, we are rescaling to the range [0, 255].
Example procedure for uint8_t values, with no floating point operations
```
uint8_t normalise(strobe, read, uint8_t value):
(calLow, calHigh) <- calibrationValues(strobe, read)
// clamp to minimum and maximum values
if (value < calLow)
value <- calLow
else if (value > calHigh)
value <- calHigh
uint16_t numerator <- 0xFF * (value - calLow)
uint8_t denominator <- calHigh - calLow
return (uint8_t) (numerator / denominator);
```
## Calibration
### Overview
Repeated readings from the ADC for a single key might look something like this:
![ADC Plot](adcplot.png "ADC Plot")
This shows a full key press and release as the ADC reading crosses above the
actuation depth, and later below the release depth. We are interested in
finding the average value when a key is unpressed, and the average value when a
key is pressed, so that we can normalise values easily. The values HighMax,
LowMax, and LowMin which are marked on the plot are easily measurable (see
[calibration procedure](#calibration-procedure)) and will
allow us to calculate the averages we need. We take the noise to be
```
noise = lowMax - lowMin
```
and the signal to noise ratio to be
```
highMax - lowMax
SNR = ------------------
noise
```
This can be a convenient read on how good a key is. Generally we are looking
for at least SNR > 10, I've seen most values come in between 20 and 30 for the
above design.
The values lowAvg and highAvg are just
```
lowMax + lowMin
lowAvg = -----------------
2
```
```
noise
highAvg = highMax - -------
2
```
These should be stored in EEPROM for the normalisation routine.
### Calibration procedure
First we determine the values lowMax and lowMin. This is simple: just leave the
keyboard untouched while scanning the matrix for a short period of time (a few
seconds should do). For each key, keep a log on the highest and lowest
measurements, this will give you lowMax and lowMin respectively.
Finding highMax can be done by scanning the matrix while the user presses each
key in turn, keeping track of the highest measurement.
That's enough for a basic calibration routine, and I have found that is all
that is required for stable measurements. To improve upon this, you might
introduce continual recalibration via a more advanced signal processing method.
### A warning
It is useful to have some sort of escape route from the main key detection
routine (for example a digital button which interrupts sending keypresses to
the host). When we are scanning the matrix at 1000Hz and you mess something up,
for example your calibration values are wrong and you are normalising sane
input values to 0 or 255 randomly, you could end up sending hundreds of
keypresses per second to the host mistakenly, and if your only method of
interrupting is via your serial connection (for example through `screen`), you
will have a bad time.
## Handling the depths
Now we know how to collect the normalised depth of each key, we must do
something with it.
### Digital conversion
The most basic method of converting a normalised key depth to a digital value
is to check if a key is deeper than the desired actuation point. This
won't work very well, and will spam the host with keypresses thanks to noisy
readings. We must introduce hysteresis in the measurement of the key depth, we
do this by storing key press state as well as depth.
A simple procedure for a single key is as follows:
```
if (not pressed and depth > actuation depth):
pressed <- true
send key press signal
else if (pressed and depth < release depth):
pressed <- false
send key release signal
```
The actuation depth should be chosen somewhere around the middle. Don't set it
too close to 0 or 255 or you risk missing presses or having false presses
thanks to noisy readings. The calibration procedure should determine the noise
levels, and you can set a buffer value accordingly (use a few times the
measured noise value for safety). As a fallback, I find that giving a buffer of
around 30 works nicely for the range 0-255. The release depth should be the
actuation depth minus this buffer value.
### Analog
Theoretically you could pipe the depth directly to an axis, be it a controller
axis or something more interesting like analog mouse keys. Noisy readings mean
you probably need to introduce a deadzone.
## Example
See [my fork of Kiibohd](https://github.com/tomsmalley/controller),
specifically Scan/SplitHHKB.
Warning: it is not complete and basically is broken, it crashes after a short
time. The Topre part works fine though, it's my hacked together interconnect
solution that is the problem.

BIN
adcplot.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB