Firmware for the LXR V2 desktop drum machine: an STM32F765 (Cortex-M7, 216 MHz) running the drum voices, FX bus, step sequencer, MIDI (DIN + USB), SD-card preset storage and the LCD/button UI.
Licence and trademarks: the firmware source code is free software under the GNU GPL version 3 (LICENSE). You're welcome to study, change and share it, for example to write your own updates for your LXR. The names "Erica Synths" and "LXR" and the LXR hardware are the property of Erica Synths and are not covered by this licence. Building and installing your own firmware is at your own risk: Erica Synths provides no support for self-built or modified firmware. See Licence, trademarks and hardware below and NOTICE.md.
Every LXR has a built-in updater (the bootloader) that installs new firmware from the SD card. You don't need any extra hardware:
- Build the firmware. This creates the update file
LXR_V2_V<version>.img. - Copy it to the SD card and install it on the LXR.
You need CMake 3.21 or newer, Ninja,
the Arm GNU Toolchain
(arm-none-eabi-gcc, tested with 14.3.rel1 and 11.3.rel1) and Python 3
(to create the .img update file). The build finds them on PATH.
1. Install the tools (once)
macOS (Apple Silicon and Intel), with Homebrew:
brew install cmake ninja
brew install --cask gcc-arm-embeddedUse exactly this --cask gcc-arm-embedded package, the official Arm toolchain. The similarly named
brew install arm-none-eabi-gcc does not include the C library the firmware needs, and the build
fails with it. Python 3 comes with the Xcode Command Line Tools, which Homebrew installs.
If you installed the toolchain with Arm's own .pkg installer instead, it isn't on PATH: add
-DARM_TOOLCHAIN_DIR=/Applications/ArmGNUToolchain/<version>/arm-none-eabi/bin to the first cmake
command below.
Windows: install CMake, Ninja and Python 3 (e.g. winget install Kitware.CMake Ninja-build.Ninja Python.Python.3.12) and the Arm GNU Toolchain with its Windows installer. On the installer's last page,
tick "Add path to environment variable".
Linux: install CMake, Ninja and Python 3 with your package manager (e.g.
sudo apt install cmake ninja-build python3) and the Arm GNU Toolchain from Arm's download page, then
add its bin folder to PATH.
2. Build
Open a terminal (Terminal on macOS, PowerShell on Windows) in this folder and run:
cmake --preset release
cmake --build --preset releaseWhen the build has finished it shows where the update file is and how to install it:
======================================================================
Firmware update file:
.../lxr_desktop_app/build/release/LXR_V2_V1.7.img
...
The version in the file name, and on the LXR's start-up screen, is set by project(VERSION ...) in
CMakeLists.txt.
- Copy
LXR_V2_V<version>.imgfrombuild/release/to the top folder of the SD card. Delete any olderLXR*.imgfiles from the card: the updater installs the first one it finds. On a Mac, eject the card in Finder before you take it out. Finder also leaves hidden files starting with._on the card; the updater ignores them. - Switch the LXR off and put the card in.
- Hold down the encoder button and switch the LXR on. Keep holding the button until "LXR Updater" appears on the display.
- The display shows "Checking Firmware...", then "Loading Firmware" with the progress in percent.
- When it shows "Update OK!" the LXR restarts by itself with the new firmware.
The updater itself is never changed by an update. If an update fails or is interrupted (for example by a power cut), the LXR can always be updated again the same way.
If the updater stops with one of these messages, switch the LXR off, fix the cause and try again:
| Display | Cause |
|---|---|
can't mount / SD Card |
No SD card, or the card can't be read. The card must be formatted as FAT32. Cards larger than 32 GB usually come formatted as exFAT, which the updater can't read. On a Mac, reformat the card in Disk Utility with the format "MS-DOS (FAT)". |
LXRE*.img / Not Found! |
No update file in the top folder of the card. The file name must start with LXR and end with .img. |
Can't open / LXRE*.img! |
The update file couldn't be read. Copy it to the card again. |
LXR*.img / Not Valid! |
The file isn't an update for the desktop LXR (for example a Eurorack module update, or a damaged file). |
LXR*.img / Wrong Size!, Wrong / Checksum! |
The file is damaged or incomplete. Build it again or copy it to the card again. |
Can't erase / Flash!, Flash write / Error! |
Writing the firmware to the LXR's memory failed. Switch the LXR off and try the update again. |
| Option | Effect |
|---|---|
cmake --preset debug |
Debug build: -O3 -g3 with DEBUG defined, output in build/debug/. The release build uses -O3 -g0. Both need -O3 for the audio engine to keep up in real time. |
-DARM_TOOLCHAIN_DIR=<bin folder> |
Use a toolchain that isn't on PATH, e.g. /Applications/ArmGNUToolchain/14.3.rel1/arm-none-eabi/bin (macOS) or C:/toolchain_arm-none-eabi/11.3rel1/bin (Windows) |
-DLXR_ITCM_OPT=<flag> |
Optimisation for the code in the fast ITCM memory, see Memory map (default -O2) |
-DLXR_GENERATE_LISTING=ON |
Also write a disassembly listing (.list) |
Options are passed when configuring, e.g. cmake --preset release -DLXR_GENERATE_LISTING=ON.
The toolchain file and the CMake modules and tools are in the cmake/ folder.
All files the build creates (in build/<preset>/):
| File | Purpose |
|---|---|
LXR_V2_V<version>.img |
Update file for the SD card (see Installing the update) |
LXR_V2_STM32F765.elf |
Firmware with debug information, for a debugger |
LXR_V2_STM32F765.bin |
Raw firmware image (from 0x08008000) |
LXR_V2_STM32F765.hex |
Intel HEX of the same |
LXR_V2_STM32F765.map |
Linker map |
Remove the compiled files (including the .img) but keep the settings. The next build then
compiles everything again:
cmake --build --preset release --target cleanUse --preset debug to clean the debug build.
Start completely from scratch: delete the build folder, which also forgets all settings such as
-DARM_TOOLCHAIN_DIR or -DLXR_BOOTLOADER_HEX, then configure and build again:
rm -rf build # Linux, macOS, Git Bash
Remove-Item -Recurse -Force build # Windows PowerShell
rmdir /s /q build # Windows Command Prompt
cmake --preset release
cmake --build --preset releaseStart from scratch when you switch to a different compiler (another ARM_TOOLCHAIN_DIR, or a
different arm-none-eabi-gcc on PATH), because an existing build folder keeps using the compiler it
was first set up with. Do the same if the build behaves strangely after updating the sources.
With CMake 3.24 or newer, cmake --preset release --fresh redoes the setup without deleting the folder.
The build/ folder is never committed to git.
| Region | Address | Size | Contents |
|---|---|---|---|
| FLASH | 0x08000000 – 0x08007FFF | 32 KB | Bootloader (LXR_V2_STM32F765_Bootloader) |
| FLASH | 0x08008000 – | 992 KB | This firmware (VECT_TAB_OFFSET 0x8000) |
| ITCM | 0x00000000 | 16 KB | IN_ITCM_RAM hot DSP code, copied there by main() |
| DTCM | 0x20000000 | 128 KB | INDTCM/INDTCMZ data, DMA buffers, the stack |
| RAM | 0x20020000 | 384 KB | .data, .bss, heap |
The ITCM is tight. Current GCC releases generate too much code at -O3 for it, so the translation
units that contain ITCM code are compiled with LXR_ITCM_OPT (default -O2). The linker prints
region usage after every build.
The project was originally generated with STM32CubeMX/STM32CubeIDE, which explains the directory
layout and the USER CODE BEGIN/END comments. It no longer depends on either tool: the CubeMX project
(.ioc) was removed because regenerating code from it overwrites application code in main.c.
Pins, clocks and peripherals are configured in Core/Inc/main.h, Core/Src/main.c
(SystemClock_Config, MX_*_Init) and Core/Src/stm32f7xx_hal_msp.c; edit them there.
Every component is an OBJECT library linked into LXR_V2_STM32F765:
| Target | Sources | Contents |
|---|---|---|
stm32f7_hal |
Drivers/ |
ST HAL + CMSIS |
fatfs |
Middlewares/Third_Party/FatFs, FATFS/ |
FatFs and the SD card glue |
usb_midi |
Middlewares/JS, USB_DEVICE_MIDI/ |
USB device stack with the custom USB-MIDI class |
lxr_platform |
Core/Startup, Core/Src/main.c, ... |
Startup, clocks, peripheral init, interrupts |
lxr_io |
Core/Src/IO |
Button/LED shift registers, pots and faders |
lxr_ui |
Core/Src/menu, lcd.c, buttonHandler.c, ... |
LCD, encoder, buttons, LEDs, menu pages |
lxr_preset |
Core/Src/Preset |
Kit/pattern/project load & save on SD |
lxr_dsp |
Core/Src/Soundengine/DSPAudio |
Oscillators, filters, envelopes, voices, master FX |
lxr_engine |
Core/Src/Soundengine/*.c |
Codec/I2S DMA, sample block rendering, FX bus, clock in/out |
lxr_midi |
Core/Src/Soundengine/MIDI |
MIDI UART, parsers, parameter mapping |
lxr_sequencer |
Core/Src/Soundengine/Sequencer |
Step sequencer, Euclidean generator, clock sync |
lxr_usb_app |
Core/Src/Soundengine/USB |
USB-MIDI message queue |
Source files are listed explicitly in the CMakeLists.txt files. Add new files there.
Not needed for updates: use the SD card for that. This section is for
development, factory programming and repairs, with an ST-Link debug probe connected to the LXR's
debug header and OpenOCD installed (the build finds it on PATH; on macOS:
brew install open-ocd).
Firmware only. Writes just the firmware; the bootloader is left alone:
cmake --build --preset release --target flashBlank devices (bootloader + firmware). A blank device, or replacing the bootloader on purpose, needs the bootloader as well. The bootloader is a separate project, so by default the build doesn't include it. Tell the build where to find it in one of two ways:
# A: a prebuilt bootloader .hex (e.g. from a bootloader release)
cmake --preset release -DLXR_BOOTLOADER_HEX=path/to/LXR_V2_STM32F765_Bootloader.hex
# B: the bootloader source code, which is then built together with the firmware
cmake --preset release -DLXR_BOOTLOADER_DIR=path/to/LXR_V2_STM32F765_BootloaderThe build checks that the bootloader is valid for this device: it must fit in the 32 KB bootloader
sector and accept desktop (LXRV2IMG) update images, so the firmware, a full image or the Eurorack
bootloader are rejected. It then also writes LXR_V2_V<version>_full.hex/.bin (bootloader +
firmware, the whole flash from 0x08000000) and enables:
cmake --build --preset release --target flash_fullThe same .hex/.bin can also be loaded with STM32CubeProgrammer (the .bin goes to 0x08000000).
To go back to a firmware-only build, clear the setting: -DLXR_BOOTLOADER_HEX= or -DLXR_BOOTLOADER_DIR=.
Warning:
flash_fulloverwrites the bootloader. If that is interrupted, the device can no longer update itself from the SD card and has to be recovered with a debug probe. The target therefore prints a warning and only continues after you typeyes; without an interactive terminal (e.g. a build started from an IDE) it refuses. For scripts and factory programming, set the environment variableLXR_CONFIRM_BOOTLOADER_OVERWRITE=yesto skip the question.
The bootloader write-protects its own flash sector (sector 0) every time it boots, so on a unit that
has run before, sector 0 cannot simply be erased. flash_full therefore clears that protection first;
the new bootloader sets it again on its first boot. Other programming tools need the same step
(in STM32CubeProgrammer: Option bytes → Write protection → uncheck nWRP0).
The OpenOCD settings can be changed with LXR_OPENOCD_INTERFACE, LXR_OPENOCD_TRANSPORT,
LXR_OPENOCD_SPEED (default 1000 kHz) and LXR_OPENOCD_WORKAREA (default 0x8000). The defaults are
deliberately conservative: with a 64 KB work area at the faster default clock, an ST-Link V2 (firmware
V2J29) failed half-way through programming.
Copyright (C) 2012-2026 Erica Synths.
Firmware licence. The firmware, the bootloader and the build system are licensed under the GNU General Public License version 3 (GPL-3.0-only), with an additional permission for the STMicroelectronics libraries and the additional terms in NOTICE.md. In short:
- You may use, study and change the firmware, for example to build your own updates for your device.
- If you share or distribute the firmware, or a modified version of it, including pre-installed on a device, you must provide the complete source code under the same licence, and the people you give it to may install their own modified versions.
- Modified firmware must be marked as modified (its start-up screen must show this, e.g. with a different name or version text) and must not be presented as official Erica Synths firmware.
- The firmware comes without any warranty. Building and installing your own or modified firmware is entirely at your own risk, and Erica Synths provides no support for it.
Trademarks. "Erica Synths", "LXR" and "LXR-02" and the associated logos are trademarks of Erica Synths. The GPL licence does not grant any right to use them. They may not be used for any product, firmware release, service or commercial activity without prior written permission from Erica Synths. Truthful references such as "custom firmware for the Erica Synths LXR-02" are fine.
Hardware. The LXR hardware (schematics, circuit board layouts, front panel, enclosure and industrial design) is the intellectual property of Erica Synths and is protected by copyright and design rights. It is not part of this repository and is not licensed under the GPL or any other licence. It may not be copied, manufactured or sold, in whole or in part, without prior written permission from Erica Synths.
Third-party code. The repository includes components from STMicroelectronics, Arm and ChaN (FatFs) under their own licences. See NOTICE.md.
Use code OPENLXR15 on the Erica Synths website for a discount on the LXR (active for 1 week since open-source announcment) :)