【前半部分】stm32c8t6hal库 + 保姆级MAX30102实现心率血氧检测 + Keysking的oled移植问题解决 + 多级菜单实现【小项目开源分享1】

一、前言部分:

        历时两天半,我通过这个小项目将前段时间学习到的一些知识进行整合处理,由于我进行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不会因为延时函数而出现暂时不干活的情况,程序执行更加高效啦!

        这是定时器的配置,其他基础配置我这里就不进行展示啦,定时中断触发时间的计算公式如下:

T=(prs+1)*(arr+1)/72Mhz

        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

### STM32C8T6 使用 HAL 实现光敏电阻功能 要通过 STM32C8T6HAL 实现光敏电阻的功能,通常需要将光敏电阻连接到 ADC 输入通道上,并读取其模拟电压值。以下是详细的配置方法以及示例代码。 #### 配置 GPIO 引脚 首先,选择一个未被 JTAG 占用的 GPIO 引脚用于连接光敏电阻。例如,可以选择 PA0 作为 ADC 的输入引脚[^1]。确保该引脚在初始化时设置为模拟模式 (Analog Mode),以便能够正确采集信号。 ```c __HAL_RCC_GPIOA_CLK_ENABLE(); // 启用GPIOA时钟 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_0; // 设置PA0 GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; // 模拟模式 GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); ``` #### 初始化 ADC 接着,初始化 ADC 外设以准备采样来自光敏电阻的数据。下面是一个简单的 ADC 配置过程: ```c __HAL_RCC_ADC1_CLK_ENABLE(); // 开启ADC1时钟 ADC_HandleTypeDef hadc; hadc.Instance = ADC1; // 使用ADC1实例 hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2; // ADC同步时钟频因子 hadc.Init.Resolution = ADC_RESOLUTION_12B; // 设定辨率为12位 hadc.Init.ScanConvMode = DISABLE; // 禁用扫描转换模式 hadc.Init.ContinuousConvMode = ENABLE; // 连续转换模式开启 hadc.Init.DiscontinuousConvMode = DISABLE; // 关闭不连续转换模式 hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;// 不使用外部触发源 hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 数据右对齐 hadc.Init.NbrOfConversion = 1; // 转换次数为一次 if(HAL_ADC_Init(&hadc)!= HAL_OK){ Error_Handler(); } ``` #### 创建并启动 ADC 通道 为了从选定的 GPIO 引脚获取数据,在此定义相应的 ADC 通道号(对于 PA0 来说通常是 Channel 0),然后执行单次转换或者周期性轮询/中断操作。 ```c uint16_t adc_value; // 将第一个常规组通道配置成对应于所选pin的adc channel. ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; // 对应PA0 sConfig.Rank = 1; // 排序等 sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; // 采样时间设定 if(HAL_ADC_ConfigChannel(&hadc,&sConfig) != HAL_OK){ Error_Handler(); } while(1){ HAL_ADC_Start(&hadc); // 开始ADC转换 if(HAL_ADC_PollForConversion(&hadc,10)==HAL_OK){ adc_value=HAL_ADC_GetValue(&hadc); // 获取当前ADC数值 } HAL_Delay(100); // 延迟一段时间再重复测量 } ``` 以上即完成了基本的硬件抽象层 (HAL) 下针对 STM32C8T6 微控制器利用光敏电阻检测环境光线强度的应用程序框架构建工作流程说明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值