px-fwlib 0.10.0
Cross-platform embedded library and documentation for 8/16/32-bit microcontrollers generated with Doxygen 1.9.2
  1. Introduction

The foundation of px-fwlib is knowledge and freedom: the knowledge and freedom to use the right microcontroller for the job and not trying to force a square peg in a round hole.

px-fwlib is a collection of open source C firmware and documentation for microcontrollers to develop portable bare-metal code that is vendor and architecture neutral (or easier to reuse). It is tough to find the best compromise between lean 8-bit targets, middle-of-the-road 16-bit targets, and resource rich 32-bit targets, but this cross-platform library aims to provide a good foundation before you are forced to add target specific code and getting locked in.

Click HERE to download releases of the open source library (source code and offline documentation).

STM32L072 PX-HER0 Board now in stock at Crowd Supply! Click HERE

1.1 Goals

  • Teach good firmware development practices to guide the next generation of professional embedded engineers.
  • Share source code, knowledge and expertise with our global community of engineers, scientists and enthusiasts.
  • Publish concise quick start guides and tutorials to reduce the learning curve of new microcontrollers.
  • Provide a standard framework and drivers for rapid code development.
  • Minimize porting by providing unified peripheral driver APIs (to maximize device driver reuse).

My sincere hope is that seasoned veterans will pitch in and share their years of experience to mentor the future generation and raise the bar in terms of quality and quantity. See 1.7 Questions or Feedback?

1.2 Sections

This documentation is divided into the following sections:

1.3 Important Links

1.4 Companion Boards

Here is the collection of boards that are supported with Board Support Packages (BSP), documentation and complete examples:

Microcontroller Board
Microchip ATmega328P Arduino Uno R3 board
Microchip ATmega328P Microchip ATmega328P-XMINI Board
Microchip ATmega328PB Microchip ATmega328PB-XMINI Board
Microchip ATmega328P Piconomix ATmega328P Scorpion Board
ST STM32L072RB Piconomix STM32L072RB PX-HER0 Board
ST STM32L053R8T6 ST Nucleo64 L053R8 Board

1.5 Library Examples

Here are four examples selected to pique your interest:

1.5.1 CLI Exlorer (Command Line Interpreter)

Included with the library is a CLI (Command Line Interpreter) executing on an Arduino Uno R3 that creates a "Un\*x Shell"-like environment running on the microcontroller so that you can experiment with GPIO, ADC, I2C and SPI using only an ANSI/VT100 terminal emulator (for example Tera Term).

Connect your board, fire up the CLI and verify that it works:

The BMP280 is found at 7-bit slave address 0x76. The chip identification register value for the BMP280 is 0x58. This confirms that you are able to write to and read from the I2C slave.

For more info see CLI Explorer (Command Line Interpreter).

1.5.2 I2C

This example demonstrates how easy it is to set up and communicate with an I2C slave device after you have verified with the CLI that it works:

#include "px_i2c.h"
#include "px_board.h"
// Bosch BMP280 I2C Slave Address
#define I2C_SLA_ADR 0x76
// Create I2C Slave handle object
px_i2c_handle_t px_i2c_handle;
int main(void)
uint8_t data[1];
// Initialise board
// Initialise I2C driver
// Open handle to I2C slave device
px_i2c_open(&px_i2c_handle, PX_I2C_NR_0, I2C_SLA_ADR);
// START I2C write transaction and write register address
data[0] = 0xd0;
px_i2c_wr(&px_i2c_handle, data, 1, PX_I2C_FLAG_START_AND_END);
// REPEATED START I2C read transaction and read register value
px_i2c_rd(&px_i2c_handle, data, 1, PX_I2C_FLAG_REP_START_AND_STOP);
// Close I2C Handle
void px_board_init(void)
Initialise the board hardware.
Definition: px_board.c:168
bool px_i2c_close(px_i2c_handle_t *handle)
Close specified handle.
Definition: px_i2c.c:184
void px_i2c_init(void)
Initialise I2C driver.
Definition: px_i2c.c:107
bool px_i2c_open(px_i2c_handle_t *handle, px_i2c_nr_t i2c_nr, uint8_t slave_adr)
Open I2C slave handle.
Definition: px_i2c.c:118
bool px_i2c_rd(px_i2c_handle_t *handle, void *data, size_t nr_of_bytes, uint8_t flags)
Perform an I2C read transaction with an I2C slave.
Definition: px_i2c.c:430
Begin and finish an I2C transaction with a REP START and STOP condition.
Definition: px_i2c.h:97
Begin and finish an I2C transaction with a START and END condition.
Definition: px_i2c.h:95
bool px_i2c_wr(px_i2c_handle_t *handle, const void *data, size_t nr_of_bytes, uint8_t flags)
Perform an I2C write transaction with an I2C slave.
Definition: px_i2c.c:240
Define I2C handle for a slave.
Definition: px_i2c.h:104

