-
Notifications
You must be signed in to change notification settings - Fork 128
QMK
- Caution: nobody really uses QMK for nrf5x (critical bugs, licensing issues, etc.), go ZMK.
This article is about an experimental nrf52 QMK branch. It wasn't updated for a while. Not recommended in favor of ZMK.
- There's an article about the new Zephyr-based ZMK firmware, it's work in progress but it's a standard de-facto: ZMK
- There's an article about alternative firmware by jpconstantineau, it's much simpler than QMK (recommended!): Bluemicro
- Articles about certain QMK-based keyboards are here: http://github.com/joric/qmk/wiki (Jorne BLE gets most updates)
- Also read about my take on open-sourcing the BLE-Micro-Pro "Default Firmware" bootloader BMPAPI (doesn't work yet)
- Also check out Circuitpython article, it's a new hip thing that already kind of works (needs more people!)
If you got your nRFMicro without a preinstalled bootloader, read the Bootloader article first.
The nRF5x-compatible nrf52 QMK fork is maintaned by sekigon-gonnoc. My copy is always a little bit outdated, so please base all your keyboards off his repository. You will also need my custom_board.h for the nRFMicro pin definitions. Note that nrf52 fork will never be officially merged into QMK, because QMK apparently сonflicts with the Nordic licensing terms.
Issue tracker: https://github.com/sekigon-gonnoc/qmk_firmware/issues
- https://github.com/sekigon-gonnoc/qmk_firmware/tree/nrf52 (original fork by sekigon)
- https://github.com/joric/qmk/tree/nrf52-jorne (my fork, RGB sync and EEPROM, see jorne_ble)
See other repositories in the Alternative firmware section.
This section is about sekigon's QMK fork.
To flash the keyboard firmware using a standard .uf2 bootloader you have to convert standard .hex to the UF2 format, using uf2conv.py:
uf2conv.py firmware.hex -c -f 0xADA52840
Press reset button twice within 500ms interval to boot to the UF2 bootloader.
Then just copy the resulting flash.uf2 file to the UF2 USB drive (split keyboards require either master or slave firmware file accordingly). The module will automatically reboot after firmware update.
You can get example precompiled firmware here:
You may try sekigon's nrf52 fork, but it's probably easier to get jorne fork, because it's already fixed:
You will need nRF52 SDK (namely nRF5_SDK_15.0.0_a53641a) to build the firmware:
Unpack it into a directory, then use path in your build scripts, e.g. export NRFSDK15_ROOT=/mnt/c/SDK/nRF5_SDK_15.0.0_a53641a
.
Upd. If you're using WSL2 (I am using WSL2 now, it builds much faster), copy everyting to the Linux partition
to the home directory, e.g. export NRFSDK15_ROOT=~/nRF5_SDK_15.0.0_a53641a
WSL1 is 10+ times faster than WSL2 if you work with NTFS partition. You will need following packages:
sudo apt-get install make gcc unzip wget zip gcc-avr binutils-avr avr-libc dfu-programmer dfu-util gcc-arm-none-eabi binutils-arm-none-eabi libnewlib-arm-none-eabi
Make sure that arm-gcc --version is at least 5.4.0 (I'm using WSL on Windows 10 with Ubuntu 18.04).
You might need to patch the makefile tmk_core/nrf.mk beforehand if you're using WSL.
- error: 'for' loop initial declarations are only allowed in C99 or C11 mode:
-CC = arm-none-eabi-gcc
+CC = arm-none-eabi-gcc -std=c99
- get rid of wchar_t size warnings:
# LDFLAGS += -L. $(NRFLIB)
+ LDFLAGS += -Wl,--no-wchar-size-warning
You can build nrf52 QMK fork as so (paths are WSL-compatible):
make git-submodule
export NRFSDK15_ROOT=/mnt/c/SDK/nRF5_SDK_15.0.0_a53641a
make ergo42_ble/master
make ergo42_ble/slave
You have to flash Master and Slave firmwares separately, there's no unified firmware for both halves.
See example firmware and all the hardware keyboard shortcuts here: https://github.com/joric/qmk/wiki/jorne_ble
This was not tested with nrf52, so do that on your own risk. I've never tried that.
TL;DR install msys2, read (or run) qmk/util/msys2_install.sh for dependencies.
Couldn't make it work but you could start from here:
- https://distortos.org/documentation/arm-toolchain-windows/
- https://launchpad.net/gcc-arm-embedded/
- https://github.com/gnu-mcu-eclipse/windows-build-tools/releases
The easiest way is to install nRF52 boards from Arduino IDE and add toolchain to the path. "Bleeding edge" ARM toolchain also looks promising, but needs rebuilding.
- MacOS Mojave 10.14.4
- MacBook Pro (Retina, 13-inch, Late 2012)
- nRF52-DK (PCA10040)
- Install GCC
brew tap PX4/homebrew-px4
brew update
brew install gcc-arm-none-eabi
- Download and unzip inside
~/Development/nRF52/
-
nRF5-SDK 15.3.0 in
SDK_15.3.0
folder -
nRF5 Command Line Tools 9.8.1 in
CLT_9.8.1
folder
- Symlink command line tools
ln -s ~/Development/nRF52/CLT_9.8.1/nrfjprog/nrfjprog /usr/local/bin/nrfjprog
ln -s ~/Development/nRF52/CLT_9.8.1/mergehex/mergehex /usr/local/bin/mergehex
- Change the nRF SDK to use my version of arm-gcc changing the file
~/Development/nRF52/SDK_15.3.0/components/toolchain/gcc/Makefile.posix
to reflect the location of my homebrew installed version
GNU_INSTALL_ROOT := /usr/local/Cellar/gcc-arm-none-eabi/20180627/bin
GNU_VERSION := 7.3.1
GNU_PREFIX := arm-none-eabi
cd ~/Development/nRF52/SDK_15.3.0/examples/peripheral/blinky/pca10040/blank/armgcc
make
make flash
A concise version of Aaron Eiche's Programming an nRF52 on a Mac
(Mac OS guide taken from here.)
Every nrf52 keyboard automatically creates two virtual interfaces on a single USB port - USB HID and USB Serial for debugging. Use terminal at 115200 baud to debug. You can debug both Master and Slave using two USB cables and two ports.
All the nrf info messages are also redirected there. It's also bidirectional so it's a whole debug console. I'm using Putty serial at 115200 baud, you can try something like screen /dev/ttyACM0 115200
on Linux.
Example output (send "dfu" to reboot into bootloader, send "help" for help):
<info> app: Erase all bonds!
<info> app: Fast advertising.
<info> app: Slave keyboard is disconnected
<info> app: Scanning slave keyboard...
<info> app: Connected to the slave keyboard
Example debug string:
NRF_LOG_INFO("process_record_user, keycode: %d\n", keycode);
Stock QMK nrf52 is pretty laggy. Use these settings from let's split BLE config.h to make it better.
#define BLE_NUS_MIN_INTERVAL 30 // default 60
#define BLE_NUS_MAX_INTERVAL 60 // default 75
#define BLE_HID_MAX_INTERVAL 80 // default 90
#define BLE_HID_SLAVE_LATENCY 10 // default 4
It's all usually the smaller the faster, and bigger values save the battery life (e.g. use smaller BLE_HID_SLAVE_LATENCY
if master-slave link lags). More info here https://github.com/sekigon-gonnoc/BLE-Micro-Pro (in Japanese). Also this guy talks about lags on QMK nrf52 (in Japanese): https://log.brdr.jp/post/395. Also follow this reddit thread: https://redd.it/brcxha.
How the RGB sync feature works basically: master is NUS (Nordic UART Service) central, slave is peripheral, the connection is bidirectional so along with receiving keystrokes from slave master also can send RGB sync packet to slave using ble_nus_c_string_send
and slave can receive that data with ble_nus_recv_bytes
. There's no much traffic, it doesn't affect the battery, it sends RGB config once when you change the mode. Related links (the commit adds reverse communication from master to slave and RGB sync, the eeprom file adds eeprom support because sekigon just used a stub):
- https://github.com/sekigon-gonnoc/qmk_firmware/issues/28 (enabling LEDs from battery)
- https://github.com/joric/qmk/commit/b6363608f97c91fdfb4b28cb1c48832b1bb8e9b2 (rgb sync commit)
- https://github.com/joric/qmk/blob/nrf52-jorne/tmk_core/common/nrf/eeprom.c (eeprom)
- https://youtu.be/db0lufQQNZY (video)
Power mosfet (pin 1.09, since version 1.0) disconnects external GND to improve battery life for power hungry switchboards such as HHKB and SK6812 RGB LEDs (each LED consumes ~1mA of current when OFF). Keyboard matrix still works no matter what, because matrix connects to GND internally via MCU. Note that in 1.3 you have to init this pin because mosfet gate is not pulled down. Usage:
#define POWER_PIN GPIO(1,9)
...
nrf_gpio_cfg_output(POWER_PIN);
...
nrf_gpio_pin_clear(POWER_PIN); // set power on
...
nrf_gpio_pin_set(POWER_PIN); // set power off
Note that all the newer versions use logical 1 to turn the power off (1.1 worked in reverse). MCU retains GPIO state so is fine.
Versions 1.2+ can control charger PROG pin (it's connected via 10K resistor), it can be used for setting charger modes:
#define PROG_PIN GPIO(0,5)
...
nrf_gpio_cfg_output(PROG_PIN); nrf_gpio_pin_clear(PROG_PIN); // PROG to GND via 10K, ~100 mA (80+ mAh batteries)
...
nrf_gpio_cfg_input(PROG_PIN, NRF_GPIO_PIN_NOPULL); // PROG to float/high-z (non-chargeable batteries)
...
nrf_gpio_cfg_input(PROG_PIN, NRF_GPIO_PIN_PULLDONW); // PROG to GND via 10K+13K, ~43 mA (45+ mAh LIR2032 batteries)
Don't disable the charger if the battery is full, charger IC has a built in control for that. Status pin (STAT) is not available in nRFMicro 1.3 revisions (there's no red LED also) but you can check the battery voltage from the battery pin.
Note that In 1.3 you absolutely have to init and set the PROG_PIN properly.
Pin 0.26 (revisions up to 1.1) is connected to the battery switch via 10K resistor and detects whether the battery switch (SW1) is ON or OFF in wired mode. The switch disconnects the battery from the BT module, but not from the Li-Po charger. Used as wireless/wired mode flag in Jian BLE while USB is plugged in:
#define SWITCH_PIN GPIO(0,26)
...
nrf_gpio_cfg_input(SWITCH_PIN, NRF_GPIO_PIN_PULLDOWN);
...
if (nrf_gpio_pin_read(SWITCH_PIN)) {
// battery switch is ON
} else {
// battery switch is OFF
}
Switch pin is obsolete in 1.2+ version because there's no physical on/off switch on board anymore.
I was under impression 0.26 could be configured as an analog pin but it only detects two logic levels. After bridging 0.26 to analog pin (currently 0.04/AIN2) it's possible to read the actual voltage. If you use 10K resistor and the internal pull-down of 13kOhms, you get a useable range of 2.37V (at 4.2V) to 1.36 (at 2.4V). Newer revisions use fixed 820K/2M divider. Code in question (adc.c):
void adc_start() {
#ifdef USE_BATTERY_PIN
nrf_saadc_channel_config_t pincfg = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2); // pin 0.04
#else
nrf_saadc_channel_config_t pincfg = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_VDD);
#endif
nrf_drv_saadc_channel_init(0, &pincfg);
nrf_drv_saadc_buffer_convert(adc_buffer, 1);
nrf_drv_saadc_sample();
}
uint16_t get_vcc() {
#ifdef USE_BATTERY_PIN
return ((uint32_t)adc_buffer[0]*6*600/255) * (BATTERY_R1 + BATTERY_R2) / BATTERY_R2;
#else
return ((uint32_t)adc_buffer[0]*6*600/255);
#endif
}
One could experience problems with Bluetooth pairing between the halves or to the computer.
Hardware shortcut that resets bonds on the slave keyboard could be hard to find. Check out the code, it's usually first 3 keys on the row0 (hold while powering on).
Note that in the old version to reconnect master to the computer you had to restart advertising without whitelist yet again, it did not connect automatically (new define ENABLE_STARTUP_ADV_NOLIST
fixes that issue). You also need to reset the bonds beforehand (it's easier on master, usually defined in the keymap).
- https://github.com/jpconstantineau/BlueMicro_BLE (BlueMicro firmware)
- https://github.com/joric/bmpapi (Open-source version of the BLE-Micro-Pro "Default Firmware" bootloader)
- https://docs.nicekeyboards.com (Nice!nano documentation)
- https://github.com/Lenbok/qmk_firmware/ (integration, staging and features by Lenbok)
- https://github.com/chie4hao/qmk_firmware_nrf52840 (I took eeprom.c from here)
- https://github.com/jiaxin96/qmk/ (feature branches, e.g. nrf52-jiaxin-r2c)
- https://github.com/luantty2/qmk (includes EEPROM support, maybe more, haven't really checked)
- https://github.com/luantty2/nRF52840-instruction (some documentation and encoder support, in Chinese)
- https://github.com/tsiank/dev_qmk_nrf52840_ciank (something QMK, also see other repositories)
- https://github.com/Lotlab/nrf52-keyboard (TMK-based nRF52840 firmware)
- https://github.com/mrzealot/absolem-firmware (mrzealot's Absolem Firmware for nRF52 and Arduino IDE)
- https://github.com/zmkfirmware/zmk (Zephyr-based Mechanical Keyboard Firmware)
- https://www.zephyrproject.org (GPL2+ licensed Bluetooth platform, but it's not a library). Doesn't use Nordic's softdevice.
- https://github.com/apache/mynewt-nimble (NimBLE - open source SoftDevice for Nordic, same license as ChibiOS) looks promising for merging into QMK upstream. Sadly up to date NimBLE lacks any keyboard-related samples or even reference HID implementation.
nRFMicro 1.1 vs QMK nrf52 - RGB Sync
This API is specific to the closed source bootloader for the Japanese board, "BLE-Micro-Pro" by sekigon. If you're not interested in reverse-engineering this board and its bootloader just skip this chapter, use ZMK or QMK instead.
BMPAPI stands for BLE-Micro-Pro API. My open source version doesn't work for now, see ZMK or QMK for the working stuff.
There's a QMK dev/ble_micro_pro branch (based on the abandoned nrf52 branch) that can import JSON keyboard layouts from the USB drive. It needs a binary blob that exposes a set of nRF52-based API (BMPAPI) functions at address 0xFDE00 (see apidef.h), this is an attempt to weasel out of the Nordic/QMK licensing issues (are there any?), so the BMPAPI-based QMK branch could be eventially merged into the QMK upstream. Looks like it's totally one way to do it.
The binary blob is distributed as UF2 and it's called "BLE Micro Pro Bootloader". Unfortunately it was closed source last time I checked and it's impossible to proxy because it only gives access to a fixed set of BLE-Micro-Pro pins, so you'd need to rewrite it from scratch. If you want it opensourced, bother @_gonnok here: https://discord.gg/MqufYtS.
As of May 2020 I have no idea if he is going to opensource this closed source blob. I can't even tell if he has slightest intention to do so or not. Closed source aside, there's a duplication of so much stuff so it ends up with its own matrix, encoder, etc. implementation rather than implementing the current GPIO interface, so it still won't get merged in its current form.
Another possible workaround to redefine pins using the standard BMP bootloader would be using nrf_gpio_pin_read() instead of read_pin() in the QMK (see matrix_basic.c file). You can start from my "Older version" setup below (dev/ble_micro_pro QMK branch). It breaks licensing, but you get useful BMP features, such as JSON config files.
- https://github.com/sekigon-gonnoc/BLE-Micro-Pro (BLE Micro Pro "bootloader" releases and documentation)
- https://github.com/sekigon-gonnoc/qmk_firmware/tree/dev/ble_micro_pro (QMK branch that uses BMPAPI)
- https://sekigon-gonnoc.github.io/BLE-Micro-Pro-WebConfigurator (BLE Micro Pro Web Configurator)
- https://sekigon-gonnoc.github.io/qmk_configurator (BLE Micro Pro QMK Configurator)
- https://github.com/sekigon-gonnoc/qmk_firmware/tree/nrf52 (nRF52 QMK branch)
- https://github.com/joric/bmpapi (my take on BMPAPI)
I got API working at much higher address (0xA0000 instead of 0xFDE00). Looks like Adafruit bootloader is refusing to rewrite higher address which is of course reserved for bootloader itself. So it's either change the API address to a lower one in the QMK firmware or ditch Adafruit bootloader altogether which is kind of a major nuisance.
It's possible to update bootloader from uf2 with rewriting fuses from the userspace app and proper bootstrapping. The easiest way to make it work would be adding BMPAPI to the Adafruit nRF52 Bootloader and rebuilding the bootloader, but it needs a lot of SWD flashing at least at the initial stage.
The way I'm doing it now is just two userspace apps, a hardware layer (bmpapi.uf2), and a QMK app (ble_micro_pro_default.uf2) that I flash next to each other in arbitrary order (it's also really easy to merge them into a single uf2 file).
The main issue for now is that USB CDC doesn't work as two apps, it only works as a single app. I have no idea why it's happening, but USB only works in a monolithic firmware built with origin 0x26000. Tried to change the upload order but it doesn't help. Probably linker script issues.
The latest version is in the nRF5x folder, it's currently has a built in launcher to test USB code.
if you want to run it from the another launcher app you just have to set bmpapi.ld
back to default (+0x16000 from start):
- FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0xD2000
+ FLASH (rx) : ORIGIN = 0x26000+0x16000, LENGTH = 0xD2000-0x16000
That would need a separate launcher app at 0x26000 (max size 0x16000), either my test launcher from the launcher folder or the BMPAPI QMK version. I haven't even tried calling BLE softdevice functions from the blob, probably there would be the same problems as with USB CDC (init calls missing or something, not sure why it doesn't work, blinker app works just fine).
- 2020-06-25 Trying to run USB CDC in nrf5x (hangs in app_usbd_power_events_enable, only works as a single app)
- 2020-06-24 Created launcher, QMK, nrf5x and minimal configurations (exported at 0xA0000), calls API, blinks.
- Burn the Adafruit bootloader with the SWD programmer (skip this if you already have a bootloader)
- Flash QMK as ble_micro_pro_default.uf2 (or use launcher app for testing)
- Flash BMPAPI as bmpapi.uf2
To flash the .uf2 file, press reset twice withing 500 ms interval, then drag and drop the file to the appeared USB drive.
It works just fine with QMK firmware at 0x26000 with length 0x16000 (90kB) and BMPAPI exported at 0xA0000 (BMPAPI firmware starts from 0x26000+0x16000 with length 0xD2000-0x16000, so it occupies the rest of the FLASH memory).
TL;DR: just flash those two .uf2 files (ble_micro_pro_default.uf2 and bmpapi.uf2) in any order:
- https://github.com/joric/bmpapi/raw/master/precompiled/ble_micro_pro_default.uf2 (QMK built for BMPAPI)
- https://github.com/joric/bmpapi/raw/master/precompiled/bmpapi.uf2 (BMPAPI built for nRF5x/nRFMicro)
Repository: https://github.com/joric/bmpapi
The nRF5 SDK have to be changed in components/toolchain/gcc/Makefile.posix
, the line: GNU_INSTALL_ROOT := /usr/local/gcc-arm-none-eabi-4_9-2015q1
replaced with: GNU_INSTALL_ROOT := /usr/bin/
(same as with mitosis firmware).
git clone https://github.com/joric/bmpapi && cd bmpapi
cd minimal
export NRFSDK15_ROOT=~/nRF5_SDK_15.0.0_a53641a && make
python uf2conv.py .build/nrf52840_xxaa.hex -c -f 0xADA52840 -o bmpapi.uf2
The "minimal" version just blinks for now. The nrf5x version is in progress (it's just a matter of porting the nrf52 branch).
Repository: https://github.com/sekigon-gonnoc/qmk_firmware
It should be only done once. There are "default" and "no_msc" keymaps, the latter disables "Mass Storage Class" (USB drive). The whole point of BMPAPI is that QMK branch (dev/ble_micro_pro) doesn't have any Nordic dependencies, only generic ARM.
git clone https://github.com/sekigon-gonnoc/qmk_firmware && cd qmk_firmware
git checkout dev/ble_micro_pro
Fix keyboards/ble_micro_pro/ld/nrf52840_ao.ld
:
FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0x16000
Fix tmk_core/protocol/nrf/sdk15/apidef.h
:
#define BMPAPI ((bmp_api_t*)0xA0000)
You can also use my branch that's already fixed: https://github.com/joric/qmk/tree/dev/ble_micro_pro
Build the rest of QMK:
make git-submodule
make ble_micro_pro:default
python uf2conv.py .build/ble_micro_pro_default.hex -c -f 0xADA52840 -o ble_micro_pro_default.uf2
You can get uf2conv.py here: https://github.com/microsoft/uf2/blob/master/utils/uf2conv.py
There's also another experimental repository (obsolete):
- https://github.com/joric/qmk/tree/qmk_bmpapi (QMK-based experimental version)
This firmware is based on the dev/ble_micro_pro branch and my version of the BLE-Micro-Pro "Default Firmware" binary blob (BMPAPI) ported from nrf52 branch, but instead of using a separate binary blob I'm building everything as a single uf2 file, it's a little bit easier to test and debug on initial stages. What it currently does:
- adds bmpapi folder with all the Nordic SDK-dependent code
- modifies nrf.mk to add Nordic SDK files to the compilation
- modifies apidef.h so
BMPAPI
is an extern symbol and not0xFDE00
- modifies bmp.c to add
bmpapi_init()
call, filling theBMPAPI
structure
It does just a few very basic things (will be eventually deleted and merged into bmpapi repository):
- Pointers to functions are filled, firmware compiles and runs, doesn't crash
- ws2812-based RGB strip, built in nRFMicro status LED
- virtual COM port via USB, microshell and dfu handler
Figures the monolithic version can actually make USB work (can't make it work in split version so far).
BMPAPI uses its own set of pins (PIN1 ... PIN21) there's no access to arbirary nRF5x pins. See bmp_api_gpio_t structure:
typedef struct {
bmp_error_t (*set_mode)(uint8_t pin_num, bmp_api_gpio_mode_t const* const);
uint32_t (*read_pin)(uint8_t pin_num);
bmp_error_t (*set_pin)(uint8_t pin_num);
bmp_error_t (*clear_pin)(uint8_t pin_num);
} bmp_api_gpio_t;
That means stock BMPAPI couldn't run on boards other than BLE Micro Pro (pin mapping is almost always different on different boards, there are hardware limitations). This is the main reason for reimplementing this closed source blob from scratch, patching it inplace seems a much harder task than rewriting it (pin references are pretty much scattered across the address space). Also I haven't managed to run this binary blob on the nRFMicro just yet, it just hangs (probably stock Adafruit bootloader issues, but it won't work as intended anyway).
First, add this to the linker script:
SECTIONS
{
.api_table 0xFDE00:
{
KEEP(*(.api_table))
} > FLASH
}
Then prepare the content in C like this:
void bootloader_jump(void) { }
void reset(uint32_t _) { }
void enter_sleep_mode(void) { }
typedef struct
{
// int32_t (*init)(bmp_api_config_t const * const);
void (*reset)(uint32_t);
void (*enter_sleep_mode)(void);
void (*main_task_start)(void(*main_task)(void*), uint8_t interval_ms);
/*
void (*process_task)(void);
bmp_error_t (*push_keystate_change)(bmp_api_key_event_t const * const key);
uint32_t (*pop_keystate_change)(bmp_api_key_event_t* key, uint32_t len, uint8_t burst_threshold);
uint16_t (*keymap_key_to_keycode)(uint8_t layer, bmp_api_keypos_t const * const key);
bmp_error_t (*set_keymap)(const uint16_t* keymap, uint16_t len, const char * layout_name);
bmp_error_t (*set_config)(bmp_api_config_t const * const);
bmp_error_t (*get_keymap_info)(bmp_api_keymap_info_t* const keymap_info);
const bmp_api_config_t* (*get_config)(void);
bmp_error_t (*save_file)(uint8_t file_id);
bmp_error_t (*delete_file)(uint8_t file_id);
bmp_error_t (*get_file)(uint8_t file_id, uint8_t **buf, uint32_t * const len);
uint16_t (*get_vcc_mv)(void);
bmp_error_t (*set_state_change_cb)(bmp_api_state_change_cb_t);
*/
} bmp_api_app_t;
typedef struct
{
//////DO NOT CHANGE///////
uint32_t api_version;
void (*bootloader_jump)(void);
/////////////////////////
const char* (*get_bootloader_info)(void);
bmp_api_app_t app;
/*
bmp_api_usb_t usb;
bmp_api_ble_t ble;
bmp_api_gpio_t gpio;
bmp_api_i2c_master_t i2cm;
bmp_api_i2c_slave_t i2cs;
bmp_api_spi_master_t spim;
bmp_api_ws2812_t ws2812;
bmp_api_logger_t logger;
bmp_api_web_config_t web_config;
bmp_api_encoder_t encoder;
bmp_api_adc_t adc;
*/
} bmp_api_t;
__attribute__((section(".api_table")))
bmp_api_t API_TABLE = {
.api_version = 0x01020304,
.bootloader_jump = bootloader_jump,
.app = { reset, enter_sleep_mode, /* ... */ },
// ...
};
You can export the bmp_api_t structure at 0xFDE00 with the define in the linker ld file, no issues here. All pointers to functions should be filled somehow, either in compile time or dynamically. Mind that there's no init call in the QMK. it just starts from BMPAPI->api_version
and then BMPAPI->bootloader_jump()
and then right away it's BMPAPI->logger.init()
as the next call. It's either linker can set them properly at the compile time or some kind of the init call (similar to dllmain function) should fill up the structures. I'm not sure if everything could be settled up in compile time or I still would need an init call.
You can examine the original bootloader dump:
od -Ax -tx1 ble_micro_pro_bootloader_0_5_1.uf2 | less
- bootloader starts at 0xE0000 (00 00 0e 00)
- api table starts from 00 de 0e 00
- UICR region starts from 0x10001000
Address 0x10001014 (13 UICR — User information configuration registers) is called NRFFW[0] and it is where MBR looks to know the location of the main application. It this case bootloader is the main application. and qmk is sort of child application launched from bootloader.
Use objdump to make sure if it's properly compiled:
arm-none-eabi-objdump -d -D -C _build/nrf52840_xxaa.out | less
Example BMPAPI builds fine with this .ld but doesn't call anything (apparently I also need to modify the UICR region from the bootloader).
MEMORY
{
FLASH (rx) : ORIGIN = 0xE0000, LENGTH = 0x1E000
RAM (rwx) : ORIGIN = 0x20018000, LENGTH = 0x28000
}
SECTIONS
{
.api_table 0xFDE00:
{
KEEP(*(.api_table))
} > FLASH
}
For ORIGIN = 0x26000, you should make LENGTH = 0xD2000 or something like that so it could compile without overflow.
Default FLASH memory placement for nRF52840-QIAA (see Nordic infocenter):
Usage | Start | End | Size | Size (kB) |
---|---|---|---|---|
Master Boot Record (MBR) | 0x0 | 0x1000 | 0x1000 | 4 kB |
SoftDevice (S140 v6.1.x) | 0x1000 | 0x26000 | 0x25000 | 148 kB |
Application area (incl. free space) | 0x26000 | 0xF8000 | 0xD2000 | 840 kB |
Bootloader | 0xF8000 | 0xFE000 | 0x6000 | 24 kB |
MBR parameter storage | 0xFE000 | 0xFF000 | 0x1000 | 4 kB |
Bootloader settings | 0xFF000 | 0x100000 | 0x1000 | 4 kB |
- Example Nordic App: 0x26000 ... 0xF8000 (length 0xD2000)
- Original Adafruit Bootloader: 0xF4000 ... 0xFD800 (length 0x9800)
- Original dev/ble_micro_pro QMK: 0x26000 ... 0xE0000 (length 0xBA000)
- Original BMPAPI offset: 0xFDE00
There is a separate launcher app (https://github.com/joric/bmpapi/tree/master/launcher) for testing.
Launcher .ld script:
FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0x16000
BMPAPI .ld script:
FLASH (rx) : ORIGIN = 0x26000+0x16000, LENGTH = 0xD2000-0x16000
BMPAPI only works starting from 0xA0000 for some reason (0xFDE00 is out of reach for the userspace app, it's a bootloader area). I tried overlapping addresses first but it crashed in the very middle (there still was a subtle LED flash). This is because functions were overwritten by the launcher so I needed to move all the functions out of the launcher memory space as well. I did it with the setup above and two apps now work together just fine.
Picture for clarity (taken from Nordic)
GCC linker description file can force symbol to be at specific address, see:
- gcc linker description file force symbol to be at specific address
- GCC: how to tell GCC to put the 'main' function at the start of the .text section?
- Using a linker script in GCC to locate functions at specific memory regions
- Placing jump tables in ROM
The best read about this is probably https://github.com/adafruit/Adafruit_nRF52_Bootloader it has a lot of stuff aligned at specific addresses.
C file:
#define LOCATE_FUNC __attribute__((__section__(".mysection")))
...
void LOCATE_FUNC Delay(uint32_t dlyTicks)
{
...
)
Linker .ld file:
MEMORY
{
FLASH (rx) : ORIGIN = 0x0, LENGTH = 128K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32k
MY_MEMORY (rx) : ORIGIN = 0x20000, LENGTH = 128k
}
...
SECTIONS
{
.mysection :
{
. = ALIGN(4);
__mysection_start__ = .;
*(.mysection*)
__mysection_end__ = .;
} > MY_MEMORY
}
Clean and build the project again to use the updated linker script, open the .map file to verify that the Delay() function is indeed located at 0x20000.
All that could be much easier if instead of trying to be an OS, QMK could be a standalone library that reads layouts, parses keyboard matrix and outputs keycodes. There's a setup that tries to use QMK as a library (QMK hardware platform is still the same), but I don't really know its current state, from the look of it it's kind of stalled and wasn't updated for 2 years.
- https://github.com/AndruPol/nrf52832-recover/issues/1 (nRF52 unlocking)
- https://github.com/blacksphere/blackmagic/issues/230 (unlocking with BMP)
- https://github.com/adafruit/Adafruit_nRF52_Bootloader (Adafruit bootloader)
- https://github.com/sekigon-gonnoc/qmk_firmware/tree/nrf52 (nRF5x QMK platform)
- https://github.com/joric/qmk_firmware/tree/nrf52 (my fork, working on it!)
- https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/
- https://github.com/jpconstantineau/BlueMicro_BLE (NRF52-Board firmware)
- https://github.com/joric/qmk/wiki (my nRF52 firmware for different keyboards)