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

一、前言部分:

        历时两天半,我通过这个小项目将前段时间学习到的一些知识进行整合处理,由于我进行MAX30102模块代码移植时没有看到比较详细的教学,下面我将手把手教会大家移植这个模块的代码,内容包括:江协科技的定时器处理按键、Keysking的0.96oled代码移植、函数指针实现多级菜单、MAX30102模块代码封装等等,下面将实现这些功能的过程进行详细的讲解,源码我将会放在最后大家自行提取,如果觉得有帮助还请来个免费的三连哦!

二、最终效果展示:

迷你小手表

三、Keysking的oled移植问题解决:

1、首先是我为什么要移植这个oled驱动代码的理由:

        那就是这个up做的开源取模网站太好用啦,以及支持直接输出中文让我选择了移植这个oled驱动代码!!!下述过程中我只讲述我自己移植过程中所遇到的问题,移植部分请大家移步到官方视频观看更为详细的解说吧!

【【STM32入门教程-2024】第14集 如何在OLED屏幕上挥毫】https://www.bilibili.com/video/BV19u4y197df?vd_source=7beec3ced57aa9410a349f3a9036f6df

2、移植过程中我所遇到的问题:

(1)、移植后屏幕不亮,但烧录江协科技代码没有问题:

        刚开始我按照视频中提供的代码进行移植,但烧录程序后oled屏幕不亮,看来看去我配置应该没有出错呀。烧录江协科技的代码后正常显示,后面我发现up使用的oled的iic地址跟我的不同(大家可以看看是不是这个问题,自己买的oled可以向商家询问资料)。但更改地址后也还是有问题,oled屏幕出现了旁边有一条白线而且有时候还不听使唤。解决办法如下:

        后来我才发现up网站里有各种驱动,原来是我的驱动芯片与up不同,选择对应的驱动芯片代码库后这个问题解决啦;(我的代码使用的是SSD1306芯片,大家要核对是否与我一致哦) 

(2)、移植后我遇到了不能直接输出中文的问题:

#include "oled.h"
int main(void)
{
    HAL_Delay(20);  
    OLED_Init();     // 初始化
    while(1)
    {
        OLED_NewFrame();
        OLED_PrintString(0, 40, "鸽鸽照骗", &font16x16, OLED_COLOR_NORMAL);
        OLED_ShowFrame();
    }    
}

        首先在取模网站进行字样取模并将生成的代码覆盖掉原来font.c中相同字体大小的代码后,之后按照上述初始化后(记得初始化前延时20ms,因为单片机启动比oled快,防止oled初始化失败),应该来说这个时候我就能正常输出中文了,但万万没想到这个环节出了岔子!

        翻来覆去我找到了原因,之所以up的库之所以能直接输出中文,是因为取模软件取模后前四位是UTF-8编码格式的数据用来寻找检索的中文。那么我们使用 OLED_PrintString 这个函数输入需要打印的中文时(比如上述的 "鸽鸽照骗"),就必须使用的是 UTF-8 编码格式

        解决方法如下:

首先按图示添加(作用是防止出现中文乱码并报错): --locale=english

 其次将keil的编码格式改为上图所示后,将原来 OLED_PrintString 函数中的中文删除后进行重写,这时候我们就能够正常输出中文啦!

四、函数指针实现多级菜单:

1、函数指针的简单使用:

int (*func_ptr)(int, int);

 首先,函数指针大概长上面那样;

int add(int a, int b);
int (*func_ptr)(int, int) = add; // 将 add 函数的地址赋给 func_ptr
int result = func_ptr(3, 5); // 通过函数指针调用 add(3, 5)

在实际使用中,将具体函数的地址(即函数名)传给函数指针,之后就可以使用函数指针代替指向的函数了。

2、多级菜单的大体实现思路:

        简单了解了函数指针的使用,下面就可以来理解怎么使用它实现多级菜单了。

 (1)、大体实现思路:

这里按键的实现大家参考我上一期博客,链接如下:

CSDN

        首先我们需要三个按键,这三个按键按下分别代表着确认、下一个、返回。其次还需要一个用来索引的序号。按键按下就会进入相应的索引序号,主函数中执行当前索引序号所对应的函数,这里也就需要一个函数指针作为实现函数的接口。

typedef struct
{
	uint8_t CurrentNum;  // 菜单当前的索引	
	uint8_t Enter;  // 确认按键		 
	uint8_t Next;  // 下一个按键
	uint8_t Return;  // 返回按键
	void (*Current_Operation)(void);  // 当前索引下执行的函数
} Menu_table_t;

        上述定义完结构体后,定义下面的结构体数组,分别对应三个功能:

