18. Chapter LVGL Heartrate

In this chapter, we will learn how to create a heartrate monitor.

18.1. Project 18.1 LVGL Heartrate

In this project, we will learn how to obtain the raw data and heartrate data of the MAX30102 module and display them on the screen.

18.1.1. Component List

ESP32-S3 WROOM x1

Chapter02_00

USB cable x1

Chapter02_01

SDcard x1

Chapter04_00

Card reader x1 (random color)

(Not a USB flash drive.)

Chapter04_01

2.8-inch screen

Chapter07_00

ESP32-S3 WROOM Shield x1

(Not a USB flash drive.)

Chapter01_01

9V battery x1

(Not included in the kit, prepared by yourself)

Chapter01_03

9V battery cable x1

Chapter05_02

18.1.2. Circuit

If you have not yet used the SD card, please refer to Chapter 4. Click here to navigate back to Chapter 4.

Before connecting the USB cable, insert the SD card into the SD card slot on the back of the ESP32-S3.

../../../_images/Chapter05_08.png

Connect Freenove ESP32-S3 to the computer using the USB cable.

../../../_images/Chapter05_09.png

18.1.3. Sketch

18.1.3.1. Sketch_18_LVGL_Heartrate

../../../_images/Chapter18_00.png

The following is the program code:

 1#include "display.h"
 2#include <lvgl.h>
 3#include <TFT_eSPI.h>
 4#include "FT6336U.h"
 5
 6#include "heartrate_ui.h"
 7
 8Display screen;
 9
10void setup(){
11    /* prepare for possible serial debug */
12    Serial.begin( 115200 );
13
14   /*** Init drivers ***/
15    screen.init();
16    heartrate_init();
17
18    String LVGL_Arduino = "Hello Arduino! ";
19    LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
20    Serial.println( LVGL_Arduino );
21    Serial.println( "I am LVGL_Arduino" );
22
23    setup_scr_heartrate(&guider_heartrate_ui);
24    lv_scr_load(guider_heartrate_ui.heartrate);
25
26    Serial.println( "Setup done" );
27}
28
29void loop(){
30    screen.routine(); /* let the GUI do its work */
31    delay( 5 );
32}

Configure the heart rate monitor interface and load this interface.

1setup_scr_heartrate(&guider_heartrate_ui);
2lv_scr_load(guider_heartrate_ui.heartrate);

18.1.3.2. heartrate_ui.h

Declare the functions so that they can be called in the ino file.

 1#ifndef __HEARTRATE_UI_H
 2#define __HEARTRATE_UI_H
 3
 4#include "Arduino.h"
 5#include "lvgl.h"
 6
 7#define I2C_SDA 2
 8#define I2C_SCL 1
 9
10#define HEARTRATE_SERIAL 0     //Serial print the original ir value, average value and dynamic data.
11#define CHART_LOW_LIMIT 0      //Lower limit of Chart
12#define CHART_HIGH_LIMIT 2000  //Upper limit of Chart
13
14typedef struct lvgl_heartrate   
15{
16	lv_obj_t *heartrate;
17	lv_obj_t *heartrate_chart;
18	lv_obj_t *heartrate_label;
19	lv_obj_t *heartrate_home;
20}lvgl_heartrate_ui;
21
22extern lvgl_heartrate_ui guider_heartrate_ui;     //music ui structure 
23
24int heartrate_init(void);                        //Initialization heartrate module
25void heartrate_shutdown(void);                   //The heartrate module is enabled to enter low power mode
26void heartrate_wake_up(void);                    //Wake up the heartrate module
27
28void loopTask_heartrate(void *pvParameters);     //heartrate task thread
29void create_heartrate_task(void);                //Create heartrate task thread
30void stop_heartrate_task(void);                  //Close the heartrate thread
31
32void setup_scr_heartrate(lvgl_heartrate_ui *ui);  //Parameter configuration function on the heartrate screen
33
34#endif

