【stm32】手把手用cubemx配置血氧传感器(MAX30102)

2023-11-07

一、前言

        网上流传血氧传感器的代码有好几个版本,听说这个不准,那个不准的。突然间我看到了一篇好文章,大概是自己用软件测试测量结果是否准确,秀的我头皮发麻呀(外部中断触发),本文将通过他的例程来手把手教大家如何配置。本文适合小白,只讲如何应用,原理请大家查阅其他资料,文末分享我的工程关于血氧传感器优秀资料的链接。

二、材料准备

        某宝搜索关键字 “血氧传感器”,然后焊接成这个样子。

三、引脚说明

        我们只用到了6个引脚,RD、IRD为该模块LED有关的引脚,一般不接。之后在cubemx中可以看到具体怎么连接。

VIN:主电源输入端 1.8V-5V

SCL:接I2C总线的时钟     -->PB6(根据cubemx)

SDA:接I2C总线的数据    -->PB9(根据cubemx)

INT:芯片的中断引脚        -->PC0(根据cubemx)

GND:接地(有两个)

四、cubemx工程配置

1.选择自己单片机的芯片,把RCC、时钟、DEBUG啥的都配置好。(没啥要求,按平时来就行)

2.任选一个引脚作为外部中断触发的引脚,这样当我们放下手指时,血氧传感器才会检测。这里我选PC0。

3.配置PC0。下降沿触发、上拉、设置用户标签MAX30102_INT

 4.使能PC0中断

不同引脚中断号不一样,具体看数据手册,当然你也可以看配置完多出来哪一行来判断。

5.配置IIC

6.配置串口,我这里用串口10,参数默认就行

7.然后大家就可以生成工程了

五、keil工程配置

1.勾选MicroLIB

2.开启DSP

 

3.添加宏定义 ARM_MATH_CM7(<--这个根据自己内核的情况配置,我的是M7),__FPU_PRESENT 

六、代码

1.新建四个文件,将C文件添加到工程中

max30102.c

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : max30102.c
 * @brief          : 血氧传感器
 ******************************************************************************
 * @attention
 * 1.要宏定义 ARM_MATH_CM7,__FPU_PRESENT
 * 2.打开DSP
 * 3.main函数定义如下全局变量
 *	uint8_t max30102_int_flag = 0; // 中断标志
 *	float ppg_data_cache_RED[CACHE_NUMS] = {0}; // 缓存区
 *	float ppg_data_cache_IR[CACHE_NUMS] = {0};  // 缓存区
 *	uint16_t cache_counter = 0; // 缓存计数器
 * 
 ******************************************************************************
 */
/* USER CODE END Header */
#include "./max30102/max30102.h"
#include "./max30102/max30102_fir.h"
#include "stdio.h"

extern uint8_t max30102_int_flag;
extern float ppg_data_cache_RED[CACHE_NUMS] ; // 缓存区
extern float ppg_data_cache_IR[CACHE_NUMS] ;  // 缓存区
extern uint16_t cache_counter;
/**
 * @brief IIC 写入
 * @retval None
 */
void max30102_i2c_write(uint8_t reg_adder, uint8_t data)
{
	uint8_t transmit_data[2];
	transmit_data[0] = reg_adder;
	transmit_data[1] = data;
	i2c_transmit(transmit_data, 2);
}

/**
 * @brief IIC 读取
 * @retval None
 */
void max30102_i2c_read(uint8_t reg_adder, uint8_t *pdata, uint8_t data_size)
{
	uint8_t adder = reg_adder;
	i2c_transmit(&adder, 1);
	i2c_receive(pdata, data_size);
}

/**
 * @brief max30102初始化
 * @retval None
 */
