Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

Since the previous series of FreeRTOS learning notes have not yet been applied in practice, this issue introduces the use of FreeRTOS to detect motion and heart rate status.
You can go directly to the end of the demo video~
The source code can be obtained by joining the QQ group~~ 656210280
This content was supposed to be released last week, but because my code was initially written on the F4 and then ported to the F1, it crashed when FreeRTOS was enabled due to chip issues, causing a delay of a few days.
The materials used in this issue are:
  • STM32F103C8T6
  • MAX30100 Heart Rate and Blood Oxygen Detection Module
  • MPU6050 Attitude Detection Module
  • 0.96-inch OLED Display Module
Aimed at creating a device that detects heart rate and motion posture, sending an alarm when the motion acceleration exceeds the fall threshold.

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring DeviceReal-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

  • Program Outline

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

  • CubeMX Configuration
  • Clock Configuration

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

Since the core board is borrowed, the high-speed clock selects a quartz crystal oscillator.
  • Clock Source

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

The clock source selects a timer. FreeRTOS does not recommend using the system tick timer because, although the system tick timer has high precision, it is not convenient for RTOS to schedule tasks. Software timers can be trimmed and configured according to actual needs, so it is recommended to use a timer to replace the system tick timer.
  • Peripheral Configuration

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

A serial port and two hardware I2C peripherals are selected. The hardware I2C can be used for communication with OLED, MAX30100, and MPU6050.
Here, I connected the OLED to I2C1, and MAX30100 and MPU6050 to I2C2. You can connect multiple devices to the same I2C bus. I used two because the OLED was tested separately at the beginning.
  • FreeRTOS Settings

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

Modify the stack size to prevent stack overflow when allocating tasks later.

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

Create a task for data collection from MPU6050 and set this task’s priority to high because the fall detection priority should be the highest.

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

In addition, there are tasks for data processing of MAX30100, abnormal state handling of MPU, and OLED refresh tasks.

Here, I decided to use a global buffer to store and process data (instead of using temporary variables in tasks).

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

Set a timer to periodically read the MAX30100 code and set three semaphores for notifying MAX30100 data processing, MPU6050 abnormal handling, and OLED refresh display.

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

Use an event group to mark various states, such as the initialization state of each module and the abnormal state of MPU6050.

  • KEIL Code

Real-World Application of FreeRTOS on STM32: Fall Detection and Heart Rate Monitoring Device

In Keil FreeRTOS, it helps us to initialize, but we need to enable the timer ourselves and set the timer interval to 15ms.

float x[3];// store int stepSum;/* USER CODE END Header_MPURuning */void MPURuning(void *argument){  /* USER CODE BEGIN MPURuning */  /* Infinite loop */  for(;;)  {    MPU6050_ReadAccel(&mpu6050, x, x+1, x+2);    // here x[3] is the acceleration in three directions, which should calculate the resultant acceleration    //printf("B:%.2f\r\n",x[2]);    if(x[1]>1.5&&x[1]!=1.9)    {      stepNow = stepSum;      MpuErrorFlag = 1;      xSemaphoreGive(MPU6050EventHandle);    }    if(x[1]>0.7)    {      stepSum++;    }    osDelay(10);  }  /* USER CODE END MPURuning */}

Here, MPU6050 continuously acquires acceleration and determines whether a fall occurs by adjusting the threshold.

void MAXData(void *argument){  /* USER CODE BEGIN MAXData */    static int i = 0;  uint32_t DCRED;  update();  DCRED = -removeRedDcComponent(rawIRValue)*2;  Data[i++] = DCRED;  if(i==300)  {    xSemaphoreGive(MAXStartHandle);    i = 0;  }  /* USER CODE END MAXData */}

MAX30100 reads data at a sampling rate of 15ms through a software timer, writing the data into a buffer, and sends a binary semaphore when 300 samples are collected.

void MAXUsing(void *argument){  /* USER CODE BEGIN MAXUsing */  /* Infinite loop */  for(;;)  {   if (xSemaphoreTake(MAXStartHandle,portMAX_DELAY)) {        xTimerStop(MAXDataHandleHandle,0);        smoothFilter(Data,smooth,BuffLenth,3);                int index;        float cot;        float sum;        float Heart;//        for(int i = 0;i<300;i++)//        {//          printf("A:%d\r\n",smooth[i]);//        }          for(int i = 0;i<300;i++)        {      if(smooth[i]>70&&smooth[i]<300)      {          index = i;          while(smooth[i]>0)          {            i++;            if(i==BuffLenth-1)            {              break;            }          }          while(smooth[i]<70)          {            i++;            if(i==BuffLenth-1)            {              break;            }          }          index = i-index;          if(index>23)//23 corresponds to heart rate 173 considered error          {              sum+= index;              cot++;              //printf("A:%d\r\n",index);          }      }        }                        Heart = (float)(sum/cot)*0.015;        Heart = 60/Heart;        HeartFre= Heart;        sum = 0;        cot = 0;        if((HeartFre>160&&HeartFre<250)||HeartFre<30)        {          xEventGroupSetBits(TaskStauteHandle,1<<0);// set bit 0 to 1        }        xSemaphoreGive(OLEDShowSemHandle);        xTimerStart(MAXDataHandleHandle,0);   }     }  /* USER CODE END MAXUsing */}

The MAX30100 data processing task waits for the semaphore to be released. When it receives the semaphore, it stops the timer and processes the data.

Extract heart rate information from the data (blood oxygen information can also be extracted, but calibration is required, which I did not do).

If abnormal heart rate is detected, set the 0th bit of the event group to 1.

Then notify the OLED function to refresh.

void MPUERROR(void *argument){  /* USER CODE BEGIN MPUERROR */  /* Infinite loop */  for(;;)  {    int flag = 1;    if (xSemaphoreTake(MPU6050EventHandle, portMAX_DELAY) == pdPASS)    {            for(int i = 0;i<200;i++)      {        //printf("A:%d\r\n",stepSum);        if((stepNow-stepSum)>2)        {            flag = 0;        }        osDelay(10);      }      if(flag)      {        xEventGroupSetBits(TaskStauteHandle,1<<1);// set bit 1 to 1        xSemaphoreGive(OLEDShowSemHandle);      }  }  /* USER CODE END MPUERROR */  }

In the MPU6050 abnormal data handling task, if there is no increase in steps within two seconds (in actual situations, a longer time should be set), it marks an abnormal position and then notifies the OLED to refresh.

void OLEDShow(void *argument){  /* USER CODE BEGIN OLEDShow */  char s[50];  /* Infinite loop */  for(;;)  {        if (xSemaphoreTake(OLEDShowSemHandle,portMAX_DELAY)) {        sprintf(s,"Heart:%.2f",HeartFre);        OLED_Clear();        OLED_ShowString(0,1,(unsigned char *)s,2);        EventBits_t Bits = xEventGroupGetBits(TaskStauteHandle);        if(Bits&1<<0)        {          // abnormal at bit 0, heart rate abnormal          sprintf(s,"Heart is Error");          OLED_ShowString(0,2,(unsigned char *)s,2);        }        if(Bits&1<<1)        {          // abnormal at bit 1, posture abnormal          sprintf(s,"MPU Error");          OLED_ShowString(0,3,(unsigned char *)s,2);        }    }  }  /* USER CODE END OLEDShow */}

In the OLED refresh task, it displays the heart rate in real-time and shows error messages based on the errors.

Effect Display

Due to the rushed nature of the project, it is demonstrated using Dupont wires and the core board.

Leave a Comment

×