18.1.3.3. heartrate_ui.cpp

  1#include "heartrate_ui.h"
  2//#include "main_ui.h"
  3#include "lv_img.h"
  4#include <Wire.h>
  5#include "heartRate.h"
  6
  7#define I2C_BUFFER_LENGTH 128
  8#include "MAX30105.h"
  9
 10MAX30105 heartrate;                  //apply a module object
 11static lv_chart_series_t *series;   //apply an lvgl chart variable
 12lvgl_heartrate_ui guider_heartrate_ui;//heartrate ui structure 
 13int heartrate_task_flag = 0;         //heartrate thread running flag
 14TaskHandle_t heartrateTaskHandle;    //heartrate thread task handle
 15
 16const byte RATE_SIZE = 4;           //Increase this for more averaging. 4 is good.
 17byte rates[RATE_SIZE];              //Array of heart rates
 18byte rateSpot = 0;                  //Record the number of bits in the heart rate array
 19long lastBeat = 0;                  //Time at which the last beat occurred
 20float beatsPerMinute;               //store the heartbeat value per minute
 21int beatAvg = 0;                    //Heart rate average
 22int beatAvgLast = 0;                //Average heart rate from last time
 23byte irvalueSpot = 0;               //Record the number of bits in the heartrate irvalue array
 24const byte AVERAGE_NUM = 10;        //Increase this for more averaging. 10 is good.
 25long heartrate_irvalue[AVERAGE_NUM]; //Array of heartrate irvalue
 26long show_value = 0;                //The data values of the points in the heart rate line plot
 27
 28//Initialization heartrate module
 29int heartrate_init(void) {
 30  if (!heartrate.begin(Wire, I2C_SPEED_FAST)) {
 31    Serial.println("MAX30105 was not found. Please check wiring/power. ");
 32    return -1;
 33  }
 34  heartrate.setup();  //Configure sensor with these settings
 35  heartrate_shutdown();
 36  return 0;
 37}
 38
 39//The heartrate module is enabled to enter low power mode
 40void heartrate_shutdown(void) {
 41  heartrate.shutDown();
 42}
 43
 44//Wake up the heartrate module
 45void heartrate_wake_up(void) {
 46  heartrate.wakeUp();
 47}
 48
 49//Calculate the average value of the array
 50long heartrate_average(long array[], int num) {
 51  long average = 0;
 52  for (int i = 0; i < num; i++) {
 53    average += array[i];
 54  }
 55  average = average / num;
 56  return average;
 57}
 58
 59//heartrate task thread
 60void loopTask_heartrate(void *pvParameters) {
 61  Serial.println("loopTask_heartrate start...");
 62
 63  while (heartrate_task_flag) {
 64    long irValue = heartrate.getIR();  //Get the raw data
 65    if (irValue < 50000) {
 66      delay(100);
 67    }
 68    else{
 69      if (checkForBeat(irValue) == true) {
 70        //We sensed a beat!
 71        long delta = millis() - lastBeat;
 72        lastBeat = millis();
 73        beatsPerMinute = 60.0 / (delta / 1000.0);
 74
 75        if (beatsPerMinute < 255 && beatsPerMinute > 50) {
 76          rates[rateSpot++] = (byte)beatsPerMinute;  //Store this reading in the array
 77          rateSpot %= RATE_SIZE;                     //Wrap variable
 78          //Take average of readings
 79          beatAvg = 0;
 80          for (byte x = 0; x < RATE_SIZE; x++)
 81            beatAvg += rates[x];
 82          beatAvg /= RATE_SIZE;
 83        }
 84      }
 85      heartrate_irvalue[irvalueSpot++] = irValue;  //Put the raw data into an array
 86      irvalueSpot %= AVERAGE_NUM;                 
 87      long average = heartrate_average(heartrate_irvalue, AVERAGE_NUM);
 88      show_value = irValue - average + (CHART_HIGH_LIMIT / 2);
 89
 90      #if HEARTRATE_SERIAL
 91        Serial.printf("%ld %ld %ld\r\n", irValue, average, show_value);
 92      #endif
 93
 94      if (irValue > 50000 && show_value > CHART_LOW_LIMIT && show_value < CHART_HIGH_LIMIT)
 95        lv_chart_set_next_value(guider_heartrate_ui.heartrate_chart, series, show_value);
 96      if (beatAvg != beatAvgLast) {
 97        beatAvgLast = beatAvg;
 98        lv_label_set_text_fmt(guider_heartrate_ui.heartrate_label, "HeartRate: %d", beatAvg);
 99      }
100    }
101  }
102  heartrate_shutdown(); 
103  vTaskDelete(heartrateTaskHandle);
104}
105
106//Create heartrate task thread
107void create_heartrate_task(void) {
108  if (heartrate_task_flag == 0) {
109    heartrate_task_flag = 1;
110    xTaskCreate(loopTask_heartrate, "loopTask_heartrate", 8192, NULL, 1, &heartrateTaskHandle);
111  } else {
112    Serial.println("loopTask_heartrate is running...");
113  }
114}
115
116//Close the heartrate thread
117void stop_heartrate_task(void) {
118  if (heartrate_task_flag == 1) {
119    heartrate_task_flag = 0;
120    while (1) {
121      if (eTaskGetState(heartrateTaskHandle) == eDeleted) {
122        break;
123      }
124      vTaskDelay(10);
125    }
126    Serial.println("loopTask_heartrate deleted!");
127  }
128}
129
130//Click the logo icon, callback function: goes to the main ui interface
131static void heartrate_home_event_handler(lv_event_t *e) {
132  lv_event_code_t code = lv_event_get_code(e);
133  switch (code) {
134    case LV_EVENT_CLICKED:
135      {
136        Serial.println("Clicked the logo button.");
137      }
138      break;
139    case LV_EVENT_RELEASED:
140      {
141        /*
142        stop_heartrate_task();
143        delay(100);
144        if (!lv_obj_is_valid(guider_main_ui.main))
145          setup_scr_main(&guider_main_ui);
146        lv_disp_t *d = lv_obj_get_disp(lv_scr_act());
147        if (d->prev_scr == NULL && d->scr_to_load == NULL)
148          lv_scr_load(guider_main_ui.main);
149        lv_obj_del(guider_heartrate_ui.heartrate);
150        */
151      }
152      break;
153    default:
154      break;
155  }
156}
157
158//Parameter configuration function on the heartrate screen
159void setup_scr_heartrate(lvgl_heartrate_ui *ui) {
160  ui->heartrate = lv_obj_create(NULL);
161
162  static lv_style_t bg_style;
163  lv_style_init(&bg_style);
164  lv_style_set_bg_color(&bg_style, lv_color_hex(0xffffff));
165  lv_obj_add_style(ui->heartrate, &bg_style, LV_PART_MAIN);  
166
167  lv_img_home_init();
168
169  /*Init the pressed style*/
170  static lv_style_t style_pr;//Apply for a style
171  lv_style_init(&style_pr);  //Initialize it
172  lv_style_set_translate_y(&style_pr, 5);//Style: Every time you trigger, move down 5 pixels
173
174  /*Create a chart1*/
175  ui->heartrate_chart = lv_chart_create(ui->heartrate);
176
177  lv_obj_set_size(ui->heartrate_chart, 180, 200);
178  lv_obj_set_pos(ui->heartrate_chart, 60, 120);
179  lv_chart_set_type(ui->heartrate_chart, LV_CHART_TYPE_LINE); /*Show lines and points too*/
180  lv_chart_set_div_line_count(ui->heartrate_chart, 5, 10);          //Set chart to 5 rows and 10 columns
181  lv_chart_set_point_count(ui->heartrate_chart, 100);               //chart shows the data for 100 points
182  lv_obj_set_style_size(ui->heartrate_chart, 0, LV_PART_INDICATOR); //Make each data point unsalient   
183
184  lv_chart_set_update_mode(ui->heartrate_chart, LV_CHART_UPDATE_MODE_SHIFT);//Move chart to update data
185  /*Add two data series*/
186  series = lv_chart_add_series(ui->heartrate_chart, lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_SECONDARY_Y);//Let the data be on the right y axis
187  lv_chart_set_range(ui->heartrate_chart, LV_CHART_AXIS_SECONDARY_Y, CHART_LOW_LIMIT, CHART_HIGH_LIMIT);         //Set the value range of right y axis
188
189  lv_chart_set_axis_tick(ui->heartrate_chart, LV_CHART_AXIS_PRIMARY_Y, 3, 1, 11, 2, true, 60);                   //Let the axis be on the left y axis
190  lv_chart_set_range(ui->heartrate_chart, LV_CHART_AXIS_PRIMARY_Y, CHART_LOW_LIMIT, CHART_HIGH_LIMIT);           //Set the value range of left y axis
191
192  ui->heartrate_label = lv_label_create(ui->heartrate);
193  lv_obj_set_pos(ui->heartrate_label, 0, 50);
194  lv_obj_set_size(ui->heartrate_label, 140, 40);
195  lv_label_set_text(ui->heartrate_label, "HeartRate:0");
196  lv_label_set_long_mode(ui->heartrate_label, LV_LABEL_LONG_WRAP);
197  lv_obj_set_style_text_align(ui->heartrate_label, LV_TEXT_ALIGN_CENTER, 0);
198
199  ui->heartrate_home = lv_imgbtn_create(ui->heartrate);
200  lv_obj_set_pos(ui->heartrate_home, 150, 20);
201  lv_obj_set_size(ui->heartrate_home, 80, 80);
202  lv_img_set_src(ui->heartrate_home, &img_home);
203  lv_obj_add_style(ui->heartrate_home, &style_pr, LV_STATE_PRESSED);//Triggered when the button is pressed
204
205  lv_obj_add_event_cb(ui->heartrate_home, heartrate_home_event_handler, LV_EVENT_ALL, NULL);

Initialize the heart rate module and configure it to sleep mode.

1long show_value = 0;                //The data values of the points in the heart rate line plot
2
3//Initialization heartrate module
4int heartrate_init(void) {
5  if (!heartrate.begin(Wire, I2C_SPEED_FAST)) {
6    Serial.println("MAX30105 was not found. Please check wiring/power. ");
7    return -1;
8  }
9  heartrate.setup();  //Configure sensor with these settings

Heartrate module sleep function and module wake-up function.

1  return 0;
2}
3
4//The heartrate module is enabled to enter low power mode
5void heartrate_shutdown(void) {
6  heartrate.shutDown();
7}
8
9//Wake up the heartrate module

Average the heartrate values obtained multiple times.

1  return 0;
2}
3
4//The heartrate module is enabled to enter low power mode
5void heartrate_shutdown(void) {
6  heartrate.shutDown();
7}
8
9//Wake up the heartrate module