void max30102_init(void)
{
	uint8_t data;

	max30102_i2c_write(MODE_CONFIGURATION, 0x40); // reset the device

	delay_ms(5);

	max30102_i2c_write(INTERRUPT_ENABLE1, 0xE0);
	max30102_i2c_write(INTERRUPT_ENABLE2, 0x00); // interrupt enable: FIFO almost full flag, new FIFO Data Ready,
												 //                    ambient light cancellation overflow, power ready flag,
												 //						    		internal temperature ready flag

	max30102_i2c_write(FIFO_WR_POINTER, 0x00);
	max30102_i2c_write(FIFO_OV_COUNTER, 0x00);
	max30102_i2c_write(FIFO_RD_POINTER, 0x00); // clear the pointer

	max30102_i2c_write(FIFO_CONFIGURATION, 0x4F); // FIFO configuration: sample averaging(1),FIFO rolls on full(0), FIFO almost full value(15 empty data samples when interrupt is issued)

	max30102_i2c_write(MODE_CONFIGURATION, 0x03); // MODE configuration:SpO2 mode

	max30102_i2c_write(SPO2_CONFIGURATION, 0x2A); // SpO2 configuration:ACD resolution:15.63pA,sample rate control:200Hz, LED pulse width:215 us

	max30102_i2c_write(LED1_PULSE_AMPLITUDE, 0x2f); // IR LED
	max30102_i2c_write(LED2_PULSE_AMPLITUDE, 0x2f); // RED LED current

	max30102_i2c_write(TEMPERATURE_CONFIG, 0x01); // temp

	max30102_i2c_read(INTERRUPT_STATUS1, &data, 1);
	max30102_i2c_read(INTERRUPT_STATUS2, &data, 1); // clear status
}

/**
 * @brief fifo区读取
 * @param output_data
 * @retval None
 */
void max30102_fifo_read(float *output_data)
{
	uint8_t receive_data[6];
	uint32_t data[2];
	max30102_i2c_read(FIFO_DATA, receive_data, 6);
	data[0] = ((receive_data[0] << 16 | receive_data[1] << 8 | receive_data[2]) & 0x03ffff);
	data[1] = ((receive_data[3] << 16 | receive_data[4] << 8 | receive_data[5]) & 0x03ffff);
	*output_data = data[0];
	*(output_data + 1) = data[1];
}

/**
 * @brief 获取心率
 * @param input_data cache_nums(缓存区的最大数字)
 * @retval (uint16_t)心率
 */
uint16_t max30102_getHeartRate(float *input_data, uint16_t cache_nums)
{
	float input_data_sum_aver = 0;
	uint16_t i, temp;

	for (i = 0; i < cache_nums; i++)
	{
		input_data_sum_aver += *(input_data + i);
	}
	input_data_sum_aver = input_data_sum_aver / cache_nums;
	for (i = 0; i < cache_nums; i++)
	{
		if ((*(input_data + i) > input_data_sum_aver) && (*(input_data + i + 1) < input_data_sum_aver))
		{
			temp = i;
			break;
		}
	}
	i++;
	for (; i < cache_nums; i++)
	{
		if ((*(input_data + i) > input_data_sum_aver) && (*(input_data + i + 1) < input_data_sum_aver))
		{
			temp = i - temp;
			break;
		}
	}
	if ((temp > 14) && (temp < 100))
	{
		return 3000 / temp;
	}
	else
	{
		return 0;
	}
}

/**
 * @brief 获取血氧
 * @param input_data red_input_data cache_nums(缓存区的最大数字)
 * @retval (float)血氧
 */
float max30102_getSpO2(float *ir_input_data, float *red_input_data, uint16_t cache_nums)
{
	float ir_max = *ir_input_data, ir_min = *ir_input_data;
	float red_max = *red_input_data, red_min = *red_input_data;
	float R;
	uint16_t i;
	for (i = 1; i < cache_nums; i++)
	{
		if (ir_max < *(ir_input_data + i))
		{
			ir_max = *(ir_input_data + i);
		}
		if (ir_min > *(ir_input_data + i))
		{
			ir_min = *(ir_input_data + i);
		}
		if (red_max < *(red_input_data + i))
		{
			red_max = *(red_input_data + i);
		}
		if (red_min > *(red_input_data + i))
		{
			red_min = *(red_input_data + i);
		}
	}

	R = ((ir_max + ir_min) * (red_max - red_min)) / ((red_max + red_min) * (ir_max - ir_min));
	return ((-45.060) * R * R + 30.354 * R + 94.845);
}

/**
 * @brief MAX30102服务函数
 * @param HeartRate(心率) SpO2(血氧) max30102_data fir_output
 * @retval (uint8_t)MAX30102_DATA_OK:结束读取  (uint8_t)!MAX30102_DATA_OK:还在读取
 */