这里以二级菜单的心率测试显示为例:进入主菜单后按下确认键会进入二级菜单,二级菜单里 "下一个按键" 按下跳转到 "小恐龙显示 ","确认键" 按下进入 "心率测试" "返回键" 按下则返回主菜单。

volatile uint8_t taskIndex = 0; // 用来索引的序号
g_keynum = Key_GetNum();
    if (KEY_SURE == g_keynum)
    {
      taskIndex = taskTable[taskIndex].Enter;
    }
    if (KEY_NEXT == g_keynum)
    {
      taskIndex = taskTable[taskIndex].Next;
    }
    if (KEY_RETURN == g_keynum)
    {
      taskIndex = taskTable[taskIndex].Return;
    }
    taskTable[taskIndex].Current_Operation(); // 执行相应函数

        这里就是主循环中按键按下所执行的代码了,简单来说就是按键按下改变索引序号,再通过函数指针执行当前序号下对应的函数!

void Menu_Interface(void)

void Heart_Test_Display(void)
void Dino_Game_Display(void)
void Ikun_Display(void)

void Heart_Test(void)
void Dino_Game(void)
void Jinitaimei(void)

        之后再定义各个功能函数就好。下面我只选取心率测试为例进行较为详细的讲解:

volatile uint8_t g_heart_flag = 0;
void Heart_Test_Display(void)
{
    OLED_NewFrame();
    OLED_PrintString(0, 0, "心率测试✔", &font16x16, OLED_COLOR_NORMAL);
    OLED_PrintString(0, 20, "小恐龙游戏", &font16x16, OLED_COLOR_NORMAL);
    OLED_PrintString(0, 40, "鸽鸽照骗", &font16x16, OLED_COLOR_NORMAL);
    OLED_ShowFrame();
}

void Heart_Test(void)
{
    if (0 == g_heart_flag)
    {
        OLED_NewFrame();
        OLED_PrintString(0, 0, "心率测试✔", &font16x16, OLED_COLOR_NORMAL);
        OLED_PrintString(0, 20, "游戏", &font16x16, OLED_COLOR_NORMAL);
        OLED_PrintString(0, 40, "鸽鸽", &font16x16, OLED_COLOR_NORMAL);
        OLED_ShowFrame();
    }
    if (Key_entern)  // Key_entern  0 == HAL_GPIO_ReadPin(KEY_SURE_Port, KEY_SURE_Pin) 
    {
        if (0 == g_heart_flag)
            OLED_NewFrame();
        g_heart_flag = 1;
    }
}

         进入二级菜单后,这时选中心率测试按下后,会进入三级菜单,也就是 void Heart_Test(void) 函数,大家可以看看上述的的函数原型,逻辑是这样的:定义一个全局变量 g_heart_flag 用于状态的判断,刚开始正常显示三级菜单(g_heart_flag 等于0时),检测到按键按下,也即是  if (Key_entern)  就会将屏幕刷新并且让标志位置一,这时候就进入实现功能的界面啦!(看下图, 注意这里 不能像主循环里使用 KEY_SURE == g_keynum 来判断是否按下确认键,因为我们进入三级菜单是通过按下确认键进入,这里 g_keynum 变量还保持在 确认 的状态,这里我们使用 读取确认键电平的方式

if (1 == g_heart_flag)
    {
      if (KEY_RETURN == g_keynum)
      {
        g_heart_flag = 0;
      }
      else
      {
        MAX30102_Read_Data();
        Calculate_Heart_Rate_and_SpO2();
        Update_Signal_Min_Max();
        Process_And_Display_Data();
      }
    }

        这段代码是放在主循环里的,通过判断标志位确认是否进入 测试心率 这个功能函数,进入前需要一个条件判断,时刻判断是否按下了返回按键,按下就将标志位清零即可。总体的逻辑就是: 按下确认键,将标志位置一,主循环中检测标志位,出现标志位置一就执行心率检测,同时检测是否有按下返回键用于返回

        有了思路之后大家就能按自己的想法进行菜单的绘制啦!!!

结束语:

        这次的分享就到这里结束啦!如果觉得有帮助请三连支持一下吧!最后附上工程源码(连接好引脚烧录就能使用)

通过网盘分享的文件:blood_test.zip
链接: https://pan.baidu.com/s/1ylzjuBUehRFk0LE7lYgBOw 提取码: 7ii2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值