Acquire raw infrared data.

1Serial.println("loopTask_heartrate start...");

Heartrate checking function. Returns true if a mentality spike is detected, otherwise returns false.

checkForBeat(irValue)

If the HEARTRATE_SERIAL macro definition is set to 1, the original data will be printed to the serial port, allowing you to view the heart rate waveform using a serial port drawing function. By default, serial port data is not printed.

1long average = heartrate_average(heartrate_irvalue, AVERAGE_NUM);
2show_value = irValue - average + (CHART_HIGH_LIMIT / 2);
3

Label component setup function, which can be used similarly like printf().

1lv_chart_set_next_value(guider_heartrate_ui.heartrate_chart, series, show_value);

Create a line chart with 100 data points to make the waveform more closely resemble real data. Set the chart to scroll continuously for optimal viewing.

 1lv_style_init(&style_pr);  //Initialize it
 2lv_style_set_translate_y(&style_pr, 5);//Style: Every time you trigger, move down 5 pixels
 3
 4/*Create a chart1*/
 5ui->heartrate_chart = lv_chart_create(ui->heartrate);
 6
 7lv_obj_set_size(ui->heartrate_chart, 180, 200);
 8lv_obj_set_pos(ui->heartrate_chart, 60, 120);
 9lv_chart_set_type(ui->heartrate_chart, LV_CHART_TYPE_LINE); /*Show lines and points too*/
10lv_chart_set_div_line_count(ui->heartrate_chart, 5, 10);          //Set chart to 5 rows and 10 columns
11lv_chart_set_point_count(ui->heartrate_chart, 100);               //chart shows the data for 100 points
12lv_obj_set_style_size(ui->heartrate_chart, 0, LV_PART_INDICATOR); //Make each data point unsalient   
13
14lv_chart_set_update_mode(ui->heartrate_chart, LV_CHART_UPDATE_MODE_SHIFT);//Move chart to update data
15/*Add two data series*/
16series = lv_chart_add_series(ui->heartrate_chart, lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_SECONDARY_Y);//Let the data be on the right y axis
17lv_chart_set_range(ui->heartrate_chart, LV_CHART_AXIS_SECONDARY_Y, CHART_LOW_LIMIT, CHART_HIGH_LIMIT);         //Set the value range of right y axis

Display heart rate data on a line chart.

1#endif