MAGDA API Reference
Every function, type, keycode and value of the MAGDA library (the QMK community module acheron/mag, directory mag/ of acheron_qmk_modules), grouped by layer. Each entry says who calls it, when, what it does, and what to watch out for.
- How to put a board on MAGDA, step by step: MAGDA (Magnetic Switches).
- Why things work the way they do (algorithms, data layout, design decisions): the specification, MAGDA architecture.
- Every
MAG_*configuration setting and its default: the specification, section 3.4.
Conventions
- Who uses what. Entries are marked board (a keyboard calls it, implements it or places it in a keymap) or internal (the library uses it; a board should not call it).
- Key index. Per-key functions take
idx = row * MATRIX_COLS + col, over the whole matrix, including positions without a key. - Units. Raw counts: ADC readings, 0 to
MAG_RAW_MAX(4095 for 12 bits). Travel: 0 (rest) toMAG_TRAVEL_MAX(1023, bottom). Percent: what users see; converted aspercent × MAG_TRAVEL_MAX / 100, rounded. How raw counts and travel relate: How magnetic switches work. - Context. Everything runs in QMK's main loop, never from an interrupt. Scanning is blocking; nothing here is thread-safe or reentrant, and nothing needs to be.
- Console output. Messages are printed with QMK's
uprintf, which produces output only in builds withCONSOLE_ENABLE = yes.
Call flow
power-up
QMK matrix_init() → matrix_init_custom() mag_hw_init, mag_calib_init, mag_act_init
QMK quantum_init() (eeconfig and the datablock become readable)
QMK keyboard_post_init() → keyboard_post_init_mag() → mag_init() load settings and calibration, boot rest capture
then board keyboard_post_init_kb()
every main-loop pass
QMK matrix_scan() → matrix_scan_custom() mag_hw_scan → per key: mag_calib_update → mag_act_update
(or one step of the calibration procedure)
QMK process_record() → process_record_mag() → mag_process_record() library keycodes
then board process_record_kb() (if not handled)
QMK housekeeping_task() → housekeeping_task_mag() → mag_task() delayed saves, MAG_DUMP output
then board housekeeping_task_kb()
configurator app, e.g. VIA (builds with VIA_ENABLE)
QMK via.c → board via_custom_value_command_kb() → mag_via_command()
Board-facing API (mag.h)
A board includes mag.h, which also brings in mag_config.h (defaults) and mag_hw.h (backend contract).
Module hooks (mag.c)
internal · QMK's community-module hooks, called by QMK before the board's own functions: keyboard_post_init_mag() calls mag_init(), process_record_mag() calls mag_process_record(), housekeeping_task_mag() calls mag_task(). Each then calls the module's _kb hook, which QMK generates as a weak function calling the _user one (keyboard_post_init_mag_kb() / _user() and so on); a board or keymap may override these to run code at the same point. The board does not call mag_init, mag_process_record or mag_task itself.
void mag_init(void)
internal · called once by keyboard_post_init_mag(), before the board's keyboard_post_init_kb().
Brings the library up once QMK's stored data is readable:
- Loads the module datablock into RAM (
mag_store_load). - Applies a stored user field exponent to the travel curve (
mag_curve_set_exponent). - Loads each present key's calibration: stored, else the factory table
mag_default_cal, else none (provisional). - Applies the stored global settings to every key (actuation point, rapid trigger and its distance;
mag_settings_apply) and callsmag_rapidtrigger_kb()with the stored state. - Runs
MAG_CAL_FRAMESblocking scans (64 by default) and averages them per key as the boot rest level (mag_calib_boot_sample/mag_calib_boot_done). - Enables scanning: until
mag_inithas run,matrix_scan_customreports no keys.
Notes: do not touch the keys during these first scans; a key held at power-up keeps its stored rest (see mag_calib_boot_done). mag_rapidtrigger_kb is first called here, before the board's keyboard_post_init_kb(): a board that updates an indicator from it records the state and shows it once the indicator is initialised, or initialises the indicator in keyboard_pre_init_kb().
bool mag_process_record(uint16_t keycode, keyrecord_t *record)
internal · called by process_record_mag(), which QMK runs before the board's process_record_kb().
Handles the library's keycodes (below). Returns false if keycode is a library keycode (handled: QMK stops there, and the board and user handlers do not see it), true otherwise (QMK continues with the module's _kb hook, then the board and user handlers).
void mag_task(void)
internal · called by housekeeping_task_mag(), before the board's housekeeping_task_kb().
Writes pending settings to the EEPROM MAG_SAVE_DELAY_MS after the last change (mag_store_task), and, while MAG_DUMP streaming is on, prints one frame every MAG_DUMP_INTERVAL_MS (console builds only). Cheap when there is nothing to do.
bool mag_via_command(uint8_t *data, uint8_t length)
board · call from via_custom_value_command_kb() in VIA builds (compiled under VIA_ENABLE).
Handles custom-value packets of QMK's VIA protocol (quantum/via.c), which configurator apps such as VIA use, for the library's values. This is how a board lets an app offer MAGDA's settings as sliders and switches; it is optional, and a board without it simply has no app control. data is the raw-HID packet [command, channel, value_id, value…], answered in place; length its length. Returns true if it handled the packet; on false the board sets data[0] = id_unhandled.
| Command | Behaviour |
|---|---|
id_custom_set_value |
sets the value (table below), applies it, prints the new value, schedules a delayed save |
id_custom_get_value |
writes the current value into data[3] |
id_custom_save |
saves immediately; accepted with any value id (VIA sends it per channel without one) |
#ifdef VIA_ENABLE
# include "via.h"
void via_custom_value_command_kb(uint8_t *data, uint8_t length) {
if (!mag_via_command(data, length)) {
data[0] = id_unhandled;
}
}
#endif
VIA value ids
| Id | Value | Range | Set applies |
|---|---|---|---|
MAG_VIA_ACTUATION = 1 |
actuation point, percent | 10–90, rounded to 5 | to every key (held keys on release); prints Actuation NN% |
MAG_VIA_EXPONENT = 2 |
field exponent × 10 | 5–60 (0.5–6.0) | rebuilds the travel curve; ignored unless MAG_TRAVEL_CURVE is MAG_CURVE_POWER (get returns 0 then) |
MAG_VIA_RAPID_TRIGGER = 3 |
rapid trigger | 0 off, 1 on | like MAG_RT_TOG when the state changes |
MAG_VIA_RT_DISTANCE = 4 |
rapid-trigger distance, percent | 1–20 | to every key; prints Rapid trigger distance NN% |
Keycodes
Starting at QK_KB_0, in this order (VIA's custom keycodes follow it). A board's own keycodes start at MAG_KC_LAST + 1.
| Keycode | Acts on | Effect |
|---|---|---|
MAG_CAL |
release | starts the calibration procedure (ignored while it runs); on release so the key is up when rest levels are measured |
MAG_DUMP |
press | toggles console streaming of raw and travel frames |
MAG_CAL_DUMP |
press | prints the calibration and settings in use, with their source, and a pasteable mag_default_cal table |
MAG_ACT_UP |
press | actuation point +5 % (up to 90 %); prints Actuation NN% |
MAG_ACT_DN |
press | actuation point −5 % (down to 10 %) |
MAG_RT_TOG |
press | rapid trigger on/off; prints Rapid trigger on/off; calls mag_rapidtrigger_kb() |
MAG_KC_LAST |
— | equal to the last library keycode (MAG_RT_TOG) |
MAG_DUMP and MAG_CAL_DUMP do nothing without CONSOLE_ENABLE.
void mag_calibration_mode_kb(bool active)
board · optional; weak no-op in the library.
Called with true when calibration starts (Calibration mode started) and with false when it completes or times out. Use it for an indicator; while calibration runs no keys are sent to the computer, so the user relies on it and on the console. Praxis HE lights its three LEDs white.
void mag_rapidtrigger_kb(bool active)
board · optional; weak no-op in the library.
Called once from mag_init with the stored state, then whenever rapid trigger is switched (keycode, or a configurator app through mag_via_command). Praxis HE lights LED D4 while it is on.
Backend contract (mag_hw.h)
The hardware side. A board either implements these itself, or uses the stock mux_adc driver, selected in its post_rules.mk with MAG_DRIVER = mux_adc; the driver then provides mag_hw_init and mag_hw_scan.
void mag_hw_init(void)
board (or mux_adc) · called once by matrix_init_custom, before anything else.
Sets up the sensing hardware. mux_adc: select pins as outputs (low), enable asserted (and left asserted), ADC pins in analog mode, one ADC1 conversion group with one channel per multiplexer at MAG_ADC_SAMPLE_TIME, adcStart.
void mag_hw_scan(uint16_t raw[MATRIX_ROWS][MATRIX_COLS])
board (or mux_adc) · called by matrix_scan_custom once per main-loop pass, and MAG_CAL_FRAMES times by mag_init.
Fills raw with one raw reading per matrix position (one frame), blocking until done. Readings must be monotonic in key travel (either direction, any scale). Values at positions not in mag_key_mask are ignored by the library but still shown by MAG_DUMP (useful: grounded inputs should read about 0).
mux_adc, per select step: write the select lines, wait_us(MAG_SETTLE_US), adcConvert (DMA, blocking), store one reading per multiplexer. Its duration sets the scan rate, which in turn sets how fast rest drift and the filter act (both count frames).
const matrix_row_t mag_key_mask[MATRIX_ROWS]
board · required data.
One bit per matrix position, set where a sensor with a switch is fitted. Positions not set are never processed, calibrated or reported.
bool mag_hw_selftest(void)
board · optional; weak default returns true.
Meant to return false on a detected hardware fault (e.g. grounded inputs not reading 0). The library does not call it yet; when to run it and what to do on failure is undecided.
mag_cal_t and const mag_cal_t mag_default_cal[MATRIX_ROWS][MATRIX_COLS]
board · optional data; weak default all zero.
typedef struct {
uint16_t rest; // raw count with the key released
uint16_t bottom; // raw count with the key fully pressed
} mag_cal_t;
The factory calibration: used for a key that has no stored calibration of its own (a new board, or after EE_CLR). rest == bottom (e.g. {0, 0}) means "no factory value" for that position. MAG_CAL_DUMP prints a ready-made initializer.
QMK entry points (mag_matrix.c)
internal · the library implements QMK's custom-matrix interface ("matrix_pins": {"custom_lite": true} in the board's keyboard.json); QMK calls these, a board does not.
void matrix_init_custom(void)
Called by QMK's matrix_init() before eeconfig is ready. Calls mag_hw_init, mag_calib_init (all keys uncalibrated), mag_act_init (default settings). Stored data is not read here; that happens in mag_init.
bool matrix_scan_custom(matrix_row_t current_matrix[])
Called by QMK's matrix_scan() once per main-loop pass. Returns true if any bit in current_matrix changed.
- Before
mag_inithas run: returnsfalseand reports nothing. - Normal operation: one
mag_hw_scan, then for each present keymag_calib_update(with the key's current matrix bit aspressed) and, if the key is valid,mag_act_update; the result is the key's bit. Invalid keys still pass throughmag_calib_updateso filtering and drift stay current. - After
MAG_CALwas released: starts the calibration procedure. While it runs, every key is still filtered, one step of the procedure runs per frame, and the matrix reports no keys, so nothing reaches the computer and no keycodes are processed. The procedure and its console messages: specification, section 4.4.
Core: calibration (mag_calib.h)
internal · pure C with no QMK dependencies, host-tested (mag/tests/). Turns raw readings into travel, per key.
void mag_calib_init(void)
Clears every key: uncalibrated, no rest level (which reads as the 0 rail, so the key is invalid until a rest is set), filter empty. Builds the travel curve (mag_curve_init). Called by matrix_init_custom.
void mag_calib_set(uint16_t idx, uint16_t rest, uint16_t bottom)
Loads a key's calibration in raw counts (stored, factory or just measured). rest == bottom leaves the key uncalibrated (provisional actuation). Derives direction (bottom above or below rest), validity and the travel-curve constants.
void mag_calib_boot_sample(uint16_t idx, uint16_t raw) · void mag_calib_boot_done(uint16_t idx)
Boot rest capture, used by mag_init: feed MAG_CAL_FRAMES raw readings with boot_sample, then call boot_done. The average becomes the key's rest, except for a calibrated key whose average is farther than MAG_REST_WINDOW from its calibrated rest: that key is assumed held at power-up and keeps its rest. An uncalibrated key always takes the average.
uint16_t mag_calib_update(uint16_t idx, uint16_t raw, bool pressed)
The per-scan step. Returns travel (0 … MAG_TRAVEL_MAX).
- Filters
raw(moving average, strengthMAG_FILTER_SHIFT). - Rest drift: only while
pressedisfalseand the filtered value is withinMAG_REST_WINDOWof rest, rest moves toward it by 1/2^MAG_DRIFT_SHIFTper call. - Travel:
- invalid key: 0;
- uncalibrated key:
MAG_TRAVEL_MAXwhile |raw − rest| >MAG_MIN_SPAN, else 0 (provisional actuation); - calibrated key: the filtered reading, clamped to the rest–bottom interval, converted by the travel curve if it fits the key, else linearly.
pressed must be the key's current actuation state (its matrix bit), so drift never follows a pressed key.
bool mag_calib_valid(uint16_t idx)
false if the key's rest is within MAG_RAIL_MARGIN of 0 or MAG_RAW_MAX (including "no rest yet"), or if it is calibrated with a span below MAG_MIN_SPAN. Invalid keys never actuate.
void mag_calib_refresh_curve(void)
Recomputes every key's travel-curve constants. Call immediately after mag_curve_set_exponent.
uint16_t mag_calib_filtered(uint16_t idx)
The key's current filtered reading in raw counts (as of the last mag_calib_update). Used by the calibration procedure to find each key's deepest reading.
uint16_t mag_calib_get_rest(uint16_t idx)
The key's current rest level in raw counts: the calibrated rest as updated by boot capture and drift. Shown as now by MAG_CAL_DUMP.
Core: actuation (mag_actuation.h)
internal · pure C, host-tested. Turns travel into pressed / released, per key.
Types
typedef enum { MAG_MODE_FIXED, MAG_MODE_RAPID } mag_act_mode_t;
typedef struct { // all distances in travel units
uint8_t mode; // mag_act_mode_t
uint16_t actuation; // press point
uint16_t hysteresis; // release (fixed) / reset (rapid) point = actuation − hysteresis
uint16_t rapidtrigger_down; // rapid trigger: rise from the trough that presses again
uint16_t rapidtrigger_up; // rapid trigger: drop from the peak that releases
} mag_act_settings_t;
void mag_act_init(void)
Resets every key to released, with the default settings: fixed mode, MAG_ACTUATION_DEFAULT, MAG_HYSTERESIS_DEFAULT, MAG_RT_DOWN_DEFAULT, MAG_RT_UP_DEFAULT.
void mag_act_set(uint16_t idx, const mag_act_settings_t *s)
Gives one key new settings. Clamps actuation to 1 … MAG_TRAVEL_MAX and both rapid-trigger distances to at least 1. A released key takes them at once. A key that is down (pressed, or armed in rapid trigger) keeps its current settings until it is fully released and below the new settings' release point, so a change never presses or releases a key by itself.
bool mag_act_update(uint16_t idx, uint16_t travel)
The per-scan step. Returns the key's state (true = pressed).
- Fixed: press at travel ≥
actuation; release at travel <actuation − hysteresis(at 0 ifhysteresis ≥ actuation). - Rapid: arms and presses at travel ≥
actuation; while armed, releases when travel dropsrapidtrigger_upbelow its deepest point, and presses again when it risesrapidtrigger_downabove its highest point since the release; fully resets (released, disarmed) at travel <actuation − hysteresis.
Travel curve (mag_curve.h)
internal · one implementation is compiled, chosen by MAG_TRAVEL_CURVE (curves/linear.c, curves/power.c). A new curve implements these four functions; see the specification, section 3.2.
void mag_curve_init(void)
Called once by mag_calib_init; precompute here. power: builds a lookup table of \(1/\lvert\text{field}\rvert^{1/n}\) for every field magnitude (8 KB of RAM for 12-bit readings).
float mag_curve_position(uint16_t reading)
A coordinate proportional to the magnet's physical position for a raw reading, with any scale, offset and sign (mag_calib normalises it per key). Runs for every key on every scan: must be cheap. power: one table lookup.
bool mag_curve_fits(uint16_t rest, uint16_t bottom)
Whether the model applies to a key with this calibration; if not, the key's travel stays linear. linear: always false. power: rest and bottom on the same side of MAG_ZERO_FIELD, the field growing from rest to bottom, and a rest field of at least 16 counts.
void mag_curve_set_exponent(float n)
Replaces the curve's exponent at runtime (the user value, set through mag_via_command); curves without an exponent ignore it. power rebuilds its table (a few ms). Call mag_calib_refresh_curve() immediately after: until then, keys' curve constants belong to the old curve.
Storage (mag_store.h)
internal · a RAM copy of the module datablock (QMK's EECONFIG_MODULE_MAG_DATA_SIZE region, defined in the module's config.h). Layout and version tag: specification, section 4.2.
| Function | Does |
|---|---|
void mag_store_load(void) |
reads the datablock into RAM (called by mag_init); a block with a wrong version tag reads as all zero |
mag_cal_t mag_store_get_cal(uint16_t idx) / void mag_store_put_cal(uint16_t idx, mag_cal_t cal) |
a key's stored calibration; rest == bottom = none |
uint16_t mag_store_get_actuation(void) / void mag_store_put_actuation(uint16_t) |
global actuation point, travel units; 0 = not set |
bool mag_store_get_rapidtrigger(void) / void mag_store_put_rapidtrigger(bool) |
rapid trigger on or off |
uint16_t mag_store_get_exponent(void) / void mag_store_put_exponent(uint16_t n100) |
user field exponent × 100; 0 = not set (use MAG_FIELD_EXPONENT) |
uint8_t mag_store_get_rapidtrigger_distance(void) / void mag_store_put_rapidtrigger_distance(uint8_t pct) |
rapid-trigger distance in percent; 0 = not set (use the MAG_RT_*_DEFAULT values); clamped to 63 |
void mag_store_save(void) |
writes the RAM copy to the EEPROM now |
void mag_store_task(void) |
from mag_task: writes once MAG_SAVE_DELAY_MS have passed since the last put |
Every put only changes the RAM copy and marks it dirty; the write follows from mag_store_task, or at once with mag_store_save (calibration and VIA's save command use that). The rapid-trigger flag and distance share bits with the actuation and exponent fields; each accessor reads and writes only its own bits.
Library internals
internal · shared between the library's own files; not for boards.
| Name | Where | Role |
|---|---|---|
void mag_settings_apply(void) |
mag_process.c |
builds one mag_act_settings_t from the store (actuation point, rapid trigger, distance; hysteresis MAG_HYSTERESIS_DEFAULT) and passes it to mag_act_set for every key; used by mag_init, the actuation keycodes, MAG_RT_TOG and VIA |
bool mag_dump_enabled |
mag_process.c |
MAG_DUMP streaming on or off; read by mag_task |
bool mag_cal_requested |
mag_process.c |
set on the release of MAG_CAL; consumed by matrix_scan_custom |