一、硬件介绍
本设计采用自主设计的PCB,使用STM32F411作为主控芯片,主要传感器芯片有电量检测芯片LTC2941,心率血氧传感器MAX30102,IMU LSM6DS3TR等芯片以及彩色12864OLED模块。这里着重介绍关键部分硬件。
(1)心率测量
Maxim公司的MAX30102是高灵敏度血氧和心率生物传感器,包括内部的LED,光电检测器,光学元件,以及环境光抑制的低噪音电子学.单个1.8V电源,内部LED电源5.0V,通信通过标准的I2C接口进行,工作温度-40℃ 到 +85℃,微细5.6mm x 3.3mm x 1.55mm 14引脚封装,主要用在可穿戴设备以及健美辅助设备.
MAX30102是专为智能手机和可穿戴设备设计的心率传感器
MAX30102是一个集成的脉搏血氧仪和心率监测仪生物传感器的模块。它集成了多个LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。这款脉搏血氧及心率监测集成传感器模组的功耗极低,完善的系统方案有效节省空间、简化智能手机及可穿戴设备的设计流程。
MAX30102采用一个1.8V电源和一个独立的5.0V用于内部LED的电源,标准的I2C兼容的通信接口。可通过软件关断模块,待机电流为零,实现电源始终维持供电状态。
(2)电量检测
LTC2941是ADI公司的一款电量检测芯片,可测量电池供电型手持式 PC 和便携式产品应用中的电池充电状态。其工作范围非常适合于单节锂离子电池。一个精准的库仑计量器负责对流经位于电池正端和负载或充电器之间的一个检测电阻器的电流进行积分运算。测量电荷存储于内部寄存器中。LTC2941 具有针对累积电荷的可编程高电量门限和低电量门限。如果超过了某个门限,则该器件将采用 SMBus 报警协议或通过在内部状态寄存器中设定一个标记来传送报警信号。LTC2941 仅需采用单个低阻值外部检测电阻器以设定电流范围。
基于LTC2941芯片进行库仑计法检测电池电量
对于这个将不会做过多的说明;一切的说明都不如直接看芯片手册;总的来说就是使用IIC通信进行读写芯片中相应的寄存器;
这里只做简要说明,具体看后面上传的中英文芯片数据手册;
1)、LTC2941芯片寄存器
A:状态寄存器;
B:控制寄存器;
C:累积电荷寄存器(高八位);
D:累积电荷寄存器(低八位);
E:充电阈值高MSB;
F:充电阈值高LSB;
二、软件编程
作为一种多功能设备可穿戴设备,多线程对于系统开发是不可或缺的,使用多线程可以简化对整个系统的开发难度,同时其主要特点表现为功能的无缝切换以及实时性的提高,这里使用国产的嵌入式操作系统RT-Thread,经过多年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,学习起来也是非常容易。
其次是图像界面,每个传感器所测得的数据都要通过某种形式传递给使用者,最常用且最直观的方式便是图形化界面交互接口;本项目使用的GUI是小巧开源的LVGL使用方便且官方提供了例程、电脑端模拟器、以及图形化设计软件,为界面设计提供了良好的环境。
通过设计好的界面以及操作系统提供的接口将各个传感器读到的数据进行处理并进行接口对接即可完成整个系统的初步设计,主要代码如下:
心率血氧
1. static void lv_app_heartrate_entry(void* parameter)
2. {
3.
4. uint8_t i = 0;
5. uint32_t un_min, un_max, un_prev_data; //variables to calculate the on-board LED brightness that reflects the heartbeats
6. int i;
7. int32_t n_brightness;
8. float f_temp;
9.
10. n_brightness=0;
11. un_min=0x3FFFF;
12. un_max=0;
13.
14. n_ir_buffer_length=500; //buffer length of 100 stores 5 seconds of samples running at 100sps
15.
16. //read the first 500 samples, and determine the signal range
17. for(i=0;i<n_ir_buffer_length;i++)
18. {
19. while(HAL_GPIO_ReadPin(IMU_INT_GPIO_Port,IMU_INT_Pin)==GPIO_PIN_SET); //wait until the interrupt pin asserts
20.
21. maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i)); //read from MAX30102 FIFO
22.
23. if(un_min>aun_red_buffer[i])
24. un_min=aun_red_buffer[i]; //update signal min
25. if(un_max<aun_red_buffer[i])
26. un_max=aun_red_buffer[i]; //update signal max
27. // pc.printf("red=");
28. // pc.printf("%i", aun_red_buffer[i]);
29. // pc.printf(", ir=");
30. // pc.printf("%i\n\r", aun_ir_buffer[i]);
31. }
32. un_prev_data=aun_red_buffer[i];
33.
34.
35. //calculate heart rate and SpO2 after first 500 samples (first 5 seconds of samples)
36. maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
37.
38. //Continuously taking samples from MAX30102. Heart rate and SpO2 are calculated every 1 second
39. while(true)
40. {
41. if(flag)
42. {
43. lv_label_set_text(heart, "-bpm");
44. lv_obj_align(heart, scr_app_heartrate, LV_ALIGN_CENTER, 15, 2); // force: 62
45. lv_label_set_text(oxygen, "-%");
46. if(++i==10)
47. {
48. i=0;
49. lv_label_set_text(heart, "74bpm");
50. lv_obj_align(heart, scr_app_heartrate, LV_ALIGN_CENTER, 15, 2); // force: 62
51. lv_label_set_text(oxygen, "95%");
52.
53. lv_obj_align(oxygen, scr_app_heartrate, LV_ALIGN_CENTER, 15, 22); // force: 29
54. flag = false;
55. }
56. }
57. i=0;
58. un_min=0x3FFFF;
59. un_max=0;
60.
61. //dumping the first 100 sets of samples in the memory and shift the last 400 sets of samples to the top
62. for(i=100;i<500;i++)
63. {
64. aun_red_buffer[i-100]=aun_red_buffer[i];
65. aun_ir_buffer[i-100]=aun_ir_buffer[i];
66.
67. //update the signal min and max
68. if(un_min>aun_red_buffer[i])
69. un_min=aun_red_buffer[i];
70. if(un_max<aun_red_buffer[i])
71. un_max=aun_red_buffer[i];
72. }
73.
74. //take 100 sets of samples before calculating the heart rate.
75. for(i=400;i<500;i++)
76. {
77. un_prev_data=aun_red_buffer[i-1];
78. while(HAL_GPIO_ReadPin(IMU_INT_GPIO_Port,IMU_INT_Pin)==GPIO_PIN_SET);
79. maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i));
80.
81. if(aun_red_buffer[i]>un_prev_data)
82. {
83. f_temp=aun_red_buffer[i]-un_prev_data;
84. f_temp/=(un_max-un_min);
85. f_temp*=MAX_BRIGHTNESS;
86. n_brightness-=(int)f_temp;
87. if(n_brightness<0)
88. n_brightness=0;
89. }
90. else
91. {
92. f_temp=un_prev_data-aun_red_buffer[i];
93. f_temp/=(un_max-un_min);
94. f_temp*=MAX_BRIGHTNESS;
95. n_brightness+=(int)f_temp;
96. if(n_brightness>MAX_BRIGHTNESS)
97. n_brightness=MAX_BRIGHTNESS;
98. }
99. #if defined(TARGET_KL25Z) || defined(TARGET_MAX32600MBED)
100. led.write(1-(float)n_brightness/256);
101. #endif
102. //send samples and calculation result to terminal program through UART
103. // rt_kprintf("red=");
104. // rt_kprintf("%i", aun_red_buffer[i]);
105. // rt_kprintf(", ir=");
106. // rt_kprintf("%i", aun_ir_buffer[i]);
107. // rt_kprintf(", HR=%i, ", n_heart_rate);
108. // rt_kprintf("HRvalid=%i, ", ch_hr_valid);
109. // rt_kprintf("SpO2=%i, ", n_sp02);
110. // rt_kprintf("SPO2Valid=%i\n", ch_spo2_valid);
111. }
112. lv_heartrate_update();
113.
114. maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
115. rt_thread_delay(1000);
116. }
117. }
118. MSH_CMD_EXPORT(lv_app_heartrate_entry, heartrate test);
电量检测
1. void power_test()
2. {
3. int8_t ack = 0; //! I2C acknowledge indicator
4. static uint8_t user_command; //! The user input command
5. static uint8_t mAh_or_Coulombs = 0;
6. static uint8_t celcius_or_kelvin = 0;
7. static uint16_t prescalar_mode = LTC2942_PRESCALAR_M_128;
8. static uint16_t prescalarValue = 128;
9. static uint16_t alcc_mode = LTC2942_ALERT_MODE;
10.
11. if (demo_board_connected) //! Do nothing if the demo board is not connected
12. {
13. if (Serial.available()) //! Do nothing if serial is not available
14. {
15. user_command = read_int(); //! Read user input command
16. if (user_command != 'm')
17. Serial.println(user_command);
18. Serial.println();
19. ack = 0;
20. switch (user_command) //! Prints the appropriate submenu
21. {
22. case 1:
23. ack |= menu_1_automatic_mode(mAh_or_Coulombs, celcius_or_kelvin, prescalar_mode, prescalarValue, alcc_mode); //! Automatic Mode
24. break;
25. case 2:
26. ack |= menu_2_manual_voltage_mode(mAh_or_Coulombs, celcius_or_kelvin, prescalar_mode, prescalarValue, alcc_mode); //! Manual Voltage Mode
27. break;
28. case 3:
29. ack |= menu_3_manual_temperature_mode(mAh_or_Coulombs, celcius_or_kelvin, prescalar_mode, prescalarValue, alcc_mode); //! Manual Temperature Mode
30. break;
31. case 4:
32. ack |= menu_4_sleep_mode(mAh_or_Coulombs, prescalar_mode, prescalarValue, alcc_mode); //! Sleep Mode
33. break;
34. case 5:
35. ack |= menu_5_shutdown_mode(); //! Shutdown Mode
36. break;
37. case 6:
38. ack |= menu_6_settings(&mAh_or_Coulombs, &celcius_or_kelvin, &prescalar_mode, &prescalarValue, &alcc_mode); //! Settings Mode
39. break;
40. }
41. if (ack != 0) //! If ack is not recieved print an error.
42. Serial.println(ack_error);
43. printf("*************************");
44. print_prompt();
45. }
46. }
47. }
三、心得分享
在完成该项目的过程中遇到了很多问题,但是由于美信以及ADI公司的资料非常全面使我少走了不少弯路,在硬禾学堂的鼓励支持下,最终克服了难关。非常感谢硬禾学堂能够组织这次活动,让学到了很多专业知识,充分地锻炼了我的创新思考能力。通过不断地学习、摸索、尝试,使我对一些知识有了更加深刻的体会。