
Pulse heart
The pulse heart shows a four-chambered heart animation.
The design goal was to come up with some sort of approximation of heart rate based on accelerometer data.
I attempted to build a machine learning model, modeling off of my own heart rate, building a bluetooth data capture system with a wireless chest strap heart rate monitor.
I spent ages trying to build a model that could be run on the ATtiny84, which is a very basic microcontroller that does not support floating point or division. I designed features based on addition and bit-shifting only, and came up with more than 20 features.
I trained the model in a python notebook, carefully recreating the features I had designed in C to behave exactly the same way in python.
The result was something like 80% accurate, which is not that bad an approximation. (There’s no way to get heart rate for real without an on-skin sensor, though it could be receieved over BLE efficiently I scoped that out of this project.)
I liked the idea of modelling my own heart and giving it to people. The badge would, in a sense, approximate what my heart would do if I were acting like you. I like the idea of creating something that not just looks pretty but feels meaningful.
The circuit uses 8 banks of leds for each chamber wall, including intersections.
Integer only heart rate model
I trained a linear regression model using only integer math.
The model was:
uint32_t(
+ 259
+ scale16(getFilterValue(mov10), 808)
+ scale16(getFilterValue(mov12), 1193)
+ scale16(getFilterValue(mov06), 619)
+ scale16(getFilterValue(mov08), 661)
+ scale16(getFilterValue(buckets[0]), 1463)
- scale16(getFilterValue(buckets[1]), 1117)
- scale16(getFilterValue(buckets[2]), 2313)
- scale16(getFilterValue(buckets[3]), 522)
- scale16(getFilterValue(buckets[4]), 3373)
- scale16(getFilterValue(buckets[5]), 2037)
- scale16(getFilterValue(buckets[6]), 8812)
- scale16(getFilterValue(avgTimeSinceCross), 1594)
) << 8;
A simple integer low-pass filter can be constructed using only shifts and addition. This is essential for feature development without floating point:
void smoothInt(uint16_t sample, uint8_t bits, int32_t *filter) {
int32_t local_sample = ((int32_t) sample) << 16;
*filter += (local_sample - *filter) >> bits;
}
Another essential function which lets you effectively do division through reciprocal multiplication:
uint16_t scale16(uint16_t i, fract16 scale) {
uint16_t result;
result = ((uint32_t)(i) * (uint32_t)(scale)) >> 16;
return result;
}
Although the ATtiny84 does not even have a hardware multiply operation.