uint8_t MAX30102_Get_DATA(uint16_t *HeartRate,float *SpO2,float max30102_data[2],float fir_output[2])
{
	if (max30102_int_flag) // 中断信号产生
	{
		max30102_int_flag = 0;
		max30102_fifo_read(max30102_data); // 读取数据
		ir_max30102_fir(&max30102_data[0], &fir_output[0]);
		red_max30102_fir(&max30102_data[1], &fir_output[1]);                                    // 滤波
		if ((max30102_data[0] > PPG_DATA_THRESHOLD) && (max30102_data[1] > PPG_DATA_THRESHOLD)) // 大于阈值,说明传感器有接触
		{
			ppg_data_cache_IR[cache_counter] = fir_output[0];
			ppg_data_cache_RED[cache_counter] = fir_output[1];
			cache_counter++;
		}
		else // 小于阈值
		{
			cache_counter = 0;
		}
		if (cache_counter >= CACHE_NUMS) // 收集满了数据
		{
			*HeartRate = max30102_getHeartRate(ppg_data_cache_IR, CACHE_NUMS);
			*SpO2 = max30102_getSpO2(ppg_data_cache_IR, ppg_data_cache_RED, CACHE_NUMS);
			cache_counter = 0;
			return MAX30102_DATA_OK;
		}
	}
	return !MAX30102_DATA_OK;
}

/**
 * @brief MAX30102输入引脚外部中断触发
 * @param GPIO_Pin
 * @attention cubemx配置下降沿 上拉 允许中断
 * @retval None
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if (GPIO_Pin == MAX30102_INT_Pin)
  {
    max30102_int_flag = 1;
  }
}

max30102.h

#ifndef __MAX30102_H
#define __MAX30102_H

/*******************************以下根据实际情况设置*******************************/
#include "main.h"
extern I2C_HandleTypeDef hi2c1;
#define  i2c_transmit(pdata,data_size)              HAL_I2C_Master_Transmit(&hi2c1,I2C_WRITE_ADDR,pdata,data_size,10)
#define  i2c_receive(pdata,data_size)   						HAL_I2C_Master_Receive(&hi2c1,I2C_READ_ADDR,pdata,data_size,10)
#define  delay_ms(ms)                                HAL_Delay(ms)
/***********************************************************************************/

#define CACHE_NUMS 150//缓存数
#define PPG_DATA_THRESHOLD 100000 	//检测阈值


#define I2C_WRITE_ADDR 0xAE
#define I2C_READ_ADDR 0xAF

#define INTERRUPT_STATUS1 0X00
#define INTERRUPT_STATUS2 0X01
#define INTERRUPT_ENABLE1 0X02
#define INTERRUPT_ENABLE2 0X03

#define FIFO_WR_POINTER 0X04
#define FIFO_OV_COUNTER 0X05
#define FIFO_RD_POINTER 0X06
#define FIFO_DATA 0X07

#define FIFO_CONFIGURATION 0X08
#define MODE_CONFIGURATION 0X09
#define SPO2_CONFIGURATION 0X0A
#define LED1_PULSE_AMPLITUDE 0X0C
#define LED2_PULSE_AMPLITUDE 0X0D

#define MULTILED1_MODE 0X11
#define MULTILED2_MODE 0X12

#define TEMPERATURE_INTEGER 0X1F
#define TEMPERATURE_FRACTION 0X20
#define TEMPERATURE_CONFIG 0X21

#define VERSION_ID 0XFE
#define PART_ID 0XFF

#define MAX30102_DATA_OK 1

void max30102_init(void);
void max30102_fifo_read(float *data);
void max30102_i2c_read(uint8_t reg_adder,uint8_t *pdata, uint8_t data_size);
uint16_t max30102_getHeartRate(float *input_data,uint16_t cache_nums);
float max30102_getSpO2(float *ir_input_data,float *red_input_data,uint16_t cache_nums);
uint8_t MAX30102_Get_DATA(uint16_t *HeartRate,float *SpO2,float max30102_data[2],float fir_output[2]);
void MAX30102_LCD_Data(uint16_t HeartRate,float SpO2,char *PHeartRate,char *PSpO2);
#endif /* __MAX30102_H */

max30102_fir.c

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : max30102_fir.c
 * @brief          : 滤波算法实现
 ******************************************************************************
 * @attention
 * 1.要宏定义 ARM_MATH_CM7,__FPU_PRESENT
 * 2.打开DSP
 
 ******************************************************************************
 */
