libpi2c is a small, C++ thread and exception safe set of core files and drivers for a number of I2C devices from Adafruit.
There is a main() in mustang.cpp, which demonstrates my goals by implementing a thread safe, exception safe, self-healing program to monitor engine temperature and control both a fan and a water pump via external PWM controllers.
Mostly, this is a chance for me to refresh my C++ skills. I've done a lot of concurrent C++ programming, and decided to combine this into a project to run my cooling system on a 1965 Mustang that I'm using cheap PWM controllers to control the electric fan and water pump with.
Most previous work I've done was using older boost libraries, but as newer versions of C++ have come along, they've included more and better primitives for concurrency, which I wanted to explore here.
I've also wanted to try adapting "my view" of what a microcontroller might look like in modern C++, using threads and exceptions to improve readability, separation of concerns, testing, and self-healing in flaky I2C circuits.
Building requires C++, cmake and probably a few other things installed.
To build libpi2c:
cmake -Dbuild
cd build
make
The core file is i2cbus.cpp, which provides thread safe access to the i2c bus. If a function returns, it worked. If an exception was thrown, it did not.
All devices are designed to accept a shared pointer to a single global i2cbus. All communications occur through the i2cbus object. Eventually, I will add support for other i2c busses, hopefully including "bit bang" to digital I/O pins.
The list of devices is not long, but includes I2C devices from adafruit and sparkfun.
- ads1015 - 12 bit precision analog to digitial converter Adafruit
- aht20 - temperature and humidity sensor Adafruit
- ds3502 - digital 10K potentiometer Adafruit
- lsm6ds33 - 9 degrees of freedom IMU with Accel/Gyro/ Mag Adafruit
- mpl3115a2 - barometric pressure/temperature Adafruit
- pca9685 - 16 channel PWM/servo interface Adafruit
- ssd1306 - 128x32 OLED Adafruit
- tca9548a - I2c Multiplexer Adafruit
- tmp117 - high accuracy temperature sensor Adafruit
- vcnl4040 - proximity and Lux sensor Adafruit
This repository is an experiment in demonstrating thoughts I've had about microcontroller type devices - how they are implemented goes a long way towards determining how reusable the engineering is, how robust they are, and how much fun they are to work on.
Use RAII contruction, smart pointers to manage lifetime, and proper destruction, such that the sytem can be either "uncleanly" terminated via signal, or cleanly shut down, and yet maintain memory coherency.
External, asychronous events are important to the life of a microcontroller. Though done in Debian/Ubuntu user space, I want this to grow to be able to properly handle asychronous events.
In the included code, the signal is crude - simply handle a SIGTERM signal, but do so in such a way that all threads exit cleanly with no memory leaks or hangs.
In my breadboarding experience, it is common for I2C devices to drop offline for various reasons - noise on the power lines, jostling connectors, and so on.
A thread managing a single device should not die - there must be a sane, easy to read retry/reset/reconnect mechanism, short of a power cycle.
Ideally, any I2C device should be allowed to be removed from the system, and all devices that don't depend on it will continue functioning as expected.
This is a goal, and mostly one to experiment with this combination of C++ exceptions with self-healing.
I'm quite frustrated reading other code libraries that mix concerns between different layers of drivers, and end up with a mess of different ways of detecting errors and acting on them. As things get more complicated, it gets harder and harder to ensure that exceptional conditions are being dealt with in a correct fashion, thus making it ever more important to make core business logic logic of an application aware of, and very clearly able to deal with, exceptional conditions.
Doing this well is hard, but proper abstraction, consistent idiomatic forms of error detection and handling should work to make a more robust, safer to use, device.
In general, I feel code that is isolated to simple areas of concern is easier to maintain, easier to test, and easier to "prove" that it is correct. This is obviously impossible to truly achieve, but every reasonable effor should be made to do so, and is especially fitting for a concurrent implementation like this.
Avoid copy/paste where possible. Any instance of common code is getting pushed down a layer so that it can be shared by other drivers if possible.