A firmware for next-gen 16n devices, such as 16nx.
16nx is a controller for electronic music. It is 16 faders that manifest as:
It is edited and managed via a web-based editor.
3.0.3
This is firmware for a "next-gen" version of the 16n faderbank; the first manifestation of that is the 16nx design. It could, however, run on other boards / designs too, hence the generic name.
The firmware is written for the Raspberry Pi RP2040 chip; the 16nx board itself is a custom design, but it could be repurposed to run on almost any RP2040 board with enough pinouts.
It's coded in Pico SDK (raw C++); it does not use Arduino (unlike the previous firmware). This is largely owing to when it was begun, and the state of the Arduino core - and also the author's preference for it.
pico-sdk
1.5.0+ somewhere on your path, with the PIDO_SDK
environment variable pointing at itcmake
arm-none-eabi-gcc
available on your path.Basically: a very standard Pico SDK development toolchain.
The author recommends VS Code, with the C++ and CMake extensions available. A .clang-format
file is included; if you have the C++ extension installed, you will be able to autoformat all files according to this on save, which will make your life more pleasant. Unless you disagree with my code preferences.
You'll need to pull down the midi_uart_lib
dependency before you can compile this code:
git submodule update --recursive
With this pulled down, you should be able to Configure cmake
(eg, using the VS Code plugin: "CMake: Configure"), and then Build ("CMake: Build").
Or, from the command line, configure:
mkdir build
cd build
cmake ..
and then, from that directory:
make 16next
This will produce the file ./build/16next.uf2
which can be flashed to your 16nx board.
The RP2040 has no on-board flash memory whatsoever, and uses external flash RAM to store code. It also has no internal EEPROM. To save user data, we use the end of the onboard flash RAM.
Specifically, the code uses the last sector (4096 bytes) of onboard flash RAM as a wear-levelled 256byte storage, a bit like an EEPROM:
lastEmptyPage-1
256 bytes is fine enough for the storage we need; to store more than 256 bytes, we'd have to use a larger section of RAM.
Flashing the MCU will only delete user data if the firmware is big enough to extend to that part of Flash RAM; currently, that seems unlikely.
16next.cpp
is our main entry point and executable. Most code is in here.16next.h
is effectively a configuration file for that.midi_uart_lib_config.h
configures the library we use for TRS MIDI over the UART pins.tusb_config.h
and usb_descriptors.h
configure TinyUSB, used for MIDI.lib
contains:
config.h/cpp
, which contain Structs and functions for applying configuration data to the device, and saving/loading it from RAM.flash_onboard.h/cpp
which implement storage of user data in Flash RAMi2c_utils.h/cpp
which contain functionality useful for I2C, particular Leader mode.sysex.h/cpp
which contains functions related to sysex data handling.board
contains a board definition for the 16nx hardware.The device is edited from a web-based tool; this sends and receives data over MIDI system exclusive ("sysex") data; the spec is in SYSEX_SPEC.md
.
MIDI is enabled via TinyUSB. tusb_config.h
configures this, and usb_descriptors.c
is where the device name and descriptors are set up. The serial number is based on the unique identifier of the flash RAM used to store program data.
The MIDI buffer is 64 bytes long for a low-speed device. As such, sysex messages need to be processed by a little state machine to handle messages longer than 64 bytes.
When the device fails to detect an initial configuration (ie, the second byte of the storage ram is not 0xFF
) it overwrites it with the default config.
At any point, the default config can be restored by sending sysex message 0x1A
(for 1nitiAlize).
16nx is configured by its web-based editor. The editor sourcecode is available here.
There's an "internal" LED on a 16nx board, available at GPIO2. If midiLed
is true in the controller config, this LED will flash on MIDI activity. If powerLed
is true in the controller config, this LED will be on permanently.
The user data stored in flash (and indeed, sent via sysex) has the following shape:
Address | Format | Description |
---|---|---|
0 | 0/1 | LED on when powered |
1 | 0/1 | LED blink on MIDI data |
2 | 0/1 | Rotate controller outputs via 180º |
3 | 0/1 | I2C Master/Follower |
4,5 | 0-127 | FADERMIN lsb/msb |
6,7 | 0-127 | FADERMAX lsb/msb |
8 | 0/1 | Soft MIDI thru (default 0) |
9-15 | Currently unused | |
16-31 | 0-15 | Channel for each control (USB) |
32-47 | 0-15 | Channel for each control (TRS) |
48-63 | 0-127 | CC for each control (USB) |
64-79 | 0-127 | CC for each control (TRS) |
A debug connector on the front of the board is a JST-SH connector breaking out ARM SWD (single-wire-debug) ready for connection to a Pico Debug Probe. This allows developers to use open-source debug tools (OpenOCD, Cortex Debug) to debug the firmware in a more... pleasant manner than endless serial dumps.
As an RP2040 developer, you might wish to flash a board without having to reach for a BOOTSEL
or RESET
button. Unfortunately, I've had no joy enabling the method picotool
uses to force the board into BOOTSEL
mode, as it requires the UART to be handled over USB, and we're already using UART for our MIDI out. So I recommend making a BOOTSEL
button easily accessible on any board you'd like to use this firmware with.
I'm using 16next to refer to all next-gen, RP2040, 16n-style devices. It was the original name of the project. I'm separating out the firmware and hardware repositories because they move at different rates, and have their own releases.
Also: it's highly likely that this firmware will run just fine on multiple different boards. Given that, let's give it a more generic name.
Licensed under the MIT License (MIT). See LICENSE.md
for details.