Development Guide
Firmware Development Guide
Section titled “Firmware Development Guide”This guide covers everything you need to know to build, modify, and debug the Multiflexmeter 3.7.0 firmware.
Development Environment
Section titled “Development Environment”Required Software
Section titled “Required Software”-
PlatformIO Core or IDE
-
AVR Programmer Software
- AVRDude (usually included with PlatformIO)
- Drivers for your ISP programmer
-
Git (for version control and LMIC submodule)
-
Serial Terminal (for debugging)
screen
,minicom
(Linux/Mac)- PuTTY, TeraTerm (Windows)
- OR VS Code Serial Monitor extension
Required Hardware
Section titled “Required Hardware”- AVR ISP programmer (USBasp, AVRISP mkII, or Arduino as ISP)
- 6-pin ISP cable
- FTDI USB-to-Serial adapter (for debugging)
- Multiflexmeter 3.7.0 hardware
Project Setup
Section titled “Project Setup”1. Get the Source Code
Section titled “1. Get the Source Code”- Download 3.7.0 from the GitHub Releases page.
- Extract the archive to a working directory.
2. Initialize LMIC Submodule
Section titled “2. Initialize LMIC Submodule”The project uses MCCI Arduino-LMIC as a Git submodule:
git submodule update --init
This will clone the LMIC library into lib/arduino-lmic/
.
3. Open in PlatformIO
Section titled “3. Open in PlatformIO”VS Code Method:
Section titled “VS Code Method:”- Open VS Code
- File → Open Folder → Select
Multiflexmeter-3.7.0
- PlatformIO will auto-detect the project
- Select environment:
mfm_v3_m1284p
CLI Method:
Section titled “CLI Method:”cd Multiflexmeter-3.7.0pio project init
Project Structure
Section titled “Project Structure”Multiflexmeter-3.7.0/├── platformio.ini # Build configuration├── boards/ # Custom board definitions│ └── mfm_v3_m1284p.json├── include/ # Header files│ ├── board.h # Board abstraction│ ├── config.h # Compile-time configuration│ ├── main.h # Main application header│ ├── rom_conf.h # EEPROM configuration│ ├── sensors.h # Sensor interface│ ├── smbus.h # SMBus/I²C driver│ ├── wdt.h # Watchdog timer│ ├── errors.h # Error definitions│ ├── debug.h # Debug macros│ └── board_config/ # Board-specific configs│ └── mfm_v3_m1284p.h├── src/ # Source files│ ├── main.cpp # Main application logic│ ├── rom_conf.cpp # EEPROM management│ ├── sensors.cpp # Sensor drivers│ ├── smbus.cpp # SMBus implementation│ ├── wdt.cpp # Watchdog implementation│ └── boards/ # Board-specific implementations│ └── mfm_v3_m1284p.cpp└── lib/ # Libraries ├── arduino-lmic/ # LoRaWAN stack (submodule) └── MedianFilter/ # Median filtering library
Building the Firmware
Section titled “Building the Firmware”Build Configuration
Section titled “Build Configuration”The platformio.ini
file defines the build environment:
[platformio]boards_dir = boards
[env]platform = atmelavrframework = arduino
build_flags = -DARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS -DDISABLE_BEACONS -DDISABLE_PING -DLMIC_PRINTF_TO=Serial -DLMIC_DEBUG_LEVEL=2 -DFW_VERSION_MAJOR=0 -DFW_VERSION_MINOR=0 -DFW_VERSION_PATCH=0 -DFW_VERSION_PROTO=1
[env:mfm_v3_m1284p]board = mfm_v3_m1284pboard_fuses.lfuse = 0xFFboard_fuses.hfuse = 0xD1board_fuses.efuse = 0xFF
Build Commands
Section titled “Build Commands”Standard Build
Section titled “Standard Build”pio run -e mfm_v3_m1284p
Clean Build
Section titled “Clean Build”pio run -e mfm_v3_m1284p -t cleanpio run -e mfm_v3_m1284p
Upload via ISP
Section titled “Upload via ISP”pio run -e mfm_v3_m1284p -t upload
Set Fuses
Section titled “Set Fuses”pio run -e mfm_v3_m1284p -t fuses
Build Output
Section titled “Build Output”Successful build will generate:
.pio/build/mfm_v3_m1284p/├── firmware.elf # ELF file with debug symbols├── firmware.hex # Flash image└── firmware.eep # EEPROM image (if any)
Compile-Time Configuration
Section titled “Compile-Time Configuration”Debug Mode
Section titled “Debug Mode”Enable debug output in include/config.h
:
// Whether to output debug information to the serial output#define DEBUG
// Whether to print the firmware build date/time on power on#define PRINT_BUILD_DATE_TIME
When enabled:
- Debug messages printed to UART at 115200 baud
- Build timestamp printed on startup
- Detailed LoRaWAN event logging
LoRa Configuration
Section titled “LoRa Configuration”Minimum Data Rate (SF) in include/config.h
:
// The lowest DR (thus highest SF) the device will join and TX at#define MIN_LORA_DR 0 // DR0 = SF12 (slowest, longest range)
Measurement Timing
Section titled “Measurement Timing”Adjust timing in src/main.cpp
:
#define MEASUREMENT_SEND_DELAY_AFTER_PERFORM_S 10 // Sensor measurement delay#define MEASUREMENT_DELAY_AFTER_PING_S 45 // Delay after version ping
Interval Limits
Section titled “Interval Limits”Hard limits in include/config.h
:
#define MIN_INTERVAL 20 // Minimum 20 seconds#define MAX_INTERVAL 4270 // Maximum ~71 minutes
Firmware Version Management
Section titled “Firmware Version Management”Setting Version Numbers
Section titled “Setting Version Numbers”Versions are set as build flags in platformio.ini
:
build_flags = -DFW_VERSION_MAJOR=0 -DFW_VERSION_MINOR=0 -DFW_VERSION_PATCH=0 -DFW_VERSION_PROTO=1 # 0=dev, 1=release
Version Encoding
Section titled “Version Encoding”Version format (16-bit):
Bit 15: Proto (0=dev, 1=release)Bits 14-10: Major version (0-31)Bits 9-5: Minor version (0-31)Bits 4-0: Patch version (0-31)
Example: Version 1.3.7 (release) = 0b1_00001_00011_00111
= 0x8467
Flashing the Firmware
Section titled “Flashing the Firmware”Method 1: PlatformIO Upload
Section titled “Method 1: PlatformIO Upload”Configure your programmer in platformio.ini
:
upload_protocol = usbasp # or atmelice_isp, arduino, etc.upload_port = usbupload_flags = -e # Erase chip -B0.25 # Set SCK period
Then upload:
pio run -e mfm_v3_m1284p -t upload
Method 2: AVRDude Directly
Section titled “Method 2: AVRDude Directly”avrdude -c usbasp -p m1284p \ -U flash:w:.pio/build/mfm_v3_m1284p/firmware.hex:i
Method 3: Using Makefile
Section titled “Method 3: Using Makefile”If you prefer traditional Makefile workflow:
makemake flash
Programming EEPROM
Section titled “Programming EEPROM”Generate Configuration
Section titled “Generate Configuration”Create eeprom_config.bin
with your credentials:
import struct
MAGIC = b"MFM\x00"HW_VERSION = (0, 3) # MSB, LSBAPP_EUI = bytes.fromhex("7766554433221100") # Little-endianDEV_EUI = bytes.fromhex("0011223344556677") # Little-endianAPP_KEY = bytes.fromhex("0123456789ABCDEF0123456789ABCDEF") # Big-endianINTERVAL = 900 # 15 minutes in secondsTTN_FAIR_USE = 1
eeprom_data = struct.pack( "<4s2B8s8s16sHB", MAGIC, HW_VERSION[0], HW_VERSION[1], APP_EUI, DEV_EUI, APP_KEY, INTERVAL, TTN_FAIR_USE)
with open("eeprom_config.bin", "wb") as f: f.write(eeprom_data)
Flash EEPROM
Section titled “Flash EEPROM”avrdude -c usbasp -p m1284p -U eeprom:w:eeprom_config.bin:r
Verify EEPROM
Section titled “Verify EEPROM”avrdude -c usbasp -p m1284p -U eeprom:r:eeprom_read.bin:rhexdump -C eeprom_read.bin | head -n 3
Expected output:
00000000 4d 46 4d 00 00 03 00 11 22 33 44 55 66 77 77 66 |MFM..... "3DUfwwf|00000010 55 44 33 22 11 00 01 23 45 67 89 ab cd ef 01 23 |UD3"...#Eg.....#|00000020 45 67 89 ab cd ef 84 03 01 |Eg.......|
Debugging
Section titled “Debugging”Serial Debug Output
Section titled “Serial Debug Output”-
Connect FTDI Adapter
- TX (FTDI) → RX (MFM)
- RX (FTDI) → TX (MFM)
- GND → GND
- Do NOT connect VCC (device should be powered separately)
-
Open Serial Monitor
Terminal window screen /dev/ttyUSB0 115200# orpio device monitor -b 115200 -
Debug Output Example
Build at: Oct 09 2025 14:30:00[00000000] EV_JOINING[00005234] EV_JOINED[00005250] job_pingVersion[00010500] EV_TXCOMPLETE[00055234] job_performMeasurements[00065234] job_fetchAndSend[00070123] EV_TXCOMPLETEMeasurement scheduled: 900000
Debug Macros
Section titled “Debug Macros”Use debug macros in your code:
#include "debug.h"
void myFunction() { _debugTime(); // Print timestamp _debug(F("Hello World\n")); // Print string _debug(variableValue); // Print variable _debug(F("\n")); _debugFlush(); // Flush serial buffer}
Watchdog Debugging
Section titled “Watchdog Debugging”If device resets unexpectedly:
- Check if watchdog is triggering
- Add watchdog resets in long loops:
#include "wdt.h"while (condition) {wdt_reset(); // Reset watchdog// ... long operation ...}
Common Development Tasks
Section titled “Common Development Tasks”Adding a New Sensor
Section titled “Adding a New Sensor”See the firmware architecture documentation for sensor integration details.
Quick overview:
- Define sensor commands in
include/sensors.h
- Implement I²C communication in
src/sensors.cpp
- Update measurement reading logic in
src/main.cpp
Modifying Measurement Interval
Section titled “Modifying Measurement Interval”Runtime (via downlink):
Downlink: 0x10 <MSB> <LSB>Example: 0x10 07 08 (1800 seconds = 30 minutes)
Compile-time (default): Modify EEPROM configuration before flashing.
Changing LoRaWAN Parameters
Section titled “Changing LoRaWAN Parameters”- Credentials: Update EEPROM
- ADR Mode: Modify
src/main.cpp
:LMIC_setAdrMode(1); // 1=enabled, 0=disabled - Data Rate: Set minimum DR in
include/config.h
Custom Downlink Commands
Section titled “Custom Downlink Commands”Add new commands in src/main.cpp
:
#define DL_CMD_MY_CUSTOM 0x12
void processDownlink(uint8_t cmd, uint8_t *args, uint8_t len) { switch (cmd) { case DL_CMD_MY_CUSTOM: // Handle custom command _debug(F("Custom command received\n")); break; // ... existing cases ... }}
Testing
Section titled “Testing”Unit Testing
Section titled “Unit Testing”PlatformIO supports unit testing:
pio test
Create tests in test/
directory.
Integration Testing
Section titled “Integration Testing”- Flash firmware with DEBUG enabled
- Monitor serial output
- Verify join procedure
- Send downlink commands
- Check measurement transmission
Field Testing
Section titled “Field Testing”- Deploy with DEBUG disabled
- Monitor via TTN console
- Check battery voltage over time
- Verify measurement intervals
- Test range and coverage
Performance Optimization
Section titled “Performance Optimization”Code Size Reduction
Section titled “Code Size Reduction”- Disable DEBUG in production
- Use
F()
macro for strings (store in flash):_debug(F("String in flash\n")); // Good_debug("String in RAM\n"); // Wastes RAM - Enable Link Time Optimization (LTO) in platformio.ini:
build_flags = -flto
RAM Optimization
Section titled “RAM Optimization”- Use
const
andPROGMEM
for constants - Minimize global variables
- Use stack allocation when possible
Power Optimization
Section titled “Power Optimization”- Reduce measurement frequency
- Use sleep modes (already implemented)
- Disable unnecessary peripherals
- Optimize sensor reading time
Troubleshooting Build Issues
Section titled “Troubleshooting Build Issues”LMIC Submodule Missing
Section titled “LMIC Submodule Missing”fatal error: lmic.h: No such file or directory
Solution:
git submodule update --init
Fuse Programming Fails
Section titled “Fuse Programming Fails”avrdude: verification error
Solution:
- Check programmer connection
- Try slower SCK speed:
-B10
- Verify programmer works with simple blink sketch
Upload Fails
Section titled “Upload Fails”avrdude: error: programm enable: target doesn't answer
Solutions:
- Check ISP cable orientation
- Ensure device is powered
- Try different programmer
- Check fuses aren’t set incorrectly
Out of Memory
Section titled “Out of Memory”region `text' overflowed by XXX bytes
Solutions:
- Disable DEBUG
- Remove unused code
- Enable LTO
- Optimize data structures