2020-09-09

Extensible C APIs

The problem we want to solve is how to add functionality to an existing C API in an elegant way. You may have seen the practice to add one extra parameter at the end of a function call, reserved for future use. It must always be 0 in the first version of the API. The implementation should enforce it to be 0 to avoid unpleasant surprises in the future.

uint32_t do_work(int32_t a, uint8_t b, void* rfu);

Later, when additional requirements make it necessary to vary how "work is done" and the variation needs one additional parameter, uint16_t c. It is added in the second version of the API.

struct do_work_extensionA {
    uint16_t c;
    void* rfu;
};
uint32_t do_work(int32_t a, uint8_t b, do_work_extensionA* extA);

When extA is 0 do_work() should behave exactly like the first version to be backwards compatible. Existing users can keep their code as it is until they need the new functionality.

You may have spotted the pattern, and to be sure we will add a third version of the API with yet more parameters.

struct do_work_extensionB {
    uint8_t* d;
    uint32_t e;
    void* rfu;
};
struct do_work_extensionA {
    uint16_t c;
    do_work_extensionB* extB;
};
uint32_t do_work(int32_t a, uint8_t b, do_work_extensionA* extA);

An alternative to the extension scheme described above is to use multiple entry points.

uint32_t do_work(int32_t a, uint8_t b);
uint32_t do_work_extA(int32_t a, uint8_t b, uint16_t c);
uint32_t do_work_extB(int32_t a, uint8_t b, uint16_t c, uint8_t* d, uint32_t e);

You decide which method suits you best.

2020-09-05

PCB Version and Assembly Discrimination

You should give embedded software a way to figure out its place in the world. "I just woke up. Where am I?" This can be done by giving the PCB a version number. Allocate a few GPIOs, say three, and you can have the same binary run on 8 different PCBs. The software has the corresponding 8-bit vector which tells itself or a bootloader if it is compatible with a certain PCB. 0b00000101 means the software is compatible with PCB version 0 and 2. This prevents old, incompatible software to run afoul on new PCBs which it hasn't got routines to handle. It is common to forget to check this in software before there are more than one PCB version, but this check is needed from the start to prevent that old versions of the software is installed and executed on new versions of PCBs they don't support.

The next step is to allocate a few GPIOs for variations in the assembly. These are by default high by internal pullups. Zero Ohm resistors can be mounted to sink them to ground. You now have the possibility to vary the assembled components on the board and all variants can be handled by the same binary. Exactly what it means that any one GPIO is low can be decided at the moment when the need for an alternate assembly arises.

 If you have an analog pin to spare you can encode the assembly version with a voltage divider instead. The analog range is divided up in discrete intervals, each representing an assembly version. With 1% resistors it is reasonable to divide the full range (5V) into 25 separate ranges, i.e. 0.2V per range. One analog input can decode 25 different assembly versions. If you don't want the divider to always consume current you can connect the low side to a GPIO and turn off the divider by setting it high.

What if you only have one GPIO pin and want to encode more than two versions? Set the GPIO as output and charge a capacitor. Switch over to input and measure the discharge time. The more versions you need, the longer it will take to find out which is present.

It is possible to solve the problem with evolving hardware by building separate binaries for each configuration. The drawback is that you need to be careful with which binary you install on which hardware, because mistakes are not automatically detected. This is a complication I prefer to avoid.

Felfri produktutveckling

Skojade bara. När vi konstruerar och tillverkar produkter är det alltid möjligt att göra misstag. Konstruktionen fungerar inte som det var t...