/* USER CODE END Header */
#include "./max30102/max30102_fir.h"

#define BLOCK_SIZE 1 /* 调用一次arm_fir_f32处理的采样点个数 */
#define NUM_TAPS 29  /* 滤波器系数个数 */

uint32_t blockSize = BLOCK_SIZE;
uint32_t numBlocks = BLOCK_SIZE; /* 需要调用arm_fir_f32的次数 */
arm_fir_instance_f32 S_ir, S_red;
static float firStateF32_ir[BLOCK_SIZE + NUM_TAPS - 1];  /* 状态缓存,大小numTaps + blockSize - 1*/
static float firStateF32_red[BLOCK_SIZE + NUM_TAPS - 1]; /* 状态缓存,大小numTaps + blockSize - 1*/
/* 低通滤波器系数 通过fadtool获取*/
const float firCoeffs32LP[NUM_TAPS] = {
    -0.001542701735, -0.002211477375, -0.003286228748, -0.00442651147, -0.004758632276,
    -0.003007677384, 0.002192312852, 0.01188309677, 0.02637642808, 0.04498152807,
    0.06596207619, 0.0867607221, 0.1044560149, 0.1163498312, 0.1205424443,
    0.1163498312, 0.1044560149, 0.0867607221, 0.06596207619, 0.04498152807,
    0.02637642808, 0.01188309677, 0.002192312852, -0.003007677384, -0.004758632276,
    -0.00442651147, -0.003286228748, -0.002211477375, -0.001542701735};

void max30102_fir_init(void)
{
  arm_fir_init_f32(&S_ir, NUM_TAPS, (float32_t *)&firCoeffs32LP[0], &firStateF32_ir[0], blockSize);
  arm_fir_init_f32(&S_red, NUM_TAPS, (float32_t *)&firCoeffs32LP[0], &firStateF32_red[0], blockSize);
}

void ir_max30102_fir(float *input, float *output)
{
  arm_fir_f32(&S_ir, input, output, blockSize);
}

void red_max30102_fir(float *input, float *output)
{
  arm_fir_f32(&S_red, input, output, blockSize);
}

max30102_fir.h

#ifndef __MAX30102_FIR_H
#define __MAX30102_FIR_H

#include "./math/arm_const_structs.h"


void max30102_fir_init(void);
void ir_max30102_fir(float *input,float *output);
void red_max30102_fir(float *input,float *output);
#endif /* __MAX30102_FIR_H */

2.代码添加完毕,在main.c中使用

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "./max30102/max30102.h"
#include "./max30102/max30102_fir.h"
/* USER CODE END Includes */

 3.main.c定义全局变量

/* USER CODE BEGIN PV */
uint8_t max30102_int_flag = 0; // 中断标志
float ppg_data_cache_RED[CACHE_NUMS] = {0}; // 缓存区
float ppg_data_cache_IR[CACHE_NUMS] = {0};  // 缓存区
uint16_t cache_counter = 0; // 缓存计数器
/* USER CODE END PV */

 4.begin1这里定义心率 血氧

  /* USER CODE BEGIN 1 */
	uint16_t HeartRate = 0;
	float SpO2 = 0;
  /* USER CODE END 1 */

5.begin2这里初始化

  /* USER CODE BEGIN 2 */
  max30102_init();
  max30102_fir_init();
  float max30102_data[2], fir_output[2];
  printf("Max30102 Init\r\n");
  /* USER CODE END 2 */

6.begin while这里添加服务函数

    if(MAX30102_Get_DATA(&HeartRate,&SpO2,max30102_data,fir_output) == MAX30102_DATA_OK)
	{
	   printf("心率:%d  次/min   ", HeartRate);
       printf("血氧:%.2f  %%\r\n", SpO2);
	}

7.最后别忘了添加串口重定向,我用的是串口10,在begin0添加

/* USER CODE BEGIN 0 */
int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit (&huart10 ,(uint8_t *)&ch,1,HAL_MAX_DELAY );
	//采用轮询方式发送一个字节的数据,没有发送成功就一直等待
	return ch;
}
int fgetc(FILE *f)
//int fgetc(int ch, FILE *F)
{
	uint8_t ch;
	HAL_UART_Receive (&huart10 ,(uint8_t *)&ch,1,HAL_MAX_DELAY );
	return ch;
}
/* USER CODE END 0 */