1.5.3 GPIO

This example demonstrates how easy and efficient it is to define and use a GPIO pin:

#include "px_gpio.h"
// LED is on PORT B, pin 0, configured as an output, initally off
// Push Button is on PORT D, pin 6, configured as an input, pull-up enabled
* Declare gpio handle objects as 'static const' to tell the compiler that it
* will not be used anywhere else ('static') and it will never change ('const').
* This gives the compiler the most freedom to optimize the code.
static const px_gpio_handle_t px_gpio_led = {PX_GPIO_LED};
static const px_gpio_handle_t px_gpio_pb = {PX_GPIO_PB};
int main(void)
// Initialise pins
// Loop forever
// Is button being pressed?
// Enable LED
// Disable LED
void px_gpio_out_set_lo(const px_gpio_handle_t *gpio)
Set GPIO pin output low.
Definition: px_gpio.h:414
bool px_gpio_in_is_lo(const px_gpio_handle_t *gpio)
Test if GPIO pin input is low.
Definition: px_gpio.h:444
void px_gpio_out_set_hi(const px_gpio_handle_t *gpio)
Set GPIO pin output high.
Definition: px_gpio.h:408
void px_gpio_init(const px_gpio_handle_t *gpio)
Initialise a GPIO pin using supplied handle.
Definition: px_gpio.c:69
GPIO pin handle definition.
Definition: px_gpio.h:114

Even though it looks like functions calls are used, the lines reduce to single assembly statements for maximum efficiency and minimum code size. This is the generated assembly to inspect:

0000007a <main>:
// Initialise pins
7a: 28 98 cbi 0x05, 0 ; 5
7c: 20 9a sbi 0x04, 0 ; 4
7e: 5e 9a sbi 0x0b, 6 ; 11
80: 56 98 cbi 0x0a, 6 ; 10
// Loop forever
// Is button being pressed?
82: 4e 99 sbic 0x09, 6 ; 9
84: 02 c0 rjmp .+4 ; 0x8a <main+0x10>
// Enable LED
86: 28 9a sbi 0x05, 0 ; 5
88: fc cf rjmp .-8 ; 0x82 <main+0x8>
// Disable LED
8a: 28 98 cbi 0x05, 0 ; 5
8c: fa cf rjmp .-12 ; 0x82 <main+0x8>

For IoT low power sensor nodes it is extremely important to wake up, do the job as fast as possible and go back to sleep again. C efficiency trumps C++ flexibility. Less code translates to a cheaper solution.

1.5.4 Data Logging

px_log_fs is a basic (but resilient) record-based file system to log data to AT45D DataFlash (or similiar serial flash storage). It is perfect for IoT (Internet of Things) low power sensor nodes that need to log data periodically, because it has basic wear levelling, handles power loss gracefully, requires less code and data memory and executes quickly which means you can get away with a smaller, cheaper microcontroller.

A section of serial flash can also be reserved for secure "over-the-air" encrypted firmware upgrades.

Usage example:

#include <stdio.h>
#include "px_board.h"
#include "px_spi.h"
#include "px_at25s.h"
#include "px_log_fs.h"
// Define record data content
typedef struct
int16_t temperature;
} px_log_fs_data_t;
static px_spi_handle_t px_at25s_spi_handle;
static px_log_fs_handle_t px_log_fs_handle;
static px_log_fs_data_t px_log_fs_data;
int main(void)
uint8_t i;
px_log_fs_err_t px_log_fs_err;
// Initialise modules
px_spi_open2(&px_at25s_spi_handle, PX_SPI_NR_1, PX_BOARD_SPI_CS_AT25S,
// Initialise file system
px_log_fs_init(&px_log_fs_handle, 0, PX_AT25S_PAGES - 1);
// Write 8 records
px_log_fs_data.temperature = 160; // 16.0 deg Celsius
for(i = 0; i < 8; i++)
// Write record
px_log_fs_wr(&px_log_fs_handle, &px_log_fs_data, sizeof(px_log_fs_data));
// Increment temperature by 1.3 deg
px_log_fs_data.temperature += 13;
// Read first record
px_log_fs_err = px_log_fs_rd_first(&px_log_fs_handle, &px_log_fs_data, sizeof(px_log_fs_data));
while(px_log_fs_err == PX_LOG_FS_ERR_NONE)
// Display record data
printf("%d\n", px_log_fs_data.temp);
// Read next record
px_log_fs_err = px_log_fs_rd_next(&px_log_fs_handle, &px_log_fs_data, sizeof(px_log_fs_data));
void px_at25s_init(px_spi_handle_t *handle)
Initialise driver.
Definition: px_at25s.c:95
SPI Data order.
Definition: px_at25s.h:129
#define PX_AT25S_SPI_MODE
SPI Clock / Data phase.
Definition: px_at25s.h:127
Maximum SPI Clock rate.
Definition: px_at25s.h:125
px_log_fs_err_t px_log_fs_init(px_log_fs_handle_t *handle, uint16_t fs_page_start, uint16_t fs_page_end)
Initialise log file system.
Definition: px_log_fs.c:452
px_log_fs_err_t px_log_fs_rd_next(px_log_fs_handle_t *handle, void *data, size_t nr_of_bytes)
Read next (newer) record.
Definition: px_log_fs.c:675
px_log_fs_err_t px_log_fs_rd_first(px_log_fs_handle_t *handle, void *data, size_t nr_of_bytes)
Read first (oldest) record.
Definition: px_log_fs.c:607
px_log_fs_err_t px_log_fs_wr(px_log_fs_handle_t *handle, const void *data, size_t nr_of_bytes)
Write a record to the file.
Definition: px_log_fs.c:897
Error codes.
Definition: px_log_fs.h:236
No error.
Definition: px_log_fs.h:237
void px_spi_init(void)
Initialise SPI driver.
Definition: px_spi.c:162
bool px_spi_open2(px_spi_handle_t *handle, px_spi_nr_t spi_nr, uint8_t cs_id, px_spi_baud_t baud, px_spi_mode_t mode, px_spi_dord_t data_order, uint8_t mo_dummy_byte)
Open SPI peripheral using specified parameters.
Definition: px_spi.c:252
Define SPI handle.
Definition: px_spi.h:126

For a complete working example (with nice flash file system debugging), see Temp&Pressure Data Logger Application.

1.6 Coding Evolution

Great athletes spend years tweaking tiny aspects of their performance to get a competitive edge: bicycle riders adapt their posture in a wind tunnel, golfers analyse their swing,... Like the shark became the apex predator of the sea, we as coders must be inspired to evolve and perfect our art or wither.

Without mentorship, we tend to get stuck with the first coding style and programming patterns that we learned and keep on using it without a second thought, sometimes even justifying and defending it religiously.

After you have written a piece of code, take a break, return and spend 15 minutes to question if the code you have just written is the optimal / best / most readable solution (applying advice from Edward de Bono, the father of lateral thinking). Refactor, rinse, repeat. Next time you will use this improved snippet instead of copying and adapting the same old, inferior code from memory.

Ask yourself critically: which is better?

// Small fixed delay
for(i = 0; i < 100; i++)
__asm__ __volatile__("nop \n\t"::);


// Small fixed delay
for(i = 100; i != 0; i--)
__asm__ __volatile__("nop \n\t"::);

Inspect the assembly generated by the compiler. Maybe the compiler is smarter than you give it credit for (or not... it depends)

Evolve, improve or be left behind!

1.7 Questions or Feedback?

Questions or feedback (positive and negative) is great and will help to improve the library:

Methods of communication:

1.8 Code Contributions

You are most welcome to submit bug fixes, new code or documentation. It should:

1.9 Support

A significant amount of time has been invested to craft and refine this free open source library. If you saved development time, acquired a new skill, or advanced your career, you are welcome to support this project with a visit to the shop.

Click HERE to visit the shop