API Reference
Complete API reference for Multiflexmeter 3.7.0 firmware functions and interfaces.
Configuration API
Section titled “Configuration API”rom_conf.cpp / rom_conf.h
Section titled “rom_conf.cpp / rom_conf.h”conf_load()
Section titled “conf_load()”bool conf_load(void);Loads configuration from EEPROM into RAM and validates magic bytes.
Returns:
true- Configuration loaded successfullyfalse- Invalid magic bytes “MFM\0” or EEPROM corrupted
Side Effects:
- Reads 41 bytes from EEPROM address 0x00
- Populates static
rom_conf_t configstructure - Validates MAGIC field
Location: rom_conf.cpp:42-59
conf_save()
Section titled “conf_save()”void conf_save(void);Saves current configuration from RAM to EEPROM.
Side Effects:
- Writes 41 bytes to EEPROM address 0x00
- Updates MAGIC field to “MFM\0”
Location: rom_conf.cpp:61-66
conf_getDevEui()
Section titled “conf_getDevEui()”void conf_getDevEui(uint8_t *buf);Copies Device EUI to provided buffer.
Parameters:
buf- Pointer to 8-byte buffer
Format: LSB-first (little-endian) as required by LMIC
Location: rom_conf.cpp:68-70
conf_getAppEui()
Section titled “conf_getAppEui()”void conf_getAppEui(uint8_t *buf);Copies Application EUI to provided buffer.
Parameters:
buf- Pointer to 8-byte buffer
Format: LSB-first (little-endian) as required by LMIC
Location: rom_conf.cpp:72-74
conf_getAppKey()
Section titled “conf_getAppKey()”void conf_getAppKey(uint8_t *buf);Copies Application Key to provided buffer.
Parameters:
buf- Pointer to 16-byte buffer
Format: MSB-first (big-endian)
Location: rom_conf.cpp:76-78
conf_getMeasurementInterval()
Section titled “conf_getMeasurementInterval()”uint16_t conf_getMeasurementInterval(void);Retrieves measurement interval with bounds checking.
Returns: Interval in seconds (20-4270)
Bounds:
- Minimum: 20 seconds (MIN_INTERVAL)
- Maximum: 4270 seconds (MAX_INTERVAL)
Location: rom_conf.cpp:80-86
conf_setMeasurementInterval()
Section titled “conf_setMeasurementInterval()”void conf_setMeasurementInterval(uint16_t interval);Sets measurement interval with bounds checking.
Parameters:
interval- Interval in seconds (clamped to 20-4270)
Side Effects:
- Updates RAM configuration
- Does NOT save to EEPROM (call
conf_save()separately)
Location: rom_conf.cpp:88-93
conf_getUseTTNFairUsePolicy()
Section titled “conf_getUseTTNFairUsePolicy()”uint8_t conf_getUseTTNFairUsePolicy(void);Returns TTN Fair Use Policy flag.
Returns:
1- Fair use policy enabled (30s/day airtime limit)0- Fair use policy disabled
Location: rom_conf.cpp:95-97
conf_getFirmwareVersion()
Section titled “conf_getFirmwareVersion()”version conf_getFirmwareVersion(void);Returns firmware version from build flags.
Returns: version struct with proto, major, minor, patch fields
Source: Compile-time defines (FW_VERSION_MAJOR, MINOR, PATCH)
Location: rom_conf.cpp:99-106
conf_getHardwareVersion()
Section titled “conf_getHardwareVersion()”version conf_getHardwareVersion(void);Returns hardware version from EEPROM.
Returns: version struct with proto, major, minor, patch fields
Source: EEPROM HW_VERSION field (2 bytes)
Location: rom_conf.cpp:108-115
versionToUint16()
Section titled “versionToUint16()”uint16_t versionToUint16(version v);Encodes version struct to 16-bit integer.
Parameters:
v- Version structure
Returns: 16-bit encoded version
Format: [proto:1][major:5][minor:5][patch:5]
Example:
version fw = conf_getFirmwareVersion();uint16_t encoded = versionToUint16(fw);// v3.7.0 (release) → 0x8E00Location: rom_conf.cpp:117-121
Sensor API
Section titled “Sensor API”sensors.cpp / sensors.h
Section titled “sensors.cpp / sensors.h”sensors_init()
Section titled “sensors_init()”error_t sensors_init(void);Initializes SMBus/I²C interface for sensor communication.
Returns:
ERR_NONE- Initialization successful- Other error codes from
smbus_init()
Side Effects:
- Configures I²C hardware at 80kHz
- Sets up TWI peripheral
Location: sensors.cpp:45-47
sensors_performMeasurement()
Section titled “sensors_performMeasurement()”error_t sensors_performMeasurement(void);Triggers measurement on sensor board at address 0x36 by sending CMD_PERFORM (0x10).
Returns:
ERR_NONE- Command sent successfullyERR_SMBUS_SLAVE_NACK- Sensor did not acknowledgeERR_SMBUS_*- Other SMBus errors
Protocol:
- Sends byte
0x10to I²C address0x36
Usage:
if (sensors_performMeasurement() == ERR_NONE) { // Wait 10 seconds before reading delay(10000);}Location: sensors.cpp:59-61
sensors_readMeasurement()
Section titled “sensors_readMeasurement()”error_t sensors_readMeasurement(uint8_t *buf, uint8_t *length);Reads measurement data from sensor using CMD_READ (0x11) via SMBus block read.
Parameters:
buf- Buffer to store measurement data (up to 32 bytes)length- Input: buffer size, Output: actual bytes read
Returns:
ERR_NONE- Data read successfullyERR_SMBUS_SLAVE_NACK- Sensor did not respondERR_SMBUS_*- Other SMBus errors
Protocol:
- Sends command
0x11to address0x36 - Performs SMBus block read (variable length)
- First byte from sensor indicates data length
Example:
uint8_t sensor_data[32];uint8_t data_length = sizeof(sensor_data);
if (sensors_readMeasurement(sensor_data, &data_length) == ERR_NONE) { // Process sensor_data[0..data_length-1] // Format depends on connected sensor module}Location: sensors.cpp:73-75
SMBus/I²C API
Section titled “SMBus/I²C API”smbus.cpp / smbus.h
Section titled “smbus.cpp / smbus.h”smbus_init()
Section titled “smbus_init()”error_t smbus_init(void);Initializes TWI (I²C) hardware for SMBus communication.
Returns: ERR_NONE
Configuration:
- Clock speed: 80kHz (F_SMBUS = 80000L)
- Calculates TWBR register value
- External 4.7kΩ pull-ups required on SDA/SCL
Location: smbus.cpp:108-111
smbus_sendByte()
Section titled “smbus_sendByte()”error_t smbus_sendByte(uint8_t addr, uint8_t byte);Performs SMBus Send Byte operation.
Parameters:
addr- 7-bit I²C address (not shifted)byte- Command or data byte to send
Returns:
ERR_NONE- SuccessERR_SMBUS_SLAVE_NACK- Device did not acknowledgeERR_SMBUS_ARB_LOST- Arbitration lostERR_SMBUS_ERR- General communication error
Protocol: [START][ADDR+W][BYTE][STOP]
Location: smbus.cpp:113-127
smbus_blockRead()
Section titled “smbus_blockRead()”error_t smbus_blockRead(uint8_t addr, uint8_t cmd, uint8_t *rx_buf, uint8_t *rx_length);Performs SMBus Block Read operation.
Parameters:
addr- 7-bit I²C addresscmd- Command byterx_buf- Buffer for received datarx_length- Input: buffer size, Output: bytes received
Returns:
ERR_NONE- SuccessERR_SMBUS_*- Various error codes
Protocol:
- [START][ADDR+W][CMD][RESTART][ADDR+R][LENGTH][DATA…][STOP]
- First received byte is data length
- Master ACKs all bytes except last (NACKed)
Location: smbus.cpp:129-163
smbus_blockWrite()
Section titled “smbus_blockWrite()”error_t smbus_blockWrite(uint8_t addr, uint8_t cmd, uint8_t *tx_buf, uint8_t tx_length);Performs SMBus Block Write operation.
Parameters:
addr- 7-bit I²C addresscmd- Command bytetx_buf- Data to transmittx_length- Number of bytes to send
Returns:
ERR_NONE- SuccessERR_SMBUS_*- Various error codes
Protocol: [START][ADDR+W][CMD][LENGTH][DATA…][STOP]
Used by: Downlink command 0x11 to forward data to sensor modules
Location: smbus.cpp:165-190
smbus_alertAddress()
Section titled “smbus_alertAddress()”error_t smbus_alertAddress(uint8_t *addr);Handles SMBus Alert Response Address (ARA) protocol.
Parameters:
addr- Output: Address of alerting device
Returns:
ERR_NONE- Alert address receivedERR_SMBUS_NO_ALERT- No device alertingERR_SMBUS_*- Communication error
Protocol:
- Reads from special address 0x0C (SMBUS_ADDR_ARA)
- Device responds with its address
- Used for interrupt-driven sensor polling
Location: smbus.cpp:192-211
Low-Level TWI Functions
Section titled “Low-Level TWI Functions”These functions are internal to smbus.cpp and not part of the public API:
twi_start()- Generate START condition and address devicetwi_tx()- Transmit one bytetwi_rx()- Receive one byte (with ACK/NACK)twi_stop()- Generate STOP conditiontwi_error()- Translate TWI status to error_t
Location: smbus.cpp:41-105
Board Support API
Section titled “Board Support API”boards/mfm_v3.cpp and boards/mfm_v3_m1284p.cpp
Section titled “boards/mfm_v3.cpp and boards/mfm_v3_m1284p.cpp”board_setup()
Section titled “board_setup()”void board_setup(void);Initializes board-specific hardware configuration.
Variants:
MFM V3 (ATmega328P):
- No special initialization required
- Default Arduino setup sufficient
- Location:
mfm_v3.cpp:41-44
MFM V3 M1284P (ATmega1284P):
- Optional clock prescaler configuration (commented out)
- Can reduce clock from 8MHz to 4MHz for power savings
- Currently uses default 8MHz
- Location:
mfm_v3_m1284p.cpp:43-51
Example (enabling clock prescaler):
void board_setup(void) { CLKPR = 1 << CLKPCE; // Enable prescaler change CLKPR = 0b0001; // Set /2 prescaler}Board Pin Definitions
Section titled “Board Pin Definitions”include/board_config/mfm_v3.h
Section titled “include/board_config/mfm_v3.h”ATmega328P Variant:
#define PIN_PERIF_PWR 2 // Peripheral power control#define PIN_JSN_TX 4 // JSN sensor TX#define PIN_JSN_RX 3 // JSN sensor RX#define PIN_ONE_WIRE 5 // OneWire bus#define PIN_BUZZER 17 // Buzzer (Analog 3)#define PIN_NSS 10 // LoRa chip select#define PIN_RST 9 // LoRa reset#define PIN_DIO_0 8 // LoRa DIO0#define PIN_DIO_1 7 // LoRa DIO1#define PIN_DIO_2 6 // LoRa DIO2include/board_config/mfm_v3_m1284p.h
Section titled “include/board_config/mfm_v3_m1284p.h”ATmega1284P Variant:
#define PIN_PERIF_PWR 20 // Peripheral power control#define PIN_JSN_TX 10 // JSN sensor TX#define PIN_JSN_RX 11 // JSN sensor RX#define PIN_ONE_WIRE 12 // OneWire bus#define PIN_BUZZER 17 // Buzzer (Analog 3)#define PIN_NSS 24 // LoRa chip select#define PIN_RST 25 // LoRa reset#define PIN_DIO_0 2 // LoRa DIO0#define PIN_DIO_1 3 // LoRa DIO1#define PIN_DIO_2 4 // LoRa DIO2#define PIN_DIO_3 0 // LoRa DIO3#define PIN_DIO_4 1 // LoRa DIO4#define PIN_DIO_5 26 // LoRa DIO5Watchdog & Reset API
Section titled “Watchdog & Reset API”wdt.cpp / wdt.h
Section titled “wdt.cpp / wdt.h”mcu_reset()
Section titled “mcu_reset()”void mcu_reset(void);Forces MCU reset using watchdog timer.
Behavior:
- Sets global flag
do_reset = 1 - Enables watchdog with 15ms timeout
- Enters infinite loop
- Watchdog fires and resets MCU
Usage: Called by downlink command 0xDEAD or error recovery
Warning: This function does not return!
Location: wdt.cpp:47-53
ISR(WDT_vect)
Section titled “ISR(WDT_vect)”ISR(WDT_vect);Watchdog timer interrupt service routine.
Behavior:
- If
do_reset == 1: Allows reset to proceed - If
do_reset == 0: Resets watchdog and disables it
Purpose: Distinguishes intentional reset from watchdog protection
Location: wdt.cpp:38-45
Native AVR Watchdog Functions
Section titled “Native AVR Watchdog Functions”The firmware also uses standard AVR watchdog functions:
#include <avr/wdt.h>
wdt_reset(); // Reset watchdog counterwdt_disable(); // Disable watchdogwdt_enable(WDTO_15MS); // Enable with 15ms timeoutTimeouts: WDTO_15MS, WDTO_30MS, WDTO_60MS, WDTO_120MS, WDTO_250MS, WDTO_500MS, WDTO_1S, WDTO_2S, WDTO_4S, WDTO_8S
LoRaWAN Integration
Section titled “LoRaWAN Integration”main.cpp - Application Jobs and Event Handlers
Section titled “main.cpp - Application Jobs and Event Handlers”job_performMeasurements()
Section titled “job_performMeasurements()”void job_performMeasurements(osjob_t *job);Job function that triggers sensor measurement.
Actions:
- Calls
sensors_performMeasurement()to send CMD_PERFORM (0x10) - Schedules
job_fetchAndSend()after 10 seconds delay
Scheduling: Called by scheduleNextMeasurement() based on interval
Location: main.cpp:101-112
job_fetchAndSend()
Section titled “job_fetchAndSend()”void job_fetchAndSend(osjob_t *job);Job function that reads sensor data and transmits via LoRaWAN.
Actions:
- Reads measurement data with
sensors_readMeasurement() - Transmits data on FPort 1 using
LMIC_setTxData2() - Handles TXRX_PENDING case (skips if radio busy)
Location: main.cpp:114-147
job_pingVersion()
Section titled “job_pingVersion()”void job_pingVersion(osjob_t *job);Job function that sends firmware/hardware version after join.
Actions:
- Encodes firmware and hardware versions to uint16
- Builds 5-byte payload: [0x10][FW_MSB][FW_LSB][HW_MSB][HW_LSB]
- Transmits on FPort 2
- Schedules first measurement after 45 seconds
Trigger: Automatically called on EV_JOINED
Location: main.cpp:149-167
job_reset()
Section titled “job_reset()”void job_reset(osjob_t *job);Job function that performs MCU reset.
Actions:
- Calls
mcu_reset()to trigger watchdog reset - Function does not return
Trigger: Scheduled by downlink command 0xDEAD after 5 second delay
Location: main.cpp:169-171
job_error()
Section titled “job_error()”void job_error(osjob_t *job);Error state job - enters infinite sleep loop.
Actions:
- Prints error message to debug output
- Enters infinite loop calling
os_runloop_once() - Device effectively halts
Trigger: Called when configuration load fails
Location: main.cpp:173-175
onEvent()
Section titled “onEvent()”void onEvent(ev_t ev);LMIC event handler for LoRaWAN protocol events.
Key Events Handled:
EV_JOINING- Debug output, join in progressEV_JOINED- Schedulesjob_pingVersion()after join successEV_TXCOMPLETE- Processes downlinks, schedules next measurementEV_JOIN_TXCOMPLETE- Debug output for join attemptEV_LINK_DEAD- Automatic rejoin on link failure
Downlink Processing:
case EV_TXCOMPLETE: if (LMIC.dataLen > 0) { processDownlink(LMIC.frame[LMIC.dataBeg], &LMIC.frame[LMIC.dataBeg + 1], LMIC.dataLen - 1); } scheduleNextMeasurement();Location: main.cpp:318-386
processDownlink()
Section titled “processDownlink()”void processDownlink(uint8_t cmd, uint8_t *args, uint8_t len);Processes downlink commands received from network.
Parameters:
cmd- Command byte (first byte of downlink)args- Remaining payload byteslen- Length of args
Commands:
- 0xDE (+ 0xAD): Schedule device reset in 5 seconds
- 0x10: Set measurement interval (args = MSB, LSB)
- 0x11: Forward to sensor module via SMBus
Location: main.cpp:248-302
scheduleNextMeasurement()
Section titled “scheduleNextMeasurement()”void scheduleNextMeasurement(void);Calculates and schedules next measurement job.
Logic:
- Reads interval from configuration
- If Fair Use Policy enabled, enforces airtime limits
- Checks duty cycle availability
- Schedules
job_performMeasurements()at calculated time
Location: main.cpp:124-198
getTransmissionTime()
Section titled “getTransmissionTime()”ostime_t getTransmissionTime(ostime_t req_time);Enforces duty cycle compliance for transmission scheduling.
Parameters:
req_time- Requested transmission time
Returns: Adjusted time respecting duty cycle
Location: main.cpp:200-246
LMIC Credential Callbacks
Section titled “LMIC Credential Callbacks”These functions are called by LMIC to retrieve OTAA credentials:
os_getArtEui()
Section titled “os_getArtEui()”void os_getArtEui(u1_t *buf);Provides Application EUI to LMIC.
Location: main.cpp:391-393
os_getDevEui()
Section titled “os_getDevEui()”void os_getDevEui(u1_t *buf);Provides Device EUI to LMIC.
Location: main.cpp:395-397
os_getDevKey()
Section titled “os_getDevKey()”void os_getDevKey(u1_t *buf);Provides Application Key to LMIC.
Location: main.cpp:399-401
Data Structures
Section titled “Data Structures”rom_conf_t
Section titled “rom_conf_t”EEPROM configuration structure:
struct __attribute__((packed)) rom_conf_t { uint8_t MAGIC[4]; // "MFM\0" struct { uint8_t MSB; // Hardware version MSB uint8_t LSB; // Hardware version LSB } HW_VERSION; // Hardware/firmware version (2 bytes) uint8_t APP_EUI[8]; // LoRaWAN Application EUI uint8_t DEV_EUI[8]; // LoRaWAN Device EUI uint8_t APP_KEY[16]; // LoRaWAN Application Key uint16_t MEASUREMENT_INTERVAL; // Measurement interval (seconds, little-endian) uint8_t USE_TTN_FAIR_USE_POLICY; // Fair Use Policy (0=disabled, 1=enabled)};Total Size: 41 bytes
Constants
Section titled “Constants”Sensor Command Codes (sensors.cpp)
Section titled “Sensor Command Codes (sensors.cpp)”#define CMD_PERFORM 0x10 // Trigger measurement on sensor#define CMD_READ 0x11 // Read measurement data from sensor#define SENSOR_BOARD_ADDR 0x36 // I²C address of sensor boardDownlink Commands (main.cpp)
Section titled “Downlink Commands (main.cpp)”#define DL_CMD_REJOIN 0xDE // Reset device (requires 0xAD as second byte)#define DL_CMD_INTERVAL 0x10 // Update measurement interval#define DL_CMD_MODULE 0x11 // Forward command to sensor moduleConfiguration Limits (config.h)
Section titled “Configuration Limits (config.h)”#define MIN_INTERVAL 20 // Minimum interval (seconds)#define MAX_INTERVAL 4270 // Maximum interval (seconds)#define SENSOR_JSN_TIMEOUT 200 // JSN sensor timeout (ms)#define MIN_LORA_DR 0 // Minimum LoRa data rateTiming Constants (main.cpp)
Section titled “Timing Constants (main.cpp)”#define MEASUREMENT_SEND_DELAY_AFTER_PERFORM_S 10 // Wait 10s between perform and read#define MEASUREMENT_DELAY_AFTER_PING_S 45 // Wait 45s after version ping#define RESET_DELAY_S 5 // Wait 5s before resetUsage Examples
Section titled “Usage Examples”Complete Measurement Cycle (Actual Implementation)
Section titled “Complete Measurement Cycle (Actual Implementation)”// From main.cpp - job_performMeasurements()void job_performMeasurements(osjob_t *job) { error_t err = sensors_performMeasurement(); if (err != ERR_NONE) { _debug("Sensor error\n"); scheduleNextMeasurement(); return; } // Schedule read after 10 seconds os_setTimedCallback(&main_job, os_getTime() + sec2osticks(10), FUNC_ADDR(job_fetchAndSend));}
// job_fetchAndSend()void job_fetchAndSend(osjob_t *job) { uint8_t tx_buf[32]; uint8_t tx_len = sizeof(tx_buf);
error_t err = sensors_readMeasurement(tx_buf, &tx_len); if (err != ERR_NONE) { _debug("Read error\n"); scheduleNextMeasurement(); return; }
// Check if radio is busy if (LMIC.opmode & OP_TXRXPEND) { _debug("TXRX Pending...\n"); scheduleNextMeasurement(); return; }
// Transmit on FPort 1 LMIC_setTxData2(1, tx_buf, tx_len, 0);}Processing Downlink (Actual Implementation)
Section titled “Processing Downlink (Actual Implementation)”// From main.cpp - processDownlink()void processDownlink(uint8_t cmd, uint8_t *args, uint8_t len) { if (cmd == DL_CMD_REJOIN && len >= 1 && args[0] == 0xAD) { // Reset command (0xDEAD) _debug("Reset command received\n"); os_setTimedCallback(&main_job, os_getTime() + sec2osticks(RESET_DELAY_S), FUNC_ADDR(job_reset)); return; }
if (cmd == DL_CMD_INTERVAL && len >= 2) { // Set interval command (0x10) uint16_t new_interval = (args[0] << 8) | args[1]; _debugf("Changing interval: %u\n", new_interval); conf_setMeasurementInterval(new_interval); conf_save(); return; }
if (cmd == DL_CMD_MODULE && len >= 2) { // Forward to module (0x11) uint8_t addr = args[0]; uint8_t module_cmd = args[1]; error_t err = smbus_blockWrite(addr, module_cmd, &args[2], len - 2); if (err != ERR_NONE) { _debug("Module command failed\n"); } return; }}Reading Configuration
Section titled “Reading Configuration”// Load configuration at startupif (!conf_load()) { // Invalid configuration os_setCallback(&main_job, FUNC_ADDR(job_error)); return;}
// Get credentials for LMICuint8_t appeui[8], deveui[8], appkey[16];conf_getAppEui(appeui);conf_getDevEui(deveui);conf_getAppKey(appkey);
// Get measurement settingsuint16_t interval = conf_getMeasurementInterval();uint8_t fair_use = conf_getUseTTNFairUsePolicy();Next Steps
Section titled “Next Steps”- Firmware Architecture - System design
- Build System - Compilation details
- Development Guide - Practical examples