MAX32660简单智能手表模型一.相关模块的基本介绍:
Max30102是一款美信开发的用于心率以及血氧浓度测量的传感器,它在可穿戴式设备中可以拥有非常好的应用前景,我也是首次接触穿戴式设备中的模块,因此也参考了不少的资料,从基本的测量原理到对其进行嵌入式的开发都进行了尝试。
MAX30102——SCH
135*240的中景园IPS屏幕也成为了我使用的屏幕,因为无论是在小型设备上的使用率还是其RGB以及不会烧屏的性质,都是一选,而且比较重要的就是还能移植LVGL,后期还能添加一些非常有意思的动画效果,还是非常不错的!
二.光学心率传感器的测量原理:
在Max30102上采用的是广电容积脉搏波描记法PPG,说实话这个名字确实看起来非常的高端,说简单点其实就是利用光测量脉搏的一种技术,它的应用也是十分广泛的,下面就对其基本原理进行介绍:
对于穿戴过智能手表或者是智能手环的朋友们来说,光学心率传感器可以说是并不稀奇。拿上简单的AppleWatch来说,测量的时候底部的表盘就会发出绿色的光,那为什么通过LED发光就能测量心率呢? 这个过程其实也不是特别的复杂,简单点来说就是光-->电-->数字信号,但是还需要消除一定的外部环境差的影响,尤其我使用的面包板这样的开放式平台,就很容易受到外部环境的干扰,导致测量的心率以及血氧浓度实验数据误差很大。所以我觉得如果要真正好把这个模块用到实际的手表中,还需要高度的集成和相对干净的测量环境!
三.相关参考网址链接:
网上找到的别人修改mbed程序的例子:http://bbs.eeworld.com.cn/thread-563552-1-1.html 有很好的参考价值和意义,主要的难点就在于移植这个代码,计算的过程是没有什么问题的,因为在之前模拟IIC的驱动以及中景园1.14寸IPS的屏幕我都基本上将驱动完工了,没有什么特别大的问题,然后将数据显示在界面上就OK了,为了后期能够达到更好的效果,我移植了LVGL,大家参考这篇案例就很容易搭建好最简单的littleVGL框架,完成 disp_flush()里面的带color的画点函数(尽量实现地高效一些,也对刷新率和效率有一定的提高)
四.硬件驱动部分代码设计:
//对所需要用到的SPI0硬件外设进行初始化
#define SPI_SPEED 48000000 // Bit Rate 通讯速率的设定
void spi_init(void) //SPI初始化函数,我采用的是硬件SPI0
{
while(SPI_Init(SPI0A, 3,SPI_SPEED)!=0); //使用SPI_Init()对硬件外设进行初始化
SPI_Enable(SPI0A);//SPI使能
}
//对ST7789VW芯片进行操作,写一个字节的函数,对req结构体进行初始化,这个结构体就相当于硬件SPI设备对发送和接收的数据进行一个打包,一起操作,我们只需要根据我们发送和接收的数据的要求对其中的参数进行对应的修改就可以完成数据的发送和接收
void LCD_Writ_Bus(u8 dat) //硬件SPI的方式对ST7789VW进行读写
{
req.rx_data=NULL; //不考虑接收的数据,只发送,所以给定一个空指针
req.tx_data=&dat; //在定义里tx_data实际上是一个指针,它用来指向任意 ,我我们把这个dat的指针指向这个u8的数据dat就可以了
req.width = SPI17Y_WIDTH_1; //只有一根数据线进行数据传输
req.len = 1;
req.ssel = 0;
req.ssel_pol=SPI_POL_LOW;
req.deass = 0;
req.tx_num = 1;
req.rx_num = 0;
req.bits =8;
req.callback = NULL;
SPI_MasterTransAsync(SPI0A, &req); //进行数据发送
}
五.主函数模块设计:
void analog( lv_obj_t *win) //模拟时钟界面创建
{
lv_obj_t* central = win; //建立一个屏幕界面
lv_obj_set_size(central, LV_VER_RES_MAX, LV_HOR_RES_MAX); //设置屏幕的大小 ,先垂直分辨率
lv_obj_set_pos(central, 0, 0); //设置控件的位置
lv_obj_t * img = lv_img_create(central,NULL);
lv_img_set_src(img, &watch_bg);
lv_obj_set_size(img, 200, 200);
lv_obj_set_auto_realign(img, true);
lv_obj_align(img, central, LV_ALIGN_CENTER, 0, 0);
//下面是设置初始时间,大家从RTC取时间就可以了,我这里为了模拟演示效果就自定义了一个时间
lvHour = lv_img_create(central,NULL);
lv_img_set_src( lvHour, &hour);
lv_obj_align( lvHour, img,LV_ALIGN_CENTER, 0, 0);
uint16_t h = 0 * 300 + 0 / 12 % 12 * 60;
lv_img_set_angle( lvHour, h);
lvMinute = lv_img_create(central,NULL); //把其原点放在中心
lv_img_set_src( lvMinute, &minute);
lv_obj_align( lvMinute, img,LV_ALIGN_CENTER, 0, 0);
lv_img_set_angle( lvMinute, 0*60);
lvSecond = lv_img_create(central,NULL);
lv_img_set_src( lvSecond, &second);
lv_obj_align( lvSecond, img,LV_ALIGN_CENTER, 0, 0);
lv_img_set_angle( lvSecond, 0*60);
lv_task_create(update_time, 1000, LV_TASK_PRIO_LOW, NULL);
//第一个参数选择要进入任务的回调函数,第二个参数是任务的回调周期,这里也就是1S执行一次,第三个参数是任务的优先级,最后一个是用户自定义数据,这里是没有的,所以就给一个空指针就好了
}
//按键切换屏幕显示,采用的是外部中断的方式
#define GPIO_PORT_KEY PORT_0
#define GPIO_PIN_KEY PIN_12 //按下是低电平
gpio_cfg_t gpio_key;
void gpio_disr(void) //中断服务函数 作用就是切换LCD屏幕的界面
{
page_num++;
if(page_num==2)
{
page_num=0;
}
}
gpio_int.port = GPIO_PORT_KEY;
gpio_int.mask = GPIO_PIN_KEY gpio_key.port = GPIO_PORT_KEY;
gpio_key.mask = GPIO_PIN_KEY;
gpio_key.pad = GPIO_PAD_PULL_UP; //默认高电平
gpio_key.func = GPIO_FUNC_IN;//设置成输入
GPIO_Config(&gpio_key);
GPIO_RegisterCallback(&gpio_key, gpio_disr, &page_num);
GPIO_IntConfig(&gpio_key, GPIO_INT_EDGE, GPIO_INT_FALLING); //边沿触发而且还是下降沿触发,这里选择下降沿触发
GPIO_IntEnable(&gpio_key);//使能外部中断
//心率检测部分的代码设计
i=0;
un_min=0x3FFFF;
un_max=0;
//dumping the first 100 sets of samples in the memory and shift the last 400 sets of samples to the top
for(i=100;i<500;i++)
{
aun_red_buffer[i-50]=aun_red_buffer[i];
aun_ir_buffer[i-50]=aun_ir_buffer[i];
//update the signal min and max 时刻更新信号的最小值和最大值
if(un_min>aun_red_buffer[i])
un_min=aun_red_buffer[i];
if(un_max<aun_red_buffer[i])
un_max=aun_red_buffer[i];
}
//take 100 sets of samples before calculating the heart rate.
for(i=400;i<500;i++)
{
un_prev_data=aun_red_buffer[i-1];
while(GPIO_InGet(&gpio_int)==1);
max30102_FIFO_ReadBytes(REG_FIFO_DATA,temp);
aun_red_buffer[i] = (long)((long)((long)temp[0]&0x03)<<16) | (long)temp[1]<<8 | (long)temp[2]; // Combine values to get the actual number
aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03)<<16) |(long)temp[4]<<8 | (long)temp[5]; // Combine values to get the actual number
if(aun_red_buffer[i]>un_prev_data)
{
f_temp=aun_red_buffer[i]-un_prev_data;
f_temp/=(un_max-un_min);
f_temp*=MAX_BRIGHTNESS;
n_brightness-=(int)f_temp;
if(n_brightness<0)
n_brightness=0;
}
else
{
f_temp=un_prev_data-aun_red_buffer[i];
f_temp/=(un_max-un_min);
f_temp*=MAX_BRIGHTNESS;
n_brightness+=(int)f_temp;
if(n_brightness>MAX_BRIGHTNESS)
n_brightness=MAX_BRIGHTNESS;
}
LCD_ShowIntNum(100,30,n_heart_rate,2,RED,WHITE,16); //显示刷新
LCD_ShowIntNum(70,50,n_sp02,2,RED,WHITE,16); //显示刷新
LCD_ShowIntNum(60,70,aun_red_buffer[i],6,RED,WHITE,16);
LCD_ShowIntNum(60,90,aun_ir_buffer[i],6,RED,WHITE,16);
}
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);//进行心率和血氧的计算,该函数放在了algorithm.c源文件下
代码链接:https://gitee.com/qihongzhang/max32660_-watch_demo.git
如果有任何问题都可以在funpack群里@我,我叫张启弘,是一名西北工业大学大三在读本科生。
六.心得和体会:
我也已经参加了很多期的funpack活动,作为一名学物理的大学生,这个活动很好地让我学习了课堂之外的知识,拓宽了我的知识面,接触到了很多不一样的板卡,不一样的平台,相信在以后的学习中也能够将这些平台利用起来,最后很感谢得捷电子和硬禾学堂对此次活动的大力支持!