Skip to content
Yuri Khan edited this page Mar 16, 2015 · 5 revisions

Motivation

Truly Ergonomic reports:

[S]ome users mention that they have problems with “key chattering” or “contact bouncing” which is a natural occurrence in mechanical switches that can cause one switch-press to be detected as multiple presses. Usually the firmware can detect this and change the repetition by using a timer and minor logic. We were told our firmware has this timer and logic, but it appears not to work under all circumstances. We wonder if you can take a look and test different settings so this repetition does not occur or occurs the least possible.

Review

Specs

Cherry Corp. specifies that the bounce time for MX switches is ≤5ms.

Original firmware (v3)

The original firmware (which my hack is based on) contains some code that looks like a debouncing routine at addresses 1583h through 15ACh. It works roughly along the following algorithm:

  • Keep the last seen state of each column in memory at 26h÷37h.
  • Read each column in turn.
  • If the new column value is equal to the last, return without registering any new events.
  • Wait for 117µs (100 iterations of micro_delay).
  • Read the same column again.
  • Loop until the two readings give the same value. (This can trigger the early break if the next reading gives the previous stored value.)
  • Process the changed keys (if any).
  • Store the new value into the “last seen” array.
  • Delay for 500 iterations
  • When the column index advances to 18, transmit a HID report to the host.

In this scheme, changes stabilize after two reads of the same value, either immediately subsequent, or after a 117µs delay. This can be described as 117µs debouncing interval.

v3.16

Later Truly Ergonomic released a modified version that attempts to increase the debouncing interval.

This version adds a new mini_delay function which takes two arguments h, l and runs two nested delay loops. It looks like it was intended to give a delay of h×l×15 clocks (h×l×1.25µs), but due to a coding error does 20×h + 17×l + 11 clocks (because the inner loop does not preserve the original count and runs for 0 iterations in all but the first iteration of the outer loop).

This version replaces the elementary column read function (select column, wait 7.5µs, read value) with a more complicated fragment:

  • Do 6 times:
  • Select column.
  • Call mini_delay(2, 250) (which delays for 358µs but probably intended 625µs).
  • Read column value.
  • In order to be considered stable, the last column value must be equal to at least 3 other readings of the 6.

The original logic of doing two reads remains, only this time a single read is replaced with 6 reads, so now the firmware does 12 reads of the same column with 358 (625?) µs intervals (except the interval between 6th and 7th reads is 75µs), and declares the value stable when the the same value is obtained on 6th, 12th and 6 other reads.

The minimum stabilization interval in such scheme is 3×358 + 75 + 5×358 ≈ 3000µs (3ms), or 3×625 + 75 + 5×625 ≈ 5000µs (5ms) if the mini_delay routine is fixed.

The drawback is that the duration of one column scan is now 3ms, so the interval between two scans of the same column is 18×3 = 54ms.

Other algorithms

No debouncing

Since there is a 583µs delay between each two successive column samplings and there are 18 columns, in theory, this gives each column a 10.71 millisecond sampling interval, which should be enough for any noise to die out.

In practice, this is not the case; on my test keyboard, removing the debouncing code gives noticeable chatter in the left Shift, left Ctrl and Enter keys.

Bitwise filter over past readings

An article by The Ganssle Group describes an algorithm to debounce a whole column at once. They keep a circular buffer of several (≈10) last readings, and use their bitwise AND as the debounced output. They recommend to take readings from a timer interrupt handler, e.g. every 1ms. The algorithm has two customization parameters — the interval between reads and the size of the circular buffer — whose product gives the resulting debouncing interval. (For 1ms read interval and 10 readings, the key must produce the “down” bit value for 9ms to be considered stable-down; if any of the 10 readings is “up” then it’s taken to be stable-up.)

This method uses N bytes of memory per column for the circular buffers (where N is the number of samples to consider).

Vertical counters

Scott Dattalo uses a clever technique known as vertical counters. This allows to track the time for which a change has been stable, using only a few bits for each counter. The change becomes stable when the counter overflows.

Vertical counters use log2(N) bytes of memory per column, where N is the counter period. So, 2 bytes allow to track 4 samples, while 3 bytes can track 8 samples. Sampling rate can be adjusted separately.

Assume a 2-bit counter. This takes 36 bytes of memory, and gives a 4-sample stabilization interval. Assuming the delay in read_column of 10 iterations (11.7µs) and keeping the original delay between subsequent column scans (500 iterations or 583µs), the sampling rate of each column works out to 18 × (583+11.7)µs = 10.71ms; thus 32.13ms stabilization interval, which is 6 times the recommended value.

Therefore, the default sampling interval will need to be reduced from 500 iterations to about 70. The formula for sampling delay iteration count is t × 1000/63 − 10, where t is in milliseconds. Here are some values:

  • 3ms 38
  • 5ms 70
  • 7ms 100
  • 10ms 150
  • 15ms 230
  • 20ms 300