一、前言部分:
历时两天半,我通过这个小项目将前段时间学习到的一些知识进行整合处理,由于我进行MAX30102模块代码移植时没有看到比较详细的教学,下面我将手把手教会大家移植这个模块的代码,内容包括:江协科技的定时器处理按键、Keysking的0.96oled代码移植、函数指针实现多级菜单、MAX30102模块代码封装等等,下面将实现这些功能的过程进行详细的讲解,源码我将会放在最后大家自行提取,如果觉得有帮助还请来个免费的三连哦!
二、最终效果展示:
迷你小手表
三、MAX30102代码实现部分:
这一部分我就不进行代码的讲解了,大家可直接移植我调整好的代码文件,我下面会讲解大家需要移植的代码文件以及文件中可以更改的部分,直接快速完成模块的使用,下面两个.c文件和三个.h文件是大家需要加入自己工程中的文件。
1、sys.h文件:
他的作用大家可以理解为设置和读取相应引脚电平的文件,直接移植就好,不用更改。
2、max30102.c文件和对应的.h文件
他们是商家给我提供的一些有关这个模块的引脚定义以及软件iic还有主体算法的实现文件,原文件有部分错误以及对萌新移植不友好,我整合后需要更改的部分如下(代码太多粘贴后不太好看我只给出需要更改的部分):
首先是,max30102.c需要更改的部分我都将其移到了最开头部分,并用注释进行了框选哦(格式如下)!
由于max30102.c文件里使用到了us级延时,但hal库并没有这部分函数,所以在代码部分我加入了自己写的us级延时,但由于它是依赖于系统滴答时钟,所以它只适用于72MHz的c8t6哦,这一部分小伙伴们自行进行更改哦!
void delay_us(uint32_t udelay)
{
uint32_t startval, tickn, delays, wait;
startval = SysTick->VAL;
tickn = HAL_GetTick();
// sysc = 72000; //SystemCoreClock / (1000U / uwTickFreq);
delays = udelay * 72; // sysc / 1000 * udelay;
if (delays > startval)
{
while (HAL_GetTick() == tickn)
{
}
wait = 72000 + startval - delays;
while (wait < SysTick->VAL)
{
}
}
else
{
wait = startval - delays;
while (wait < SysTick->VAL && HAL_GetTick() == tickn)
{
}
}
}
其次是max30102.h文件,这是软件iic实现的引脚配置,我将他们放在了最开头,大家可根据自己的引脚进行更改,这个英文名字已经清楚的不能再清楚了哦,最后那一组是模块int引脚的哈,这个引脚是传感器用于指示收到数据的输出引脚(并不用管,只需要配置和连接的具体gpio引脚就好哦,具体实现部分我已经处理好了,只需要更改宏定义就好)
3、max30102_calculation.c文件和对应的.h文件
这部分是我写的心率测量代码以及滤波算法以及oled显示函数(keysking的oled库),这部分几乎不用更改,(前提是移植我使用到的oled显示库,以及看完我之后oled相关介绍),不然需要更改这个.c文件末尾定义到的显示函数:
// 数据处理与显示
void Process_And_Display_Data(void)
{
if (max30102_data.heart_rate_valid == 1 && max30102_data.heart_rate < 120)
{
dis_hr = max30102_data.heart_rate;
dis_spo2 = max30102_data.spO2;
}
else
{
dis_hr = 0;
dis_spo2 = 0;
}
OLED_PrintString(0, 10, "蹇冪巼:", &font16x16, OLED_COLOR_NORMAL);
// 将心率和血氧转化为字符串格式
sprintf(hr_str, "蹇冪巼: %d", dis_hr - 20);
sprintf(spo2_str, "琛€姘�: %d%%", dis_spo2);
// 显示心率
if (dis_hr != 0)
{
OLED_PrintString(0, 10, hr_str, &font16x16, OLED_COLOR_NORMAL);
}
// 显示血氧值
OLED_PrintString(0, 40, spo2_str, &font16x16, OLED_COLOR_NORMAL);
OLED_ShowFrame();
}
其次是对应的.h文件:这一部分需要更改的只有下述三个参数,调节这三个参数使心率检测更精准哦!
4、主函数中的代码:
移植完上述五个文件后,这是需要在main.c中相应位置加入的东西,至此这个模块就可以正常进行工作啦!!!
#include "max30102_calculation.h"
int main(void)
{
MAX30102_Init();
HAL_Delay(20);
OLED_Init();
while (1)
{
MAX30102_Read_Data();
Calculate_Heart_Rate_and_SpO2();
Update_Signal_Min_Max();
Process_And_Display_Data();
}
}
四、定时器定时中断实现按键检测:
开始这一部分前我进行一下说明,这一部分是我学习江协科技课程后自己动手实现操作后进行更改的代码哦!更为详细的教程大家请移步置江协科技,B站搜索进行观看学习哦:
[编程技巧] 第1期 定时器实现非阻塞式程序 按键控制LED闪烁模式
下面我就开始进行讲解啦:
1、定时器定时中断
首先是为什么要使用定时器定时中断实现按键输入的检测而不使用外部中断呢?我记不太清up怎么说的啦,我们使用20ms的定时器中断进行按键的检测,就相当于软件进行了消抖操作,就能避免在外部中断中引用延时中断函数,从而cpu不会因为延时函数而出现暂时不干活的情况,程序执行更加高效啦!
这是定时器的配置,其他基础配置我这里就不进行展示啦,定时中断触发时间的计算公式如下:
prs为分频系数,arr为重装载值,72MHz要化为hz也就是乘以十的六次方;的出来的时间单位为秒s,自动重装载记得打开,不然定时中断只能触发一次,
接下来是在主循环中开启定时中断:
HAL_TIM_Base_Start_IT(&htim2);
之后是重写中断回调函数(由于hal库的中断回调机制,触发中断后会进入相应的中断回调函数,处理中断函数里的代码),有个好习惯是这里记得判断是哪个时钟源触发的中断哦!!!
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static int cont = 0;
if (htim == &htim2)
{
Key_Tick();
cont++;
}
}
2、按键部分代码:
这一部分代码,原理我只做简单的介绍,具体实现大家参考上述更为详细的江协科技的视频讲解哦!
uint8_t Key_Num;
volatile uint8_t g_keynum = 0;
uint8_t Key_GetNum(void)
{
uint8_t Temp;
if (Key_Num)
{
Temp = Key_Num;
Key_Num = 0;
return Temp;
}
return 0;
}
uint8_t Key_GetState(void)
{
if (0 == HAL_GPIO_ReadPin(KEY_SURE_Port, KEY_SURE_Pin))
{
return KEY_SURE;
}
if (0 == HAL_GPIO_ReadPin(KEY_NEXT_Port, KEY_NEXT_Pin))
{
return KEY_NEXT;
}
if (0 == HAL_GPIO_ReadPin(KEY_RETURN_Port, KEY_RETURN_Pin))
{
return KEY_RETURN;
}
return 0;
}
void Key_Tick(void)
{
static uint8_t Count;
static uint8_t CurrState, PrevState;
Count ++;
if (Count >= 20)
{
Count = 0;
PrevState = CurrState;
CurrState = Key_GetState();
if (CurrState == 0 && PrevState != 0)
{
Key_Num = PrevState;
}
}
}
简单思路如下:首先书写一个检测具体按键按下并返回相应值的函数(也就是uint8_t Key_GetState(void));其次得到了哪个按键按下后,我们需要另一个函数在定时器中进行每20ms扫描一次并返回按下按键后的相应值(根据上述给出的定时器时间计算公式可以得出我们1ms进入一次定时中断回调,那我们在void Key_Tick(void)函数中进行计数,计数20次也就是20ms将按键值进行赋值);最后我们需要一个函数,将赋值后的变量进行返回以便于外部的调用(也就是uint8_t Key_GetNum(void)函数)。
理解上述思路后实现也就十分的简单啦,将void Key_Tick(void)函数置于定时器中断回调函数中,之后使用uint8_t Key_GetNum(void)函数读取返回的按键值也就能准确知道哪个按键按下啦。
3、为对接之后多级菜单而定义的宏介绍:
#define Key_entern 0 == HAL_GPIO_ReadPin(KEY_SURE_Port, KEY_SURE_Pin) //三级菜单进行确认的宏
#define Key_back 0 == HAL_GPIO_ReadPin(KEY_RETURN_Port, KEY_RETURN_Pin) //三级菜单进行返回的宏
#define KEY_SURE_Port GPIOB
#define KEY_SURE_Pin GPIO_PIN_9
#define KEY_NEXT_Port GPIOB
#define KEY_NEXT_Pin GPIO_PIN_12
#define KEY_RETURN_Port GPIOB
#define KEY_RETURN_Pin GPIO_PIN_0
这些宏定义看名称大家应该就能明白其大体含义,也就是我视频中使用到的三个按键对应的宏,分别是确认键,下一个键,返回键;大概使用方式就是:定义一个全局变量,将这个全局变量用于接收uint8_t Key_GetNum(void)函数返回的值(不同按键按下返回不同的值,否则返回0),将这个全局变量与下述三个宏进行比较,就可以实现一个按键按下对应不同的功能啦!
#define KEY_SURE 1
#define KEY_NEXT 2
#define KEY_RETURN 3
五、暂时的结束:
由于篇幅有限,这里我选择分开进行讲解,看在保姆级讲解以及免费开源的份上,大家也不要吝啬自己免费的三连啦!最后附上MAX30102模块对应的五个文件源码:
通过网盘分享的文件:MAX30102.zip
链接: https://pan.baidu.com/s/1WeWo3SHZ6ix_OlR9FxoueQ 提取码: x7h2