diff --git a/src/kaleidoscope/driver/keyscanner/ATmegaDirect.h b/src/kaleidoscope/driver/keyscanner/ATmegaDirect.h
new file mode 100644
index 00000000..22477f04
--- /dev/null
+++ b/src/kaleidoscope/driver/keyscanner/ATmegaDirect.h
@@ -0,0 +1,205 @@
+/* -*- mode: c++ -*-
+ * Kaleidoscope - Firmware for computer input devices
+ * Copyright (C) 2022 Keyboard.io, Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include // for uint16_t, uint8_t
+
+#include "kaleidoscope/device/avr/pins_and_ports.h" // IWYU pragma: keep
+#include "kaleidoscope/driver/keyscanner/Base.h" // for BaseProps
+#include "kaleidoscope/driver/keyscanner/None.h" // for None
+
+#ifndef KALEIDOSCOPE_VIRTUAL_BUILD
+#include
+#endif // ifndef KALEIDOSCOPE_VIRTUAL_BUILD
+
+namespace kaleidoscope {
+namespace driver {
+namespace keyscanner {
+
+struct ATmegaDirectProps : public kaleidoscope::driver::keyscanner::BaseProps {
+ static const uint16_t keyscan_interval = 1500;
+ typedef uint16_t PinState;
+
+ /*
+ * The following lines declares empty arrays, and both must be shadowed by the
+ * descendant keyscanner description class.
+ */
+ static constexpr uint8_t direct_pins[] = {};
+ static constexpr uint8_t unused_pins[] = {};
+};
+
+#ifndef KALEIDOSCOPE_VIRTUAL_BUILD
+template
+class ATmegaDirect : kaleidoscope::driver::keyscanner::Base<_KeyScannerProps> {
+ private:
+ typedef ATmegaDirect<_KeyScannerProps> ThisType;
+
+ public:
+ void setup() {
+ static_assert(
+ sizeof(_KeyScannerProps::direct_pins) > 0,
+ "The key scanner description has an empty array of direct pins.");
+ static_assert(
+ _KeyScannerProps::matrix_rows == 1,
+ "ATmegaDirect requires matrix_rows to be 1.");
+
+ for (uint8_t i = 0; i < sizeof(_KeyScannerProps::unused_pins); i++) {
+ DDR_OUTPUT(_KeyScannerProps::unused_pins[i]);
+ DISABLE_PULLUP(_KeyScannerProps::unused_pins[i]);
+ }
+
+ for (uint8_t i = 0; i < sizeof(_KeyScannerProps::direct_pins); i++) {
+ DDR_INPUT(_KeyScannerProps::direct_pins[i]);
+ ENABLE_PULLUP(_KeyScannerProps::direct_pins[i]);
+ }
+
+ setScanCycleTime(_KeyScannerProps::keyscan_interval);
+ }
+
+ /* setScanCycleTime takes a value of between 0 and 8192. This corresponds
+ * (roughly) to the number of microseconds to wait between scanning the key
+ * matrix. Our debouncing algorithm does four checks before deciding that a
+ * result is valid. Most normal mechanical switches specify a 5ms debounce
+ * period. On an ATMega32U4, 1700 gets you about 5ms of debouncing.
+ *
+ * Because keycanning is triggered by an interrupt but not run in that
+ * interrupt, the actual amount of time between scans is prone to a little bit
+ * of jitter.
+ */
+ void setScanCycleTime(uint16_t c) {
+ TCCR1B = _BV(WGM13);
+ TCCR1A = 0;
+
+ const uint32_t cycles = (F_CPU / 2000000) * c;
+
+ ICR1 = cycles;
+ TCCR1B = _BV(WGM13) | _BV(CS10);
+ TIMSK1 = _BV(TOIE1);
+ }
+
+ void readMatrix() {
+ typename _KeyScannerProps::PinState hot_pins = readPins();
+ typename _KeyScannerProps::PinState debounced_changes =
+ debounce(hot_pins, &(pin_state_.debouncer));
+
+ if (debounced_changes) {
+ pin_state_.current = pin_state_.debouncer.debounced_state;
+ }
+ }
+
+ void scanMatrix() {
+ if (do_scan_) {
+ do_scan_ = false;
+ readMatrix();
+ }
+ actOnMatrixScan();
+ }
+
+ void actOnMatrixScan() {
+ for (uint8_t pin = 0; pin < sizeof(_KeyScannerProps::direct_pins); pin++) {
+ uint8_t keyState = (bitRead(pin_state_.previous, pin) << 0) |
+ (bitRead(pin_state_.current, pin) << 1);
+ if (keyState) {
+ ThisType::handleKeyswitchEvent(Key_NoKey, typename _KeyScannerProps::KeyAddr(0, pin), keyState);
+ }
+ }
+ pin_state_.previous = pin_state_.current;
+ }
+
+ uint8_t pressedKeyswitchCount() {
+ return __builtin_popcount(pin_state_.current);
+ }
+ bool isKeyswitchPressed(typename _KeyScannerProps::KeyAddr key_addr) {
+ return (bitRead(pin_state_.current, key_addr.col()) != 0);
+ }
+
+ uint8_t previousPressedKeyswitchCount() {
+ return __builtin_popcount(pin_state_.previous);
+ }
+ bool wasKeyswitchPressed(typename _KeyScannerProps::KeyAddr key_addr) {
+ return (bitRead(pin_state_.previous, key_addr.col()) != 0);
+ }
+
+ bool do_scan_;
+
+ protected:
+ /*
+ each of these variables are storing the state for a row of keys
+
+ so for key 0, the counter is represented by db0[0] and db1[0]
+ and the state in debounced_state[0].
+ */
+ struct debounce_t {
+ typename _KeyScannerProps::PinState db0; // counter bit 0
+ typename _KeyScannerProps::PinState db1; // counter bit 1
+ typename _KeyScannerProps::PinState debounced_state; // debounced state
+ };
+
+ struct pin_state_t {
+ typename _KeyScannerProps::PinState previous;
+ typename _KeyScannerProps::PinState current;
+ debounce_t debouncer;
+ };
+
+ private:
+ typedef _KeyScannerProps KeyScannerProps_;
+ static pin_state_t pin_state_;
+
+ typename _KeyScannerProps::PinState
+ readPins() {
+ typename _KeyScannerProps::PinState hot_pins = 0;
+ for (uint8_t i = 0; i < sizeof(_KeyScannerProps::direct_pins); i++) {
+ hot_pins |= (!READ_PIN(_KeyScannerProps::direct_pins[i]) << i);
+ }
+
+ return hot_pins;
+ }
+
+ static inline typename _KeyScannerProps::PinState debounce(
+ typename _KeyScannerProps::PinState sample, debounce_t *debouncer) {
+ typename _KeyScannerProps::PinState delta, changes;
+
+ // Use xor to detect changes from last stable state:
+ // if a key has changed, it's bit will be 1, otherwise 0
+ delta = sample ^ debouncer->debounced_state;
+
+ // Increment counters and reset any unchanged bits:
+ // increment bit 1 for all changed keys
+ debouncer->db1 = ((debouncer->db1) ^ (debouncer->db0)) & delta;
+ // increment bit 0 for all changed keys
+ debouncer->db0 = ~(debouncer->db0) & delta;
+
+ // Calculate returned change set: if delta is still true
+ // and the counter has wrapped back to 0, the key is changed.
+
+ changes = ~(~delta | (debouncer->db0) | (debouncer->db1));
+ // Update state: in this case use xor to flip any bit that is true in changes.
+ debouncer->debounced_state ^= changes;
+
+ return changes;
+ }
+};
+#else // ifndef KALEIDOSCOPE_VIRTUAL_BUILD
+template
+class ATmegaDirect : public keyscanner::None {};
+#endif // ifndef KALEIDOSCOPE_VIRTUAL_BUILD
+
+} // namespace keyscanner
+} // namespace driver
+} // namespace kaleidoscope