summary refs log tree commit diff
path: root/quantum
diff options
context:
space:
mode:
authorJames Young <18669334+noroadsleft@users.noreply.github.com>2020-11-28 12:02:18 -0800
committerGitHub <noreply@github.com>2020-11-28 12:02:18 -0800
commitc66df1664497546f32662409778731143e45a552 (patch)
treeda73a2d532a27685a31d932b3a44a707d4a3af81 /quantum
parent15385d4113414d42bd062c60c9de5df797d3157f (diff)
2020 November 28 Breaking Changes Update (#11053)
* Branch point for 2020 November 28 Breaking Change                                                

* Remove matrix_col_t to allow MATRIX_ROWS > 32 (#10183)                                           

* Add support for soft serial to ATmega32U2 (#10204)                                               

* Change MIDI velocity implementation to allow direct control of velocity value (#9940)            

* Add ability to build a subset of all keyboards based on platform.                                

* Actually use eeprom_driver_init().                                                               

* Make bootloader_jump weak for ChibiOS. (#10417)                                                  

* Joystick 16-bit support (#10439)                                                                 

* Per-encoder resolutions (#10259)                                                                 

* Share button state from mousekey to pointing_device (#10179)                                     

* Add hotfix for chibios keyboards not wake (#10088)                                               

* Add advanced/efficient RGB Matrix Indicators (#8564)                                             

* Naming change.                                                                                   

* Support for STM32 GPIOF,G,H,I,J,K (#10206)                                                       

* Add milc as a dependency and remove the installed milc (#10563)                                  

* ChibiOS upgrade: early init conversions (#10214)                                                 

* ChibiOS upgrade: configuration file migrator (#9952)                                             

* Haptic and solenoid cleanup (#9700)                                                              

* XD75 cleanup (#10524)                                                                            

* OLED display update interval support (#10388)                                                    

* Add definition based on currently-selected serial driver. (#10716)                               

* New feature: Retro Tapping per key (#10622)                                                      

* Allow for modification of output RGB values when using rgblight/rgb_matrix. (#10638)             

* Add housekeeping task callbacks so that keyboards/keymaps are capable of executing code for each main loop iteration. (#10530)

* Rescale both ChibiOS and AVR backlighting.                                                       

* Reduce Helix keyboard build variation (#8669)                                                    

* Minor change to behavior allowing display updates to continue between task ticks (#10750)        

* Some GPIO manipulations in matrix.c change to atomic. (#10491)                                   

* qmk cformat (#10767)                                                                             

* [Keyboard] Update the Speedo firmware for v3.0 (#10657)                                          

* Maartenwut/Maarten namechange to evyd13/Evy (#10274)                                             

* [quantum] combine repeated lines of code (#10837)                                                

* Add step sequencer feature (#9703)                                                               

* aeboards/ext65 refactor (#10820)                                                                 

* Refactor xelus/dawn60 for Rev2 later (#10584)                                                    

* add DEBUG_MATRIX_SCAN_RATE_ENABLE to common_features.mk (#10824)                                 

* [Core] Added `add_oneshot_mods` & `del_oneshot_mods` (#10549)                                    

* update chibios os usb for the otg driver (#8893)                                                 

* Remove HD44780 References, Part 4 (#10735)                                                       

* [Keyboard] Add Valor FRL TKL (+refactor) (#10512)                                                

* Fix cursor position bug in oled_write_raw functions (#10800)                                     

* Fixup version.h writing when using SKIP_VERSION=yes (#10972)                                     

* Allow for certain code in the codebase assuming length of string. (#10974)                       

* Add AT90USB support for serial.c (#10706)                                                        

* Auto shift: support repeats and early registration (#9826)                                       

* Rename ledmatrix.h to match .c file (#7949)                                                      

* Split RGB_MATRIX_ENABLE into _ENABLE and _DRIVER (#10231)                                        

* Split LED_MATRIX_ENABLE into _ENABLE and _DRIVER (#10840)                                        

* Merge point for 2020 Nov 28 Breaking Change                                                      
Diffstat (limited to 'quantum')
-rw-r--r--quantum/backlight/backlight_avr.c12
-rw-r--r--quantum/backlight/backlight_chibios.c14
-rw-r--r--quantum/config_common.h92
-rw-r--r--quantum/encoder.c24
-rw-r--r--quantum/joystick.h12
-rw-r--r--quantum/led_matrix.c2
-rw-r--r--quantum/led_matrix.h (renamed from quantum/ledmatrix.h)0
-rw-r--r--quantum/led_matrix_drivers.c2
-rw-r--r--quantum/matrix.c35
-rw-r--r--quantum/mcu_selection.mk3
-rw-r--r--quantum/process_keycode/process_auto_shift.c199
-rw-r--r--quantum/process_keycode/process_auto_shift.h1
-rw-r--r--quantum/process_keycode/process_joystick.c8
-rw-r--r--quantum/process_keycode/process_midi.c25
-rw-r--r--quantum/process_keycode/process_midi.h2
-rw-r--r--quantum/process_keycode/process_sequencer.c62
-rw-r--r--quantum/process_keycode/process_sequencer.h21
-rw-r--r--quantum/quantum.c15
-rw-r--r--quantum/quantum.h62
-rw-r--r--quantum/quantum_keycodes.h38
-rw-r--r--quantum/rgb_matrix.c32
-rw-r--r--quantum/rgb_matrix.h9
-rw-r--r--quantum/rgb_matrix_animations/alpha_mods_anim.h4
-rw-r--r--quantum/rgb_matrix_animations/breathing_anim.h2
-rw-r--r--quantum/rgb_matrix_animations/gradient_left_right_anim.h2
-rw-r--r--quantum/rgb_matrix_animations/gradient_up_down_anim.h2
-rw-r--r--quantum/rgb_matrix_animations/jellybean_raindrops_anim.h2
-rw-r--r--quantum/rgb_matrix_animations/raindrops_anim.h2
-rw-r--r--quantum/rgb_matrix_animations/solid_color_anim.h2
-rw-r--r--quantum/rgb_matrix_animations/typing_heatmap_anim.h2
-rw-r--r--quantum/rgb_matrix_runners/effect_runner_dx_dy.h2
-rw-r--r--quantum/rgb_matrix_runners/effect_runner_dx_dy_dist.h2
-rw-r--r--quantum/rgb_matrix_runners/effect_runner_i.h2
-rw-r--r--quantum/rgb_matrix_runners/effect_runner_reactive.h2
-rw-r--r--quantum/rgb_matrix_runners/effect_runner_reactive_splash.h2
-rw-r--r--quantum/rgb_matrix_runners/effect_runner_sin_cos_i.h2
-rw-r--r--quantum/rgblight.c4
-rw-r--r--quantum/sequencer/sequencer.c275
-rw-r--r--quantum/sequencer/sequencer.h122
-rw-r--r--quantum/sequencer/tests/midi_mock.c26
-rw-r--r--quantum/sequencer/tests/midi_mock.h26
-rw-r--r--quantum/sequencer/tests/rules.mk11
-rw-r--r--quantum/sequencer/tests/sequencer_tests.cpp590
-rw-r--r--quantum/sequencer/tests/testlist.mk1
-rw-r--r--quantum/split_common/matrix.c35
45 files changed, 1668 insertions, 122 deletions
diff --git a/quantum/backlight/backlight_avr.c b/quantum/backlight/backlight_avr.c
index b3e882ffe1..4d66da80ba 100644
--- a/quantum/backlight/backlight_avr.c
+++ b/quantum/backlight/backlight_avr.c
@@ -3,6 +3,11 @@
 #include "backlight_driver_common.h"
 #include "debug.h"
 
+// Maximum duty cycle limit
+#ifndef BACKLIGHT_LIMIT_VAL
+#    define BACKLIGHT_LIMIT_VAL 255
+#endif
+
 // This logic is a bit complex, we support 3 setups:
 //
 //   1. Hardware PWM when backlight is wired to a PWM pin.
@@ -240,6 +245,9 @@ static uint16_t cie_lightness(uint16_t v) {
     }
 }
 
+// rescale the supplied backlight value to be in terms of the value limit
+static uint32_t rescale_limit_val(uint32_t val) { return (val * (BACKLIGHT_LIMIT_VAL + 1)) / 256; }
+
 // range for val is [0..TIMER_TOP]. PWM pin is high while the timer count is below val.
 static inline void set_pwm(uint16_t val) { OCRxx = val; }
 
@@ -269,7 +277,7 @@ void backlight_set(uint8_t level) {
 #endif
     }
     // Set the brightness
-    set_pwm(cie_lightness(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS));
+    set_pwm(cie_lightness(rescale_limit_val(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS)));
 }
 
 void backlight_task(void) {}
@@ -375,7 +383,7 @@ ISR(TIMERx_OVF_vect)
         breathing_interrupt_disable();
     }
 
-    set_pwm(cie_lightness(scale_backlight((uint16_t)pgm_read_byte(&breathing_table[index]) * 0x0101U)));
+    set_pwm(cie_lightness(rescale_limit_val(scale_backlight((uint16_t)pgm_read_byte(&breathing_table[index]) * 0x0101U))));
 }
 
 #endif  // BACKLIGHT_BREATHING
diff --git a/quantum/backlight/backlight_chibios.c b/quantum/backlight/backlight_chibios.c
index 0fe812bf27..4d5a69e14e 100644
--- a/quantum/backlight/backlight_chibios.c
+++ b/quantum/backlight/backlight_chibios.c
@@ -3,6 +3,11 @@
 #include <hal.h>
 #include "debug.h"
 
+// Maximum duty cycle limit
+#ifndef BACKLIGHT_LIMIT_VAL
+#    define BACKLIGHT_LIMIT_VAL 255
+#endif
+
 // GPIOV2 && GPIOV3
 #ifndef BACKLIGHT_PAL_MODE
 #    define BACKLIGHT_PAL_MODE 2
@@ -58,6 +63,11 @@ static uint16_t cie_lightness(uint16_t v) {
     }
 }
 
+static uint32_t rescale_limit_val(uint32_t val) {
+    // rescale the supplied backlight value to be in terms of the value limit
+    return (val * (BACKLIGHT_LIMIT_VAL + 1)) / 256;
+}
+
 void backlight_init_ports(void) {
 #ifdef USE_GPIOV1
     palSetPadMode(PAL_PORT(BACKLIGHT_PIN), PAL_PAD(BACKLIGHT_PIN), PAL_MODE_STM32_ALTERNATE_PUSHPULL);
@@ -85,7 +95,7 @@ void backlight_set(uint8_t level) {
         pwmDisableChannel(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1);
     } else {
         // Turn backlight on
-        uint32_t duty = (uint32_t)(cie_lightness(0xFFFF * (uint32_t)level / BACKLIGHT_LEVELS));
+        uint32_t duty = (uint32_t)(cie_lightness(rescale_limit_val(0xFFFF * (uint32_t)level / BACKLIGHT_LEVELS)));
         pwmEnableChannel(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, duty));
     }
 }
@@ -129,7 +139,7 @@ void breathing_callback(PWMDriver *pwmp) {
     static uint16_t breathing_counter = 0;
     breathing_counter                 = (breathing_counter + 1) % (breathing_period * 256);
     uint8_t  index                    = breathing_counter / interval % BREATHING_STEPS;
-    uint32_t duty                     = cie_lightness(scale_backlight(breathing_table[index] * 256));
+    uint32_t duty                     = cie_lightness(rescale_limit_val(scale_backlight(breathing_table[index] * 256)));
 
     chSysLockFromISR();
     pwmEnableChannelI(pwmp, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, duty));
diff --git a/quantum/config_common.h b/quantum/config_common.h
index c1e6698e50..2d9c70b08d 100644
--- a/quantum/config_common.h
+++ b/quantum/config_common.h
@@ -39,7 +39,7 @@
 #        define PIND_ADDRESS 0x9
 #        define PINE_ADDRESS 0xC
 #        define PINF_ADDRESS 0xF
-#    elif defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U2__)
+#    elif defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)
 #        define ADDRESS_BASE 0x00
 #        define PINB_ADDRESS 0x3
 #        define PINC_ADDRESS 0x6
@@ -58,11 +58,6 @@
 #        define PINC_ADDRESS 0x3
 #        define PINB_ADDRESS 0x6
 #        define PINA_ADDRESS 0x9
-#    elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)
-#        define ADDRESS_BASE 0x00
-#        define PINB_ADDRESS 0x3
-#        define PINC_ADDRESS 0x6
-#        define PIND_ADDRESS 0x9
 #    elif defined(__AVR_ATtiny85__)
 #        define ADDRESS_BASE 0x10
 #        define PINB_ADDRESS 0x6
@@ -284,6 +279,91 @@
 #        define F13 PAL_LINE(GPIOF, 13)
 #        define F14 PAL_LINE(GPIOF, 14)
 #        define F15 PAL_LINE(GPIOF, 15)
+#        define G0 PAL_LINE(GPIOG, 0)
+#        define G1 PAL_LINE(GPIOG, 1)
+#        define G2 PAL_LINE(GPIOG, 2)
+#        define G3 PAL_LINE(GPIOG, 3)
+#        define G4 PAL_LINE(GPIOG, 4)
+#        define G5 PAL_LINE(GPIOG, 5)
+#        define G6 PAL_LINE(GPIOG, 6)
+#        define G7 PAL_LINE(GPIOG, 7)
+#        define G8 PAL_LINE(GPIOG, 8)
+#        define G9 PAL_LINE(GPIOG, 9)
+#        define G10 PAL_LINE(GPIOG, 10)
+#        define G11 PAL_LINE(GPIOG, 11)
+#        define G12 PAL_LINE(GPIOG, 12)
+#        define G13 PAL_LINE(GPIOG, 13)
+#        define G14 PAL_LINE(GPIOG, 14)
+#        define G15 PAL_LINE(GPIOG, 15)
+#        define H0 PAL_LINE(GPIOH, 0)
+#        define H1 PAL_LINE(GPIOH, 1)
+#        define H2 PAL_LINE(GPIOH, 2)
+#        define H3 PAL_LINE(GPIOH, 3)
+#        define H4 PAL_LINE(GPIOH, 4)
+#        define H5 PAL_LINE(GPIOH, 5)
+#        define H6 PAL_LINE(GPIOH, 6)
+#        define H7 PAL_LINE(GPIOH, 7)
+#        define H8 PAL_LINE(GPIOH, 8)
+#        define H9 PAL_LINE(GPIOH, 9)
+#        define H10 PAL_LINE(GPIOH, 10)
+#        define H11 PAL_LINE(GPIOH, 11)
+#        define H12 PAL_LINE(GPIOH, 12)
+#        define H13 PAL_LINE(GPIOH, 13)
+#        define H14 PAL_LINE(GPIOH, 14)
+#        define H15 PAL_LINE(GPIOH, 15)
+#        define I0 PAL_LINE(GPIOI, 0)
+#        define I1 PAL_LINE(GPIOI, 1)
+#        define I2 PAL_LINE(GPIOI, 2)
+#        define I3 PAL_LINE(GPIOI, 3)
+#        define I4 PAL_LINE(GPIOI, 4)
+#        define I5 PAL_LINE(GPIOI, 5)
+#        define I6 PAL_LINE(GPIOI, 6)
+#        define I7 PAL_LINE(GPIOI, 7)
+#        define I8 PAL_LINE(GPIOI, 8)
+#        define I9 PAL_LINE(GPIOI, 9)
+#        define I10 PAL_LINE(GPIOI, 10)
+#        define I11 PAL_LINE(GPIOI, 11)
+#        define I12 PAL_LINE(GPIOI, 12)
+#        define I13 PAL_LINE(GPIOI, 13)
+#        define I14 PAL_LINE(GPIOI, 14)
+#        define I15 PAL_LINE(GPIOI, 15)
+#        define J0 PAL_LINE(GPIOJ, 0)
+#        define J1 PAL_LINE(GPIOJ, 1)
+#        define J2 PAL_LINE(GPIOJ, 2)
+#        define J3 PAL_LINE(GPIOJ, 3)
+#        define J4 PAL_LINE(GPIOJ, 4)
+#        define J5 PAL_LINE(GPIOJ, 5)
+#        define J6 PAL_LINE(GPIOJ, 6)
+#        define J7 PAL_LINE(GPIOJ, 7)
+#        define J8 PAL_LINE(GPIOJ, 8)
+#        define J9 PAL_LINE(GPIOJ, 9)
+#        define J10 PAL_LINE(GPIOJ, 10)
+#        define J11 PAL_LINE(GPIOJ, 11)
+#        define J12 PAL_LINE(GPIOJ, 12)
+#        define J13 PAL_LINE(GPIOJ, 13)
+#        define J14 PAL_LINE(GPIOJ, 14)
+#        define J15 PAL_LINE(GPIOJ, 15)
+// Keyboards can `#define KEYBOARD_REQUIRES_GPIOK` if they need to access GPIO-K pins. These conflict with a whole
+// bunch of layout definitions, so it's intentionally left out unless absolutely required -- in that case, the
+// keyboard designer should use a different symbol when defining their layout macros.
+#        ifdef KEYBOARD_REQUIRES_GPIOK
+#            define K0 PAL_LINE(GPIOK, 0)
+#            define K1 PAL_LINE(GPIOK, 1)
+#            define K2 PAL_LINE(GPIOK, 2)
+#            define K3 PAL_LINE(GPIOK, 3)
+#            define K4 PAL_LINE(GPIOK, 4)
+#            define K5 PAL_LINE(GPIOK, 5)
+#            define K6 PAL_LINE(GPIOK, 6)
+#            define K7 PAL_LINE(GPIOK, 7)
+#            define K8 PAL_LINE(GPIOK, 8)
+#            define K9 PAL_LINE(GPIOK, 9)
+#            define K10 PAL_LINE(GPIOK, 10)
+#            define K11 PAL_LINE(GPIOK, 11)
+#            define K12 PAL_LINE(GPIOK, 12)
+#            define K13 PAL_LINE(GPIOK, 13)
+#            define K14 PAL_LINE(GPIOK, 14)
+#            define K15 PAL_LINE(GPIOK, 15)
+#        endif
 #    endif
 #endif
 
diff --git a/quantum/encoder.c b/quantum/encoder.c
index 81ec1bb376..7ca31afedc 100644
--- a/quantum/encoder.c
+++ b/quantum/encoder.c
@@ -23,7 +23,7 @@
 // for memcpy
 #include <string.h>
 
-#ifndef ENCODER_RESOLUTION
+#if !defined(ENCODER_RESOLUTIONS) && !defined(ENCODER_RESOLUTION)
 #    define ENCODER_RESOLUTION 4
 #endif
 
@@ -34,6 +34,9 @@
 #define NUMBER_OF_ENCODERS (sizeof(encoders_pad_a) / sizeof(pin_t))
 static pin_t encoders_pad_a[] = ENCODERS_PAD_A;
 static pin_t encoders_pad_b[] = ENCODERS_PAD_B;
+#ifdef ENCODER_RESOLUTIONS
+static uint8_t encoder_resolutions[] = ENCODER_RESOLUTIONS;
+#endif
 
 #ifndef ENCODER_DIRECTION_FLIP
 #    define ENCODER_CLOCKWISE true
@@ -65,9 +68,15 @@ void encoder_init(void) {
     if (!isLeftHand) {
         const pin_t encoders_pad_a_right[] = ENCODERS_PAD_A_RIGHT;
         const pin_t encoders_pad_b_right[] = ENCODERS_PAD_B_RIGHT;
+#    if defined(ENCODER_RESOLUTIONS_RIGHT)
+        const uint8_t encoder_resolutions_right[] = ENCODER_RESOLUTIONS_RIGHT;
+#    endif
         for (uint8_t i = 0; i < NUMBER_OF_ENCODERS; i++) {
             encoders_pad_a[i] = encoders_pad_a_right[i];
             encoders_pad_b[i] = encoders_pad_b_right[i];
+#    if defined(ENCODER_RESOLUTIONS_RIGHT)
+            encoder_resolutions[i] = encoder_resolutions_right[i];
+#    endif
         }
     }
 #endif
@@ -87,19 +96,26 @@ void encoder_init(void) {
 
 static void encoder_update(int8_t index, uint8_t state) {
     uint8_t i = index;
+
+#ifdef ENCODER_RESOLUTIONS
+    int8_t resolution = encoder_resolutions[i];
+#else
+    int8_t resolution = ENCODER_RESOLUTION;
+#endif
+
 #ifdef SPLIT_KEYBOARD
     index += thisHand;
 #endif
     encoder_pulses[i] += encoder_LUT[state & 0xF];
-    if (encoder_pulses[i] >= ENCODER_RESOLUTION) {
+    if (encoder_pulses[i] >= resolution) {
         encoder_value[index]++;
         encoder_update_kb(index, ENCODER_COUNTER_CLOCKWISE);
     }
-    if (encoder_pulses[i] <= -ENCODER_RESOLUTION) {  // direction is arbitrary here, but this clockwise
+    if (encoder_pulses[i] <= -resolution) {  // direction is arbitrary here, but this clockwise
         encoder_value[index]--;
         encoder_update_kb(index, ENCODER_CLOCKWISE);
     }
-    encoder_pulses[i] %= ENCODER_RESOLUTION;
+    encoder_pulses[i] %= resolution;
 }
 
 void encoder_read(void) {
diff --git a/quantum/joystick.h b/quantum/joystick.h
index a95472b9fd..87dbc24aff 100644
--- a/quantum/joystick.h
+++ b/quantum/joystick.h
@@ -1,5 +1,9 @@
 #pragma once
 
+#include "quantum.h"
+
+#include <stdint.h>
+
 #ifndef JOYSTICK_BUTTON_COUNT
 #    define JOYSTICK_BUTTON_COUNT 8
 #endif
@@ -8,9 +12,13 @@
 #    define JOYSTICK_AXES_COUNT 4
 #endif
 
-#include "quantum.h"
+#ifndef JOYSTICK_AXES_RESOLUTION
+#    define JOYSTICK_AXES_RESOLUTION 8
+#elif JOYSTICK_AXES_RESOLUTION < 8 || JOYSTICK_AXES_RESOLUTION > 16
+#    error JOYSTICK_AXES_RESOLUTION must be between 8 and 16
+#endif
 
-#include <stdint.h>
+#define JOYSTICK_RESOLUTION ((1L << (JOYSTICK_AXES_RESOLUTION - 1)) - 1)
 
 // configure on input_pin of the joystick_axes array entry to JS_VIRTUAL_AXIS
 // to prevent it from being read from the ADC. This allows outputing forged axis value.
diff --git a/quantum/led_matrix.c b/quantum/led_matrix.c
index 5c24c797a9..eb523990a6 100644
--- a/quantum/led_matrix.c
+++ b/quantum/led_matrix.c
@@ -20,7 +20,7 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include "quantum.h"
-#include "ledmatrix.h"
+#include "led_matrix.h"
 #include "progmem.h"
 #include "config.h"
 #include "eeprom.h"
diff --git a/quantum/ledmatrix.h b/quantum/led_matrix.h
index 5867ba9876..5867ba9876 100644
--- a/quantum/ledmatrix.h
+++ b/quantum/led_matrix.h
diff --git a/quantum/led_matrix_drivers.c b/quantum/led_matrix_drivers.c
index 6877bf4c6b..9decaa33c2 100644
--- a/quantum/led_matrix_drivers.c
+++ b/quantum/led_matrix_drivers.c
@@ -18,7 +18,7 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include "quantum.h"
-#include "ledmatrix.h"
+#include "led_matrix.h"
 
 /* Each driver needs to define a struct:
  *
diff --git a/quantum/matrix.c b/quantum/matrix.c
index c68c56cac2..cab0d2ddca 100644
--- a/quantum/matrix.c
+++ b/quantum/matrix.c
@@ -32,6 +32,19 @@ static const pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS;
 extern matrix_row_t raw_matrix[MATRIX_ROWS];  // raw values
 extern matrix_row_t matrix[MATRIX_ROWS];      // debounced values
 
+static inline void setPinOutput_writeLow(pin_t pin) {
+    ATOMIC_BLOCK_FORCEON {
+        setPinOutput(pin);
+        writePinLow(pin);
+    }
+}
+
+static inline void setPinInputHigh_atomic(pin_t pin) {
+    ATOMIC_BLOCK_FORCEON {
+        setPinInputHigh(pin);
+    }
+}
+
 // matrix code
 
 #ifdef DIRECT_PINS
@@ -70,22 +83,23 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)
 #    if (DIODE_DIRECTION == COL2ROW)
 
 static void select_row(uint8_t row) {
-    setPinOutput(row_pins[row]);
-    writePinLow(row_pins[row]);
+    setPinOutput_writeLow(row_pins[row]);
 }
 
-static void unselect_row(uint8_t row) { setPinInputHigh(row_pins[row]); }
+static void unselect_row(uint8_t row) {
+    setPinInputHigh_atomic(row_pins[row]);
+}
 
 static void unselect_rows(void) {
     for (uint8_t x = 0; x < MATRIX_ROWS; x++) {
-        setPinInputHigh(row_pins[x]);
+        setPinInputHigh_atomic(row_pins[x]);
     }
 }
 
 static void init_pins(void) {
     unselect_rows();
     for (uint8_t x = 0; x < MATRIX_COLS; x++) {
-        setPinInputHigh(col_pins[x]);
+        setPinInputHigh_atomic(col_pins[x]);
     }
 }
 
@@ -120,22 +134,23 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)
 #    elif (DIODE_DIRECTION == ROW2COL)
 
 static void select_col(uint8_t col) {
-    setPinOutput(col_pins[col]);
-    writePinLow(col_pins[col]);
+    setPinOutput_writeLow(col_pins[col]);
 }
 
-static void unselect_col(uint8_t col) { setPinInputHigh(col_pins[col]); }
+static void unselect_col(uint8_t col) {
+    setPinInputHigh_atomic(col_pins[col]);
+}
 
 static void unselect_cols(void) {
     for (uint8_t x = 0; x < MATRIX_COLS; x++) {
-        setPinInputHigh(col_pins[x]);
+        setPinInputHigh_atomic(col_pins[x]);
     }
 }
 
 static void init_pins(void) {
     unselect_cols();
     for (uint8_t x = 0; x < MATRIX_ROWS; x++) {
-        setPinInputHigh(row_pins[x]);
+        setPinInputHigh_atomic(row_pins[x]);
     }
 }
 
diff --git a/quantum/mcu_selection.mk b/quantum/mcu_selection.mk
index 295dfd3189..9518a6463f 100644
--- a/quantum/mcu_selection.mk
+++ b/quantum/mcu_selection.mk
@@ -318,6 +318,9 @@ ifneq (,$(filter $(MCU),atmega16u2 atmega32u2 atmega16u4 atmega32u4 at90usb646 a
   ifeq (,$(filter $(NO_INTERRUPT_CONTROL_ENDPOINT),yes))
     OPT_DEFS += -DINTERRUPT_CONTROL_ENDPOINT
   endif
+  ifneq (,$(filter $(MCU),atmega16u2 atmega32u2))
+    NO_I2C = yes
+  endif
 endif
 
 ifneq (,$(filter $(MCU),atmega32a))
diff --git a/quantum/process_keycode/process_auto_shift.c b/quantum/process_keycode/process_auto_shift.c
index b1267922ce..a2d315408b 100644
--- a/quantum/process_keycode/process_auto_shift.c
+++ b/quantum/process_keycode/process_auto_shift.c
@@ -16,48 +16,149 @@
 
 #ifdef AUTO_SHIFT_ENABLE
 
+#    include <stdbool.h>
 #    include <stdio.h>
 
 #    include "process_auto_shift.h"
 
-static bool     autoshift_enabled = true;
 static uint16_t autoshift_time    = 0;
 static uint16_t autoshift_timeout = AUTO_SHIFT_TIMEOUT;
 static uint16_t autoshift_lastkey = KC_NO;
+static struct {
+    // Whether autoshift is enabled.
+    bool enabled : 1;
+    // Whether the last auto-shifted key was released after the timeout.  This
+    // is used to replicate the last key for a tap-then-hold.
+    bool lastshifted : 1;
+    // Whether an auto-shiftable key has been pressed but not processed.
+    bool in_progress : 1;
+    // Whether the auto-shifted keypress has been registered.
+    bool holding_shift : 1;
+} autoshift_flags = {true, false, false, false};
+
+/** \brief Record the press of an autoshiftable key
+ *
+ *  \return Whether the record should be further processed.
+ */
+static bool autoshift_press(uint16_t keycode, uint16_t now, keyrecord_t *record) {
+    if (!autoshift_flags.enabled) {
+        return true;
+    }
+
+#    ifndef AUTO_SHIFT_MODIFIERS
+    if (get_mods() & (~MOD_BIT(KC_LSFT))) {
+        return true;
+    }
+#    endif
+#    ifdef AUTO_SHIFT_REPEAT
+    const uint16_t elapsed = TIMER_DIFF_16(now, autoshift_time);
+#        ifndef AUTO_SHIFT_NO_AUTO_REPEAT
+    if (!autoshift_flags.lastshifted) {
+#        endif
+        if (elapsed < TAPPING_TERM && keycode == autoshift_lastkey) {
+            // Allow a tap-then-hold for keyrepeat.
+            if (!autoshift_flags.lastshifted) {
+                register_code(autoshift_lastkey);
+            } else {
+                // Simulate pressing the shift key.
+                add_weak_mods(MOD_BIT(KC_LSFT));
+                register_code(autoshift_lastkey);
+            }
+            return false;
+        }
+#        ifndef AUTO_SHIFT_NO_AUTO_REPEAT
+    }
+#        endif
+#    endif
 
-void autoshift_flush(void) {
-    if (autoshift_lastkey != KC_NO) {
-        uint16_t elapsed = timer_elapsed(autoshift_time);
+    // Record the keycode so we can simulate it later.
+    autoshift_lastkey           = keycode;
+    autoshift_time              = now;
+    autoshift_flags.in_progress = true;
 
-        if (elapsed > autoshift_timeout) {
-            tap_code16(LSFT(autoshift_lastkey));
+#    if !defined(NO_ACTION_ONESHOT) && !defined(NO_ACTION_TAPPING)
+    clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED);
+#    endif
+    return false;
+}
+
+/** \brief Registers an autoshiftable key under the right conditions
+ *
+ * If the autoshift delay has elapsed, register a shift and the key.
+ *
+ * If the autoshift key is released before the delay has elapsed, register the
+ * key without a shift.
+ */
+static void autoshift_end(uint16_t keycode, uint16_t now, bool matrix_trigger) {
+    // Called on key down with KC_NO, auto-shifted key up, and timeout.
+    if (autoshift_flags.in_progress) {
+        // Process the auto-shiftable key.
+        autoshift_flags.in_progress = false;
+
+        // Time since the initial press was recorded.
+        const uint16_t elapsed = TIMER_DIFF_16(now, autoshift_time);
+        if (elapsed < autoshift_timeout) {
+            register_code(autoshift_lastkey);
+            autoshift_flags.lastshifted = false;
         } else {
-            tap_code(autoshift_lastkey);
+            // Simulate pressing the shift key.
+            add_weak_mods(MOD_BIT(KC_LSFT));
+            register_code(autoshift_lastkey);
+            autoshift_flags.lastshifted = true;
+#    if defined(AUTO_SHIFT_REPEAT) && !defined(AUTO_SHIFT_NO_AUTO_REPEAT)
+            if (matrix_trigger) {
+                // Prevents release.
+                return;
+            }
+#    endif
         }
 
-        autoshift_time    = 0;
-        autoshift_lastkey = KC_NO;
+#    if TAP_CODE_DELAY > 0
+        wait_ms(TAP_CODE_DELAY);
+#    endif
+        unregister_code(autoshift_lastkey);
+        del_weak_mods(MOD_BIT(KC_LSFT));
+    } else {
+        // Release after keyrepeat.
+        unregister_code(keycode);
+        if (keycode == autoshift_lastkey) {
+            // This will only fire when the key was the last auto-shiftable
+            // pressed. That prevents aaaaBBBB then releasing a from unshifting
+            // later Bs (if B wasn't auto-shiftable).
+            del_weak_mods(MOD_BIT(KC_LSFT));
+        }
     }
+    send_keyboard_report();  // del_weak_mods doesn't send one.
+    // Roll the autoshift_time forward for detecting tap-and-hold.
+    autoshift_time = now;
 }
 
-void autoshift_on(uint16_t keycode) {
-    autoshift_time    = timer_read();
-    autoshift_lastkey = keycode;
+/** \brief Simulates auto-shifted key releases when timeout is hit
+ *
+ *  Can be called from \c matrix_scan_user so that auto-shifted keys are sent
+ *  immediately after the timeout has expired, rather than waiting for the key
+ *  to be released.
+ */
+void autoshift_matrix_scan(void) {
+    if (autoshift_flags.in_progress) {
+        const uint16_t now     = timer_read();
+        const uint16_t elapsed = TIMER_DIFF_16(now, autoshift_time);
+        if (elapsed >= autoshift_timeout) {
+            autoshift_end(autoshift_lastkey, now, true);
+        }
+    }
 }
 
 void autoshift_toggle(void) {
-    if (autoshift_enabled) {
-        autoshift_enabled = false;
-        autoshift_flush();
-    } else {
-        autoshift_enabled = true;
-    }
+    autoshift_flags.enabled = !autoshift_flags.enabled;
+    del_weak_mods(MOD_BIT(KC_LSFT));
 }
 
-void autoshift_enable(void) { autoshift_enabled = true; }
+void autoshift_enable(void) { autoshift_flags.enabled = true; }
+
 void autoshift_disable(void) {
-    autoshift_enabled = false;
-    autoshift_flush();
+    autoshift_flags.enabled = false;
+    del_weak_mods(MOD_BIT(KC_LSFT));
 }
 
 #    ifndef AUTO_SHIFT_NO_SETUP
@@ -70,19 +171,30 @@ void autoshift_timer_report(void) {
 }
 #    endif
 
-bool get_autoshift_state(void) { return autoshift_enabled; }
+bool get_autoshift_state(void) { return autoshift_flags.enabled; }
 
 uint16_t get_autoshift_timeout(void) { return autoshift_timeout; }
 
 void set_autoshift_timeout(uint16_t timeout) { autoshift_timeout = timeout; }
 
 bool process_auto_shift(uint16_t keycode, keyrecord_t *record) {
+    // Note that record->event.time isn't reliable, see:
+    // https://github.com/qmk/qmk_firmware/pull/9826#issuecomment-733559550
+    const uint16_t now = timer_read();
+
     if (record->event.pressed) {
+        if (autoshift_flags.in_progress) {
+            // Evaluate previous key if there is one. Doing this elsewhere is
+            // more complicated and easier to break.
+            autoshift_end(KC_NO, now, false);
+        }
+        // For pressing another key while keyrepeating shifted autoshift.
+        del_weak_mods(MOD_BIT(KC_LSFT));
+
         switch (keycode) {
             case KC_ASTG:
                 autoshift_toggle();
                 return true;
-
             case KC_ASON:
                 autoshift_enable();
                 return true;
@@ -102,41 +214,28 @@ bool process_auto_shift(uint16_t keycode, keyrecord_t *record) {
                 autoshift_timer_report();
                 return true;
 #    endif
+        }
+    }
+
+    switch (keycode) {
 #    ifndef NO_AUTO_SHIFT_ALPHA
-            case KC_A ... KC_Z:
+        case KC_A ... KC_Z:
 #    endif
 #    ifndef NO_AUTO_SHIFT_NUMERIC
-            case KC_1 ... KC_0:
+        case KC_1 ... KC_0:
 #    endif
 #    ifndef NO_AUTO_SHIFT_SPECIAL
-            case KC_TAB:
-            case KC_MINUS ... KC_SLASH:
-            case KC_NONUS_BSLASH:
-#    endif
-                autoshift_flush();
-                if (!autoshift_enabled) return true;
-
-#    ifndef AUTO_SHIFT_MODIFIERS
-                if (get_mods()) {
-                    return true;
-                }
-#    endif
-                autoshift_on(keycode);
-
-                // We need some extra handling here for OSL edge cases
-#    if !defined(NO_ACTION_ONESHOT) && !defined(NO_ACTION_TAPPING)
-                clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED);
+        case KC_TAB:
+        case KC_MINUS ... KC_SLASH:
+        case KC_NONUS_BSLASH:
 #    endif
+            if (record->event.pressed) {
+                return autoshift_press(keycode, now, record);
+            } else {
+                autoshift_end(keycode, now, false);
                 return false;
-
-            default:
-                autoshift_flush();
-                return true;
-        }
-    } else {
-        autoshift_flush();
+            }
     }
-
     return true;
 }
 
diff --git a/quantum/process_keycode/process_auto_shift.h b/quantum/process_keycode/process_auto_shift.h
index e86c4658e9..5b2718f11c 100644
--- a/quantum/process_keycode/process_auto_shift.h
+++ b/quantum/process_keycode/process_auto_shift.h
@@ -30,3 +30,4 @@ void     autoshift_toggle(void);
 bool     get_autoshift_state(void);
 uint16_t get_autoshift_timeout(void);
 void     set_autoshift_timeout(uint16_t timeout);
+void     autoshift_matrix_scan(void);
diff --git a/quantum/process_keycode/process_joystick.c b/quantum/process_keycode/process_joystick.c
index 5778a7434c..3ffaf42bf8 100644
--- a/quantum/process_keycode/process_joystick.c
+++ b/quantum/process_keycode/process_joystick.c
@@ -129,17 +129,17 @@ bool process_joystick_analogread_quantum() {
         // test the converted value against the lower range
         int32_t ref        = joystick_axes[axis_index].mid_digit;
         int32_t range      = joystick_axes[axis_index].min_digit;
-        int32_t ranged_val = ((axis_val - ref) * -127) / (range - ref);
+        int32_t ranged_val = ((axis_val - ref) * -JOYSTICK_RESOLUTION) / (range - ref);
 
         if (ranged_val > 0) {
             // the value is in the higher range
             range      = joystick_axes[axis_index].max_digit;
-            ranged_val = ((axis_val - ref) * 127) / (range - ref);
+            ranged_val = ((axis_val - ref) * JOYSTICK_RESOLUTION) / (range - ref);
         }
 
         // clamp the result in the valid range
-        ranged_val = ranged_val < -127 ? -127 : ranged_val;
-        ranged_val = ranged_val > 127 ? 127 : ranged_val;
+        ranged_val = ranged_val < -JOYSTICK_RESOLUTION ? -JOYSTICK_RESOLUTION : ranged_val;
+        ranged_val = ranged_val > JOYSTICK_RESOLUTION ? JOYSTICK_RESOLUTION : ranged_val;
 
         if (ranged_val != joystick_status.axes[axis_index]) {
             joystick_status.axes[axis_index] = ranged_val;
diff --git a/quantum/process_keycode/process_midi.c b/quantum/process_keycode/process_midi.c
index e525770144..8e2fb955e7 100644
--- a/quantum/process_keycode/process_midi.c
+++ b/quantum/process_keycode/process_midi.c
@@ -41,12 +41,12 @@ static int8_t   midi_modulation_step;
 static uint16_t midi_modulation_timer;
 midi_config_t   midi_config;
 
-inline uint8_t compute_velocity(uint8_t setting) { return (setting + 1) * (128 / (MIDI_VELOCITY_MAX - MIDI_VELOCITY_MIN + 1)); }
+inline uint8_t compute_velocity(uint8_t setting) { return setting * (128 / (MIDI_VELOCITY_MAX - MIDI_VELOCITY_MIN)); }
 
 void midi_init(void) {
     midi_config.octave              = MI_OCT_2 - MIDI_OCTAVE_MIN;
     midi_config.transpose           = 0;
-    midi_config.velocity            = (MIDI_VELOCITY_MAX - MIDI_VELOCITY_MIN);
+    midi_config.velocity            = 127;
     midi_config.channel             = 0;
     midi_config.modulation_interval = 8;
 
@@ -66,7 +66,7 @@ bool process_midi(uint16_t keycode, keyrecord_t *record) {
         case MIDI_TONE_MIN ... MIDI_TONE_MAX: {
             uint8_t channel  = midi_config.channel;
             uint8_t tone     = keycode - MIDI_TONE_MIN;
-            uint8_t velocity = compute_velocity(midi_config.velocity);
+            uint8_t velocity = midi_config.velocity;
             if (record->event.pressed) {
                 if (tone_status[tone] == MIDI_INVALID_NOTE) {
                     uint8_t note = midi_compute_note(keycode);
@@ -124,19 +124,30 @@ bool process_midi(uint16_t keycode, keyrecord_t *record) {
             return false;
         case MIDI_VELOCITY_MIN ... MIDI_VELOCITY_MAX:
             if (record->event.pressed) {
-                midi_config.velocity = keycode - MIDI_VELOCITY_MIN;
+                midi_config.velocity = compute_velocity(keycode - MIDI_VELOCITY_MIN);
                 dprintf("midi velocity %d\n", midi_config.velocity);
             }
             return false;
         case MI_VELD:
             if (record->event.pressed && midi_config.velocity > 0) {
-                midi_config.velocity--;
+                if (midi_config.velocity == 127) {
+                    midi_config.velocity -= 10;
+                } else if (midi_config.velocity > 12) {
+                    midi_config.velocity -= 13;
+                } else {
+                    midi_config.velocity = 0;
+                }
+
                 dprintf("midi velocity %d\n", midi_config.velocity);
             }
             return false;
         case MI_VELU:
-            if (record->event.pressed) {
-                midi_config.velocity++;
+            if (record->event.pressed && midi_config.velocity < 127) {
+                if (midi_config.velocity < 115) {
+                    midi_config.velocity += 13;
+                } else {
+                    midi_config.velocity = 127;
+                }
                 dprintf("midi velocity %d\n", midi_config.velocity);
             }
             return false;
diff --git a/quantum/process_keycode/process_midi.h b/quantum/process_keycode/process_midi.h
index 0007b3ed25..ef5661dd4d 100644
--- a/quantum/process_keycode/process_midi.h
+++ b/quantum/process_keycode/process_midi.h
@@ -35,7 +35,7 @@ typedef union {
     struct {
         uint8_t octave : 4;
         int8_t  transpose : 4;
-        uint8_t velocity : 4;
+        uint8_t velocity : 7;
         uint8_t channel : 4;
         uint8_t modulation_interval : 4;
     };
diff --git a/quantum/process_keycode/process_sequencer.c b/quantum/process_keycode/process_sequencer.c
new file mode 100644
index 0000000000..334b4c0092
--- /dev/null
+++ b/quantum/process_keycode/process_sequencer.c
@@ -0,0 +1,62 @@
+/* Copyright 2020 Rodolphe Belouin
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "process_sequencer.h"
+
+bool process_sequencer(uint16_t keycode, keyrecord_t *record) {
+    if (record->event.pressed) {
+        switch (keycode) {
+            case SQ_ON:
+                sequencer_on();
+                return false;
+            case SQ_OFF:
+                sequencer_off();
+                return false;
+            case SQ_TOG:
+                sequencer_toggle();
+                return false;
+            case SQ_TMPD:
+                sequencer_decrease_tempo();
+                return false;
+            case SQ_TMPU:
+                sequencer_increase_tempo();
+                return false;
+            case SEQUENCER_RESOLUTION_MIN ... SEQUENCER_RESOLUTION_MAX:
+                sequencer_set_resolution(keycode - SEQUENCER_RESOLUTION_MIN);
+                return false;
+            case SQ_RESD:
+                sequencer_decrease_resolution();
+                return false;
+            case SQ_RESU:
+                sequencer_increase_resolution();
+                return false;
+            case SQ_SALL:
+                sequencer_set_all_steps_on();
+                return false;
+            case SQ_SCLR:
+                sequencer_set_all_steps_off();
+                return false;
+            case SEQUENCER_STEP_MIN ... SEQUENCER_STEP_MAX:
+                sequencer_toggle_step(keycode - SEQUENCER_STEP_MIN);
+                return false;
+            case SEQUENCER_TRACK_MIN ... SEQUENCER_TRACK_MAX:
+                sequencer_toggle_single_active_track(keycode - SEQUENCER_TRACK_MIN);
+                return false;
+        }
+    }
+
+    return true;
+}
diff --git a/quantum/process_keycode/process_sequencer.h b/quantum/process_keycode/process_sequencer.h
new file mode 100644
index 0000000000..2b85f24299
--- /dev/null
+++ b/quantum/process_keycode/process_sequencer.h
@@ -0,0 +1,21 @@
+/* Copyright 2020 Rodolphe Belouin
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "quantum.h"
+
+bool process_sequencer(uint16_t keycode, keyrecord_t *record);
diff --git a/quantum/quantum.c b/quantum/quantum.c
index 0b2f98762d..3ac0ed8716 100644
--- a/quantum/quantum.c
+++ b/quantum/quantum.c
@@ -58,6 +58,10 @@ float bell_song[][2] = SONG(TERMINAL_SOUND);
 #    endif
 #endif
 
+#ifdef AUTO_SHIFT_ENABLE
+#    include "process_auto_shift.h"
+#endif
+
 static void do_code16(uint16_t code, void (*f)(uint8_t)) {
     switch (code) {
         case QK_MODS ... QK_MODS_MAX:
@@ -228,6 +232,9 @@ bool process_record_quantum(keyrecord_t *record) {
             process_record_via(keycode, record) &&
 #endif
             process_record_kb(keycode, record) &&
+#if defined(SEQUENCER_ENABLE)
+            process_sequencer(keycode, record) &&
+#endif
 #if defined(MIDI_ENABLE) && defined(MIDI_ADVANCED)
             process_midi(keycode, record) &&
 #endif
@@ -636,6 +643,10 @@ void matrix_scan_quantum() {
     matrix_scan_music();
 #endif
 
+#ifdef SEQUENCER_ENABLE
+    matrix_scan_sequencer();
+#endif
+
 #ifdef TAP_DANCE_ENABLE
     matrix_scan_tap_dance();
 #endif
@@ -664,6 +675,10 @@ void matrix_scan_quantum() {
     dip_switch_read(false);
 #endif
 
+#ifdef AUTO_SHIFT_ENABLE
+    autoshift_matrix_scan();
+#endif
+
     matrix_scan_kb();
 }
 
diff --git a/quantum/quantum.h b/quantum/quantum.h
index 0e452a062d..cb0af306ac 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -31,7 +31,7 @@
 
 #ifdef BACKLIGHT_ENABLE
 #    ifdef LED_MATRIX_ENABLE
-#        include "ledmatrix.h"
+#        include "led_matrix.h"
 #    else
 #        include "backlight.h"
 #    endif
@@ -68,6 +68,11 @@ extern layer_state_t default_layer_state;
 extern layer_state_t layer_state;
 #endif
 
+#if defined(SEQUENCER_ENABLE)
+#    include "sequencer.h"
+#    include "process_sequencer.h"
+#endif
+
 #if defined(MIDI_ENABLE) && defined(MIDI_ADVANCED)
 #    include "process_midi.h"
 #endif
@@ -220,6 +225,61 @@ typedef ioline_t pin_t;
 #    define togglePin(pin) palToggleLine(pin)
 #endif
 
+// Atomic macro to help make GPIO and other controls atomic.
+#ifdef IGNORE_ATOMIC_BLOCK
+/* do nothing atomic macro */
+#    define ATOMIC_BLOCK for (uint8_t __ToDo = 1; __ToDo; __ToDo = 0)
+#    define ATOMIC_BLOCK_RESTORESTATE ATOMIC_BLOCK
+#    define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK
+
+#elif defined(__AVR__)
+/* atomic macro for AVR */
+#    include <util/atomic.h>
+
+#    define ATOMIC_BLOCK_RESTORESTATE ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
+#    define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK(ATOMIC_FORCEON)
+
+#elif defined(PROTOCOL_CHIBIOS) || defined(PROTOCOL_ARM_ATSAM)
+/* atomic macro for ChibiOS / ARM ATSAM */
+#    if defined(PROTOCOL_ARM_ATSAM)
+#        include "arm_atsam_protocol.h"
+#    endif
+
+static __inline__ uint8_t __interrupt_disable__(void) {
+#    if defined(PROTOCOL_CHIBIOS)
+    chSysLock();
+#    endif
+#    if defined(PROTOCOL_ARM_ATSAM)
+    __disable_irq();
+#    endif
+    return 1;
+}
+
+static __inline__ void __interrupt_enable__(const uint8_t *__s) {
+#    if defined(PROTOCOL_CHIBIOS)
+    chSysUnlock();
+#    endif
+#    if defined(PROTOCOL_ARM_ATSAM)
+    __enable_irq();
+#    endif
+    __asm__ volatile("" ::: "memory");
+    (void)__s;
+}
+
+#    define ATOMIC_BLOCK(type) for (type, __ToDo = __interrupt_disable__(); __ToDo; __ToDo = 0)
+#    define ATOMIC_FORCEON uint8_t sreg_save __attribute__((__cleanup__(__interrupt_enable__))) = 0
+
+#    define ATOMIC_BLOCK_RESTORESTATE _Static_assert(0, "ATOMIC_BLOCK_RESTORESTATE dose not implement")
+#    define ATOMIC_BLOCK_FORCEON ATOMIC_BLOCK(ATOMIC_FORCEON)
+
+/* Other platform */
+#else
+
+#    define ATOMIC_BLOCK_RESTORESTATE _Static_assert(0, "ATOMIC_BLOCK_RESTORESTATE dose not implement")
+#    define ATOMIC_BLOCK_FORCEON _Static_assert(0, "ATOMIC_BLOCK_FORCEON dose not implement")
+
+#endif
+
 #define SEND_STRING(string) send_string_P(PSTR(string))
 #define SEND_STRING_DELAY(string, interval) send_string_with_delay_P(PSTR(string), interval)
 
diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h
index a0a7bc340f..a2cc7b38d9 100644
--- a/quantum/quantum_keycodes.h
+++ b/quantum/quantum_keycodes.h
@@ -16,6 +16,10 @@
 #ifndef QUANTUM_KEYCODES_H
 #define QUANTUM_KEYCODES_H
 
+#if defined(SEQUENCER_ENABLE)
+#    include "sequencer.h"
+#endif
+
 #ifndef MIDI_ENABLE_STRICT
 #    define MIDI_ENABLE_STRICT 0
 #endif
@@ -343,7 +347,8 @@ enum quantum_keycodes {
     MI_TRNSU,  // transpose up
 
     MIDI_VELOCITY_MIN,
-    MI_VEL_1 = MIDI_VELOCITY_MIN,
+    MI_VEL_0 = MIDI_VELOCITY_MIN,
+    MI_VEL_1,
     MI_VEL_2,
     MI_VEL_3,
     MI_VEL_4,
@@ -549,6 +554,37 @@ enum quantum_keycodes {
     JS_BUTTON31,
     JS_BUTTON_MAX = JS_BUTTON31,
 
+#if defined(SEQUENCER_ENABLE)
+    SQ_ON,
+    SQ_OFF,
+    SQ_TOG,
+
+    SQ_TMPD,  // Decrease tempo
+    SQ_TMPU,  // Increase tempo
+
+    SEQUENCER_RESOLUTION_MIN,
+    SEQUENCER_RESOLUTION_MAX = SEQUENCER_RESOLUTION_MIN + SEQUENCER_RESOLUTIONS,
+    SQ_RESD,  // Decrease resolution
+    SQ_RESU,  // Increase resolution
+
+    SQ_SALL,  // All steps on
+    SQ_SCLR,  // All steps off
+    SEQUENCER_STEP_MIN,
+    SEQUENCER_STEP_MAX = SEQUENCER_STEP_MIN + SEQUENCER_STEPS,
+
+    SEQUENCER_TRACK_MIN,
+    SEQUENCER_TRACK_MAX = SEQUENCER_TRACK_MIN + SEQUENCER_TRACKS,
+
+/**
+ * Helpers to assign a keycode to a step, a resolution, or a track.
+ * Falls back to NOOP if n is out of range.
+ */
+#    define SQ_S(n) (n < SEQUENCER_STEPS ? SEQUENCER_STEP_MIN + n : XXXXXXX)
+#    define SQ_R(n) (n < SEQUENCER_RESOLUTIONS ? SEQUENCER_RESOLUTION_MIN + n : XXXXXXX)
+#    define SQ_T(n) (n < SEQUENCER_TRACKS ? SEQUENCER_TRACK_MIN + n : XXXXXXX)
+
+#endif
+
     // always leave at the end
     SAFE_RANGE
 };
diff --git a/quantum/rgb_matrix.c b/quantum/rgb_matrix.c
index 802c5afcee..f239bd582f 100644
--- a/quantum/rgb_matrix.c
+++ b/quantum/rgb_matrix.c
@@ -31,6 +31,8 @@ const point_t k_rgb_matrix_center = {112, 32};
 const point_t k_rgb_matrix_center = RGB_MATRIX_CENTER;
 #endif
 
+__attribute__((weak)) RGB rgb_matrix_hsv_to_rgb(HSV hsv) { return hsv_to_rgb(hsv); }
+
 // Generic effect runners
 #include "rgb_matrix_runners/effect_runner_dx_dy_dist.h"
 #include "rgb_matrix_runners/effect_runner_dx_dy.h"
@@ -401,6 +403,10 @@ void rgb_matrix_task(void) {
             break;
         case RENDERING:
             rgb_task_render(effect);
+            if (!suspend_backlight) {
+                rgb_matrix_indicators();
+                rgb_matrix_indicators_advanced(&rgb_effect_params);
+            }
             break;
         case FLUSHING:
             rgb_task_flush(effect);
@@ -409,10 +415,6 @@ void rgb_matrix_task(void) {
             rgb_task_sync();
             break;
     }
-
-    if (!suspend_backlight) {
-        rgb_matrix_indicators();
-    }
 }
 
 void rgb_matrix_indicators(void) {
@@ -424,6 +426,28 @@ __attribute__((weak)) void rgb_matrix_indicators_kb(void) {}
 
 __attribute__((weak)) void rgb_matrix_indicators_user(void) {}
 
+void rgb_matrix_indicators_advanced(effect_params_t *params) {
+    /* special handling is needed for "params->iter", since it's already been incremented.
+     * Could move the invocations to rgb_task_render, but then it's missing a few checks
+     * and not sure which would be better. Otherwise, this should be called from
+     * rgb_task_render, right before the iter++ line.
+     */
+#if defined(RGB_MATRIX_LED_PROCESS_LIMIT) && RGB_MATRIX_LED_PROCESS_LIMIT > 0 && RGB_MATRIX_LED_PROCESS_LIMIT < DRIVER_LED_TOTAL
+    uint8_t min = RGB_MATRIX_LED_PROCESS_LIMIT * (params->iter - 1);
+    uint8_t max = min + RGB_MATRIX_LED_PROCESS_LIMIT;
+    if (max > DRIVER_LED_TOTAL) max = DRIVER_LED_TOTAL;
+#else
+    uint8_t min = 0;
+    uint8_t max = DRIVER_LED_TOTAL;
+#endif
+    rgb_matrix_indicators_advanced_kb(min, max);
+    rgb_matrix_indicators_advanced_user(min, max);
+}
+
+__attribute__((weak)) void rgb_matrix_indicators_advanced_kb(uint8_t led_min, uint8_t led_max) {}
+
+__attribute__((weak)) void rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) {}
+
 void rgb_matrix_init(void) {
     rgb_matrix_driver.init();
 
diff --git a/quantum/rgb_matrix.h b/quantum/rgb_matrix.h
index 733333349f..771a1fcd35 100644
--- a/quantum/rgb_matrix.h
+++ b/quantum/rgb_matrix.h
@@ -57,6 +57,11 @@
         uint8_t max = DRIVER_LED_TOTAL;
 #endif
 
+#define RGB_MATRIX_INDICATOR_SET_COLOR(i, r, g, b) \
+    if (i >= led_min && i <= led_max) {            \
+        rgb_matrix_set_color(i, r, g, b);          \
+    }
+
 #define RGB_MATRIX_TEST_LED_FLAGS() \
     if (!HAS_ANY_FLAGS(g_led_config.flags[i], params->flags)) continue
 
@@ -103,6 +108,10 @@ void rgb_matrix_indicators(void);
 void rgb_matrix_indicators_kb(void);
 void rgb_matrix_indicators_user(void);
 
+void rgb_matrix_indicators_advanced(effect_params_t *params);
+void rgb_matrix_indicators_advanced_kb(uint8_t led_min, uint8_t led_max);
+void rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max);
+
 void rgb_matrix_init(void);
 
 void        rgb_matrix_set_suspend_state(bool state);
diff --git a/quantum/rgb_matrix_animations/alpha_mods_anim.h b/quantum/rgb_matrix_animations/alpha_mods_anim.h
index 0778ab2098..426d88ef35 100644
--- a/quantum/rgb_matrix_animations/alpha_mods_anim.h
+++ b/quantum/rgb_matrix_animations/alpha_mods_anim.h
@@ -7,9 +7,9 @@ bool ALPHAS_MODS(effect_params_t* params) {
     RGB_MATRIX_USE_LIMITS(led_min, led_max);
 
     HSV hsv  = rgb_matrix_config.hsv;
-    RGB rgb1 = hsv_to_rgb(hsv);
+    RGB rgb1 = rgb_matrix_hsv_to_rgb(hsv);
     hsv.h += rgb_matrix_config.speed;
-    RGB rgb2 = hsv_to_rgb(hsv);
+    RGB rgb2 = rgb_matrix_hsv_to_rgb(hsv);
 
     for (uint8_t i = led_min; i < led_max; i++) {
         RGB_MATRIX_TEST_LED_FLAGS();
diff --git a/quantum/rgb_matrix_animations/breathing_anim.h b/quantum/rgb_matrix_animations/breathing_anim.h
index 887425f9da..340bd93e5d 100644
--- a/quantum/rgb_matrix_animations/breathing_anim.h
+++ b/quantum/rgb_matrix_animations/breathing_anim.h
@@ -8,7 +8,7 @@ bool BREATHING(effect_params_t* params) {
     HSV      hsv  = rgb_matrix_config.hsv;
     uint16_t time = scale16by8(g_rgb_timer, rgb_matrix_config.speed / 8);
     hsv.v         = scale8(abs8(sin8(time) - 128) * 2, hsv.v);
-    RGB rgb       = hsv_to_rgb(hsv);
+    RGB rgb       = rgb_matrix_hsv_to_rgb(hsv);
     for (uint8_t i = led_min; i < led_max; i++) {
         RGB_MATRIX_TEST_LED_FLAGS();
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
diff --git a/quantum/rgb_matrix_animations/gradient_left_right_anim.h b/quantum/rgb_matrix_animations/gradient_left_right_anim.h
index 2eab2eb759..53dfd04e2c 100644
--- a/quantum/rgb_matrix_animations/gradient_left_right_anim.h
+++ b/quantum/rgb_matrix_animations/gradient_left_right_anim.h
@@ -12,7 +12,7 @@ bool GRADIENT_LEFT_RIGHT(effect_params_t* params) {
         // The x range will be 0..224, map this to 0..7
         // Relies on hue being 8-bit and wrapping
         hsv.h   = rgb_matrix_config.hsv.h + (scale * g_led_config.point[i].x >> 5);
-        RGB rgb = hsv_to_rgb(hsv);
+        RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
     }
     return led_max < DRIVER_LED_TOTAL;
diff --git a/quantum/rgb_matrix_animations/gradient_up_down_anim.h b/quantum/rgb_matrix_animations/gradient_up_down_anim.h
index 0f1f8e23cf..7e0d2898cf 100644
--- a/quantum/rgb_matrix_animations/gradient_up_down_anim.h
+++ b/quantum/rgb_matrix_animations/gradient_up_down_anim.h
@@ -12,7 +12,7 @@ bool GRADIENT_UP_DOWN(effect_params_t* params) {
         // The y range will be 0..64, map this to 0..4
         // Relies on hue being 8-bit and wrapping
         hsv.h   = rgb_matrix_config.hsv.h + scale * (g_led_config.point[i].y >> 4);
-        RGB rgb = hsv_to_rgb(hsv);
+        RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
     }
     return led_max < DRIVER_LED_TOTAL;
diff --git a/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h b/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h
index ef2d1500b0..9493b38508 100644
--- a/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h
+++ b/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h
@@ -5,7 +5,7 @@ RGB_MATRIX_EFFECT(JELLYBEAN_RAINDROPS)
 static void jellybean_raindrops_set_color(int i, effect_params_t* params) {
     if (!HAS_ANY_FLAGS(g_led_config.flags[i], params->flags)) return;
     HSV hsv = {rand() & 0xFF, rand() & 0xFF, rgb_matrix_config.hsv.v};
-    RGB rgb = hsv_to_rgb(hsv);
+    RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
     rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
 }
 
diff --git a/quantum/rgb_matrix_animations/raindrops_anim.h b/quantum/rgb_matrix_animations/raindrops_anim.h
index 6e1b5acb0d..38359cdca7 100644
--- a/quantum/rgb_matrix_animations/raindrops_anim.h
+++ b/quantum/rgb_matrix_animations/raindrops_anim.h
@@ -15,7 +15,7 @@ static void raindrops_set_color(int i, effect_params_t* params) {
     }
 
     hsv.h   = rgb_matrix_config.hsv.h + (deltaH * (rand() & 0x03));
-    RGB rgb = hsv_to_rgb(hsv);
+    RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
     rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
 }
 
diff --git a/quantum/rgb_matrix_animations/solid_color_anim.h b/quantum/rgb_matrix_animations/solid_color_anim.h
index c8f5e70e7a..79d63cf133 100644
--- a/quantum/rgb_matrix_animations/solid_color_anim.h
+++ b/quantum/rgb_matrix_animations/solid_color_anim.h
@@ -4,7 +4,7 @@ RGB_MATRIX_EFFECT(SOLID_COLOR)
 bool SOLID_COLOR(effect_params_t* params) {
     RGB_MATRIX_USE_LIMITS(led_min, led_max);
 
-    RGB rgb = hsv_to_rgb(rgb_matrix_config.hsv);
+    RGB rgb = rgb_matrix_hsv_to_rgb(rgb_matrix_config.hsv);
     for (uint8_t i = led_min; i < led_max; i++) {
         RGB_MATRIX_TEST_LED_FLAGS();
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
diff --git a/quantum/rgb_matrix_animations/typing_heatmap_anim.h b/quantum/rgb_matrix_animations/typing_heatmap_anim.h
index e82c1b49ee..b855fdc190 100644
--- a/quantum/rgb_matrix_animations/typing_heatmap_anim.h
+++ b/quantum/rgb_matrix_animations/typing_heatmap_anim.h
@@ -51,7 +51,7 @@ bool TYPING_HEATMAP(effect_params_t* params) {
             if (!HAS_ANY_FLAGS(g_led_config.flags[led[j]], params->flags)) continue;
 
             HSV hsv = {170 - qsub8(val, 85), rgb_matrix_config.hsv.s, scale8((qadd8(170, val) - 170) * 3, rgb_matrix_config.hsv.v)};
-            RGB rgb = hsv_to_rgb(hsv);
+            RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
             rgb_matrix_set_color(led[j], rgb.r, rgb.g, rgb.b);
         }
 
diff --git a/quantum/rgb_matrix_runners/effect_runner_dx_dy.h b/quantum/rgb_matrix_runners/effect_runner_dx_dy.h
index 9d0c9fab19..4867609c81 100644
--- a/quantum/rgb_matrix_runners/effect_runner_dx_dy.h
+++ b/quantum/rgb_matrix_runners/effect_runner_dx_dy.h
@@ -10,7 +10,7 @@ bool effect_runner_dx_dy(effect_params_t* params, dx_dy_f effect_func) {
         RGB_MATRIX_TEST_LED_FLAGS();
         int16_t dx  = g_led_config.point[i].x - k_rgb_matrix_center.x;
         int16_t dy  = g_led_config.point[i].y - k_rgb_matrix_center.y;
-        RGB     rgb = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, time));
+        RGB     rgb = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, time));
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
     }
     return led_max < DRIVER_LED_TOTAL;
diff --git a/quantum/rgb_matrix_runners/effect_runner_dx_dy_dist.h b/quantum/rgb_matrix_runners/effect_runner_dx_dy_dist.h
index 2824c82527..9545b418d9 100644
--- a/quantum/rgb_matrix_runners/effect_runner_dx_dy_dist.h
+++ b/quantum/rgb_matrix_runners/effect_runner_dx_dy_dist.h
@@ -11,7 +11,7 @@ bool effect_runner_dx_dy_dist(effect_params_t* params, dx_dy_dist_f effect_func)
         int16_t dx   = g_led_config.point[i].x - k_rgb_matrix_center.x;
         int16_t dy   = g_led_config.point[i].y - k_rgb_matrix_center.y;
         uint8_t dist = sqrt16(dx * dx + dy * dy);
-        RGB     rgb  = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, dist, time));
+        RGB     rgb  = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, dist, time));
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
     }
     return led_max < DRIVER_LED_TOTAL;
diff --git a/quantum/rgb_matrix_runners/effect_runner_i.h b/quantum/rgb_matrix_runners/effect_runner_i.h
index 5e6bf5daaf..95bfe8b390 100644
--- a/quantum/rgb_matrix_runners/effect_runner_i.h
+++ b/quantum/rgb_matrix_runners/effect_runner_i.h
@@ -8,7 +8,7 @@ bool effect_runner_i(effect_params_t* params, i_f effect_func) {
     uint8_t time = scale16by8(g_rgb_timer, rgb_matrix_config.speed / 4);
     for (uint8_t i = led_min; i < led_max; i++) {
         RGB_MATRIX_TEST_LED_FLAGS();
-        RGB rgb = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, i, time));
+        RGB rgb = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, i, time));
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
     }
     return led_max < DRIVER_LED_TOTAL;
diff --git a/quantum/rgb_matrix_runners/effect_runner_reactive.h b/quantum/rgb_matrix_runners/effect_runner_reactive.h
index 53e77e3fb2..8485b61f3d 100644
--- a/quantum/rgb_matrix_runners/effect_runner_reactive.h
+++ b/quantum/rgb_matrix_runners/effect_runner_reactive.h
@@ -20,7 +20,7 @@ bool effect_runner_reactive(effect_params_t* params, reactive_f effect_func) {
         }
 
         uint16_t offset = scale16by8(tick, rgb_matrix_config.speed);
-        RGB      rgb    = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, offset));
+        RGB      rgb    = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, offset));
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
     }
     return led_max < DRIVER_LED_TOTAL;
diff --git a/quantum/rgb_matrix_runners/effect_runner_reactive_splash.h b/quantum/rgb_matrix_runners/effect_runner_reactive_splash.h
index b5d284a40f..5c69d0fbb9 100644
--- a/quantum/rgb_matrix_runners/effect_runner_reactive_splash.h
+++ b/quantum/rgb_matrix_runners/effect_runner_reactive_splash.h
@@ -20,7 +20,7 @@ bool effect_runner_reactive_splash(uint8_t start, effect_params_t* params, react
             hsv           = effect_func(hsv, dx, dy, dist, tick);
         }
         hsv.v   = scale8(hsv.v, rgb_matrix_config.hsv.v);
-        RGB rgb = hsv_to_rgb(hsv);
+        RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
     }
     return led_max < DRIVER_LED_TOTAL;
diff --git a/quantum/rgb_matrix_runners/effect_runner_sin_cos_i.h b/quantum/rgb_matrix_runners/effect_runner_sin_cos_i.h
index 3fb7d48051..02351de51e 100644
--- a/quantum/rgb_matrix_runners/effect_runner_sin_cos_i.h
+++ b/quantum/rgb_matrix_runners/effect_runner_sin_cos_i.h
@@ -10,7 +10,7 @@ bool effect_runner_sin_cos_i(effect_params_t* params, sin_cos_i_f effect_func) {
     int8_t   sin_value = sin8(time) - 128;
     for (uint8_t i = led_min; i < led_max; i++) {
         RGB_MATRIX_TEST_LED_FLAGS();
-        RGB rgb = hsv_to_rgb(effect_func(rgb_matrix_config.hsv, cos_value, sin_value, i, time));
+        RGB rgb = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, cos_value, sin_value, i, time));
         rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
     }
     return led_max < DRIVER_LED_TOTAL;
diff --git a/quantum/rgblight.c b/quantum/rgblight.c
index 76bb6eb8cb..7f9e330d37 100644
--- a/quantum/rgblight.c
+++ b/quantum/rgblight.c
@@ -123,9 +123,11 @@ void rgblight_set_effect_range(uint8_t start_pos, uint8_t num_leds) {
     rgblight_ranges.effect_num_leds  = num_leds;
 }
 
+__attribute__((weak)) RGB rgblight_hsv_to_rgb(HSV hsv) { return hsv_to_rgb(hsv); }
+
 void sethsv_raw(uint8_t hue, uint8_t sat, uint8_t val, LED_TYPE *led1) {
     HSV hsv = {hue, sat, val};
-    RGB rgb = hsv_to_rgb(hsv);
+    RGB rgb = rgblight_hsv_to_rgb(hsv);
     setrgb(rgb.r, rgb.g, rgb.b, led1);
 }
 
diff --git a/quantum/sequencer/sequencer.c b/quantum/sequencer/sequencer.c
new file mode 100644
index 0000000000..0eaf3a17aa
--- /dev/null
+++ b/quantum/sequencer/sequencer.c
@@ -0,0 +1,275 @@
+/* Copyright 2020 Rodolphe Belouin
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "sequencer.h"
+
+#ifdef MIDI_ENABLE
+#    include "process_midi.h"
+#endif
+
+#ifdef MIDI_MOCKED
+#    include "tests/midi_mock.h"
+#endif
+
+sequencer_config_t sequencer_config = {
+    false,     // enabled
+    {false},   // steps
+    {0},       // track notes
+    60,        // tempo
+    SQ_RES_4,  // resolution
+};
+
+sequencer_state_t sequencer_internal_state = {0, 0, 0, 0, SEQUENCER_PHASE_ATTACK};
+
+bool is_sequencer_on(void) { return sequencer_config.enabled; }
+
+void sequencer_on(void) {
+    dprintln("sequencer on");
+    sequencer_config.enabled               = true;
+    sequencer_internal_state.current_track = 0;
+    sequencer_internal_state.current_step  = 0;
+    sequencer_internal_state.timer         = timer_read();
+    sequencer_internal_state.phase         = SEQUENCER_PHASE_ATTACK;
+}
+
+void sequencer_off(void) {
+    dprintln("sequencer off");
+    sequencer_config.enabled              = false;
+    sequencer_internal_state.current_step = 0;
+}
+
+void sequencer_toggle(void) {
+    if (is_sequencer_on()) {
+        sequencer_off();
+    } else {
+        sequencer_on();
+    }
+}
+
+void sequencer_set_track_notes(const uint16_t track_notes[SEQUENCER_TRACKS]) {
+    for (uint8_t i = 0; i < SEQUENCER_TRACKS; i++) {
+        sequencer_config.track_notes[i] = track_notes[i];
+    }
+}
+
+bool is_sequencer_track_active(uint8_t track) { return (sequencer_internal_state.active_tracks >> track) & true; }
+
+void sequencer_set_track_activation(uint8_t track, bool value) {
+    if (value) {
+        sequencer_internal_state.active_tracks |= (1 << track);
+    } else {
+        sequencer_internal_state.active_tracks &= ~(1 << track);
+    }
+    dprintf("sequencer: track %d is %s\n", track, value ? "active" : "inactive");
+}
+
+void sequencer_toggle_track_activation(uint8_t track) { sequencer_set_track_activation(track, !is_sequencer_track_active(track)); }
+
+void sequencer_toggle_single_active_track(uint8_t track) {
+    if (is_sequencer_track_active(track)) {
+        sequencer_internal_state.active_tracks = 0;
+    } else {
+        sequencer_internal_state.active_tracks = 1 << track;
+    }
+}
+
+bool is_sequencer_step_on(uint8_t step) { return step < SEQUENCER_STEPS && (sequencer_config.steps[step] & sequencer_internal_state.active_tracks) > 0; }
+
+bool is_sequencer_step_on_for_track(uint8_t step, uint8_t track) { return step < SEQUENCER_STEPS && (sequencer_config.steps[step] >> track) & true; }
+
+void sequencer_set_step(uint8_t step, bool value) {
+    if (step < SEQUENCER_STEPS) {
+        if (value) {
+            sequencer_config.steps[step] |= sequencer_internal_state.active_tracks;
+        } else {
+            sequencer_config.steps[step] &= ~sequencer_internal_state.active_tracks;
+        }
+        dprintf("sequencer: step %d is %s\n", step, value ? "on" : "off");
+    } else {
+        dprintf("sequencer: step %d is out of range\n", step);
+    }
+}
+
+void sequencer_toggle_step(uint8_t step) {
+    if (is_sequencer_step_on(step)) {
+        sequencer_set_step_off(step);
+    } else {
+        sequencer_set_step_on(step);
+    }
+}
+
+void sequencer_set_all_steps(bool value) {
+    for (uint8_t step = 0; step < SEQUENCER_STEPS; step++) {
+        if (value) {
+            sequencer_config.steps[step] |= sequencer_internal_state.active_tracks;
+        } else {
+            sequencer_config.steps[step] &= ~sequencer_internal_state.active_tracks;
+        }
+    }
+    dprintf("sequencer: all steps are %s\n", value ? "on" : "off");
+}
+
+uint8_t sequencer_get_tempo(void) { return sequencer_config.tempo; }
+
+void sequencer_set_tempo(uint8_t tempo) {
+    if (tempo > 0) {
+        sequencer_config.tempo = tempo;
+        dprintf("sequencer: tempo set to %d bpm\n", tempo);
+    } else {
+        dprintln("sequencer: cannot set tempo to 0");
+    }
+}
+
+void sequencer_increase_tempo(void) {
+    // Handling potential uint8_t overflow
+    if (sequencer_config.tempo < UINT8_MAX) {
+        sequencer_set_tempo(sequencer_config.tempo + 1);
+    } else {
+        dprintf("sequencer: cannot set tempo above %d\n", UINT8_MAX);
+    }
+}
+
+void sequencer_decrease_tempo(void) { sequencer_set_tempo(sequencer_config.tempo - 1); }
+
+sequencer_resolution_t sequencer_get_resolution(void) { return sequencer_config.resolution; }
+
+void sequencer_set_resolution(sequencer_resolution_t resolution) {
+    if (resolution >= 0 && resolution < SEQUENCER_RESOLUTIONS) {
+        sequencer_config.resolution = resolution;
+        dprintf("sequencer: resolution set to %d\n", resolution);
+    } else {
+        dprintf("sequencer: resolution %d is out of range\n", resolution);
+    }
+}
+
+void sequencer_increase_resolution(void) { sequencer_set_resolution(sequencer_config.resolution + 1); }
+
+void sequencer_decrease_resolution(void) { sequencer_set_resolution(sequencer_config.resolution - 1); }
+
+uint8_t sequencer_get_current_step(void) { return sequencer_internal_state.current_step; }
+
+void sequencer_phase_attack(void) {
+    dprintf("sequencer: step %d\n", sequencer_internal_state.current_step);
+    dprintf("sequencer: time %d\n", timer_read());
+
+    if (sequencer_internal_state.current_track == 0) {
+        sequencer_internal_state.timer = timer_read();
+    }
+
+    if (timer_elapsed(sequencer_internal_state.timer) < sequencer_internal_state.current_track * SEQUENCER_TRACK_THROTTLE) {
+        return;
+    }
+
+#if defined(MIDI_ENABLE) || defined(MIDI_MOCKED)
+    if (is_sequencer_step_on_for_track(sequencer_internal_state.current_step, sequencer_internal_state.current_track)) {
+        process_midi_basic_noteon(midi_compute_note(sequencer_config.track_notes[sequencer_internal_state.current_track]));
+    }
+#endif
+
+    if (sequencer_internal_state.current_track < SEQUENCER_TRACKS - 1) {
+        sequencer_internal_state.current_track++;
+    } else {
+        sequencer_internal_state.phase = SEQUENCER_PHASE_RELEASE;
+    }
+}
+
+void sequencer_phase_release(void) {
+    if (timer_elapsed(sequencer_internal_state.timer) < SEQUENCER_PHASE_RELEASE_TIMEOUT + sequencer_internal_state.current_track * SEQUENCER_TRACK_THROTTLE) {
+        return;
+    }
+#if defined(MIDI_ENABLE) || defined(MIDI_MOCKED)
+    if (is_sequencer_step_on_for_track(sequencer_internal_state.current_step, sequencer_internal_state.current_track)) {
+        process_midi_basic_noteoff(midi_compute_note(sequencer_config.track_notes[sequencer_internal_state.current_track]));
+    }
+#endif
+    if (sequencer_internal_state.current_track > 0) {
+        sequencer_internal_state.current_track--;
+    } else {
+        sequencer_internal_state.phase = SEQUENCER_PHASE_PAUSE;
+    }
+}
+
+void sequencer_phase_pause(void) {
+    if (timer_elapsed(sequencer_internal_state.timer) < sequencer_get_step_duration()) {
+        return;
+    }
+
+    sequencer_internal_state.current_step = (sequencer_internal_state.current_step + 1) % SEQUENCER_STEPS;
+    sequencer_internal_state.phase        = SEQUENCER_PHASE_ATTACK;
+}
+
+void matrix_scan_sequencer(void) {
+    if (!sequencer_config.enabled) {
+        return;
+    }
+
+    if (sequencer_internal_state.phase == SEQUENCER_PHASE_PAUSE) {
+        sequencer_phase_pause();
+    }
+
+    if (sequencer_internal_state.phase == SEQUENCER_PHASE_RELEASE) {
+        sequencer_phase_release();
+    }
+
+    if (sequencer_internal_state.phase == SEQUENCER_PHASE_ATTACK) {
+        sequencer_phase_attack();
+    }
+}
+
+uint16_t sequencer_get_beat_duration(void) { return get_beat_duration(sequencer_config.tempo); }
+
+uint16_t sequencer_get_step_duration(void) { return get_step_duration(sequencer_config.tempo, sequencer_config.resolution); }
+
+uint16_t get_beat_duration(uint8_t tempo) {
+    // Don’t crash in the unlikely case where the given tempo is 0
+    if (tempo == 0) {
+        return get_beat_duration(60);
+    }
+
+    /**
+     * Given
+     *  t = tempo and d = duration, both strictly greater than 0
+     * When
+     *  t beats / minute = 1 beat / d ms
+     * Then
+     *  t beats / 60000ms = 1 beat / d ms
+     *  d ms = 60000ms / t
+     */
+    return 60000 / tempo;
+}
+
+uint16_t get_step_duration(uint8_t tempo, sequencer_resolution_t resolution) {
+    /**
+     * Resolution cheatsheet:
+     * 1/2  => 2 steps per 4 beats
+     * 1/2T => 3 steps per 4 beats
+     * 1/4  => 4 steps per 4 beats
+     * 1/4T => 6 steps per 4 beats
+     * 1/8  => 8 steps per 4 beats
+     * 1/8T => 12 steps per 4 beats
+     * 1/16 => 16 steps per 4 beats
+     * 1/16T => 24 steps per 4 beats
+     * 1/32 => 32 steps per 4 beats
+     *
+     * The number of steps for binary resolutions follows the powers of 2.
+     * The ternary variants are simply 1.5x faster.
+     */
+    bool     is_binary            = resolution % 2 == 0;
+    uint8_t  binary_steps         = 2 << (resolution / 2);
+    uint16_t binary_step_duration = get_beat_duration(tempo) * 4 / binary_steps;
+
+    return is_binary ? binary_step_duration : 2 * binary_step_duration / 3;
+}
diff --git a/quantum/sequencer/sequencer.h b/quantum/sequencer/sequencer.h
new file mode 100644
index 0000000000..aeca7a1e9b
--- /dev/null
+++ b/quantum/sequencer/sequencer.h
@@ -0,0 +1,122 @@
+/* Copyright 2020 Rodolphe Belouin
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include "debug.h"
+#include "timer.h"
+
+// Maximum number of steps: 256
+#ifndef SEQUENCER_STEPS
+#    define SEQUENCER_STEPS 16
+#endif
+
+// Maximum number of tracks: 8
+#ifndef SEQUENCER_TRACKS
+#    define SEQUENCER_TRACKS 8
+#endif
+
+#ifndef SEQUENCER_TRACK_THROTTLE
+#    define SEQUENCER_TRACK_THROTTLE 3
+#endif
+
+#ifndef SEQUENCER_PHASE_RELEASE_TIMEOUT
+#    define SEQUENCER_PHASE_RELEASE_TIMEOUT 30
+#endif
+
+/**
+ * Make sure that the items of this enumeration follow the powers of 2, separated by a ternary variant.
+ * Check the implementation of `get_step_duration` for further explanation.
+ */
+typedef enum { SQ_RES_2, SQ_RES_2T, SQ_RES_4, SQ_RES_4T, SQ_RES_8, SQ_RES_8T, SQ_RES_16, SQ_RES_16T, SQ_RES_32, SEQUENCER_RESOLUTIONS } sequencer_resolution_t;
+
+typedef struct {
+    bool                   enabled;
+    uint8_t                steps[SEQUENCER_STEPS];
+    uint16_t               track_notes[SEQUENCER_TRACKS];
+    uint8_t                tempo;  // Is a maximum tempo of 255 reasonable?
+    sequencer_resolution_t resolution;
+} sequencer_config_t;
+
+/**
+ * Because Digital Audio Workstations get overwhelmed when too many MIDI signals are sent concurrently,
+ * We use a "phase" state machine to delay some of the events.
+ */
+typedef enum sequencer_phase_t {
+    SEQUENCER_PHASE_ATTACK,   // t=0ms, send the MIDI note on signal
+    SEQUENCER_PHASE_RELEASE,  // t=SEQUENCER_PHASE_RELEASE_TIMEOUT ms, send the MIDI note off signal
+    SEQUENCER_PHASE_PAUSE     // t=step duration ms, loop
+} sequencer_phase_t;
+
+typedef struct {
+    uint8_t           active_tracks;
+    uint8_t           current_track;
+    uint8_t           current_step;
+    uint16_t          timer;
+    sequencer_phase_t phase;
+} sequencer_state_t;
+
+extern sequencer_config_t sequencer_config;
+
+// We expose the internal state to make the feature more "unit-testable"
+extern sequencer_state_t sequencer_internal_state;
+
+bool is_sequencer_on(void);
+void sequencer_toggle(void);
+void sequencer_on(void);
+void sequencer_off(void);
+
+void sequencer_set_track_notes(const uint16_t track_notes[SEQUENCER_TRACKS]);
+
+bool is_sequencer_track_active(uint8_t track);
+void sequencer_set_track_activation(uint8_t track, bool value);
+void sequencer_toggle_track_activation(uint8_t track);
+void sequencer_toggle_single_active_track(uint8_t track);
+
+#define sequencer_activate_track(track) sequencer_set_track_activation(track, true)
+#define sequencer_deactivate_track(track) sequencer_set_track_activation(track, false)
+
+bool is_sequencer_step_on(uint8_t step);
+bool is_sequencer_step_on_for_track(uint8_t step, uint8_t track);
+void sequencer_set_step(uint8_t step, bool value);
+void sequencer_toggle_step(uint8_t step);
+void sequencer_set_all_steps(bool value);
+
+#define sequencer_set_step_on(step) sequencer_set_step(step, true)
+#define sequencer_set_step_off(step) sequencer_set_step(step, false)
+#define sequencer_set_all_steps_on() sequencer_set_all_steps(true)
+#define sequencer_set_all_steps_off() sequencer_set_all_steps(false)
+
+uint8_t sequencer_get_tempo(void);
+void    sequencer_set_tempo(uint8_t tempo);
+void    sequencer_increase_tempo(void);
+void    sequencer_decrease_tempo(void);
+
+sequencer_resolution_t sequencer_get_resolution(void);
+void                   sequencer_set_resolution(sequencer_resolution_t resolution);
+void                   sequencer_increase_resolution(void);
+void                   sequencer_decrease_resolution(void);
+
+uint8_t sequencer_get_current_step(void);
+
+uint16_t sequencer_get_beat_duration(void);
+uint16_t sequencer_get_step_duration(void);
+
+uint16_t get_beat_duration(uint8_t tempo);
+uint16_t get_step_duration(uint8_t tempo, sequencer_resolution_t resolution);
+
+void matrix_scan_sequencer(void);
diff --git a/quantum/sequencer/tests/midi_mock.c b/quantum/sequencer/tests/midi_mock.c
new file mode 100644
index 0000000000..236e16f9d7
--- /dev/null
+++ b/quantum/sequencer/tests/midi_mock.c
@@ -0,0 +1,26 @@
+/* Copyright 2020 Rodolphe Belouin
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "midi_mock.h"
+
+uint16_t last_noteon  = 0;
+uint16_t last_noteoff = 0;
+
+uint16_t midi_compute_note(uint16_t keycode) { return keycode; }
+
+void process_midi_basic_noteon(uint16_t note) { last_noteon = note; }
+
+void process_midi_basic_noteoff(uint16_t note) { last_noteoff = note; }
diff --git a/quantum/sequencer/tests/midi_mock.h b/quantum/sequencer/tests/midi_mock.h
new file mode 100644
index 0000000000..4d8c2eb307
--- /dev/null
+++ b/quantum/sequencer/tests/midi_mock.h
@@ -0,0 +1,26 @@
+/* Copyright 2020 Rodolphe Belouin
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+extern uint16_t last_noteon;
+extern uint16_t last_noteoff;
+
+uint16_t midi_compute_note(uint16_t keycode);
+void     process_midi_basic_noteon(uint16_t note);
+void     process_midi_basic_noteoff(uint16_t note);
diff --git a/quantum/sequencer/tests/rules.mk b/quantum/sequencer/tests/rules.mk
new file mode 100644
index 0000000000..76c221cf92
--- /dev/null
+++ b/quantum/sequencer/tests/rules.mk
@@ -0,0 +1,11 @@
+# The letter case of these variables might seem odd. However:
+# - it is consistent with the serial_link example that is used as a reference in the Unit Testing article (https://docs.qmk.fm/#/unit_testing?id=adding-tests-for-new-or-existing-features)
+# - Neither `make test:sequencer` or `make test:SEQUENCER` work when using SCREAMING_SNAKE_CASE
+
+sequencer_DEFS := -DNO_DEBUG -DMIDI_MOCKED
+
+sequencer_SRC := \
+	$(QUANTUM_PATH)/sequencer/tests/midi_mock.c \
+	$(QUANTUM_PATH)/sequencer/tests/sequencer_tests.cpp \
+	$(QUANTUM_PATH)/sequencer/sequencer.c \
+	$(TMK_PATH)/common/test/timer.c
diff --git a/quantum/sequencer/tests/sequencer_tests.cpp b/quantum/sequencer/tests/sequencer_tests.cpp
new file mode 100644
index 0000000000..e81984e5b5
--- /dev/null
+++ b/quantum/sequencer/tests/sequencer_tests.cpp
@@ -0,0 +1,590 @@
+/* Copyright 2020 Rodolphe Belouin
+ *
+ * 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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "gtest/gtest.h"
+
+extern "C" {
+#include "sequencer.h"
+#include "midi_mock.h"
+#include "quantum/quantum_keycodes.h"
+}
+
+extern "C" {
+void set_time(uint32_t t);
+void advance_time(uint32_t ms);
+}
+
+class SequencerTest : public ::testing::Test {
+   protected:
+    void SetUp() override {
+        config_copy.enabled = sequencer_config.enabled;
+
+        for (int i = 0; i < SEQUENCER_STEPS; i++) {
+            config_copy.steps[i] = sequencer_config.steps[i];
+        }
+
+        for (int i = 0; i < SEQUENCER_TRACKS; i++) {
+            config_copy.track_notes[i] = sequencer_config.track_notes[i];
+        }
+
+        config_copy.tempo      = sequencer_config.tempo;
+        config_copy.resolution = sequencer_config.resolution;
+
+        state_copy.active_tracks = sequencer_internal_state.active_tracks;
+        state_copy.current_track = sequencer_internal_state.current_track;
+        state_copy.current_step  = sequencer_internal_state.current_step;
+        state_copy.timer         = sequencer_internal_state.timer;
+
+        last_noteon  = 0;
+        last_noteoff = 0;
+
+        set_time(0);
+    }
+
+    void TearDown() override {
+        sequencer_config.enabled = config_copy.enabled;
+
+        for (int i = 0; i < SEQUENCER_STEPS; i++) {
+            sequencer_config.steps[i] = config_copy.steps[i];
+        }
+
+        for (int i = 0; i < SEQUENCER_TRACKS; i++) {
+            sequencer_config.track_notes[i] = config_copy.track_notes[i];
+        }
+
+        sequencer_config.tempo      = config_copy.tempo;
+        sequencer_config.resolution = config_copy.resolution;
+
+        sequencer_internal_state.active_tracks = state_copy.active_tracks;
+        sequencer_internal_state.current_track = state_copy.current_track;
+        sequencer_internal_state.current_step  = state_copy.current_step;
+        sequencer_internal_state.timer         = state_copy.timer;
+    }
+
+    sequencer_config_t config_copy;
+    sequencer_state_t  state_copy;
+};
+
+TEST_F(SequencerTest, TestOffByDefault) { EXPECT_EQ(is_sequencer_on(), false); }
+
+TEST_F(SequencerTest, TestOn) {
+    sequencer_config.enabled = false;
+
+    sequencer_on();
+    EXPECT_EQ(is_sequencer_on(), true);
+
+    // sequencer_on is idempotent
+    sequencer_on();
+    EXPECT_EQ(is_sequencer_on(), true);
+}
+
+TEST_F(SequencerTest, TestOff) {
+    sequencer_config.enabled = true;
+
+    sequencer_off();
+    EXPECT_EQ(is_sequencer_on(), false);
+
+    // sequencer_off is idempotent
+    sequencer_off();
+    EXPECT_EQ(is_sequencer_on(), false);
+}
+
+TEST_F(SequencerTest, TestToggle) {
+    sequencer_config.enabled = false;
+
+    sequencer_toggle();
+    EXPECT_EQ(is_sequencer_on(), true);
+
+    sequencer_toggle();
+    EXPECT_EQ(is_sequencer_on(), false);
+}
+
+TEST_F(SequencerTest, TestNoActiveTrackByDefault) {
+    for (int i = 0; i < SEQUENCER_TRACKS; i++) {
+        EXPECT_EQ(is_sequencer_track_active(i), false);
+    }
+}
+
+TEST_F(SequencerTest, TestGetActiveTracks) {
+    sequencer_internal_state.active_tracks = (1 << 7) + (1 << 6) + (1 << 3) + (1 << 1) + (1 << 0);
+
+    EXPECT_EQ(is_sequencer_track_active(0), true);
+    EXPECT_EQ(is_sequencer_track_active(1), true);
+    EXPECT_EQ(is_sequencer_track_active(2), false);
+    EXPECT_EQ(is_sequencer_track_active(3), true);
+    EXPECT_EQ(is_sequencer_track_active(4), false);
+    EXPECT_EQ(is_sequencer_track_active(5), false);
+    EXPECT_EQ(is_sequencer_track_active(6), true);
+    EXPECT_EQ(is_sequencer_track_active(7), true);
+}
+
+TEST_F(SequencerTest, TestGetActiveTracksOutOfBound) {
+    sequencer_set_track_activation(-1, true);
+    sequencer_set_track_activation(8, true);
+
+    EXPECT_EQ(is_sequencer_track_active(-1), false);
+    EXPECT_EQ(is_sequencer_track_active(8), false);
+}
+
+TEST_F(SequencerTest, TestToggleTrackActivation) {
+    sequencer_internal_state.active_tracks = (1 << 7) + (1 << 6) + (1 << 3) + (1 << 1) + (1 << 0);
+
+    sequencer_toggle_track_activation(6);
+
+    EXPECT_EQ(is_sequencer_track_active(0), true);
+    EXPECT_EQ(is_sequencer_track_active(1), true);
+    EXPECT_EQ(is_sequencer_track_active(2), false);
+    EXPECT_EQ(is_sequencer_track_active(3), true);
+    EXPECT_EQ(is_sequencer_track_active(4), false);
+    EXPECT_EQ(is_sequencer_track_active(5), false);
+    EXPECT_EQ(is_sequencer_track_active(6), false);
+    EXPECT_EQ(is_sequencer_track_active(7), true);
+}
+
+TEST_F(SequencerTest, TestToggleSingleTrackActivation) {
+    sequencer_internal_state.active_tracks = (1 << 7) + (1 << 6) + (1 << 3) + (1 << 1) + (1 << 0);
+
+    sequencer_toggle_single_active_track(2);
+
+    EXPECT_EQ(is_sequencer_track_active(0), false);
+    EXPECT_EQ(is_sequencer_track_active(1), false);
+    EXPECT_EQ(is_sequencer_track_active(2), true);
+    EXPECT_EQ(is_sequencer_track_active(3), false);
+    EXPECT_EQ(is_sequencer_track_active(4), false);
+    EXPECT_EQ(is_sequencer_track_active(5), false);
+    EXPECT_EQ(is_sequencer_track_active(6), false);
+    EXPECT_EQ(is_sequencer_track_active(7), false);
+}
+
+TEST_F(SequencerTest, TestStepOffByDefault) {
+    for (int i = 0; i < SEQUENCER_STEPS; i++) {
+        EXPECT_EQ(is_sequencer_step_on(i), false);
+    }
+}
+
+TEST_F(SequencerTest, TestIsStepOffWithNoActiveTracks) {
+    sequencer_config.steps[3] = 0xFF;
+    EXPECT_EQ(is_sequencer_step_on(3), false);
+}
+
+TEST_F(SequencerTest, TestIsStepOffWithGivenActiveTracks) {
+    sequencer_set_track_activation(2, true);
+    sequencer_set_track_activation(3, true);
+
+    sequencer_config.steps[3] = (1 << 0) + (1 << 1);
+
+    // No active tracks have the step enabled, so it is off
+    EXPECT_EQ(is_sequencer_step_on(3), false);
+}
+
+TEST_F(SequencerTest, TestIsStepOnWithGivenActiveTracks) {
+    sequencer_set_track_activation(2, true);
+    sequencer_set_track_activation(3, true);
+
+    sequencer_config.steps[3] = (1 << 2);
+
+    // Track 2 has the step enabled, so it is on
+    EXPECT_EQ(is_sequencer_step_on(3), true);
+}
+
+TEST_F(SequencerTest, TestIsStepOffForGivenTrack) {
+    sequencer_config.steps[3] = 0x00;
+    EXPECT_EQ(is_sequencer_step_on_for_track(3, 5), false);
+}
+
+TEST_F(SequencerTest, TestIsStepOnForGivenTrack) {
+    sequencer_config.steps[3] = (1 << 5);
+    EXPECT_EQ(is_sequencer_step_on_for_track(3, 5), true);
+}
+
+TEST_F(SequencerTest, TestSetStepOn) {
+    sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2);
+    sequencer_config.steps[2]              = (1 << 5) + (1 << 2);
+
+    sequencer_set_step(2, true);
+
+    EXPECT_EQ(sequencer_config.steps[2], (1 << 6) + (1 << 5) + (1 << 3) + (1 << 2));
+}
+
+TEST_F(SequencerTest, TestSetStepOff) {
+    sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2);
+    sequencer_config.steps[2]              = (1 << 5) + (1 << 2);
+
+    sequencer_set_step(2, false);
+
+    EXPECT_EQ(sequencer_config.steps[2], (1 << 5));
+}
+
+TEST_F(SequencerTest, TestToggleStepOff) {
+    sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2);
+    sequencer_config.steps[2]              = (1 << 5) + (1 << 2);
+
+    sequencer_toggle_step(2);
+
+    EXPECT_EQ(sequencer_config.steps[2], (1 << 5));
+}
+
+TEST_F(SequencerTest, TestToggleStepOn) {
+    sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2);
+    sequencer_config.steps[2]              = 0;
+
+    sequencer_toggle_step(2);
+
+    EXPECT_EQ(sequencer_config.steps[2], (1 << 6) + (1 << 3) + (1 << 2));
+}
+
+TEST_F(SequencerTest, TestSetAllStepsOn) {
+    sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2);
+    sequencer_config.steps[2]              = (1 << 7) + (1 << 6);
+    sequencer_config.steps[4]              = (1 << 3) + (1 << 1);
+
+    sequencer_set_all_steps(true);
+
+    EXPECT_EQ(sequencer_config.steps[2], (1 << 7) + (1 << 6) + (1 << 3) + (1 << 2));
+    EXPECT_EQ(sequencer_config.steps[4], (1 << 6) + (1 << 3) + (1 << 2) + (1 << 1));
+}
+
+TEST_F(SequencerTest, TestSetAllStepsOff) {
+    sequencer_internal_state.active_tracks = (1 << 6) + (1 << 3) + (1 << 2);
+    sequencer_config.steps[2]              = (1 << 7) + (1 << 6);
+    sequencer_config.steps[4]              = (1 << 3) + (1 << 1);
+
+    sequencer_set_all_steps(false);
+
+    EXPECT_EQ(sequencer_config.steps[2], (1 << 7));
+    EXPECT_EQ(sequencer_config.steps[4], (1 << 1));
+}
+
+TEST_F(SequencerTest, TestSetTempoZero) {
+    sequencer_config.tempo = 123;
+
+    sequencer_set_tempo(0);
+
+    EXPECT_EQ(sequencer_config.tempo, 123);
+}
+
+TEST_F(SequencerTest, TestIncreaseTempoMax) {
+    sequencer_config.tempo = UINT8_MAX;
+
+    sequencer_increase_tempo();
+
+    EXPECT_EQ(sequencer_config.tempo, UINT8_MAX);
+}
+
+TEST_F(SequencerTest, TestSetResolutionLowerBound) {
+    sequencer_config.resolution = SQ_RES_4;
+
+    sequencer_set_resolution((sequencer_resolution_t)-1);
+
+    EXPECT_EQ(sequencer_config.resolution, SQ_RES_4);
+}
+
+TEST_F(SequencerTest, TestSetResolutionUpperBound) {
+    sequencer_config.resolution = SQ_RES_4;
+
+    sequencer_set_resolution(SEQUENCER_RESOLUTIONS);
+
+    EXPECT_EQ(sequencer_config.resolution, SQ_RES_4);
+}
+
+TEST_F(SequencerTest, TestGetBeatDuration) {
+    EXPECT_EQ(get_beat_duration(60), 1000);
+    EXPECT_EQ(get_beat_duration(120), 500);
+    EXPECT_EQ(get_beat_duration(240), 250);
+    EXPECT_EQ(get_beat_duration(0), 1000);
+}
+
+TEST_F(SequencerTest, TestGetStepDuration60) {
+    /**
+     * Resolution cheatsheet:
+     * 1/2  => 2 steps per 4 beats
+     * 1/2T => 3 steps per 4 beats
+     * 1/4  => 4 steps per 4 beats
+     * 1/4T => 6 steps per 4 beats
+     * 1/8  => 8 steps per 4 beats
+     * 1/8T => 12 steps per 4 beats
+     * 1/16 => 16 steps per 4 beats
+     * 1/16T => 24 steps per 4 beats
+     * 1/32 => 32 steps per 4 beats
+     *
+     * The number of steps for binary resolutions follows the powers of 2.
+     * The ternary variants are simply 1.5x faster.
+     */
+    EXPECT_EQ(get_step_duration(60, SQ_RES_2), 2000);
+    EXPECT_EQ(get_step_duration(60, SQ_RES_4), 1000);
+    EXPECT_EQ(get_step_duration(60, SQ_RES_8), 500);
+    EXPECT_EQ(get_step_duration(60, SQ_RES_16), 250);
+    EXPECT_EQ(get_step_duration(60, SQ_RES_32), 125);
+
+    EXPECT_EQ(get_step_duration(60, SQ_RES_2T), 1333);
+    EXPECT_EQ(get_step_duration(60, SQ_RES_4T), 666);
+    EXPECT_EQ(get_step_duration(60, SQ_RES_8T), 333);
+    EXPECT_EQ(get_step_duration(60, SQ_RES_16T), 166);
+}
+
+TEST_F(SequencerTest, TestGetStepDuration120) {
+    /**
+     * Resolution cheatsheet:
+     * 1/2  => 2 steps per 4 beats
+     * 1/2T => 3 steps per 4 beats
+     * 1/4  => 4 steps per 4 beats
+     * 1/4T => 6 steps per 4 beats
+     * 1/8  => 8 steps per 4 beats
+     * 1/8T => 12 steps per 4 beats
+     * 1/16 => 16 steps per 4 beats
+     * 1/16T => 24 steps per 4 beats
+     * 1/32 => 32 steps per 4 beats
+     *
+     * The number of steps for binary resolutions follows the powers of 2.
+     * The ternary variants are simply 1.5x faster.
+     */
+    EXPECT_EQ(get_step_duration(30, SQ_RES_2), 4000);
+    EXPECT_EQ(get_step_duration(30, SQ_RES_4), 2000);
+    EXPECT_EQ(get_step_duration(30, SQ_RES_8), 1000);
+    EXPECT_EQ(get_step_duration(30, SQ_RES_16), 500);
+    EXPECT_EQ(get_step_duration(30, SQ_RES_32), 250);
+
+    EXPECT_EQ(get_step_duration(30, SQ_RES_2T), 2666);
+    EXPECT_EQ(get_step_duration(30, SQ_RES_4T), 1333);
+    EXPECT_EQ(get_step_duration(30, SQ_RES_8T), 666);
+    EXPECT_EQ(get_step_duration(30, SQ_RES_16T), 333);
+}
+
+void setUpMatrixScanSequencerTest(void) {
+    sequencer_config.enabled    = true;
+    sequencer_config.tempo      = 120;
+    sequencer_config.resolution = SQ_RES_16;
+
+    // Configure the notes for each track
+    sequencer_config.track_notes[0] = MI_C;
+    sequencer_config.track_notes[1] = MI_D;
+    sequencer_config.track_notes[2] = MI_E;
+    sequencer_config.track_notes[3] = MI_F;
+    sequencer_config.track_notes[4] = MI_G;
+    sequencer_config.track_notes[5] = MI_A;
+    sequencer_config.track_notes[6] = MI_B;
+    sequencer_config.track_notes[7] = MI_C;
+
+    // Turn on some steps
+    sequencer_config.steps[0] = (1 << 0);
+    sequencer_config.steps[2] = (1 << 1) + (1 << 0);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldAttackFirstTrackOfFirstStep) {
+    setUpMatrixScanSequencerTest();
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(last_noteon, MI_C);
+    EXPECT_EQ(last_noteoff, 0);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldAttackSecondTrackAfterFirstTrackOfFirstStep) {
+    setUpMatrixScanSequencerTest();
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(sequencer_internal_state.current_step, 0);
+    EXPECT_EQ(sequencer_internal_state.current_track, 1);
+    EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_ATTACK);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldNotAttackInactiveTrackFirstStep) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 0;
+    sequencer_internal_state.current_track = 1;
+
+    // Wait some time after the first track has been attacked
+    advance_time(SEQUENCER_TRACK_THROTTLE);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(last_noteon, 0);
+    EXPECT_EQ(last_noteoff, 0);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldAttackThirdTrackAfterSecondTrackOfFirstStep) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 0;
+    sequencer_internal_state.current_track = 1;
+
+    // Wait some time after the second track has been attacked
+    advance_time(2 * SEQUENCER_TRACK_THROTTLE);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(sequencer_internal_state.current_step, 0);
+    EXPECT_EQ(sequencer_internal_state.current_track, 2);
+    EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_ATTACK);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldEnterReleasePhaseAfterLastTrackHasBeenProcessedFirstStep) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 0;
+    sequencer_internal_state.current_track = SEQUENCER_TRACKS - 1;
+
+    // Wait until all notes have been attacked
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(last_noteon, 0);
+    EXPECT_EQ(last_noteoff, 0);
+    EXPECT_EQ(sequencer_internal_state.current_step, 0);
+    EXPECT_EQ(sequencer_internal_state.current_track, SEQUENCER_TRACKS - 1);
+    EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_RELEASE);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldReleaseBackwards) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 0;
+    sequencer_internal_state.current_track = SEQUENCER_TRACKS - 1;
+    sequencer_internal_state.phase         = SEQUENCER_PHASE_RELEASE;
+
+    // Wait until all notes have been attacked
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+    // + the release timeout
+    advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(sequencer_internal_state.current_step, 0);
+    EXPECT_EQ(sequencer_internal_state.current_track, SEQUENCER_TRACKS - 2);
+    EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_RELEASE);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldNotReleaseInactiveTrackFirstStep) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 0;
+    sequencer_internal_state.current_track = SEQUENCER_TRACKS - 1;
+    sequencer_internal_state.phase         = SEQUENCER_PHASE_RELEASE;
+
+    // Wait until all notes have been attacked
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+    // + the release timeout
+    advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(last_noteon, 0);
+    EXPECT_EQ(last_noteoff, 0);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldReleaseFirstTrackFirstStep) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 0;
+    sequencer_internal_state.current_track = 0;
+    sequencer_internal_state.phase         = SEQUENCER_PHASE_RELEASE;
+
+    // Wait until all notes have been attacked
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+    // + the release timeout
+    advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT);
+    // + all the other notes have been released
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(last_noteon, 0);
+    EXPECT_EQ(last_noteoff, MI_C);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldEnterPausePhaseAfterRelease) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 0;
+    sequencer_internal_state.current_track = 0;
+    sequencer_internal_state.phase         = SEQUENCER_PHASE_RELEASE;
+
+    // Wait until all notes have been attacked
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+    // + the release timeout
+    advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT);
+    // + all the other notes have been released
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(sequencer_internal_state.current_step, 0);
+    EXPECT_EQ(sequencer_internal_state.current_track, 0);
+    EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_PAUSE);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldProcessFirstTrackOfSecondStepAfterPause) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 0;
+    sequencer_internal_state.current_track = 0;
+    sequencer_internal_state.phase         = SEQUENCER_PHASE_PAUSE;
+
+    // Wait until all notes have been attacked
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+    // + the release timeout
+    advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT);
+    // + all the other notes have been released
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+    // + the step duration (one 16th at tempo=120 lasts 125ms)
+    advance_time(125);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(sequencer_internal_state.current_step, 1);
+    EXPECT_EQ(sequencer_internal_state.current_track, 1);
+    EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_ATTACK);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldProcessSecondTrackTooEarly) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 2;
+    sequencer_internal_state.current_track = 1;
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(last_noteon, 0);
+    EXPECT_EQ(last_noteoff, 0);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldProcessSecondTrackOnTime) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = 2;
+    sequencer_internal_state.current_track = 1;
+
+    // Wait until first track has been attacked
+    advance_time(SEQUENCER_TRACK_THROTTLE);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(last_noteon, MI_D);
+    EXPECT_EQ(last_noteoff, 0);
+}
+
+TEST_F(SequencerTest, TestMatrixScanSequencerShouldLoopOnceSequenceIsOver) {
+    setUpMatrixScanSequencerTest();
+
+    sequencer_internal_state.current_step  = SEQUENCER_STEPS - 1;
+    sequencer_internal_state.current_track = 0;
+    sequencer_internal_state.phase         = SEQUENCER_PHASE_PAUSE;
+
+    // Wait until all notes have been attacked
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+    // + the release timeout
+    advance_time(SEQUENCER_PHASE_RELEASE_TIMEOUT);
+    // + all the other notes have been released
+    advance_time((SEQUENCER_TRACKS - 1) * SEQUENCER_TRACK_THROTTLE);
+    // + the step duration (one 16th at tempo=120 lasts 125ms)
+    advance_time(125);
+
+    matrix_scan_sequencer();
+    EXPECT_EQ(sequencer_internal_state.current_step, 0);
+    EXPECT_EQ(sequencer_internal_state.current_track, 1);
+    EXPECT_EQ(sequencer_internal_state.phase, SEQUENCER_PHASE_ATTACK);
+}
diff --git a/quantum/sequencer/tests/testlist.mk b/quantum/sequencer/tests/testlist.mk
new file mode 100644
index 0000000000..bb38991109
--- /dev/null
+++ b/quantum/sequencer/tests/testlist.mk
@@ -0,0 +1 @@
+TEST_LIST += sequencer
diff --git a/quantum/split_common/matrix.c b/quantum/split_common/matrix.c
index 5bad9db08f..cd5a024c3d 100644
--- a/quantum/split_common/matrix.c
+++ b/quantum/split_common/matrix.c
@@ -45,6 +45,19 @@ uint8_t thisHand, thatHand;
 // user-defined overridable functions
 __attribute__((weak)) void matrix_slave_scan_user(void) {}
 
+static inline void setPinOutput_writeLow(pin_t pin) {
+    ATOMIC_BLOCK_FORCEON {
+        setPinOutput(pin);
+        writePinLow(pin);
+    }
+}
+
+static inline void setPinInputHigh_atomic(pin_t pin) {
+    ATOMIC_BLOCK_FORCEON {
+        setPinInputHigh(pin);
+    }
+}
+
 // matrix code
 
 #ifdef DIRECT_PINS
@@ -83,22 +96,23 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)
 #    if (DIODE_DIRECTION == COL2ROW)
 
 static void select_row(uint8_t row) {
-    setPinOutput(row_pins[row]);
-    writePinLow(row_pins[row]);
+    setPinOutput_writeLow(row_pins[row]);
 }
 
-static void unselect_row(uint8_t row) { setPinInputHigh(row_pins[row]); }
+static void unselect_row(uint8_t row) {
+    setPinInputHigh_atomic(row_pins[row]);
+}
 
 static void unselect_rows(void) {
     for (uint8_t x = 0; x < ROWS_PER_HAND; x++) {
-        setPinInputHigh(row_pins[x]);
+        setPinInputHigh_atomic(row_pins[x]);
     }
 }
 
 static void init_pins(void) {
     unselect_rows();
     for (uint8_t x = 0; x < MATRIX_COLS; x++) {
-        setPinInputHigh(col_pins[x]);
+        setPinInputHigh_atomic(col_pins[x]);
     }
 }
 
@@ -133,22 +147,23 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)
 #    elif (DIODE_DIRECTION == ROW2COL)
 
 static void select_col(uint8_t col) {
-    setPinOutput(col_pins[col]);
-    writePinLow(col_pins[col]);
+    setPinOutput_writeLow(col_pins[col]);
 }
 
-static void unselect_col(uint8_t col) { setPinInputHigh(col_pins[col]); }
+static void unselect_col(uint8_t col) {
+    setPinInputHigh_atomic(col_pins[col]);
+}
 
 static void unselect_cols(void) {
     for (uint8_t x = 0; x < MATRIX_COLS; x++) {
-        setPinInputHigh(col_pins[x]);
+        setPinInputHigh_atomic(col_pins[x]);
     }
 }
 
 static void init_pins(void) {
     unselect_cols();
     for (uint8_t x = 0; x < ROWS_PER_HAND; x++) {
-        setPinInputHigh(row_pins[x]);
+        setPinInputHigh_atomic(row_pins[x]);
     }
 }