Alex Miller
Published

Tiny ML for Big Hearts on an 8-bit Microcontroller

Predict the possibility of arrhythmias on an 8- bit Microcontroller, without sending the corresponding sensor data to the cloud.

ExpertProtip7,266
Tiny ML for Big Hearts on an 8-bit Microcontroller

Things used in this project

Hardware components

Arduino Mega 2560
Arduino Mega 2560
ATmega2560 8-bit MCU
×1

Software apps and online services

Neuton
Neuton Tiny ML Neuton

Story

Read more

Code

Packet header structure.

C/C++
As you see, the archive folder contained all the necessary files, that simply could be transferred to the microcontrollers firmware project.

Since I did not have a real cardiograph, I streamed data from a computer. To do this, I developed a simple protocol that consisted of a header, a data section, and a checksum. The packet header had the following structure:
typedef struct
{
   uint16_t preamble;
   uint16_t size;
   uint8_t  type;
   uint8_t  error;
   uint8_t  reserved[2];
}
PacketHeader;

Creation of the neural network object.

C/C++
Then I developed a packet parser that would receive a stream of bytes from the USB-UART interface of the system board as input and, upon receiving a packet with the correct checksum, will activate the callback function for processing data packets.

Let's open the user_app.c file and create a neural network object:
static NeuralNet neuralNet = { 0 };

UART data parser.

C/C++
And I wrote a code to call the parser when receiving data from the UART:
Let's compile and upload the sketch to the system board (the "Verify" and "Upload" buttons). Success!
void loop()
{
   if (!initialised)
   {
       while(1)
       {
           led_red(1);
           led_green(0);
           delay(100);
           led_red(0);
           led_green(1);
           delay(100);
       }
   }
 
   while (Serial.available() > 0)
       parser_parse(Serial.read());
}

Information about the number of model inputs, data transfers for performing prediction.

C/C++
I also provided packets with information about the number of model inputs, data transfers for performing predictions, as well as a report on the memory consumed by the calculator RAM and Flash and prediction time:
typedef enum
{
   TYPE_ERROR = 0,
   TYPE_MODEL_INFO,
   TYPE_DATASET_INFO,
   TYPE_DATASET_SAMPLE,
   TYPE_PERF_REPORT,
}
PacketType;

Setup function.

C/C++
To initialize the neural network object, I called the CalculatorInit function. Upon successful initialization, the callback function CalculatorOnInit was called, in which I loaded the model from the model.c file.

For prediction, I called the CalculatorRunInference function. This function, in its turn, activates three callback functions: before and after the prediction, as well as the one that contains the results of the prediction. I filled them in: in the CalculatorOnInferenceStart function I started, and in the CalculatorOnInferenceEnd function I stopped the timer and calculated the minimum, maximum, and average value of the prediction time.

In the CalculatorOnInferenceResult function, I analyzed the class probabilities for the presence/absence of arrhythmia. Upon its absence, I turned on the green LED, but if the arrhythmia was detected - the red one. I connected the LEDs to GPIO ports 52 and 53 and sent prediction results to the computer.

In the sketch file, I initialized the neural network object, packet parser, GPIO, and UART ports:
void setup()
{
   pinMode(LED_RED, OUTPUT);
   pinMode(LED_GREEN, OUTPUT);
 
   led_red(1);
   led_green(1);
 
   initialised = (0 == app_init());
   initialised &= (0 == parser_init(channel_on_valid_packet, app_inputs_size()));
 
   Serial.begin(230400);
}

Link to the utility for uploading the dataset.

Utility for uploading the dataset.

Link to the project for the microcontroller.

Microcontroller codebase.

Credits

Alex Miller

Alex Miller

8 projects • 76 followers
Director of Data Science and Engineering at Neuton.AI

Comments