8.移植完毕,效果图如下

不知道第一次测的结果为啥是0...知道的小伙伴可以在评论区留言。

七、结束语

移植有问题的小伙伴可以在评论区留言,最后分享一下我借鉴的资料。

MAX30102脉搏血氧仪和心率传感器(四)血氧+心率完整版(STM32)

MAX30102 血氧调试笔记

需求有点大,那就把工程发出来

链接:https://pan.baidu.com/s/1VJ551_J-mVwxHWPCOqNBPQ 
提取码:max3

------------------------------------修改于2023.4.12---------------------------------

我发现读取温湿度信息后,MAX30102的INT不能正常触发了,一种简单的不修改配置的方法如下

/**
 * @brief MAX30102服务函数
 * @param HeartRate(心率) SpO2(血氧) max30102_data fir_output
 * @retval (uint8_t)MAX30102_DATA_OK:结束读取  (uint8_t)!MAX30102_DATA_OK:还在读取
 */
uint8_t MAX30102_Get_DATA(uint16_t *HeartRate,float *SpO2,float max30102_data[2],float fir_output[2])
{
//	if (max30102_int_flag) // 中断信号产生
//	{
//		max30102_int_flag = 0;
		max30102_fifo_read(max30102_data); // 读取数据
		ir_max30102_fir(&max30102_data[0], &fir_output[0]);
		red_max30102_fir(&max30102_data[1], &fir_output[1]);                                    // 滤波
		if ((max30102_data[0] > PPG_DATA_THRESHOLD) && (max30102_data[1] > PPG_DATA_THRESHOLD)) // 大于阈值,说明传感器有接触
		{
			ppg_data_cache_IR[cache_counter] = fir_output[0];
			ppg_data_cache_RED[cache_counter] = fir_output[1];
			cache_counter++;
		}
		else // 小于阈值
		{
			cache_counter = 0;
		}
		if (cache_counter >= CACHE_NUMS) // 收集满了数据
		{
			*HeartRate = max30102_getHeartRate(ppg_data_cache_IR, CACHE_NUMS);
			*SpO2 = max30102_getSpO2(ppg_data_cache_IR, ppg_data_cache_RED, CACHE_NUMS);
			cache_counter = 0;
			return MAX30102_DATA_OK;
		}
//	}
	return !MAX30102_DATA_OK;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【stm32】手把手用cubemx配置血氧传感器(MAX30102) 的相关文章

随机推荐

  • 树莓派 安装Arch Linux ARM

    首先 需要一个linux环境 archlinuxarm系统的安装需要用linux环境复制文件 把sd卡或tf卡连接到装有linux环境的电脑上 首先 确定自己树莓派的型号是b 2b 还是3b 选择合适版本 打开终端 并获得超级权限 sudo
  • Sonar Java默认扫描规则

    规则如下 equals should not be used to test the values of Atomic classes equals 方法不应该用在原子类型的数据上 如 AtomicInteger AtomicLong At
  • MCDF实验——Lab4

    在之前的Lab3中 通过一个初具规模的MCDT的验证环境 学习到 验证环境按照隔离的观念 应分为硬件DUT 软件验证环境 和处于信号媒介的接口interface 对于软件验证环境 需要经历建立阶段 build 连接阶段 connect 产生
  • 浅谈项目售前调研

    一 概述 说到软件项目的售前调研工作 可能还得先谈谈售前顾问这个重要的角色 在IT软件行业 售前顾问位于职业金字塔顶端 是项目开发 实施人员与销售人员间的纽带和桥梁 在销售人员眼中 售前顾问扮演的是技术专家的角色 而在项目实施和开发人员眼中
  • 中兴网络设备交换机路由器查看日志命令方法

    描述 中兴网络设备交换机路由器查看日志命令方法 命令 show logfile
  • 小程序如何实现本地去水印

    自媒体时代 很多人都进行伪原创 但是有些视频本身就有水印的 这个时候我们怎么办 很多人都不懂这个方法 所以导致很多人不会使用 一般都是电脑操作 那我们就没有办法了吗 今天介绍的就是小程序如何实现本地去水印 我们也知道FFmpeg命令 去掉视
  • 波形分析软件 android,新版 PicoScope 软件提供更出色的波形分析和功能 – 免费获取!...

    全球领先的 PC 示波器制造商 Pico Technology Ltd 发布了 PicoScope 软件 6 11 7 版 此版本的 PicoScope 可为研发新一代电气和电子技术的工程师 科学家 技术人员和研究人员提供重要的新功能 本文
  • wifi感知---csi技术

    CSI在WiFi研究领域指Channel State Information 也就是通过接收到的WiFi信号来估计WiFi信号的传播信道长什么样子 它表征了一系列影响的综合 例如散射 衰落 能量随着距离的衰减 目前人们可以从CSI里提取到很
  • C#文件读写小案例

    目录 1 驱动器管理类 2 目录管理类 3 文件管理类 4 路径管理类 5 FileStream类读取文件 6 StreamReader类读取文件 7 使用FileStream类写入文件 用FileStream类写入文件可以指定要写入的位置
  • 逐个版本分析鬼火引擎

    这段时间做手游的cocos2dx的学习 和做web开发的项目 感觉很没劲 还是得研究引擎 我看到有个人的博客直接分析鬼火引擎0 1版本 这个方法不错 两万行左右代码 sourceforge里面有各个版本的代码 这样 正好可以循序渐进地进行
  • Maven导包及打包

    Maven是什么 Maven是一个跨平台的项目管理工具 作为Apache组织的一个颇为成功的开源项目 其主要服务于基于Java平台的项目创建 依赖管理和项目信息管理 是一个自动化构建工具 maven是Apache的顶级项目 解释为 专家 内
  • org.springframework.web.bind.annotation 注解详解

    处理request RequestBody RequestHeader RequestMapping RequestParam RequestPart CookieValue PathVariable 传送门 处理response Resp
  • Python算法教程:强连通分量

    强连通分量 strongly connected components SCCs 是一个能让有向路径上所有节点彼此到达的最大子图 Kosaraju的查找强连通分量算法 def strongly connected components gr
  • Windows下 VS2015编译RocksDB

    Windows下 VS2015编译RocksDB VS2015编译RocksDB RocksDB 是一个来自 facebook 的可嵌入式的支持持久化的 key value 存储系统 也可作为 C S 模式下的存储数据库 但主要目的还是嵌入
  • unity Input.GetAxis()函数

    开发手册上有相关解释 但说得很不清楚 看完也不懂 下面给出详细的解释 根据输入设备 参数分为两类 一 触屏类 1 Mouse X 鼠标沿屏幕X移动时触发 2 Mouse Y 鼠标沿屏幕Y移动时触发 3 Mouse ScrollWheel 鼠
  • Kaldi语音识别学习记录-----编译安装

    语音识别领域的开源框架有CMUSphinx HTK Kaldi等等 而目前仍然比较活跃 且工程价值较高的就数Kaldi 很多从事语音方面的公司 都使用该框架训练自己的语音识别能力 由于其内部代码逻辑较为复杂 故这里一步一步来解读 了解语音识
  • Git的安装下载基本操作与使用,git上传远程仓库gitee配置操作流程,git一站式教程

    目录 一 git的安装 gitee官网直通车 git官网 git安装流程 二 git配置与提交giee远程仓库操作方法与命令 三 本地项目导入仓库 分支操作 拷贝远程仓库 最常用 比如下载别人的仓库代码 一 git的安装 gitee官网直通
  • 2020美赛F题

    2020美赛F题 待补充 先来翻译 翻译最好用谷歌翻译 别问 问就是谷歌 研究人员确定了几个岛国 例如马尔代夫 图瓦卢 基里巴斯和 由于海平面上升 马绍尔群岛有可能完全消失 什么 岛国的土地消失后 岛上的人口会发生什么事情或应该发生什么事情
  • 对meta标签的再次认识

    META标签用来描述一个HTML网页文档的属性 例如作者 日期和时间 网页描述 关键词 页面刷新等 指定字符集 向搜索引擎说明网页的关键词 告诉搜索引擎你的站点的主要内容 告诉搜索引擎你的站点的制作的作者 响应式页面
  • 【stm32】手把手用cubemx配置血氧传感器(MAX30102)

    一 前言 网上流传血氧传感器的代码有好几个版本 听说这个不准 那个不准的 突然间我看到了一篇好文章 大概是自己用软件测试测量结果是否准确 秀的我头皮发麻呀 外部中断触发 本文将通过他的例程来手把手教大家如何配置 本文适合小白 只讲如何应用