OpenKNX Common is a library meant to be used once in every OpenKNX Device firmware.
The main functions are:
- setup and calling the knx stack
- setup and calling of the OpenKNX Modules
- flash handling for persistent data of the OpenKNX Modules
Includes ETS configuration and own group objects, see application description (German only).
It is designed for usage with the headerfile generated by OpenKNXproducer, which provides the necessary defines in knxprod.h:
MAIN_OpenKnxId
MAIN_ApplicationNumber
MAIN_ApplicationVersion
BASE_StartupDelayBase
ParamBASE_StartupDelayTimeMS
BASE_HeartbeatDelayBase
KoBASE_Heartbeat
ParamBASE_HeartbeatDelayTimeMS
| ARCH | info |
|---|---|
| RP2040 | the reference platform with full support (including dual core support) |
| SAMD21 | obsolete but still supported. no hw should be developed on this anymore |
| ESP32 | experimental |
To configure the Hardware-Setup use the following defines in hardware.h
| define | default | unit | function | mandatory/optional |
|---|---|---|---|---|
| SAVE_INTERRUPT_PIN | if defined >= 0, falling edge on this pin will trigger power save actions | optional | ||
| KNX_UART_RX_PIN | UART receive Pin to connect to BCU | mandatory for TP devices | ||
| KNX_UART_TX_PIN | UART transmit Pin to connect to BCU | mandatory for TP devices | ||
| KNX_UART_NUM | HW UART number | mandatory for TP devices | ||
| SAVE_POWER_PIN | if defined >= 0 this pin will be used to shut off non crucial power | optional | ||
| SAVE_POWER_PIN_POWER_OFF | value of SAVE_POWER_PIN to shut the power off (e.g. LOW or HIGH) | mandatory if SAVE_POWER_PIN defined | ||
| SAVE_POWER_PIN_POWER_ON | value of SAVE_POWER_PIN to shut the power on (e.g. LOW or HIGH) | mandatory if SAVE_POWER_PIN defined |
| define | default | unit | function |
|---|---|---|---|
| OPENKNX_RECOVERY_TIME | 6000 | ms | hold prog button to erase knx and openknx data (not firmware or filesystem). Turn off with 0 |
| OPENKNX_DUALCORE | build with dualcore support (only on RP2040) | ||
| OPENKNX_WATCHDOG | compile with watchdog (use only for releases. debugger not working with active watchdog) | ||
| OPENKNX_WATCHDOG_MAX_PERIOD | 16 | s | the timeout period of watchdog |
| OPENKNX_WATCHDOG_AUTOERASE_RESETS | 5 | s | erase knx flash after X fast watchdog restarts |
| OPENKNX_WATCHDOG_AUTOERASE_TIMEOUT | 16 | s | timeout after the fast restart counter is to be reset |
| OPENKNX_MAX_MODULES | 9 | ||
| OPENKNX_WAIT_FOR_SERIAL | 2000 | ms | wait at startup until SERIAL_DEBUG is connected. (optional with timeout - in devmode use high values like 20000 - 0 will disable waiting) Not supported on ESP32 |
| OPENKNX_MAX_LOOPTIME | 4000 | µs | how much time is the loop allowed to consume. (soft limit) |
| OPENKNX_LOOPTIME_WARNING | 7 | ms | issue a warning if the loop has lasted X ms or longer longer. |
| OPENKNX_LOOPTIME_WARNING_INTERVAL | 1000 | ms | how often the warning may be issued in the console |
| OPENKNX_RUNTIME_STAT | Integrate Collection of Runtime-Statistics. | ||
| OPENKNX_RUNTIME_STAT_BUCKETN | 16 | the number of histogram buckets for Runtime-Statistics | |
| OPENKNX_RUNTIME_STAT_BUCKETS | default set | µs | The upper (included) limits of histogram bucket, without last bucket as this will be limited by data-type only. Must be a comma-separated list with OPENKNX_RUNTIME_STAT_BUCKETN-1 entries |
| OPENKNX_DEBUG | Enable debug mode | ||
| OPENKNX_TRACE | Enable debug mode + tracing. To see trace logs, the log prefix (format PREFIX<SUB>, e.g. Channel<4>) must match a filter. Filter syntax: prefix exact or * suffix wildcard (Channel, Channel*); optional <sub> with exact value, numeric range 1-19, comma list 4,5,7 (combinable: 1-5,9), or * suffix wildcard (Test*). A filter without <sub> ignores the sub part (matches all). Combine several filters with ;, e.g. OPENKNX_TRACE=Test1<1-4>;Test2<8> (commas stay reserved for the sub list). |
||
| OPENKNX_DEBUGGER | Must be defined if you want to use a debugger (SWD). (e.g., switches off watchdog) | ||
| OPENKNX_TIME_DIGAGNOSTIC | Enable time diagnostic console commands. Will be automatically defined if OPENKNX_DEBUG is defined. | ||
| OPENKNX_TIME_TESTCOMMAND | Enable time text command to check the behavior of the posix time calculation functions | ||
| OPENKNX_TIME_CLOCK | arch depen. | Specifies the used time class. The default for SAMD21 is OpenKNX::Time::TimeClockMillis, for all other architectures OpenKNX::Time::TimeClockSystem | |
| OPENKNX_LITTLE_FS | arch depen. | If true, LittleFS will be enabled. Default true for RP2040 and ESP32. | |
| OPENKNX_OVERRIDE_MASK_VERSION | defines a mask version which will be returned regardless of the MASK_VERSION used for the build. Set this define if the used mask version does not match the media type. | ||
| OPENKNX_RTT | Enable RTT Mode (Disable USB Serial output) + Increase BUFFER_SIZE_UP to 10240! | ||
| BUFFER_SIZE_UP | 1024 | Bytes | Using by Segger RTT |
On ESP32 and RP2350, you can utilize PSRAM for both static data and dynamic memory allocation. The following macros are provided in helper.h:
| Macro | Function | With OPENKNX_PSRAM | Without OPENKNX_PSRAM |
|---|---|---|---|
PSRAM_MALLOC(size) |
Allocate dynamic memory | ESP32 ps_malloc, RP2350 pmalloc |
malloc(size) |
PSRAM_CALLOC(count, size) |
Allocate and zero memory | ESP32 ps_calloc, RP2350 pcalloc |
calloc(count, size) |
PSRAM_REALLOC(ptr, size) |
Reallocate memory | ESP32 ps_realloc, RP2350 realloc |
realloc(ptr, size) |
psram_new(Type) |
Placement-new in PSRAM | PSRAM allocation | Normal allocation |
psram_delete(p) |
Destruct + free PSRAM object | ~T() + free() |
delete p |
PsramAllocator<T> |
STL allocator using PSRAM | PSRAM_MALLOC-backed |
std::allocator<T> |
PSRAM_DATA |
Place variable/array in PSRAM | RP2350 section .psram, ESP32 EXT_RAM_BSS_ATTR |
No-op |
PSRAM_CODE |
Place function in PSRAM | RP2350 section .psram_code |
No-op (ESP32 cannot execute from PSRAM) |
The section names are not free-form: arduino-pico's linker script collects *(.psram*) into the PSRAM region (lib/rp2350/memmap_default.ld), ESP-IDF uses .ext_ram.bss. A section name outside those patterns links without error and silently ends up in normal RAM — verify placement in the .map file (RP2350 PSRAM window starts at 0x11000000).
ESP32 caveat verified by build: PSRAM_DATA only places data in PSRAM if the underlying sdkconfig has CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y — EXT_RAM_BSS_ATTR degrades itself to a no-op otherwise (checked in esp_attr.h), and the sdkconfig shipped with this project's pinned pioarduino release does not set it. PSRAM_MALLOC/PsramAllocator are unaffected — confirmed via nm that PsramAllocator<T>::allocate() calls the real ps_malloc, not a stub. RP2350 has no equivalent gate: PSRAM_DATA there is proven placed at 0x11000000 with just RP2350_PSRAM_CS set.
PSRAM_DATA variables live in a NOLOAD section: they cannot be statically initialized and are not zeroed at startup. Initialize them explicitly at runtime.
Control defines:
OPENKNX_PSRAM— automatically defined when ESP32BOARD_HAS_PSRAMor RP2350RP2350_PSRAM_CSis detected. Only these two are checked, because they are what the cores themselves use to bring the PSRAM heap up — defining any other name would enable the macros without an initialized heap. On PlatformIO the board must also declareupload.psram_length, otherwise the PSRAM region has zero lengthOPENKNX_DISABLE_PSRAM— define inhardware.hto disable PSRAM and force fallback to normal RAM/Flash (useful for debugging with Segger, etc.)
Example usage:
// Dynamic allocation
uint8_t *buf = (uint8_t*)PSRAM_MALLOC(8192); // PSRAM if available
free(buf); // always free with free()
MyClass *obj = psram_new(MyClass)(); // Placement-new in PSRAM
psram_delete(obj); // destructs + frees correctly
// STL container in PSRAM
std::vector<uint8_t, PsramAllocator<uint8_t>> vec;
vec.resize(8192);
// Static allocation
PSRAM_DATA uint8_t largeBuf[8192]; // Array in PSRAM
PSRAM_CODE void heavyComputation() { } // Function in PSRAM (frees Flash)see README_LED.md
| define | default | unit | function |
|---|---|---|---|
| PROG_BUTTON_PIN | undef | GPIO | to drive the OpenKNX programming button. Button supports short press (<1000ms), long press, and double press (500ms intervals). |
| PROG_BUTTON_PIN_MODE | INPUT_PULLUP | values: INPUT_PULLUP, INPUT_PULLDOWN, INPUT. Specifies the mode for the programming button pin. | |
| OPENKNX_BUTTON_DEBOUNCE | 50 | ms | Software debounce time for buttons to avoid false triggers. Setting to 0 disables it (i.e. to use Hardware debounce). |
OpenKNX Common includes an abstraction layer for GPIOs to seamlessly access GPIOs from the OpenKNX modules, if the GPIOs are integrated into the MCU or provided by port expanders.
#define OPENKNX_GPIO_NUM 1
#define OPENKNX_GPIO_TYPES OPENKNX_GPIO_T_TCA9555
#define OPENKNX_GPIO_ADDRS 0x20
#define OPENKNX_GPIO_INTS 0xFF
#define OPENKNX_GPIO_WIRE Wire
#define OPENKNX_GPIO_CLOCK 400000
#define OPENKNX_GPIO_SDA 28
#define OPENKNX_GPIO_SCL 29
#define OPENKNX_xxx_PINS 0x010D, 0x010B, 0x0102, 0x0104
Note:
If you use one or more GPIO expanders (
OPENKNX_GPIO_NUM≥ 1), the lists >OPENKNX_GPIO_TYPES,OPENKNX_GPIO_ADDRS, andOPENKNX_GPIO_INTSmust contain exactly as >many entries as specified. The order of entries determines the assignment of expander >addresses and types in the system.
GPIO Numbering:
For expander 1, the IOs are addressed as 0x0100 to 0x01FF, for expander 2 as 0x0200 to 0x02FF, and so on, where XX stands for the IO number on the respective expander (usually 0–7 or 0–15, depending on the expander type).
For MCU-internal GPIOs, use the plain pin number (e.g., 24).
Example:
0x0105means IO 5 on expander 10x0202means IO 2 on expander 2
openknx.gpio.pinMode(24, OUTPUT); // Set MCU-GPIO 24 to OUTPUT
openknx.gpio.digitalWrite(24, HIGH); // Write MCU-GPIO 24 to HIGH
openknx.gpio.pinMode(0x0105, OUTPUT); // Set IO 5 on expander 1 (addressed as 0x01) to OUTPUT
openknx.gpio.digitalWrite(0x0105, HIGH); // Write HIGH to IO 5 on expander 1
openknx.gpio.digitalRead(0x0105); // Read IO 5 on expander 1
openknx.gpio.pinMode(0x0202, INPUT_PULLUP); // Set IO 2 on expander 2 (addressed as 0x02) to INPUT_PULLUP- TCA6408: 8-bit I2C port expander
- TCA9555: 16-bit I2C port expander
- PCA9554: 8-bit I2C-bus expander (I2C slave address range 0x20 to 0x27)
- PCA9557: 8-bit I2C-bus expander (I2C slave address range 0x18 to 0x1F)
pinMode, digitalRead, digitalWrite, attachInterrupt
- Interrupt handling on expanders
- analogRead/Write
- bulk setting of pins
- Multicore
- expanders on different I2C units (e.g. 0x01 on Wire, 0x02 on Wire1)