基于STM32设计的智能手环(ESP8266+华为云IOT)

项目简介

基于STM32设计的智能手环项目,目的是为了开发一款功能齐全、性能稳定、价格亲民的智能手环。主控芯片采用STM32F103RCT6,这款芯片具有高性能、低功耗、易于开发等优点,为智能手环的稳定运行提供了有力保障。

为了满足用户对健康监测的需求,手环项目集成了血氧检测、心率检测、体温检测等功能。这些功能采用了专门的传感器模块,如MAX30102、DS18B20等,确保了测量数据的准确性和可靠性。同时,为了方便用户了解自己的运动情况,项目还加入了行走步数统计功能,通过MPU6050模块实现精确计步。

除了健康监测功能,项目还具备GPS定位功能,可实时获取用户的地理位置信息。这对于户外运动爱好者、需要关注孩子或老人行踪的家庭来说,具有很高的实用价值。

为了提供更好的用户体验,项目采用了0.96寸OLED显示屏,可实时显示各种监测数据和时间信息。项目还集成了蜂鸣器报警功能,当检测到体温、血氧、心率超过设定值时,会通过蜂鸣器发出警报,提醒用户关注自己的健康状况。

为了实现远程数据监控和管理,项目采用了ESP8266-WIFI模块,将本地采集的数据实时上传到华为云物联网平台。用户可以通过Android手机APP,随时查看自己的健康数据和位置信息。这种远程监控方式,不仅方便了用户,还为家庭健康管理提供了新的可能。

基于STM32设计的智能手环项目,结合了多种先进技术和功能模块,为用户提供一款功能全面、性能稳定的智能穿戴设备,有助于提升人们的生活质量和健康水平。

元器件

0.96cunOLED显示屏

简介

采用IIC的通信协议

OLED的代码主要是对江科大的标准库代码进行的HAL库重置,实现了不定参数的打印函数;GUI设置函数,下面都是代码

代码

OLED.c

复制代码
#include "stm32f1xx_hal.h"
#include "OLED.h"
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdarg.h>

/**
  * 数据存储格式:
  * 纵向8点,高位在下,先从左到右,再从上到下
  * 每一个Bit对应一个像素点
  * 
  *      B0 B0                  B0 B0
  *      B1 B1                  B1 B1
  *      B2 B2                  B2 B2
  *      B3 B3  ------------->  B3 B3 --
  *      B4 B4                  B4 B4  |
  *      B5 B5                  B5 B5  |
  *      B6 B6                  B6 B6  |
  *      B7 B7                  B7 B7  |
  *                                    |
  *  -----------------------------------
  *  |   
  *  |   B0 B0                  B0 B0
  *  |   B1 B1                  B1 B1
  *  |   B2 B2                  B2 B2
  *  --> B3 B3  ------------->  B3 B3
  *      B4 B4                  B4 B4
  *      B5 B5                  B5 B5
  *      B6 B6                  B6 B6
  *      B7 B7                  B7 B7
  * 
  * 坐标轴定义:
  * 左上角为(0, 0)点
  * 横向向右为X轴,取值范围:0~127
  * 纵向向下为Y轴,取值范围:0~63
  * 
  *       0             X轴           127 
  *      .------------------------------->
  *    0 |
  *      |
  *      |
  *      |
  *  Y轴 |
  *      |
  *      |
  *      |
  *   63 |
  *      v
  * 
  */


/*全局变量*********************/

/**
  * OLED显存数组
  * 所有的显示函数,都只是对此显存数组进行读写
  * 随后调用OLED_Update函数或OLED_UpdateArea函数
  * 才会将显存数组的数据发送到OLED硬件,进行显示
  */
#include "stm32f1xx_hal.h"
#include "OLED.h"
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdarg.h>

/* 全局变量 */
uint8_t OLED_DisplayBuf[8][128];
extern I2C_HandleTypeDef hi2c1;  // 声明外部I2C句柄

/* 硬件I2C通信函数 */
/**
  * 函    数:OLED写命令
  * 参    数:Command 要写入的命令值,范围:0x00~0xFF
  * 返 回 值:无
  */
void OLED_WriteCommand(uint8_t Command)
{
    uint8_t buf[2] = {0x00, Command};  // 控制字节+命令字节
    HAL_I2C_Master_Transmit(&hi2c1, 0x78, buf, 2, HAL_MAX_DELAY);
}

/**
  * 函    数:OLED写数据
  * 参    数:Data 要写入数据的起始地址
  * 参    数:Count 要写入数据的数量
  * 返 回 值:无
  */
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
    uint8_t buf[129];  // 控制字节+最多128字节数据
    buf[0] = 0x40;     // 数据控制字节
    
    if(Count > 128) Count = 128;
    memcpy(&buf[1], Data, Count);
    
    HAL_I2C_Master_Transmit(&hi2c1, 0x78, buf, Count + 1, HAL_MAX_DELAY);
}

/**
  * 函    数:OLED初始化
  * 参    数:无
  * 返 回 值:无
  */
void OLED_Init(void)
{
    /* 硬件I2C初始化已在main.c中完成,此处只需配置OLED */
    
    /* 写入一系列的命令,对OLED进行初始化配置 */
    OLED_WriteCommand(0xAE);    // 设置显示开启/关闭
    
    OLED_WriteCommand(0xD5);    // 设置显示时钟分频比/振荡器频率
    OLED_WriteCommand(0x80);
    
    OLED_WriteCommand(0xA8);    // 设置多路复用率
    OLED_WriteCommand(0x3F);
    
    OLED_WriteCommand(0xD3);    // 设置显示偏移
    OLED_WriteCommand(0x00);
    
    OLED_WriteCommand(0x40);    // 设置显示开始行
    
    OLED_WriteCommand(0xA1);    // 设置左右方向,0xA1正常
    
    OLED_WriteCommand(0xC8);    // 设置上下方向,0xC8正常

    OLED_WriteCommand(0xDA);    // 设置COM引脚硬件配置
    OLED_WriteCommand(0x12);
    
    OLED_WriteCommand(0x81);    // 设置对比度
    OLED_WriteCommand(0xCF);

    OLED_WriteCommand(0xD9);    // 设置预充电周期
    OLED_WriteCommand(0xF1);

    OLED_WriteCommand(0xDB);    // 设置VCOMH取消选择级别
    OLED_WriteCommand(0x30);

    OLED_WriteCommand(0xA4);    // 设置整个显示打开/关闭

    OLED_WriteCommand(0xA6);    // 设置正常/反色显示

    OLED_WriteCommand(0x8D);    // 设置充电泵
    OLED_WriteCommand(0x14);

    OLED_WriteCommand(0xAF);    // 开启显示
    
    OLED_Clear();               // 清空显存数组
    OLED_Update();             // 更新显示
}

/**
  * 函    数:OLED设置显示光标位置
  * 参    数:Page 指定光标所在的页,范围:0~7
  * 参    数:X 指定光标所在的X轴坐标,范围:0~127
  * 返 回 值:无
  */
void OLED_SetCursor(uint8_t Page, uint8_t X)
{
    X += 2;  // SH1106偏移调整
    
    OLED_WriteCommand(0xB0 | Page);                 // 设置页位置
    OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));    // 设置X位置高4位
    OLED_WriteCommand(0x00 | (X & 0x0F));            // 设置X位置低4位
}

/*********************硬件配置*/


/*工具函数*********************/

/*工具函数仅供内部部分函数使用*/

/**
  * 函    数:次方函数
  * 参    数:X 底数
  * 参    数:Y 指数
  * 返 回 值:等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//结果默认为1
	while (Y --)			//累乘Y次
	{
		Result *= X;		//每次把X累乘到结果上
	}
	return Result;
}

/**
  * 函    数:判断指定点是否在指定多边形内部
  * 参    数:nvert 多边形的顶点数
  * 参    数:vertx verty 包含多边形顶点的x和y坐标的数组
  * 参    数:testx testy 测试点的X和y坐标
  * 返 回 值:指定点是否在指定多边形内部,1:在内部,0:不在内部
  */
uint8_t OLED_pnpoly(uint8_t nvert, int16_t *vertx, int16_t *verty, int16_t testx, int16_t testy)
{
	int16_t i, j, c = 0;
	
	/*此算法由W. Randolph Franklin提出*/
	/*参考链接:https://wrfranklin.org/Research/Short_Notes/pnpoly.html*/
	for (i = 0, j = nvert - 1; i < nvert; j = i++)
	{
		if (((verty[i] > testy) != (verty[j] > testy)) &&
			(testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]))
		{
			c = !c;
		}
	}
	return c;
}

/**
  * 函    数:判断指定点是否在指定角度内部
  * 参    数:X Y 指定点的坐标
  * 参    数:StartAngle EndAngle 起始角度和终止角度,范围:-180~180
  *           水平向右为0度,水平向左为180度或-180度,下方为正数,上方为负数,顺时针旋转
  * 返 回 值:指定点是否在指定角度内部,1:在内部,0:不在内部
  */
uint8_t OLED_IsInAngle(int16_t X, int16_t Y, int16_t StartAngle, int16_t EndAngle)
{
	int16_t PointAngle;
	PointAngle = atan2(Y, X) / 3.14 * 180;	//计算指定点的弧度,并转换为角度表示
	if (StartAngle < EndAngle)	//起始角度小于终止角度的情况
	{
		/*如果指定角度在起始终止角度之间,则判定指定点在指定角度*/
		if (PointAngle >= StartAngle && PointAngle <= EndAngle)
		{
			return 1;
		}
	}
	else			//起始角度大于于终止角度的情况
	{
		/*如果指定角度大于起始角度或者小于终止角度,则判定指定点在指定角度*/
		if (PointAngle >= StartAngle || PointAngle <= EndAngle)
		{
			return 1;
		}
	}
	return 0;		//不满足以上条件,则判断判定指定点不在指定角度
}

/*********************工具函数*/


/*功能函数*********************/

/**
  * 函    数:将OLED显存数组更新到OLED屏幕
  * 参    数:无
  * 返 回 值:无
  * 说    明:所有的显示函数,都只是对OLED显存数组进行读写
  *           随后调用OLED_Update函数或OLED_UpdateArea函数
  *           才会将显存数组的数据发送到OLED硬件,进行显示
  *           故调用显示函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_Update(void)
{
	uint8_t j;
	/*遍历每一页*/
	for (j = 0; j < 8; j ++)
	{
		/*设置光标位置为每一页的第一列*/
		OLED_SetCursor(j, 0);
		/*连续写入128个数据,将显存数组的数据写入到OLED硬件*/
		OLED_WriteData(OLED_DisplayBuf[j], 128);
	}
}

/**
  * 函    数:将OLED显存数组部分更新到OLED屏幕
  * 参    数:X 指定区域左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定区域左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Width 指定区域的宽度,范围:0~128
  * 参    数:Height 指定区域的高度,范围:0~64
  * 返 回 值:无
  * 说    明:此函数会至少更新参数指定的区域
  *           如果更新区域Y轴只包含部分页,则同一页的剩余部分会跟随一起更新
  * 说    明:所有的显示函数,都只是对OLED显存数组进行读写
  *           随后调用OLED_Update函数或OLED_UpdateArea函数
  *           才会将显存数组的数据发送到OLED硬件,进行显示
  *           故调用显示函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_UpdateArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height)
{
	int16_t j;
	int16_t Page, Page1;
	
	/*负数坐标在计算页地址时需要加一个偏移*/
	/*(Y + Height - 1) / 8 + 1的目的是(Y + Height) / 8并向上取整*/
	Page = Y / 8;
	Page1 = (Y + Height - 1) / 8 + 1;
	if (Y < 0)
	{
		Page -= 1;
		Page1 -= 1;
	}
	
	/*遍历指定区域涉及的相关页*/
	for (j = Page; j < Page1; j ++)
	{
		if (X >= 0 && X <= 127 && j >= 0 && j <= 7)		//超出屏幕的内容不显示
		{
			/*设置光标位置为相关页的指定列*/
			OLED_SetCursor(j, X);
			/*连续写入Width个数据,将显存数组的数据写入到OLED硬件*/
			OLED_WriteData(&OLED_DisplayBuf[j][X], Width);
		}
	}
}

/**
  * 函    数:将OLED显存数组全部清零
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_Clear(void)
{
	uint8_t i, j;
	for (j = 0; j < 8; j ++)				//遍历8页
	{
		for (i = 0; i < 128; i ++)			//遍历128列
		{
			OLED_DisplayBuf[j][i] = 0x00;	//将显存数组数据全部清零
		}
	}
}

/**
  * 函    数:将OLED显存数组部分清零
  * 参    数:X 指定区域左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定区域左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Width 指定区域的宽度,范围:0~128
  * 参    数:Height 指定区域的高度,范围:0~64
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ClearArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height)
{
	int16_t i, j;
	
	for (j = Y; j < Y + Height; j ++)		//遍历指定页
	{
		for (i = X; i < X + Width; i ++)	//遍历指定列
		{
			if (i >= 0 && i <= 127 && j >=0 && j <= 63)				//超出屏幕的内容不显示
			{
				OLED_DisplayBuf[j / 8][i] &= ~(0x01 << (j % 8));	//将显存数组指定数据清零
			}
		}
	}
}

/**
  * 函    数:将OLED显存数组全部取反
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_Reverse(void)
{
	uint8_t i, j;
	for (j = 0; j < 8; j ++)				//遍历8页
	{
		for (i = 0; i < 128; i ++)			//遍历128列
		{
			OLED_DisplayBuf[j][i] ^= 0xFF;	//将显存数组数据全部取反
		}
	}
}
	
/**
  * 函    数:将OLED显存数组部分取反
  * 参    数:X 指定区域左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定区域左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Width 指定区域的宽度,范围:0~128
  * 参    数:Height 指定区域的高度,范围:0~64
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ReverseArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height)
{
	int16_t i, j;
	
	for (j = Y; j < Y + Height; j ++)		//遍历指定页
	{
		for (i = X; i < X + Width; i ++)	//遍历指定列
		{
			if (i >= 0 && i <= 127 && j >=0 && j <= 63)			//超出屏幕的内容不显示
			{
				OLED_DisplayBuf[j / 8][i] ^= 0x01 << (j % 8);	//将显存数组指定数据取反
			}
		}
	}
}

/**
  * 函    数:OLED显示一个字符
  * 参    数:X 指定字符左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定字符左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Char 指定要显示的字符,范围:ASCII码可见字符
  * 参    数:FontSize 指定字体大小
  *           范围:OLED_8X16		宽8像素,高16像素
  *                 OLED_6X8		宽6像素,高8像素
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ShowChar(int16_t X, int16_t Y, char Char, uint8_t FontSize)
{
	if (FontSize == OLED_8X16)		//字体为宽8像素,高16像素
	{
		/*将ASCII字模库OLED_F8x16的指定数据以8*16的图像格式显示*/
		OLED_ShowImage(X, Y, 8, 16, OLED_F8x16[Char - ' ']);
	}
	else if(FontSize == OLED_6X8)	//字体为宽6像素,高8像素
	{
		/*将ASCII字模库OLED_F6x8的指定数据以6*8的图像格式显示*/
		OLED_ShowImage(X, Y, 6, 8, OLED_F6x8[Char - ' ']);
	}
}

/**
  * 函    数:OLED显示字符串(支持ASCII码和中文混合写入)
  * 参    数:X 指定字符串左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定字符串左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:String 指定要显示的字符串,范围:ASCII码可见字符或中文字符组成的字符串
  * 参    数:FontSize 指定字体大小
  *           范围:OLED_8X16		宽8像素,高16像素
  *                 OLED_6X8		宽6像素,高8像素
  * 返 回 值:无
  * 说    明:显示的中文字符需要在OLED_Data.c里的OLED_CF16x16数组定义
  *           未找到指定中文字符时,会显示默认图形(一个方框,内部一个问号)
  *           当字体大小为OLED_8X16时,中文字符以16*16点阵正常显示
  *           当字体大小为OLED_6X8时,中文字符以6*8点阵显示'?'
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ShowString(int16_t X, int16_t Y, char *String, uint8_t FontSize)
{
	uint16_t i = 0;
	char SingleChar[5];
	uint8_t CharLength = 0;
	uint16_t XOffset = 0;
	uint16_t pIndex;
	
	while (String[i] != '\0')	//遍历字符串
	{
		
#ifdef OLED_CHARSET_UTF8						//定义字符集为UTF8
		/*此段代码的目的是,提取UTF8字符串中的一个字符,转存到SingleChar子字符串中*/
		/*判断UTF8编码第一个字节的标志位*/
		if ((String[i] & 0x80) == 0x00)			//第一个字节为0xxxxxxx
		{
			CharLength = 1;						//字符为1字节
			SingleChar[0] = String[i ++];		//将第一个字节写入SingleChar第0个位置,随后i指向下一个字节
			SingleChar[1] = '\0';				//为SingleChar添加字符串结束标志位
		}
		else if ((String[i] & 0xE0) == 0xC0)	//第一个字节为110xxxxx
		{
			CharLength = 2;						//字符为2字节
			SingleChar[0] = String[i ++];		//将第一个字节写入SingleChar第0个位置,随后i指向下一个字节
			if (String[i] == '\0') {break;}		//意外情况,跳出循环,结束显示
			SingleChar[1] = String[i ++];		//将第二个字节写入SingleChar第1个位置,随后i指向下一个字节
			SingleChar[2] = '\0';				//为SingleChar添加字符串结束标志位
		}
		else if ((String[i] & 0xF0) == 0xE0)	//第一个字节为1110xxxx
		{
			CharLength = 3;						//字符为3字节
			SingleChar[0] = String[i ++];
			if (String[i] == '\0') {break;}
			SingleChar[1] = String[i ++];
			if (String[i] == '\0') {break;}
			SingleChar[2] = String[i ++];
			SingleChar[3] = '\0';
		}
		else if ((String[i] & 0xF8) == 0xF0)	//第一个字节为11110xxx
		{
			CharLength = 4;						//字符为4字节
			SingleChar[0] = String[i ++];
			if (String[i] == '\0') {break;}
			SingleChar[1] = String[i ++];
			if (String[i] == '\0') {break;}
			SingleChar[2] = String[i ++];
			if (String[i] == '\0') {break;}
			SingleChar[3] = String[i ++];
			SingleChar[4] = '\0';
		}
		else
		{
			i ++;			//意外情况,i指向下一个字节,忽略此字节,继续判断下一个字节
			continue;
		}
#endif
		
#ifdef OLED_CHARSET_GB2312						//定义字符集为GB2312
		/*此段代码的目的是,提取GB2312字符串中的一个字符,转存到SingleChar子字符串中*/
		/*判断GB2312字节的最高位标志位*/
		if ((String[i] & 0x80) == 0x00)			//最高位为0
		{
			CharLength = 1;						//字符为1字节
			SingleChar[0] = String[i ++];		//将第一个字节写入SingleChar第0个位置,随后i指向下一个字节
			SingleChar[1] = '\0';				//为SingleChar添加字符串结束标志位
		}
		else									//最高位为1
		{
			CharLength = 2;						//字符为2字节
			SingleChar[0] = String[i ++];		//将第一个字节写入SingleChar第0个位置,随后i指向下一个字节
			if (String[i] == '\0') {break;}		//意外情况,跳出循环,结束显示
			SingleChar[1] = String[i ++];		//将第二个字节写入SingleChar第1个位置,随后i指向下一个字节
			SingleChar[2] = '\0';				//为SingleChar添加字符串结束标志位
		}
#endif
		
		/*显示上述代码提取到的SingleChar*/
		if (CharLength == 1)	//如果是单字节字符
		{
			/*使用OLED_ShowChar显示此字符*/
			OLED_ShowChar(X + XOffset, Y, SingleChar[0], FontSize);
			XOffset += FontSize;
		}
		else					//否则,即多字节字符
		{
			/*遍历整个字模库,从字模库中寻找此字符的数据*/
			/*如果找到最后一个字符(定义为空字符串),则表示字符未在字模库定义,停止寻找*/
			for (pIndex = 0; strcmp(OLED_CF16x16[pIndex].Index, "") != 0; pIndex ++)
			{
				/*找到匹配的字符*/
				if (strcmp(OLED_CF16x16[pIndex].Index, SingleChar) == 0)
				{
					break;		//跳出循环,此时pIndex的值为指定字符的索引
				}
			}
			if (FontSize == OLED_8X16)		//给定字体为8*16点阵
			{
				/*将字模库OLED_CF16x16的指定数据以16*16的图像格式显示*/
				OLED_ShowImage(X + XOffset, Y, 16, 16, OLED_CF16x16[pIndex].Data);
				XOffset += 16;
			}
			else if (FontSize == OLED_6X8)	//给定字体为6*8点阵
			{
				/*空间不足,此位置显示'?'*/
				OLED_ShowChar(X + XOffset, Y, '?', OLED_6X8);
				XOffset += OLED_6X8;
			}
		}
	}
}

/**
  * 函    数:OLED显示数字(十进制,正整数)
  * 参    数:X 指定数字左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定数字左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Number 指定要显示的数字,范围:0~4294967295
  * 参    数:Length 指定数字的长度,范围:0~10
  * 参    数:FontSize 指定字体大小
  *           范围:OLED_8X16		宽8像素,高16像素
  *                 OLED_6X8		宽6像素,高8像素
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ShowNum(int16_t X, int16_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{
	uint8_t i;
	for (i = 0; i < Length; i++)		//遍历数字的每一位							
	{
		/*调用OLED_ShowChar函数,依次显示每个数字*/
		/*Number / OLED_Pow(10, Length - i - 1) % 10 可以十进制提取数字的每一位*/
		/*+ '0' 可将数字转换为字符格式*/
		OLED_ShowChar(X + i * FontSize, Y, Number / OLED_Pow(10, Length - i - 1) % 10 + '0', FontSize);
	}
}

/**
  * 函    数:OLED显示有符号数字(十进制,整数)
  * 参    数:X 指定数字左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定数字左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Number 指定要显示的数字,范围:-2147483648~2147483647
  * 参    数:Length 指定数字的长度,范围:0~10
  * 参    数:FontSize 指定字体大小
  *           范围:OLED_8X16		宽8像素,高16像素
  *                 OLED_6X8		宽6像素,高8像素
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ShowSignedNum(int16_t X, int16_t Y, int32_t Number, uint8_t Length, uint8_t FontSize)
{
	uint8_t i;
	uint32_t Number1;
	
	if (Number >= 0)						//数字大于等于0
	{
		OLED_ShowChar(X, Y, '+', FontSize);	//显示+号
		Number1 = Number;					//Number1直接等于Number
	}
	else									//数字小于0
	{
		OLED_ShowChar(X, Y, '-', FontSize);	//显示-号
		Number1 = -Number;					//Number1等于Number取负
	}
	
	for (i = 0; i < Length; i++)			//遍历数字的每一位								
	{
		/*调用OLED_ShowChar函数,依次显示每个数字*/
		/*Number1 / OLED_Pow(10, Length - i - 1) % 10 可以十进制提取数字的每一位*/
		/*+ '0' 可将数字转换为字符格式*/
		OLED_ShowChar(X + (i + 1) * FontSize, Y, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0', FontSize);
	}
}

/**
  * 函    数:OLED显示十六进制数字(十六进制,正整数)
  * 参    数:X 指定数字左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定数字左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Number 指定要显示的数字,范围:0x00000000~0xFFFFFFFF
  * 参    数:Length 指定数字的长度,范围:0~8
  * 参    数:FontSize 指定字体大小
  *           范围:OLED_8X16		宽8像素,高16像素
  *                 OLED_6X8		宽6像素,高8像素
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ShowHexNum(int16_t X, int16_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)		//遍历数字的每一位
	{
		/*以十六进制提取数字的每一位*/
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		
		if (SingleNumber < 10)			//单个数字小于10
		{
			/*调用OLED_ShowChar函数,显示此数字*/
			/*+ '0' 可将数字转换为字符格式*/
			OLED_ShowChar(X + i * FontSize, Y, SingleNumber + '0', FontSize);
		}
		else							//单个数字大于10
		{
			/*调用OLED_ShowChar函数,显示此数字*/
			/*+ 'A' 可将数字转换为从A开始的十六进制字符*/
			OLED_ShowChar(X + i * FontSize, Y, SingleNumber - 10 + 'A', FontSize);
		}
	}
}

/**
  * 函    数:OLED显示二进制数字(二进制,正整数)
  * 参    数:X 指定数字左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定数字左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Number 指定要显示的数字,范围:0x00000000~0xFFFFFFFF
  * 参    数:Length 指定数字的长度,范围:0~16
  * 参    数:FontSize 指定字体大小
  *           范围:OLED_8X16		宽8像素,高16像素
  *                 OLED_6X8		宽6像素,高8像素
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ShowBinNum(int16_t X, int16_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize)
{
	uint8_t i;
	for (i = 0; i < Length; i++)		//遍历数字的每一位	
	{
		/*调用OLED_ShowChar函数,依次显示每个数字*/
		/*Number / OLED_Pow(2, Length - i - 1) % 2 可以二进制提取数字的每一位*/
		/*+ '0' 可将数字转换为字符格式*/
		OLED_ShowChar(X + i * FontSize, Y, Number / OLED_Pow(2, Length - i - 1) % 2 + '0', FontSize);
	}
}

/**
  * 函    数:OLED显示浮点数字(十进制,小数)
  * 参    数:X 指定数字左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定数字左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Number 指定要显示的数字,范围:-4294967295.0~4294967295.0
  * 参    数:IntLength 指定数字的整数位长度,范围:0~10
  * 参    数:FraLength 指定数字的小数位长度,范围:0~9,小数进行四舍五入显示
  * 参    数:FontSize 指定字体大小
  *           范围:OLED_8X16		宽8像素,高16像素
  *                 OLED_6X8		宽6像素,高8像素
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ShowFloatNum(int16_t X, int16_t Y, double Number, uint8_t IntLength, uint8_t FraLength, uint8_t FontSize)
{
	uint32_t PowNum, IntNum, FraNum;
	
	if (Number >= 0)						//数字大于等于0
	{
		OLED_ShowChar(X, Y, '+', FontSize);	//显示+号
	}
	else									//数字小于0
	{
		OLED_ShowChar(X, Y, '-', FontSize);	//显示-号
		Number = -Number;					//Number取负
	}
	
	/*提取整数部分和小数部分*/
	IntNum = Number;						//直接赋值给整型变量,提取整数
	Number -= IntNum;						//将Number的整数减掉,防止之后将小数乘到整数时因数过大造成错误
	PowNum = OLED_Pow(10, FraLength);		//根据指定小数的位数,确定乘数
	FraNum = round(Number * PowNum);		//将小数乘到整数,同时四舍五入,避免显示误差
	IntNum += FraNum / PowNum;				//若四舍五入造成了进位,则需要再加给整数
	
	/*显示整数部分*/
	OLED_ShowNum(X + FontSize, Y, IntNum, IntLength, FontSize);
	
	/*显示小数点*/
	OLED_ShowChar(X + (IntLength + 1) * FontSize, Y, '.', FontSize);
	
	/*显示小数部分*/
	OLED_ShowNum(X + (IntLength + 2) * FontSize, Y, FraNum, FraLength, FontSize);
}

/**
  * 函    数:OLED显示图像
  * 参    数:X 指定图像左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定图像左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Width 指定图像的宽度,范围:0~128
  * 参    数:Height 指定图像的高度,范围:0~64
  * 参    数:Image 指定要显示的图像
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_ShowImage(int16_t X, int16_t Y, uint8_t Width, uint8_t Height, const uint8_t *Image)
{
	uint8_t i = 0, j = 0;
	int16_t Page, Shift;
	
	/*将图像所在区域清空*/
	OLED_ClearArea(X, Y, Width, Height);
	
	/*遍历指定图像涉及的相关页*/
	/*(Height - 1) / 8 + 1的目的是Height / 8并向上取整*/
	for (j = 0; j < (Height - 1) / 8 + 1; j ++)
	{
		/*遍历指定图像涉及的相关列*/
		for (i = 0; i < Width; i ++)
		{
			if (X + i >= 0 && X + i <= 127)		//超出屏幕的内容不显示
			{
				/*负数坐标在计算页地址和移位时需要加一个偏移*/
				Page = Y / 8;
				Shift = Y % 8;
				if (Y < 0)
				{
					Page -= 1;
					Shift += 8;
				}
				
				if (Page + j >= 0 && Page + j <= 7)		//超出屏幕的内容不显示
				{
					/*显示图像在当前页的内容*/
					OLED_DisplayBuf[Page + j][X + i] |= Image[j * Width + i] << (Shift);
				}
				
				if (Page + j + 1 >= 0 && Page + j + 1 <= 7)		//超出屏幕的内容不显示
				{					
					/*显示图像在下一页的内容*/
					OLED_DisplayBuf[Page + j + 1][X + i] |= Image[j * Width + i] >> (8 - Shift);
				}
			}
		}
	}
}

/**
  * 函    数:OLED使用printf函数打印格式化字符串(支持ASCII码和中文混合写入)
  * 参    数:X 指定格式化字符串左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定格式化字符串左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:FontSize 指定字体大小
  *           范围:OLED_8X16		宽8像素,高16像素
  *                 OLED_6X8		宽6像素,高8像素
  * 参    数:format 指定要显示的格式化字符串,范围:ASCII码可见字符或中文字符组成的字符串
  * 参    数:... 格式化字符串参数列表
  * 返 回 值:无
  * 说    明:显示的中文字符需要在OLED_Data.c里的OLED_CF16x16数组定义
  *           未找到指定中文字符时,会显示默认图形(一个方框,内部一个问号)
  *           当字体大小为OLED_8X16时,中文字符以16*16点阵正常显示
  *           当字体大小为OLED_6X8时,中文字符以6*8点阵显示'?'
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_Printf(int16_t X, int16_t Y, uint8_t FontSize, char *format, ...)
{
	char String[256];						//定义字符数组
	va_list arg;							//定义可变参数列表数据类型的变量arg
	va_start(arg, format);					//从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);			//使用vsprintf打印格式化字符串和参数列表到字符数组中
	va_end(arg);							//结束变量arg
	OLED_ShowString(X, Y, String, FontSize);//OLED显示字符数组(字符串)
}

/**
  * 函    数:OLED在指定位置画一个点
  * 参    数:X 指定点的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定点的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_DrawPoint(int16_t X, int16_t Y)
{
	if (X >= 0 && X <= 127 && Y >=0 && Y <= 63)		//超出屏幕的内容不显示
	{
		/*将显存数组指定位置的一个Bit数据置1*/
		OLED_DisplayBuf[Y / 8][X] |= 0x01 << (Y % 8);
	}
}

/**
  * 函    数:OLED获取指定位置点的值
  * 参    数:X 指定点的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定点的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 返 回 值:指定位置点是否处于点亮状态,1:点亮,0:熄灭
  */
uint8_t OLED_GetPoint(int16_t X, int16_t Y)
{
	if (X >= 0 && X <= 127 && Y >=0 && Y <= 63)		//超出屏幕的内容不读取
	{
		/*判断指定位置的数据*/
		if (OLED_DisplayBuf[Y / 8][X] & 0x01 << (Y % 8))
		{
			return 1;	//为1,返回1
		}
	}
	
	return 0;		//否则,返回0
}

/**
  * 函    数:OLED画线
  * 参    数:X0 指定一个端点的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y0 指定一个端点的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:X1 指定另一个端点的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y1 指定另一个端点的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_DrawLine(int16_t X0, int16_t Y0, int16_t X1, int16_t Y1)
{
	int16_t x, y, dx, dy, d, incrE, incrNE, temp;
	int16_t x0 = X0, y0 = Y0, x1 = X1, y1 = Y1;
	uint8_t yflag = 0, xyflag = 0;
	
	if (y0 == y1)		//横线单独处理
	{
		/*0号点X坐标大于1号点X坐标,则交换两点X坐标*/
		if (x0 > x1) {temp = x0; x0 = x1; x1 = temp;}
		
		/*遍历X坐标*/
		for (x = x0; x <= x1; x ++)
		{
			OLED_DrawPoint(x, y0);	//依次画点
		}
	}
	else if (x0 == x1)	//竖线单独处理
	{
		/*0号点Y坐标大于1号点Y坐标,则交换两点Y坐标*/
		if (y0 > y1) {temp = y0; y0 = y1; y1 = temp;}
		
		/*遍历Y坐标*/
		for (y = y0; y <= y1; y ++)
		{
			OLED_DrawPoint(x0, y);	//依次画点
		}
	}
	else				//斜线
	{
		/*使用Bresenham算法画直线,可以避免耗时的浮点运算,效率更高*/
		/*参考文档:https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*/
		/*参考教程:https://www.bilibili.com/video/BV1364y1d7Lo*/
		
		if (x0 > x1)	//0号点X坐标大于1号点X坐标
		{
			/*交换两点坐标*/
			/*交换后不影响画线,但是画线方向由第一、二、三、四象限变为第一、四象限*/
			temp = x0; x0 = x1; x1 = temp;
			temp = y0; y0 = y1; y1 = temp;
		}
		
		if (y0 > y1)	//0号点Y坐标大于1号点Y坐标
		{
			/*将Y坐标取负*/
			/*取负后影响画线,但是画线方向由第一、四象限变为第一象限*/
			y0 = -y0;
			y1 = -y1;
			
			/*置标志位yflag,记住当前变换,在后续实际画线时,再将坐标换回来*/
			yflag = 1;
		}
		
		if (y1 - y0 > x1 - x0)	//画线斜率大于1
		{
			/*将X坐标与Y坐标互换*/
			/*互换后影响画线,但是画线方向由第一象限0~90度范围变为第一象限0~45度范围*/
			temp = x0; x0 = y0; y0 = temp;
			temp = x1; x1 = y1; y1 = temp;
			
			/*置标志位xyflag,记住当前变换,在后续实际画线时,再将坐标换回来*/
			xyflag = 1;
		}
		
		/*以下为Bresenham算法画直线*/
		/*算法要求,画线方向必须为第一象限0~45度范围*/
		dx = x1 - x0;
		dy = y1 - y0;
		incrE = 2 * dy;
		incrNE = 2 * (dy - dx);
		d = 2 * dy - dx;
		x = x0;
		y = y0;
		
		/*画起始点,同时判断标志位,将坐标换回来*/
		if (yflag && xyflag){OLED_DrawPoint(y, -x);}
		else if (yflag)		{OLED_DrawPoint(x, -y);}
		else if (xyflag)	{OLED_DrawPoint(y, x);}
		else				{OLED_DrawPoint(x, y);}
		
		while (x < x1)		//遍历X轴的每个点
		{
			x ++;
			if (d < 0)		//下一个点在当前点东方
			{
				d += incrE;
			}
			else			//下一个点在当前点东北方
			{
				y ++;
				d += incrNE;
			}
			
			/*画每一个点,同时判断标志位,将坐标换回来*/
			if (yflag && xyflag){OLED_DrawPoint(y, -x);}
			else if (yflag)		{OLED_DrawPoint(x, -y);}
			else if (xyflag)	{OLED_DrawPoint(y, x);}
			else				{OLED_DrawPoint(x, y);}
		}	
	}
}

/**
  * 函    数:OLED矩形
  * 参    数:X 指定矩形左上角的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定矩形左上角的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Width 指定矩形的宽度,范围:0~128
  * 参    数:Height 指定矩形的高度,范围:0~64
  * 参    数:IsFilled 指定矩形是否填充
  *           范围:OLED_UNFILLED		不填充
  *                 OLED_FILLED			填充
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_DrawRectangle(int16_t X, int16_t Y, uint8_t Width, uint8_t Height, uint8_t IsFilled)
{
	int16_t i, j;
	if (!IsFilled)		//指定矩形不填充
	{
		/*遍历上下X坐标,画矩形上下两条线*/
		for (i = X; i < X + Width; i ++)
		{
			OLED_DrawPoint(i, Y);
			OLED_DrawPoint(i, Y + Height - 1);
		}
		/*遍历左右Y坐标,画矩形左右两条线*/
		for (i = Y; i < Y + Height; i ++)
		{
			OLED_DrawPoint(X, i);
			OLED_DrawPoint(X + Width - 1, i);
		}
	}
	else				//指定矩形填充
	{
		/*遍历X坐标*/
		for (i = X; i < X + Width; i ++)
		{
			/*遍历Y坐标*/
			for (j = Y; j < Y + Height; j ++)
			{
				/*在指定区域画点,填充满矩形*/
				OLED_DrawPoint(i, j);
			}
		}
	}
}

/**
  * 函    数:OLED三角形
  * 参    数:X0 指定第一个端点的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y0 指定第一个端点的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:X1 指定第二个端点的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y1 指定第二个端点的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:X2 指定第三个端点的横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y2 指定第三个端点的纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:IsFilled 指定三角形是否填充
  *           范围:OLED_UNFILLED		不填充
  *                 OLED_FILLED			填充
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_DrawTriangle(int16_t X0, int16_t Y0, int16_t X1, int16_t Y1, int16_t X2, int16_t Y2, uint8_t IsFilled)
{
	int16_t minx = X0, miny = Y0, maxx = X0, maxy = Y0;
	int16_t i, j;
	int16_t vx[] = {X0, X1, X2};
	int16_t vy[] = {Y0, Y1, Y2};
	
	if (!IsFilled)			//指定三角形不填充
	{
		/*调用画线函数,将三个点用直线连接*/
		OLED_DrawLine(X0, Y0, X1, Y1);
		OLED_DrawLine(X0, Y0, X2, Y2);
		OLED_DrawLine(X1, Y1, X2, Y2);
	}
	else					//指定三角形填充
	{
		/*找到三个点最小的X、Y坐标*/
		if (X1 < minx) {minx = X1;}
		if (X2 < minx) {minx = X2;}
		if (Y1 < miny) {miny = Y1;}
		if (Y2 < miny) {miny = Y2;}
		
		/*找到三个点最大的X、Y坐标*/
		if (X1 > maxx) {maxx = X1;}
		if (X2 > maxx) {maxx = X2;}
		if (Y1 > maxy) {maxy = Y1;}
		if (Y2 > maxy) {maxy = Y2;}
		
		/*最小最大坐标之间的矩形为可能需要填充的区域*/
		/*遍历此区域中所有的点*/
		/*遍历X坐标*/		
		for (i = minx; i <= maxx; i ++)
		{
			/*遍历Y坐标*/	
			for (j = miny; j <= maxy; j ++)
			{
				/*调用OLED_pnpoly,判断指定点是否在指定三角形之中*/
				/*如果在,则画点,如果不在,则不做处理*/
				if (OLED_pnpoly(3, vx, vy, i, j)) {OLED_DrawPoint(i, j);}
			}
		}
	}
}

/**
  * 函    数:OLED画圆
  * 参    数:X 指定圆的圆心横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定圆的圆心纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Radius 指定圆的半径,范围:0~255
  * 参    数:IsFilled 指定圆是否填充
  *           范围:OLED_UNFILLED		不填充
  *                 OLED_FILLED			填充
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_DrawCircle(int16_t X, int16_t Y, uint8_t Radius, uint8_t IsFilled)
{
	int16_t x, y, d, j;
	
	/*使用Bresenham算法画圆,可以避免耗时的浮点运算,效率更高*/
	/*参考文档:https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*/
	/*参考教程:https://www.bilibili.com/video/BV1VM4y1u7wJ*/
	
	d = 1 - Radius;
	x = 0;
	y = Radius;
	
	/*画每个八分之一圆弧的起始点*/
	OLED_DrawPoint(X + x, Y + y);
	OLED_DrawPoint(X - x, Y - y);
	OLED_DrawPoint(X + y, Y + x);
	OLED_DrawPoint(X - y, Y - x);
	
	if (IsFilled)		//指定圆填充
	{
		/*遍历起始点Y坐标*/
		for (j = -y; j < y; j ++)
		{
			/*在指定区域画点,填充部分圆*/
			OLED_DrawPoint(X, Y + j);
		}
	}
	
	while (x < y)		//遍历X轴的每个点
	{
		x ++;
		if (d < 0)		//下一个点在当前点东方
		{
			d += 2 * x + 1;
		}
		else			//下一个点在当前点东南方
		{
			y --;
			d += 2 * (x - y) + 1;
		}
		
		/*画每个八分之一圆弧的点*/
		OLED_DrawPoint(X + x, Y + y);
		OLED_DrawPoint(X + y, Y + x);
		OLED_DrawPoint(X - x, Y - y);
		OLED_DrawPoint(X - y, Y - x);
		OLED_DrawPoint(X + x, Y - y);
		OLED_DrawPoint(X + y, Y - x);
		OLED_DrawPoint(X - x, Y + y);
		OLED_DrawPoint(X - y, Y + x);
		
		if (IsFilled)	//指定圆填充
		{
			/*遍历中间部分*/
			for (j = -y; j < y; j ++)
			{
				/*在指定区域画点,填充部分圆*/
				OLED_DrawPoint(X + x, Y + j);
				OLED_DrawPoint(X - x, Y + j);
			}
			
			/*遍历两侧部分*/
			for (j = -x; j < x; j ++)
			{
				/*在指定区域画点,填充部分圆*/
				OLED_DrawPoint(X - y, Y + j);
				OLED_DrawPoint(X + y, Y + j);
			}
		}
	}
}

/**
  * 函    数:OLED画椭圆
  * 参    数:X 指定椭圆的圆心横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定椭圆的圆心纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:A 指定椭圆的横向半轴长度,范围:0~255
  * 参    数:B 指定椭圆的纵向半轴长度,范围:0~255
  * 参    数:IsFilled 指定椭圆是否填充
  *           范围:OLED_UNFILLED		不填充
  *                 OLED_FILLED			填充
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_DrawEllipse(int16_t X, int16_t Y, uint8_t A, uint8_t B, uint8_t IsFilled)
{
	int16_t x, y, j;
	int16_t a = A, b = B;
	float d1, d2;
	
	/*使用Bresenham算法画椭圆,可以避免部分耗时的浮点运算,效率更高*/
	/*参考链接:https://blog.csdn.net/myf_666/article/details/128167392*/
	
	x = 0;
	y = b;
	d1 = b * b + a * a * (-b + 0.5);
	
	if (IsFilled)	//指定椭圆填充
	{
		/*遍历起始点Y坐标*/
		for (j = -y; j < y; j ++)
		{
			/*在指定区域画点,填充部分椭圆*/
			OLED_DrawPoint(X, Y + j);
			OLED_DrawPoint(X, Y + j);
		}
	}
	
	/*画椭圆弧的起始点*/
	OLED_DrawPoint(X + x, Y + y);
	OLED_DrawPoint(X - x, Y - y);
	OLED_DrawPoint(X - x, Y + y);
	OLED_DrawPoint(X + x, Y - y);
	
	/*画椭圆中间部分*/
	while (b * b * (x + 1) < a * a * (y - 0.5))
	{
		if (d1 <= 0)		//下一个点在当前点东方
		{
			d1 += b * b * (2 * x + 3);
		}
		else				//下一个点在当前点东南方
		{
			d1 += b * b * (2 * x + 3) + a * a * (-2 * y + 2);
			y --;
		}
		x ++;
		
		if (IsFilled)	//指定椭圆填充
		{
			/*遍历中间部分*/
			for (j = -y; j < y; j ++)
			{
				/*在指定区域画点,填充部分椭圆*/
				OLED_DrawPoint(X + x, Y + j);
				OLED_DrawPoint(X - x, Y + j);
			}
		}
		
		/*画椭圆中间部分圆弧*/
		OLED_DrawPoint(X + x, Y + y);
		OLED_DrawPoint(X - x, Y - y);
		OLED_DrawPoint(X - x, Y + y);
		OLED_DrawPoint(X + x, Y - y);
	}
	
	/*画椭圆两侧部分*/
	d2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b;
	
	while (y > 0)
	{
		if (d2 <= 0)		//下一个点在当前点东方
		{
			d2 += b * b * (2 * x + 2) + a * a * (-2 * y + 3);
			x ++;
			
		}
		else				//下一个点在当前点东南方
		{
			d2 += a * a * (-2 * y + 3);
		}
		y --;
		
		if (IsFilled)	//指定椭圆填充
		{
			/*遍历两侧部分*/
			for (j = -y; j < y; j ++)
			{
				/*在指定区域画点,填充部分椭圆*/
				OLED_DrawPoint(X + x, Y + j);
				OLED_DrawPoint(X - x, Y + j);
			}
		}
		
		/*画椭圆两侧部分圆弧*/
		OLED_DrawPoint(X + x, Y + y);
		OLED_DrawPoint(X - x, Y - y);
		OLED_DrawPoint(X - x, Y + y);
		OLED_DrawPoint(X + x, Y - y);
	}
}

/**
  * 函    数:OLED画圆弧
  * 参    数:X 指定圆弧的圆心横坐标,范围:-32768~32767,屏幕区域:0~127
  * 参    数:Y 指定圆弧的圆心纵坐标,范围:-32768~32767,屏幕区域:0~63
  * 参    数:Radius 指定圆弧的半径,范围:0~255
  * 参    数:StartAngle 指定圆弧的起始角度,范围:-180~180
  *           水平向右为0度,水平向左为180度或-180度,下方为正数,上方为负数,顺时针旋转
  * 参    数:EndAngle 指定圆弧的终止角度,范围:-180~180
  *           水平向右为0度,水平向左为180度或-180度,下方为正数,上方为负数,顺时针旋转
  * 参    数:IsFilled 指定圆弧是否填充,填充后为扇形
  *           范围:OLED_UNFILLED		不填充
  *                 OLED_FILLED			填充
  * 返 回 值:无
  * 说    明:调用此函数后,要想真正地呈现在屏幕上,还需调用更新函数
  */
void OLED_DrawArc(int16_t X, int16_t Y, uint8_t Radius, int16_t StartAngle, int16_t EndAngle, uint8_t IsFilled)
{
	int16_t x, y, d, j;
	
	/*此函数借用Bresenham算法画圆的方法*/
	
	d = 1 - Radius;
	x = 0;
	y = Radius;
	
	/*在画圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/
	if (OLED_IsInAngle(x, y, StartAngle, EndAngle))	{OLED_DrawPoint(X + x, Y + y);}
	if (OLED_IsInAngle(-x, -y, StartAngle, EndAngle)) {OLED_DrawPoint(X - x, Y - y);}
	if (OLED_IsInAngle(y, x, StartAngle, EndAngle)) {OLED_DrawPoint(X + y, Y + x);}
	if (OLED_IsInAngle(-y, -x, StartAngle, EndAngle)) {OLED_DrawPoint(X - y, Y - x);}
	
	if (IsFilled)	//指定圆弧填充
	{
		/*遍历起始点Y坐标*/
		for (j = -y; j < y; j ++)
		{
			/*在填充圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/
			if (OLED_IsInAngle(0, j, StartAngle, EndAngle)) {OLED_DrawPoint(X, Y + j);}
		}
	}
	
	while (x < y)		//遍历X轴的每个点
	{
		x ++;
		if (d < 0)		//下一个点在当前点东方
		{
			d += 2 * x + 1;
		}
		else			//下一个点在当前点东南方
		{
			y --;
			d += 2 * (x - y) + 1;
		}
		
		/*在画圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/
		if (OLED_IsInAngle(x, y, StartAngle, EndAngle)) {OLED_DrawPoint(X + x, Y + y);}
		if (OLED_IsInAngle(y, x, StartAngle, EndAngle)) {OLED_DrawPoint(X + y, Y + x);}
		if (OLED_IsInAngle(-x, -y, StartAngle, EndAngle)) {OLED_DrawPoint(X - x, Y - y);}
		if (OLED_IsInAngle(-y, -x, StartAngle, EndAngle)) {OLED_DrawPoint(X - y, Y - x);}
		if (OLED_IsInAngle(x, -y, StartAngle, EndAngle)) {OLED_DrawPoint(X + x, Y - y);}
		if (OLED_IsInAngle(y, -x, StartAngle, EndAngle)) {OLED_DrawPoint(X + y, Y - x);}
		if (OLED_IsInAngle(-x, y, StartAngle, EndAngle)) {OLED_DrawPoint(X - x, Y + y);}
		if (OLED_IsInAngle(-y, x, StartAngle, EndAngle)) {OLED_DrawPoint(X - y, Y + x);}
		
		if (IsFilled)	//指定圆弧填充
		{
			/*遍历中间部分*/
			for (j = -y; j < y; j ++)
			{
				/*在填充圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/
				if (OLED_IsInAngle(x, j, StartAngle, EndAngle)) {OLED_DrawPoint(X + x, Y + j);}
				if (OLED_IsInAngle(-x, j, StartAngle, EndAngle)) {OLED_DrawPoint(X - x, Y + j);}
			}
			
			/*遍历两侧部分*/
			for (j = -x; j < x; j ++)
			{
				/*在填充圆的每个点时,判断指定点是否在指定角度内,在,则画点,不在,则不做处理*/
				if (OLED_IsInAngle(-y, j, StartAngle, EndAngle)) {OLED_DrawPoint(X - y, Y + j);}
				if (OLED_IsInAngle(y, j, StartAngle, EndAngle)) {OLED_DrawPoint(X + y, Y + j);}
			}
		}
	}
}

/*********************功能函数*/


/*****************江协科技|版权所有****************/
/*****************jiangxiekeji.com*****************/

OLED.h函数

复制代码
#ifndef __OLED_H
#define __OLED_H

#include <stdint.h>
#include "OLED_Data.h"
#include "main.h"  

/* 参数宏定义 */
#define OLED_8X16              8
#define OLED_6X8               6
#define OLED_12X24             12
#define OLED_UNFILLED          0
#define OLED_FILLED            1

/* I2C句柄声明 */
extern I2C_HandleTypeDef hi2c1;  // 根据实际使用的I2C修改

/* 函数声明 */
void OLED_Init(void);
void OLED_Update(void);
void OLED_UpdateArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height);
void OLED_Clear(void);
void OLED_ClearArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height);
void OLED_Reverse(void);
void OLED_ReverseArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height);
void OLED_ShowChar(int16_t X, int16_t Y, char Char, uint8_t FontSize);
void OLED_ShowString(int16_t X, int16_t Y, char *String, uint8_t FontSize);
void OLED_ShowNum(int16_t X, int16_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize);
void OLED_ShowSignedNum(int16_t X, int16_t Y, int32_t Number, uint8_t Length, uint8_t FontSize);
void OLED_ShowHexNum(int16_t X, int16_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize);
void OLED_ShowBinNum(int16_t X, int16_t Y, uint32_t Number, uint8_t Length, uint8_t FontSize);
void OLED_ShowFloatNum(int16_t X, int16_t Y, double Number, uint8_t IntLength, uint8_t FraLength, uint8_t FontSize);
void OLED_ShowImage(int16_t X, int16_t Y, uint8_t Width, uint8_t Height, const uint8_t *Image);
void OLED_Printf(int16_t X, int16_t Y, uint8_t FontSize, char *format, ...);
void OLED_DrawPoint(int16_t X, int16_t Y);
uint8_t OLED_GetPoint(int16_t X, int16_t Y);
void OLED_DrawLine(int16_t X0, int16_t Y0, int16_t X1, int16_t Y1);
void OLED_DrawRectangle(int16_t X, int16_t Y, uint8_t Width, uint8_t Height, uint8_t IsFilled);
void OLED_DrawTriangle(int16_t X0, int16_t Y0, int16_t X1, int16_t Y1, int16_t X2, int16_t Y2, uint8_t IsFilled);
void OLED_DrawCircle(int16_t X, int16_t Y, uint8_t Radius, uint8_t IsFilled);
void OLED_DrawEllipse(int16_t X, int16_t Y, uint8_t A, uint8_t B, uint8_t IsFilled);
void OLED_DrawArc(int16_t X, int16_t Y, uint8_t Radius, int16_t StartAngle, int16_t EndAngle, uint8_t IsFilled);

#endif

OLED_Data.c

复制代码
#include "OLED_Data.h"
#include "stm32f1xx_hal.h"
/**
  * 数据存储格式:
  * 纵向8点,高位在下,先从左到右,再从上到下
  * 每一个Bit对应一个像素点
  * 
  *      B0 B0                  B0 B0
  *      B1 B1                  B1 B1
  *      B2 B2                  B2 B2
  *      B3 B3  ------------->  B3 B3 --
  *      B4 B4                  B4 B4  |
  *      B5 B5                  B5 B5  |
  *      B6 B6                  B6 B6  |
  *      B7 B7                  B7 B7  |
  *                                    |
  *  -----------------------------------
  *  |   
  *  |   B0 B0                  B0 B0
  *  |   B1 B1                  B1 B1
  *  |   B2 B2                  B2 B2
  *  --> B3 B3  ------------->  B3 B3
  *      B4 B4                  B4 B4
  *      B5 B5                  B5 B5
  *      B6 B6                  B6 B6
  *      B7 B7                  B7 B7
  * 
  */

/*ASCII字模数据*********************/

/*宽8像素,高16像素*/
const uint8_t OLED_F8x16[][16] =
{
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//   0
	0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,// ! 1
	0x00,0x16,0x0E,0x00,0x16,0x0E,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// " 2
	0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
	0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,// # 3
	0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
	0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,// $ 4
	0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
	0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,// % 5
	0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
	0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,// & 6
	0x00,0x00,0x00,0x16,0x0E,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ' 7
	0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
	0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,// ( 8
	0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
	0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,// ) 9
	0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
	0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,// * 10
	0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
	0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,// + 11
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,// , 12
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,// - 13
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,// . 14
	0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
	0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,// / 15
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,// 0 16
	0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// 1 17
	0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
	0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,// 2 18
	0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
	0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,// 3 19
	0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
	0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,// 4 20
	0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
	0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,// 5 21
	0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
	0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,// 6 22
	0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,// 7 23
	0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
	0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,// 8 24
	0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
	0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,// 9 25
	0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
	0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,// : 26
	0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
	0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,// ; 27
	0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
	0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,// < 28
	0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
	0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,// = 29
	0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
	0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,// > 30
	0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
	0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,// ? 31
	0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
	0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,// @ 32
	0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
	0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,// A 33
	0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,// B 34
	0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
	0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,// C 35
	0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,// D 36
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,// E 37
	0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,// F 38
	0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
	0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,// G 39
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,// H 40
	0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// I 41
	0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
	0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,// J 42
	0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
	0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,// K 43
	0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
	0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,// L 44
	0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
	0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,// M 45
	0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
	0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,// N 46
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,// O 47
	0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
	0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,// P 48
	0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
	0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,// Q 49
	0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
	0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,// R 50
	0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
	0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,// S 51
	0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,// T 52
	0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,// U 53
	0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
	0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,// V 54
	0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
	0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,// W 55
	0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
	0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,// X 56
	0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
	0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,// Y 57
	0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
	0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,// Z 58
	0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
	0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,// [ 59
	0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,// \ 60
	0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
	0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,// ] 61
	0x00,0x20,0x10,0x08,0x04,0x08,0x10,0x20,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ^ 62
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,// _ 63
	0x00,0x02,0x04,0x08,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// ` 64
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,// a 65
	0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
	0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,// b 66
	0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,// c 67
	0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
	0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,// d 68
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,// e 69
	0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// f 70
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,// g 71
	0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
	0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,// h 72
	0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// i 73
	0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
	0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,// j 74
	0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,// k 75
	0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
	0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,// l 76
	0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,// m 77
	0x00,0x80,0x80,0x00,0x80,0x80,0x00,0x00,
	0x00,0x20,0x3F,0x21,0x00,0x20,0x3F,0x20,// n 78
	0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,// o 79
	0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
	0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,// p 80
	0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
	0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,// q 81
	0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,// r 82
	0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,// s 83
	0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
	0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,// t 84
	0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
	0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,// u 85
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,// v 86
	0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
	0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,// w 87
	0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
	0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,// x 88
	0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
	0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,// y 89
	0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
	0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,// z 90
	0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
	0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,// { 91
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,// | 92
	0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
	0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,// } 93
	0x00,0x80,0x40,0x40,0x80,0x00,0x00,0x80,
	0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00,// ~ 94
};

/*宽6像素,高8像素*/
const uint8_t OLED_F6x8[][6] = 
{
	0x00,0x00,0x00,0x00,0x00,0x00,//   0
	0x00,0x00,0x00,0x2F,0x00,0x00,// ! 1
	0x00,0x00,0x07,0x00,0x07,0x00,// " 2
	0x00,0x14,0x7F,0x14,0x7F,0x14,// # 3
	0x00,0x24,0x2A,0x7F,0x2A,0x12,// $ 4
	0x00,0x23,0x13,0x08,0x64,0x62,// % 5
	0x00,0x36,0x49,0x55,0x22,0x50,// & 6
	0x00,0x00,0x00,0x07,0x00,0x00,// ' 7
	0x00,0x00,0x1C,0x22,0x41,0x00,// ( 8
	0x00,0x00,0x41,0x22,0x1C,0x00,// ) 9
	0x00,0x14,0x08,0x3E,0x08,0x14,// * 10
	0x00,0x08,0x08,0x3E,0x08,0x08,// + 11
	0x00,0x00,0x00,0xA0,0x60,0x00,// , 12
	0x00,0x08,0x08,0x08,0x08,0x08,// - 13
	0x00,0x00,0x60,0x60,0x00,0x00,// . 14
	0x00,0x20,0x10,0x08,0x04,0x02,// / 15
	0x00,0x3E,0x51,0x49,0x45,0x3E,// 0 16
	0x00,0x00,0x42,0x7F,0x40,0x00,// 1 17
	0x00,0x42,0x61,0x51,0x49,0x46,// 2 18
	0x00,0x21,0x41,0x45,0x4B,0x31,// 3 19
	0x00,0x18,0x14,0x12,0x7F,0x10,// 4 20
	0x00,0x27,0x45,0x45,0x45,0x39,// 5 21
	0x00,0x3C,0x4A,0x49,0x49,0x30,// 6 22
	0x00,0x01,0x71,0x09,0x05,0x03,// 7 23
	0x00,0x36,0x49,0x49,0x49,0x36,// 8 24
	0x00,0x06,0x49,0x49,0x29,0x1E,// 9 25
	0x00,0x00,0x36,0x36,0x00,0x00,// : 26
	0x00,0x00,0x56,0x36,0x00,0x00,// ; 27
	0x00,0x08,0x14,0x22,0x41,0x00,// < 28
	0x00,0x14,0x14,0x14,0x14,0x14,// = 29
	0x00,0x00,0x41,0x22,0x14,0x08,// > 30
	0x00,0x02,0x01,0x51,0x09,0x06,// ? 31
	0x00,0x3E,0x49,0x55,0x59,0x2E,// @ 32
	0x00,0x7C,0x12,0x11,0x12,0x7C,// A 33
	0x00,0x7F,0x49,0x49,0x49,0x36,// B 34
	0x00,0x3E,0x41,0x41,0x41,0x22,// C 35
	0x00,0x7F,0x41,0x41,0x22,0x1C,// D 36
	0x00,0x7F,0x49,0x49,0x49,0x41,// E 37
	0x00,0x7F,0x09,0x09,0x09,0x01,// F 38
	0x00,0x3E,0x41,0x49,0x49,0x7A,// G 39
	0x00,0x7F,0x08,0x08,0x08,0x7F,// H 40
	0x00,0x00,0x41,0x7F,0x41,0x00,// I 41
	0x00,0x20,0x40,0x41,0x3F,0x01,// J 42
	0x00,0x7F,0x08,0x14,0x22,0x41,// K 43
	0x00,0x7F,0x40,0x40,0x40,0x40,// L 44
	0x00,0x7F,0x02,0x0C,0x02,0x7F,// M 45
	0x00,0x7F,0x04,0x08,0x10,0x7F,// N 46
	0x00,0x3E,0x41,0x41,0x41,0x3E,// O 47
	0x00,0x7F,0x09,0x09,0x09,0x06,// P 48
	0x00,0x3E,0x41,0x51,0x21,0x5E,// Q 49
	0x00,0x7F,0x09,0x19,0x29,0x46,// R 50
	0x00,0x46,0x49,0x49,0x49,0x31,// S 51
	0x00,0x01,0x01,0x7F,0x01,0x01,// T 52
	0x00,0x3F,0x40,0x40,0x40,0x3F,// U 53
	0x00,0x1F,0x20,0x40,0x20,0x1F,// V 54
	0x00,0x3F,0x40,0x38,0x40,0x3F,// W 55
	0x00,0x63,0x14,0x08,0x14,0x63,// X 56
	0x00,0x07,0x08,0x70,0x08,0x07,// Y 57
	0x00,0x61,0x51,0x49,0x45,0x43,// Z 58
	0x00,0x00,0x7F,0x41,0x41,0x00,// [ 59
	0x00,0x02,0x04,0x08,0x10,0x20,// \ 60
	0x00,0x00,0x41,0x41,0x7F,0x00,// ] 61
	0x00,0x04,0x02,0x01,0x02,0x04,// ^ 62
	0x00,0x40,0x40,0x40,0x40,0x40,// _ 63
	0x00,0x00,0x01,0x02,0x04,0x00,// ` 64
	0x00,0x20,0x54,0x54,0x54,0x78,// a 65
	0x00,0x7F,0x48,0x44,0x44,0x38,// b 66
	0x00,0x38,0x44,0x44,0x44,0x20,// c 67
	0x00,0x38,0x44,0x44,0x48,0x7F,// d 68
	0x00,0x38,0x54,0x54,0x54,0x18,// e 69
	0x00,0x08,0x7E,0x09,0x01,0x02,// f 70
	0x00,0x18,0xA4,0xA4,0xA4,0x7C,// g 71
	0x00,0x7F,0x08,0x04,0x04,0x78,// h 72
	0x00,0x00,0x44,0x7D,0x40,0x00,// i 73
	0x00,0x40,0x80,0x84,0x7D,0x00,// j 74
	0x00,0x7F,0x10,0x28,0x44,0x00,// k 75
	0x00,0x00,0x41,0x7F,0x40,0x00,// l 76
	0x00,0x7C,0x04,0x18,0x04,0x78,// m 77
	0x00,0x7C,0x08,0x04,0x04,0x78,// n 78
	0x00,0x38,0x44,0x44,0x44,0x38,// o 79
	0x00,0xFC,0x24,0x24,0x24,0x18,// p 80
	0x00,0x18,0x24,0x24,0x18,0xFC,// q 81
	0x00,0x7C,0x08,0x04,0x04,0x08,// r 82
	0x00,0x48,0x54,0x54,0x54,0x20,// s 83
	0x00,0x04,0x3F,0x44,0x40,0x20,// t 84
	0x00,0x3C,0x40,0x40,0x20,0x7C,// u 85
	0x00,0x1C,0x20,0x40,0x20,0x1C,// v 86
	0x00,0x3C,0x40,0x30,0x40,0x3C,// w 87
	0x00,0x44,0x28,0x10,0x28,0x44,// x 88
	0x00,0x1C,0xA0,0xA0,0xA0,0x7C,// y 89
	0x00,0x44,0x64,0x54,0x4C,0x44,// z 90
	0x00,0x00,0x08,0x7F,0x41,0x00,// { 91
	0x00,0x00,0x00,0x7F,0x00,0x00,// | 92
	0x00,0x00,0x41,0x7F,0x08,0x00,// } 93
	0x00,0x08,0x04,0x08,0x10,0x08,// ~ 94
};
/*********************ASCII字模数据*/


/*汉字字模数据*********************/

/*相同的汉字只需要定义一次,汉字不分先后顺序*/
/*必须全部为汉字或者全角字符,不要加入任何半角字符*/

/*宽16像素,高16像素*/
const ChineseCell_t OLED_CF16x16[] = {
	
	",",
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x58,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	
	"。",
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x18,0x24,0x24,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	
	"你",
	0x00,0x80,0x60,0xF8,0x07,0x40,0x20,0x18,0x0F,0x08,0xC8,0x08,0x08,0x28,0x18,0x00,
	0x01,0x00,0x00,0xFF,0x00,0x10,0x0C,0x03,0x40,0x80,0x7F,0x00,0x01,0x06,0x18,0x00,
	
	"好",
	0x10,0x10,0xF0,0x1F,0x10,0xF0,0x00,0x80,0x82,0x82,0xE2,0x92,0x8A,0x86,0x80,0x00,
	0x40,0x22,0x15,0x08,0x16,0x61,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00,
	
	"世",
	0x20,0x20,0x20,0xFE,0x20,0x20,0xFF,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x00,
	0x00,0x00,0x00,0x7F,0x40,0x40,0x47,0x44,0x44,0x44,0x47,0x40,0x40,0x40,0x00,0x00,
	
	"界",
	0x00,0x00,0x00,0xFE,0x92,0x92,0x92,0xFE,0x92,0x92,0x92,0xFE,0x00,0x00,0x00,0x00,
	0x08,0x08,0x04,0x84,0x62,0x1E,0x01,0x00,0x01,0xFE,0x02,0x04,0x04,0x08,0x08,0x00,
	
	/*按照上面的格式,在这个位置加入新的汉字数据*/
	//...
	
	
	/*未找到指定汉字时显示的默认图形(一个方框,内部一个问号),请确保其位于数组最末尾*/
	"",		
	0xFF,0x01,0x01,0x01,0x31,0x09,0x09,0x09,0x09,0x89,0x71,0x01,0x01,0x01,0x01,0xFF,
	0xFF,0x80,0x80,0x80,0x80,0x80,0x80,0x96,0x81,0x80,0x80,0x80,0x80,0x80,0x80,0xFF,

};

/*********************汉字字模数据*/


/*图像数据*********************/

/*测试图像(一个方框,内部一个二极管符号),宽16像素,高16像素*/
const uint8_t Diode[] = {
	0xFF,0x01,0x81,0x81,0x81,0xFD,0x89,0x91,0xA1,0xC1,0xFD,0x81,0x81,0x81,0x01,0xFF,
	0xFF,0x80,0x80,0x80,0x80,0x9F,0x88,0x84,0x82,0x81,0x9F,0x80,0x80,0x80,0x80,0xFF,
};

/*按照上面的格式,在这个位置加入新的图像数据*/
//...

/*********************图像数据*/


/*****************江协科技|版权所有****************/
/*****************jiangxiekeji.com*****************/

OLED_Ddta.h

复制代码
#ifndef __OLED_DATA_H
#define __OLED_DATA_H

#include <stdint.h>

/*字符集定义*/
/*以下两个宏定义只可解除其中一个的注释*/
#define OLED_CHARSET_UTF8			//定义字符集为UTF8
//#define OLED_CHARSET_GB2312		//定义字符集为GB2312

/*字模基本单元*/
typedef struct 
{
	
#ifdef OLED_CHARSET_UTF8			//定义字符集为UTF8
	char Index[5];					//汉字索引,空间为5字节
#endif
	
#ifdef OLED_CHARSET_GB2312			//定义字符集为GB2312
	char Index[3];					//汉字索引,空间为3字节
#endif
	
	uint8_t Data[32];				//字模数据
} ChineseCell_t;

/*ASCII字模数据声明*/
extern const uint8_t OLED_F8x16[][16];
extern const uint8_t OLED_F6x8[][6];

/*汉字字模数据声明*/
extern const ChineseCell_t OLED_CF16x16[];

/*图像数据声明*/
extern const uint8_t Diode[];
/*按照上面的格式,在这个位置加入新的图像数据声明*/
//...

#endif


/*****************江协科技|版权所有****************/
/*****************jiangxiekeji.com*****************/

MPU6050

简介

主要有六个数据,

ax:x轴的加速度

ay:y轴的加速度

az:z轴的加速度

gx:x轴的角速度

gy:y轴的角速度

gz:z轴的角速度

六个数据组合就可以计算出姿态

代码

MPU6050.c

复制代码
#include "MPU6050.h"
#include "MPU6050_Reg.h"
#include "main.h"

/**
  * @brief  MPU6050初始化
  * @param  hi2c: I2C句柄
  * @retval 初始化状态: 0-成功, 1-失败
  */
uint8_t MPU6050_Init(I2C_HandleTypeDef *hi2c)
{
    // 检查设备ID
    if(MPU6050_GetID(hi2c) != 0x68)
    {
        return 1; // 设备ID不匹配,初始化失败
    }
    
    // 配置MPU6050寄存器
    MPU6050_WriteReg(hi2c, MPU6050_PWR_MGMT_1, 0x01);    // 取消睡眠模式,选择时钟源为X轴陀螺仪
    MPU6050_WriteReg(hi2c, MPU6050_PWR_MGMT_2, 0x00);    // 电源管理寄存器2,保持默认值0,所有轴均不待机
    MPU6050_WriteReg(hi2c, MPU6050_SMPLRT_DIV, 0x09);    // 采样率分频寄存器,配置采样率
    MPU6050_WriteReg(hi2c, MPU6050_CONFIG, 0x06);        // 配置寄存器,配置DLPF
    MPU6050_WriteReg(hi2c, MPU6050_GYRO_CONFIG, 0x18);   // 陀螺仪配置寄存器,选择满量程为±2000°/s
    MPU6050_WriteReg(hi2c, MPU6050_ACCEL_CONFIG, 0x18);  // 加速度计配置寄存器,选择满量程为±16g
    
    return 0; // 初始化成功
}

/**
  * @brief  MPU6050获取ID号
  * @param  hi2c: I2C句柄
  * @retval MPU6050的ID号
  */
uint8_t MPU6050_GetID(I2C_HandleTypeDef *hi2c)
{
    return MPU6050_ReadReg(hi2c, MPU6050_WHO_AM_I);
}

/**
  * @brief  MPU6050写寄存器
  * @param  hi2c: I2C句柄
  * @param  RegAddress: 寄存器地址
  * @param  Data: 要写入的数据
  * @retval 无
  */
void MPU6050_WriteReg(I2C_HandleTypeDef *hi2c, uint8_t RegAddress, uint8_t Data)
{
    uint8_t buffer[2] = {RegAddress, Data};
    HAL_I2C_Master_Transmit(hi2c, MPU6050_ADDRESS, buffer, 2, HAL_MAX_DELAY);
}

/**
  * @brief  MPU6050读寄存器
  * @param  hi2c: I2C句柄
  * @param  RegAddress: 寄存器地址
  * @retval 读取的数据
  */
uint8_t MPU6050_ReadReg(I2C_HandleTypeDef *hi2c, uint8_t RegAddress)
{
    uint8_t data;
    HAL_I2C_Master_Transmit(hi2c, MPU6050_ADDRESS, &RegAddress, 1, HAL_MAX_DELAY);
    HAL_I2C_Master_Receive(hi2c, MPU6050_ADDRESS, &data, 1, HAL_MAX_DELAY);
    return data;
}

/**
  * @brief  MPU6050获取数据
  * @param  hi2c: I2C句柄
  * @param  AccX: 加速度计X轴数据指针
  * @param  AccY: 加速度计Y轴数据指针
  * @param  AccZ: 加速度计Z轴数据指针
  * @param  GyroX: 陀螺仪X轴数据指针
  * @param  GyroY: 陀螺仪Y轴数据指针
  * @param  GyroZ: 陀螺仪Z轴数据指针
  * @retval 无
  */
void MPU6050_GetData(I2C_HandleTypeDef *hi2c, int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
                     int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
    uint8_t data[14];
    uint8_t reg = MPU6050_ACCEL_XOUT_H;
    
    // 发送读取数据的起始寄存器地址
    HAL_I2C_Master_Transmit(hi2c, MPU6050_ADDRESS, &reg, 1, HAL_MAX_DELAY);
    
    // 读取14字节数据(6字节加速度计 + 2字节温度 + 6字节陀螺仪)
    HAL_I2C_Master_Receive(hi2c, MPU6050_ADDRESS, data, 14, HAL_MAX_DELAY);
    
    // 处理加速度计数据
    *AccX = (int16_t)((data[0] << 8) | data[1]);
    *AccY = (int16_t)((data[2] << 8) | data[3]);
    *AccZ = (int16_t)((data[4] << 8) | data[5]);
    
    // 跳过温度数据(data[6]和data[7])
    
    // 处理陀螺仪数据
    *GyroX = (int16_t)((data[8] << 8) | data[9]);
    *GyroY = (int16_t)((data[10] << 8) | data[11]);
    *GyroZ = (int16_t)((data[12] << 8) | data[13]);
}

MPU6050.h

复制代码
#ifndef __MPU6050_H
#define __MPU6050_H

#include "main.h"
#include "i2c.h"

#define MPU6050_ADDRESS        0xD0    // MPU6050的I2C从机地址

// 初始化函数
uint8_t MPU6050_Init(I2C_HandleTypeDef *hi2c);
uint8_t MPU6050_GetID(I2C_HandleTypeDef *hi2c);

// 读写函数
void MPU6050_WriteReg(I2C_HandleTypeDef *hi2c, uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(I2C_HandleTypeDef *hi2c, uint8_t RegAddress);

// 数据获取函数
void MPU6050_GetData(I2C_HandleTypeDef *hi2c, int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
                     int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);

#endif

MPU6050_reg.h

复制代码
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

#define MPU6050_SMPLRT_DIV     0x19
#define MPU6050_CONFIG         0x1A
#define MPU6050_GYRO_CONFIG    0x1B
#define MPU6050_ACCEL_CONFIG   0x1C

#define MPU6050_ACCEL_XOUT_H   0x3B
#define MPU6050_ACCEL_XOUT_L   0x3C
#define MPU6050_ACCEL_YOUT_H   0x3D
#define MPU6050_ACCEL_YOUT_L   0x3E
#define MPU6050_ACCEL_ZOUT_H   0x3F
#define MPU6050_ACCEL_ZOUT_L   0x40
#define MPU6050_TEMP_OUT_H     0x41
#define MPU6050_TEMP_OUT_L     0x42
#define MPU6050_GYRO_XOUT_H    0x43
#define MPU6050_GYRO_XOUT_L    0x44
#define MPU6050_GYRO_YOUT_H    0x45
#define MPU6050_GYRO_YOUT_L    0x46
#define MPU6050_GYRO_ZOUT_H    0x47
#define MPU6050_GYRO_ZOUT_L    0x48

#define MPU6050_PWR_MGMT_1     0x6B
#define MPU6050_PWR_MGMT_2     0x6C
#define MPU6050_WHO_AM_I       0x75

#endif

然后经过四元数法计算姿态

MPU6050_Quaternion,c

复制代码
#include "stm32f1xx_hal.h"
#include "math.h"
#include "MPU6050.h"



#define Gyro_Range 0x18
#define Accel_Range 0x18


#define PI 3.1415926535f
#define RtA 57.2957795f  // 弧度转角度
#define AtR 0.0174532925f  // 角度转弧度

int16_t raw_ax, raw_ay, raw_az;
int16_t raw_gx, raw_gy, raw_gz;

float pitch, roll, yaw;


//定义一个关于四元数的结构体变量
typedef struct 
{
    float q0, q1, q2, q3;
} Quaternion;
 
Quaternion q = {1.0f, 0.0f, 0.0f, 0.0f};  // 初始化四元数 表示初始姿态为水平。


float MPU6050_ConvertAccel(int16_t value, uint8_t range) 
{
    float factor; // 用于存储转换因子
    // 根据加速度计的量程选择合适的转换因子
    switch (range) {
        case 0x00: factor = 16384.0; break; // ±2g,转换因子为16384
        case 0x08: factor = 8192.0; break;  // ±4g,转换因子为8192
        case 0x10: factor = 4096.0; break;  // ±8g,转换因子为4096
        case 0x18: factor = 2048.0; break;  // ±16g,转换因子为2048
        default: factor = 16384.0; break;    // 默认量程范围为±2g
    }
    // 将原始加速度值转换为g单位
    return value / factor;
}
 
float MPU6050_ConvertGyro(int16_t value, uint8_t range) 
{
    float factor; // 用于存储转换因子
    // 根据陀螺仪的量程选择合适的转换因子
    switch (range) {
        case 0x00: factor = 131.0; break;  // ±250°/s,转换因子为131
        case 0x08: factor = 65.5; break;   // ±500°/s,转换因子为65.5
        case 0x10: factor = 32.8; break;   // ±1000°/s,转换因子为32.8
        case 0x18: factor = 16.4; break;   // ±2000°/s,转换因子为16.4
        default: factor = 131.0; break;     // 默认量程范围为±250°/s
    }
    // 将原始陀螺仪值转换为°/s单位
    return value / factor;
}

void MPU6050_ReadSensors(float *ax, float *ay, float *az, float *gx, float *gy, float *gz)
{

//    MPU6050_ReadAccel(&raw_ax, &raw_ay, &raw_az);
//    MPU6050_ReadGyro(&raw_gx, &raw_gy, &raw_gz);
	
 	MPU6050_GetData(&hi2c2, &raw_ax, &raw_ay, &raw_az, &raw_gx,&raw_gy,&raw_gz);

    *ax = MPU6050_ConvertAccel(raw_ax, Accel_Range);  // ±2g
    *ay = MPU6050_ConvertAccel(raw_ay, Accel_Range);
    *az = MPU6050_ConvertAccel(raw_az, Accel_Range);
    *gx = MPU6050_ConvertGyro(raw_gx, Gyro_Range) * AtR;  // ±250°/s
    *gy = MPU6050_ConvertGyro(raw_gy, Gyro_Range) * AtR;
    *gz = MPU6050_ConvertGyro(raw_gz, Gyro_Range) * AtR;
}

void NormalizeAccel(float *ax, float *ay, float *az) 
{
    float norm = sqrt(*ax * *ax + *ay * *ay + *az * *az);
    *ax /= norm;
    *ay /= norm;
    *az /= norm;
}



void ComputeError(float ax, float ay, float az, float *error_x, float *error_y, float *error_z) 
{
    float gravity_x = 2 * (q.q1 * q.q3 - q.q0 * q.q2);
    float gravity_y = 2 * (q.q0 * q.q1 + q.q2 * q.q3);
    float gravity_z = 1 - 2 * (q.q1 * q.q1 + q.q2 * q.q2);
 
    *error_x = (ay * gravity_z - az * gravity_y);
    *error_y = (az * gravity_x - ax * gravity_z);
    *error_z = (ax * gravity_y - ay * gravity_x);
}

void UpdateQuaternion(float gx, float gy, float gz, float error_x, float error_y, float error_z, float dt) 
{
    float Kp = 0.5f;  // 互补滤波系数
 
    float q0_dot = -q.q1 * gx - q.q2 * gy - q.q3 * gz;
    float q1_dot = q.q0 * gx - q.q3 * gy + q.q2 * gz;
    float q2_dot = q.q3 * gx + q.q0 * gy - q.q1 * gz;
    float q3_dot = -q.q2 * gx + q.q1 * gy + q.q0 * gz;
 
    q.q0 += (q0_dot + Kp * error_x) * dt;
    q.q1 += (q1_dot + Kp * error_y) * dt;
    q.q2 += (q2_dot + Kp * error_z) * dt;
    q.q3 += (q3_dot) * dt;
 
    // 归一化四元数,避免数值误差会越积越大
    float norm = sqrt(q.q0 * q.q0 + q.q1 * q.q1 + q.q2 * q.q2 + q.q3 * q.q3);
    q.q0 /= norm;
    q.q1 /= norm;
    q.q2 /= norm;
    q.q3 /= norm;
}



void ComputeEulerAngles(float *pitch, float *roll, float *yaw) 
{
    *roll = atan2(2 * (q.q2 * q.q3 + q.q0 * q.q1), q.q0 * q.q0 - q.q1 * q.q1 - q.q2 * q.q2 + q.q3 * q.q3);
    *pitch = asin(-2 * (q.q1 * q.q3 - q.q0 * q.q2));
    *yaw = atan2(2 * (q.q1 * q.q2 + q.q0 * q.q3), q.q0 * q.q0 + q.q1 * q.q1 - q.q2 * q.q2 - q.q3 * q.q3);
 
    *roll *= RtA;  // 弧度转角度
    *pitch *= RtA;
    *yaw *= RtA;
}


void GetAngles(float *pitch, float *roll, float *yaw, float dt) 
{
    float ax, ay, az, gx, gy, gz;
    MPU6050_ReadSensors(&ax, &ay, &az, &gx, &gy, &gz);
 
    NormalizeAccel(&ax, &ay, &az);
 
    float error_x, error_y, error_z;
    ComputeError(ax, ay, az, &error_x, &error_y, &error_z);
 
    UpdateQuaternion(gx, gy, gz, error_x, error_y, error_z, dt);
 
    ComputeEulerAngles(pitch, roll, yaw);
}

MPU6050_Quaternion,c

复制代码
#ifndef __MPU6050_QUATERNION_H
#define __MPU6050_QUATERNION_H



extern int16_t raw_ax, raw_ay, raw_az;
extern int16_t raw_gx, raw_gy, raw_gz;


extern float pitch, roll, yaw;

void GetAngles(float *pitch, float *roll, float *yaw, float dt);



#endif 

注意事项、

MAX30102

简介

通信协议是IIC

代码

MAX30102.c

复制代码
#include "max30102.h"

u8 max30102_Bus_Write(u8 Register_Address, u8 Word_Data);

/**************************************** 需要更改的引脚 ****************************************************/

void MAX30102_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	__HAL_RCC_GPIOB_CLK_ENABLE();
	GPIO_InitStructure.Pin = MAX30102_INT_PIN;
	GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
	HAL_GPIO_Init(MAX30102_INT_PORT, &GPIO_InitStructure);

	MAX30102_IIC_Init();

	MAX30102_Reset();

	max30102_Bus_Write(REG_INTR_ENABLE_1, 0xc0); // INTR setting
	max30102_Bus_Write(REG_INTR_ENABLE_2, 0x00);
	max30102_Bus_Write(REG_FIFO_WR_PTR, 0x00); // FIFO_WR_PTR[4:0]
	max30102_Bus_Write(REG_OVF_COUNTER, 0x00); // OVF_COUNTER[4:0]
	max30102_Bus_Write(REG_FIFO_RD_PTR, 0x00); // FIFO_RD_PTR[4:0]
	max30102_Bus_Write(REG_FIFO_CONFIG, 0x0f); // sample avg = 1, fifo rollover=false, fifo almost full = 17
	max30102_Bus_Write(REG_MODE_CONFIG, 0x03); // 0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
	max30102_Bus_Write(REG_SPO2_CONFIG, 0x27); // SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS)
	max30102_Bus_Write(REG_LED1_PA, 0x24);	   // Choose value for ~ 7mA for LED1
	max30102_Bus_Write(REG_LED2_PA, 0x24);	   // Choose value for ~ 7mA for LED2
	max30102_Bus_Write(REG_PILOT_PA, 0x7f);	   // Choose value for ~ 25mA for Pilot LED
}

// MAX30102引脚输出模式控制
void MAX30102_IIC_SDA_OUT(void) // SDA输出方向配置
{
	GPIO_InitTypeDef GPIO_InitStructure;

	GPIO_InitStructure.Pin = MAX30102_IIC_SDA_PIN;
	GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
	GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // SDA推挽输出
	HAL_GPIO_Init(MAX30102_IIC_PORT, &GPIO_InitStructure);
}

void MAX30102_IIC_SDA_IN(void) // SDA输入方向配置
{
	GPIO_InitTypeDef GPIO_InitStructure;

	GPIO_InitStructure.Pin = MAX30102_IIC_SDA_PIN;
	GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
	GPIO_InitStructure.Mode = GPIO_MODE_INPUT; // SCL上拉输入
	GPIO_InitStructure.Pull = GPIO_PULLUP;
	HAL_GPIO_Init(MAX30102_IIC_PORT, &GPIO_InitStructure);
}

// 初始化IIC
void MAX30102_IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	// RCC->APB2ENR|=1<<4;//先使能外设IO PORTC时钟
	__HAL_RCC_GPIOB_CLK_ENABLE();

	GPIO_InitStructure.Pin = MAX30102_IIC_SCL_PIN | MAX30102_IIC_SDA_PIN;
	GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
	GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(MAX30102_IIC_PORT, &GPIO_InitStructure);

	MAX30102_IIC_SCL = 1;
	MAX30102_IIC_SDA = 1;
}

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)
		{
		}
	}
}

/**************************************** 需要更改的引脚 ****************************************************/

u8 max30102_Bus_Write(u8 Register_Address, u8 Word_Data)
{

	/* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */

	/* 第1步:发起I2C总线启动信号 */
	MAX30102_IIC_Start();

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	MAX30102_IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */

	/* 第3步:发送ACK */
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第4步:发送字节地址 */
	MAX30102_IIC_Send_Byte(Register_Address);
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第5步:开始写入数据 */
	MAX30102_IIC_Send_Byte(Word_Data);

	/* 第6步:发送ACK */
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 发送I2C总线停止信号 */
	MAX30102_IIC_Stop();
	return 1; /* 执行成功 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	MAX30102_IIC_Stop();
	return 0;
}

u8 max30102_Bus_Read(u8 Register_Address)
{
	u8 data;

	/* 第1步:发起I2C总线启动信号 */
	MAX30102_IIC_Start();

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	MAX30102_IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */

	/* 第3步:发送ACK */
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第4步:发送字节地址, */
	MAX30102_IIC_Send_Byte((uint8_t)Register_Address);
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第6步:重新启动I2C总线。下面开始读取数据 */
	MAX30102_IIC_Start();

	/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	MAX30102_IIC_Send_Byte(max30102_WR_address | I2C_RD); /* 此处是读指令 */

	/* 第8步:发送ACK */
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第9步:读取数据 */
	{
		data = MAX30102_IIC_Read_Byte(0); /* 读1个字节 */

		MAX30102_IIC_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
	}
	/* 发送I2C总线停止信号 */
	MAX30102_IIC_Stop();
	return data; /* 执行成功 返回data值 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	MAX30102_IIC_Stop();
	return 0;
}

void max30102_FIFO_ReadWords(u8 Register_Address, u16 Word_Data[][2], u8 count)
{
	u8 i = 0;
	u8 no = count;
	u8 data1, data2;
	/* 第1步:发起I2C总线启动信号 */
	MAX30102_IIC_Start();

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	MAX30102_IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */

	/* 第3步:发送ACK */
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第4步:发送字节地址, */
	MAX30102_IIC_Send_Byte((uint8_t)Register_Address);
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第6步:重新启动I2C总线。下面开始读取数据 */
	MAX30102_IIC_Start();

	/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	MAX30102_IIC_Send_Byte(max30102_WR_address | I2C_RD); /* 此处是读指令 */

	/* 第8步:发送ACK */
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第9步:读取数据 */
	while (no)
	{
		data1 = MAX30102_IIC_Read_Byte(0);
		MAX30102_IIC_Ack();
		data2 = MAX30102_IIC_Read_Byte(0);
		MAX30102_IIC_Ack();
		Word_Data[i][0] = (((u16)data1 << 8) | data2); //

		data1 = MAX30102_IIC_Read_Byte(0);
		MAX30102_IIC_Ack();
		data2 = MAX30102_IIC_Read_Byte(0);
		if (1 == no)
			MAX30102_IIC_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
		else
			MAX30102_IIC_Ack();
		Word_Data[i][1] = (((u16)data1 << 8) | data2);

		no--;
		i++;
	}
	/* 发送I2C总线停止信号 */
	MAX30102_IIC_Stop();

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	MAX30102_IIC_Stop();
}

void max30102_FIFO_ReadBytes(u8 Register_Address, u8 *Data)
{
	max30102_Bus_Read(REG_INTR_STATUS_1);
	max30102_Bus_Read(REG_INTR_STATUS_2);

	/* 第1步:发起I2C总线启动信号 */
	MAX30102_IIC_Start();

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	MAX30102_IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */

	/* 第3步:发送ACK */
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第4步:发送字节地址, */
	MAX30102_IIC_Send_Byte((uint8_t)Register_Address);
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第6步:重新启动I2C总线。下面开始读取数据 */
	MAX30102_IIC_Start();

	/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	MAX30102_IIC_Send_Byte(max30102_WR_address | I2C_RD); /* 此处是读指令 */

	/* 第8步:发送ACK */
	if (MAX30102_IIC_Wait_Ack() != 0)
	{
		goto cmd_fail; /* EEPROM器件无应答 */
	}

	/* 第9步:读取数据 */
	Data[0] = MAX30102_IIC_Read_Byte(1);
	Data[1] = MAX30102_IIC_Read_Byte(1);
	Data[2] = MAX30102_IIC_Read_Byte(1);
	Data[3] = MAX30102_IIC_Read_Byte(1);
	Data[4] = MAX30102_IIC_Read_Byte(1);
	Data[5] = MAX30102_IIC_Read_Byte(0);
	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
	/* 发送I2C总线停止信号 */
	MAX30102_IIC_Stop();

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	MAX30102_IIC_Stop();

	//	u8 i;
	//	u8 fifo_wr_ptr;
	//	u8 firo_rd_ptr;
	//	u8 number_tp_read;
	//	//Get the FIFO_WR_PTR
	//	fifo_wr_ptr = max30102_Bus_Read(REG_FIFO_WR_PTR);
	//	//Get the FIFO_RD_PTR
	//	firo_rd_ptr = max30102_Bus_Read(REG_FIFO_RD_PTR);
	//
	//	number_tp_read = fifo_wr_ptr - firo_rd_ptr;
	//
	//	//for(i=0;i<number_tp_read;i++){
	//	if(number_tp_read>0){
	//		MAX30102_IIC_ReadBytes(max30102_WR_address,REG_FIFO_DATA,Data,6);
	//	}

	// max30102_Bus_Write(REG_FIFO_RD_PTR,fifo_wr_ptr);
}

void MAX30102_Reset(void)
{
	max30102_Bus_Write(REG_MODE_CONFIG, 0x40);
	max30102_Bus_Write(REG_MODE_CONFIG, 0x40);
}

void maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data)
{
	//  char ach_i2c_data[2];
	//  ach_i2c_data[0]=uch_addr;
	//  ach_i2c_data[1]=uch_data;
	//
	//  MAX30102_IIC_WriteBytes(I2C_WRITE_ADDR, ach_i2c_data, 2);
	MAX30102_IIC_Write_One_Byte(I2C_WRITE_ADDR, uch_addr, uch_data);
}

void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data)
{
	//  char ch_i2c_data;
	//  ch_i2c_data=uch_addr;
	//  MAX30102_IIC_WriteBytes(I2C_WRITE_ADDR, &ch_i2c_data, 1);
	//
	//  i2c.read(I2C_READ_ADDR, &ch_i2c_data, 1);
	//
	//   *puch_data=(uint8_t) ch_i2c_data;
	MAX30102_IIC_Read_One_Byte(I2C_WRITE_ADDR, uch_addr, puch_data);
}

void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)
{
	uint32_t un_temp;
	unsigned char uch_temp;
	char ach_i2c_data[6];
	*pun_red_led = 0;
	*pun_ir_led = 0;

	// read and clear status register
	maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);
	maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);

	MAX30102_IIC_ReadBytes(I2C_WRITE_ADDR, REG_FIFO_DATA, (u8 *)ach_i2c_data, 6);

	un_temp = (unsigned char)ach_i2c_data[0];
	un_temp <<= 16;
	*pun_red_led += un_temp;
	un_temp = (unsigned char)ach_i2c_data[1];
	un_temp <<= 8;
	*pun_red_led += un_temp;
	un_temp = (unsigned char)ach_i2c_data[2];
	*pun_red_led += un_temp;

	un_temp = (unsigned char)ach_i2c_data[3];
	un_temp <<= 16;
	*pun_ir_led += un_temp;
	un_temp = (unsigned char)ach_i2c_data[4];
	un_temp <<= 8;
	*pun_ir_led += un_temp;
	un_temp = (unsigned char)ach_i2c_data[5];
	*pun_ir_led += un_temp;
	*pun_red_led &= 0x03FFFF; // Mask MSB [23:18]
	*pun_ir_led &= 0x03FFFF;  // Mask MSB [23:18]
}

// 产生IIC起始信号
void MAX30102_IIC_Start(void)
{
	MAX30102_IIC_SDA_OUT(); // sda线输出
	MAX30102_IIC_SDA = 1;
	MAX30102_IIC_SCL = 1;
	delay_us(4);
	MAX30102_IIC_SDA = 0; // START:when CLK is high,DATA change form high to low
	delay_us(4);
	MAX30102_IIC_SCL = 0; // 钳住I2C总线,准备发送或接收数据
}
// 产生IIC停止信号
void MAX30102_IIC_Stop(void)
{
	MAX30102_IIC_SDA_OUT(); // sda线输出
	MAX30102_IIC_SCL = 0;
	MAX30102_IIC_SDA = 0; // STOP:when CLK is high DATA change form low to high
	delay_us(4);
	MAX30102_IIC_SCL = 1;
	MAX30102_IIC_SDA = 1; // 发送I2C总线结束信号
	delay_us(4);
}
// 等待应答信号到来
// 返回值:1,接收应答失败
//         0,接收应答成功
u8 MAX30102_IIC_Wait_Ack(void)
{
	u8 ucErrTime = 0;
	MAX30102_IIC_SDA_IN(); // SDA设置为输入
	MAX30102_IIC_SDA = 1;
	delay_us(1);
	MAX30102_IIC_SCL = 1;
	delay_us(1);
	while (MAX30102_READ_SDA)
	{
		ucErrTime++;
		if (ucErrTime > 250)
		{
			MAX30102_IIC_Stop();
			return 1;
		}
	}
	MAX30102_IIC_SCL = 0; // 时钟输出0
	return 0;
}
// 产生ACK应答
void MAX30102_IIC_Ack(void)
{
	MAX30102_IIC_SCL = 0;
	MAX30102_IIC_SDA_OUT();
	MAX30102_IIC_SDA = 0;
	delay_us(2);
	MAX30102_IIC_SCL = 1;
	delay_us(2);
	MAX30102_IIC_SCL = 0;
}
// 不产生ACK应答
void MAX30102_IIC_NAck(void)
{
	MAX30102_IIC_SCL = 0;
	MAX30102_IIC_SDA_OUT();
	MAX30102_IIC_SDA = 1;
	delay_us(2);
	MAX30102_IIC_SCL = 1;
	delay_us(2);
	MAX30102_IIC_SCL = 0;
}
// IIC发送一个字节
// 返回从机有无应答
// 1,有应答
// 0,无应答
void MAX30102_IIC_Send_Byte(u8 txd)
{
	u8 t;
	MAX30102_IIC_SDA_OUT();
	MAX30102_IIC_SCL = 0; // 拉低时钟开始数据传输
	for (t = 0; t < 8; t++)
	{
		MAX30102_IIC_SDA = (txd & 0x80) >> 7;
		txd <<= 1;
		delay_us(2); // 对TEA5767这三个延时都是必须的
		MAX30102_IIC_SCL = 1;
		delay_us(2);
		MAX30102_IIC_SCL = 0;
		delay_us(2);
	}
}
// 读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 MAX30102_IIC_Read_Byte(unsigned char ack)
{
	unsigned char i, receive = 0;
	MAX30102_IIC_SDA_IN(); // SDA设置为输入
	for (i = 0; i < 8; i++)
	{
		MAX30102_IIC_SCL = 0;
		delay_us(2);
		MAX30102_IIC_SCL = 1;
		receive <<= 1;
		if (MAX30102_READ_SDA)
			receive++;
		delay_us(1);
	}
	if (!ack)
		MAX30102_IIC_NAck(); // 发送nACK
	else
		MAX30102_IIC_Ack(); // 发送ACK
	return receive;
}

void MAX30102_IIC_WriteBytes(u8 WriteAddr, u8 *data, u8 dataLength)
{
	u8 i;
	MAX30102_IIC_Start();

	MAX30102_IIC_Send_Byte(WriteAddr); // 发送写命令
	MAX30102_IIC_Wait_Ack();

	for (i = 0; i < dataLength; i++)
	{
		MAX30102_IIC_Send_Byte(data[i]);
		MAX30102_IIC_Wait_Ack();
	}
	MAX30102_IIC_Stop(); // 产生一个停止条件
	HAL_Delay(10);
}

void MAX30102_IIC_ReadBytes(u8 deviceAddr, u8 writeAddr, u8 *data, u8 dataLength)
{
	u8 i;
	MAX30102_IIC_Start();

	MAX30102_IIC_Send_Byte(deviceAddr); // 发送写命令
	MAX30102_IIC_Wait_Ack();
	MAX30102_IIC_Send_Byte(writeAddr);
	MAX30102_IIC_Wait_Ack();
	MAX30102_IIC_Send_Byte(deviceAddr | 0X01); // 进入接收模式
	MAX30102_IIC_Wait_Ack();

	for (i = 0; i < dataLength - 1; i++)
	{
		data[i] = MAX30102_IIC_Read_Byte(1);
	}
	data[dataLength - 1] = MAX30102_IIC_Read_Byte(0);
	MAX30102_IIC_Stop(); // 产生一个停止条件
	HAL_Delay(10);
}

void MAX30102_IIC_Read_One_Byte(u8 daddr, u8 addr, u8 *data)
{
	MAX30102_IIC_Start();

	MAX30102_IIC_Send_Byte(daddr); // 发送写命令
	MAX30102_IIC_Wait_Ack();
	MAX30102_IIC_Send_Byte(addr); // 发送地址
	MAX30102_IIC_Wait_Ack();
	MAX30102_IIC_Start();
	MAX30102_IIC_Send_Byte(daddr | 0X01); // 进入接收模式
	MAX30102_IIC_Wait_Ack();
	*data = MAX30102_IIC_Read_Byte(0);
	MAX30102_IIC_Stop(); // 产生一个停止条件
}

void MAX30102_IIC_Write_One_Byte(u8 daddr, u8 addr, u8 data)
{
	MAX30102_IIC_Start();

	MAX30102_IIC_Send_Byte(daddr); // 发送写命令
	MAX30102_IIC_Wait_Ack();
	MAX30102_IIC_Send_Byte(addr); // 发送地址
	MAX30102_IIC_Wait_Ack();
	MAX30102_IIC_Send_Byte(data); // 发送字节
	MAX30102_IIC_Wait_Ack();
	MAX30102_IIC_Stop(); // 产生一个停止条件
	HAL_Delay(10);
}

const uint16_t auw_hamm[31] = {41, 276, 512, 276, 41}; // Hamm=  long16(512* hamming(5)');
// uch_spo2_table is computed as  -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ;
const uint8_t uch_spo2_table[184] = {95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99,
									 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
									 100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97,
									 97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91,
									 90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81,
									 80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67,
									 66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50,
									 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29,
									 28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5,
									 3, 2, 1};
static int32_t an_dx[BUFFER_SIZE - MA4_SIZE]; // delta
static int32_t an_x[BUFFER_SIZE];			  // ir
static int32_t an_y[BUFFER_SIZE];			  // red

void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid,
											int32_t *pn_heart_rate, int8_t *pch_hr_valid)
/**
 * \brief        Calculate the heart rate and SpO2 level
 * \par          Details
 *               By detecting  peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the ratio for the SPO2 is computed.
 *               Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
 *               Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each ratio.
 *
 * \param[in]    *pun_ir_buffer           - IR sensor data buffer
 * \param[in]    n_ir_buffer_length      - IR sensor data buffer length
 * \param[in]    *pun_red_buffer          - Red sensor data buffer
 * \param[out]    *pn_spo2                - Calculated SpO2 value
 * \param[out]    *pch_spo2_valid         - 1 if the calculated SpO2 value is valid
 * \param[out]    *pn_heart_rate          - Calculated heart rate value
 * \param[out]    *pch_hr_valid           - 1 if the calculated heart rate value is valid
 *
 * \retval       None
 */
{
	uint32_t un_ir_mean, un_only_once;
	int32_t k, n_i_ratio_count;
	int32_t i, s, m, n_exact_ir_valley_locs_count, n_middle_idx;
	int32_t n_th1, n_npks, n_c_min;
	int32_t an_ir_valley_locs[15];
	int32_t an_exact_ir_valley_locs[15];
	int32_t an_dx_peak_locs[15];
	int32_t n_peak_interval_sum;

	int32_t n_y_ac, n_x_ac;
	int32_t n_spo2_calc;
	int32_t n_y_dc_max, n_x_dc_max;
	int32_t n_y_dc_max_idx, n_x_dc_max_idx;
	int32_t an_ratio[5], n_ratio_average;
	int32_t n_nume, n_denom;
	// remove DC of ir signal
	un_ir_mean = 0;
	for (k = 0; k < n_ir_buffer_length; k++)
		un_ir_mean += pun_ir_buffer[k];
	un_ir_mean = un_ir_mean / n_ir_buffer_length;
	for (k = 0; k < n_ir_buffer_length; k++)
		an_x[k] = pun_ir_buffer[k] - un_ir_mean;

	// 4 pt Moving Average
	for (k = 0; k < BUFFER_SIZE - MA4_SIZE; k++)
	{
		n_denom = (an_x[k] + an_x[k + 1] + an_x[k + 2] + an_x[k + 3]);
		an_x[k] = n_denom / (int32_t)4;
	}

	// get difference of smoothed IR signal

	for (k = 0; k < BUFFER_SIZE - MA4_SIZE - 1; k++)
		an_dx[k] = (an_x[k + 1] - an_x[k]);

	// 2-pt Moving Average to an_dx
	for (k = 0; k < BUFFER_SIZE - MA4_SIZE - 2; k++)
	{
		an_dx[k] = (an_dx[k] + an_dx[k + 1]) / 2;
	}

	// hamming window
	// flip wave form so that we can detect valley with peak detector
	for (i = 0; i < BUFFER_SIZE - HAMMING_SIZE - MA4_SIZE - 2; i++)
	{
		s = 0;
		for (k = i; k < i + HAMMING_SIZE; k++)
		{
			s -= an_dx[k] * auw_hamm[k - i];
		}
		an_dx[i] = s / (int32_t)1146; // divide by sum of auw_hamm
	}

	n_th1 = 0; // threshold calculation
	for (k = 0; k < BUFFER_SIZE - HAMMING_SIZE; k++)
	{
		n_th1 += ((an_dx[k] > 0) ? an_dx[k] : ((int32_t)0 - an_dx[k]));
	}
	n_th1 = n_th1 / (BUFFER_SIZE - HAMMING_SIZE);
	// peak location is acutally index for sharpest location of raw signal since we flipped the signal
	maxim_find_peaks(an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE - HAMMING_SIZE, n_th1, 8, 5); // peak_height, peak_distance, max_num_peaks

	n_peak_interval_sum = 0;
	if (n_npks >= 2)
	{
		for (k = 1; k < n_npks; k++)
			n_peak_interval_sum += (an_dx_peak_locs[k] - an_dx_peak_locs[k - 1]);
		n_peak_interval_sum = n_peak_interval_sum / (n_npks - 1);
		*pn_heart_rate = (int32_t)(6000 / n_peak_interval_sum); // beats per minutes
		*pch_hr_valid = 1;
	}
	else
	{
		*pn_heart_rate = -999;
		*pch_hr_valid = 0;
	}

	for (k = 0; k < n_npks; k++)
		an_ir_valley_locs[k] = an_dx_peak_locs[k] + HAMMING_SIZE / 2;

	// raw value : RED(=y) and IR(=X)
	// we need to assess DC and AC value of ir and red PPG.
	for (k = 0; k < n_ir_buffer_length; k++)
	{
		an_x[k] = pun_ir_buffer[k];
		an_y[k] = pun_red_buffer[k];
	}

	// find precise min near an_ir_valley_locs
	n_exact_ir_valley_locs_count = 0;
	for (k = 0; k < n_npks; k++)
	{
		un_only_once = 1;
		m = an_ir_valley_locs[k];
		n_c_min = 16777216; // 2^24;
		if (m + 5 < BUFFER_SIZE - HAMMING_SIZE && m - 5 > 0)
		{
			for (i = m - 5; i < m + 5; i++)
				if (an_x[i] < n_c_min)
				{
					if (un_only_once > 0)
					{
						un_only_once = 0;
					}
					n_c_min = an_x[i];
					an_exact_ir_valley_locs[k] = i;
				}
			if (un_only_once == 0)
				n_exact_ir_valley_locs_count++;
		}
	}
	if (n_exact_ir_valley_locs_count < 2)
	{
		*pn_spo2 = -999; // do not use SPO2 since signal ratio is out of range
		*pch_spo2_valid = 0;
		return;
	}
	// 4 pt MA
	for (k = 0; k < BUFFER_SIZE - MA4_SIZE; k++)
	{
		an_x[k] = (an_x[k] + an_x[k + 1] + an_x[k + 2] + an_x[k + 3]) / (int32_t)4;
		an_y[k] = (an_y[k] + an_y[k + 1] + an_y[k + 2] + an_y[k + 3]) / (int32_t)4;
	}

	// using an_exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio
	// finding AC/DC maximum of raw ir * red between two valley locations
	n_ratio_average = 0;
	n_i_ratio_count = 0;

	for (k = 0; k < 5; k++)
		an_ratio[k] = 0;
	for (k = 0; k < n_exact_ir_valley_locs_count; k++)
	{
		if (an_exact_ir_valley_locs[k] > BUFFER_SIZE)
		{
			*pn_spo2 = -999; // do not use SPO2 since valley loc is out of range
			*pch_spo2_valid = 0;
			return;
		}
	}
	// find max between two valley locations
	// and use ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2

	for (k = 0; k < n_exact_ir_valley_locs_count - 1; k++)
	{
		n_y_dc_max = -16777216;
		n_x_dc_max = -16777216;
		if (an_exact_ir_valley_locs[k + 1] - an_exact_ir_valley_locs[k] > 10)
		{
			for (i = an_exact_ir_valley_locs[k]; i < an_exact_ir_valley_locs[k + 1]; i++)
			{
				if (an_x[i] > n_x_dc_max)
				{
					n_x_dc_max = an_x[i];
					n_x_dc_max_idx = i;
				}
				if (an_y[i] > n_y_dc_max)
				{
					n_y_dc_max = an_y[i];
					n_y_dc_max_idx = i;
				}
			}
			n_y_ac = (an_y[an_exact_ir_valley_locs[k + 1]] - an_y[an_exact_ir_valley_locs[k]]) * (n_y_dc_max_idx - an_exact_ir_valley_locs[k]); // red
			n_y_ac = an_y[an_exact_ir_valley_locs[k]] + n_y_ac / (an_exact_ir_valley_locs[k + 1] - an_exact_ir_valley_locs[k]);

			n_y_ac = an_y[n_y_dc_max_idx] - n_y_ac;																								// subracting linear DC compoenents from raw
			n_x_ac = (an_x[an_exact_ir_valley_locs[k + 1]] - an_x[an_exact_ir_valley_locs[k]]) * (n_x_dc_max_idx - an_exact_ir_valley_locs[k]); // ir
			n_x_ac = an_x[an_exact_ir_valley_locs[k]] + n_x_ac / (an_exact_ir_valley_locs[k + 1] - an_exact_ir_valley_locs[k]);
			n_x_ac = an_x[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw
			n_nume = (n_y_ac * n_x_dc_max) >> 7;	// prepare X100 to preserve floating value
			n_denom = (n_x_ac * n_y_dc_max) >> 7;
			if (n_denom > 0 && n_i_ratio_count < 5 && n_nume != 0)
			{
				an_ratio[n_i_ratio_count] = (n_nume * 20) / n_denom; // formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ;  ///*************************n_nume原来是*100************************//
				n_i_ratio_count++;
			}
		}
	}

	maxim_sort_ascend(an_ratio, n_i_ratio_count);
	n_middle_idx = n_i_ratio_count / 2;

	if (n_middle_idx > 1)
		n_ratio_average = (an_ratio[n_middle_idx - 1] + an_ratio[n_middle_idx]) / 2; // use median
	else
		n_ratio_average = an_ratio[n_middle_idx];

	if (n_ratio_average > 2 && n_ratio_average < 184)
	{
		n_spo2_calc = uch_spo2_table[n_ratio_average];
		*pn_spo2 = n_spo2_calc;
		*pch_spo2_valid = 1; //  float_SPO2 =  -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ;  // for comparison with table
	}
	else
	{
		*pn_spo2 = -999; // do not use SPO2 since signal ratio is out of range
		*pch_spo2_valid = 0;
	}
}

void maxim_find_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num)
/**
 * \brief        Find peaks
 * \par          Details
 *               Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE
 *
 * \retval       None
 */
{
	maxim_peaks_above_min_height(pn_locs, pn_npks, pn_x, n_size, n_min_height);
	maxim_remove_close_peaks(pn_locs, pn_npks, pn_x, n_min_distance);
	*pn_npks = min(*pn_npks, n_max_num);
}

void maxim_peaks_above_min_height(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height)
/**
 * \brief        Find peaks above n_min_height
 * \par          Details
 *               Find all peaks above MIN_HEIGHT
 *
 * \retval       None
 */
{
	int32_t i = 1, n_width;
	*pn_npks = 0;

	while (i < n_size - 1)
	{
		if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i - 1])
		{ // find left edge of potential peaks
			n_width = 1;
			while (i + n_width < n_size && pn_x[i] == pn_x[i + n_width]) // find flat peaks
				n_width++;
			if (pn_x[i] > pn_x[i + n_width] && (*pn_npks) < 15)
			{ // find right edge of peaks
				pn_locs[(*pn_npks)++] = i;
				// for flat peaks, peak location is left edge
				i += n_width + 1;
			}
			else
				i += n_width;
		}
		else
			i++;
	}
}

void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance)
/**
 * \brief        Remove peaks
 * \par          Details
 *               Remove peaks separated by less than MIN_DISTANCE
 *
 * \retval       None
 */
{

	int32_t i, j, n_old_npks, n_dist;

	/* Order peaks from large to small */
	maxim_sort_indices_descend(pn_x, pn_locs, *pn_npks);

	for (i = -1; i < *pn_npks; i++)
	{
		n_old_npks = *pn_npks;
		*pn_npks = i + 1;
		for (j = i + 1; j < n_old_npks; j++)
		{
			n_dist = pn_locs[j] - (i == -1 ? -1 : pn_locs[i]); // lag-zero peak of autocorr is at index -1
			if (n_dist > n_min_distance || n_dist < -n_min_distance)
				pn_locs[(*pn_npks)++] = pn_locs[j];
		}
	}

	// Resort indices longo ascending order
	maxim_sort_ascend(pn_locs, *pn_npks);
}

void maxim_sort_ascend(int32_t *pn_x, int32_t n_size)
/**
 * \brief        Sort array
 * \par          Details
 *               Sort array in ascending order (insertion sort algorithm)
 *
 * \retval       None
 */
{
	int32_t i, j, n_temp;
	for (i = 1; i < n_size; i++)
	{
		n_temp = pn_x[i];
		for (j = i; j > 0 && n_temp < pn_x[j - 1]; j--)
			pn_x[j] = pn_x[j - 1];
		pn_x[j] = n_temp;
	}
}

void maxim_sort_indices_descend(int32_t *pn_x, int32_t *pn_indx, int32_t n_size)
/**
 * \brief        Sort indices
 * \par          Details
 *               Sort indices according to descending order (insertion sort algorithm)
 *
 * \retval       None
 */
{
	int32_t i, j, n_temp;
	for (i = 1; i < n_size; i++)
	{
		n_temp = pn_indx[i];
		for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j - 1]]; j--)
			pn_indx[j] = pn_indx[j - 1];
		pn_indx[j] = n_temp;
	}
}

MAX30102.h

复制代码
#ifndef __MAX30102_H
#define __MAX30102_H
#include "stdbool.h"
#include "sys.h"

//==============================================MAX30102硬件接口==================================================
#define		MAX30102_IIC_PORT				GPIOB
#define		MAX30102_IIC_SCL_PIN		    GPIO_PIN_15
#define		MAX30102_IIC_SDA_PIN		    GPIO_PIN_14

#define 	MAX30102_IIC_SCL				PBout(15)
#define 	MAX30102_IIC_SDA				PBout(14)
#define 	MAX30102_READ_SDA   		    PBin(14)  //输入SDA 

#define		MAX30102_INT_PORT				GPIOB
#define		MAX30102_INT_PIN		        GPIO_PIN_13
#define		MAX30102_INT                    PBin(13)

//=============================================================================================================  

#define I2C_WR	0		/* 写控制bit */
#define I2C_RD	1		/* 读控制bit */

#define I2C_WRITE_ADDR 0xAE
#define I2C_READ_ADDR 0xAF


#define true 1
#define false 0
#define FS 100
#define BUFFER_SIZE  (FS* 5) 
#define HR_FIFO_SIZE 7
#define MA4_SIZE  4 // DO NOT CHANGE
#define HAMMING_SIZE  5// DO NOT CHANGE
#define min(x,y) ((x) < (y) ? (x) : (y))



#define max30102_WR_address 0xAE

#define I2C_WRITE_ADDR 0xAE
#define I2C_READ_ADDR 0xAF

//register addresses
#define REG_INTR_STATUS_1 0x00
#define REG_INTR_STATUS_2 0x01
#define REG_INTR_ENABLE_1 0x02
#define REG_INTR_ENABLE_2 0x03
#define REG_FIFO_WR_PTR 0x04
#define REG_OVF_COUNTER 0x05
#define REG_FIFO_RD_PTR 0x06
#define REG_FIFO_DATA 0x07
#define REG_FIFO_CONFIG 0x08
#define REG_MODE_CONFIG 0x09
#define REG_SPO2_CONFIG 0x0A
#define REG_LED1_PA 0x0C
#define REG_LED2_PA 0x0D
#define REG_PILOT_PA 0x10
#define REG_MULTI_LED_CTRL1 0x11
#define REG_MULTI_LED_CTRL2 0x12
#define REG_TEMP_INTR 0x1F
#define REG_TEMP_FRAC 0x20
#define REG_TEMP_CONFIG 0x21
#define REG_PROX_INT_THRESH 0x30
#define REG_REV_ID 0xFE
#define REG_PART_ID 0xFF


//IIC所有操作函数
void MAX30102_IIC_Init(void);                //初始化IIC的IO口				 
void MAX30102_IIC_Start(void);				//发送IIC开始信号
void MAX30102_IIC_Stop(void);	  			//发送IIC停止信号
void MAX30102_IIC_Send_Byte(u8 txd);			//IIC发送一个字节
u8 MAX30102_IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 MAX30102_IIC_Wait_Ack(void); 				//IIC等待ACK信号
void MAX30102_IIC_Ack(void);					//IIC发送ACK信号
void MAX30102_IIC_NAck(void);				//IIC不发送ACK信号

void MAX30102_IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
void MAX30102_IIC_Read_One_Byte(u8 daddr,u8 addr,u8* data);

void MAX30102_IIC_WriteBytes(u8 WriteAddr,u8* data,u8 dataLength);
void MAX30102_IIC_ReadBytes(u8 deviceAddr, u8 writeAddr,u8* data,u8 dataLength);

//MAX30102所有操作函数
void MAX30102_Init(void);  
void MAX30102_Reset(void);
u8 M30102_Bus_Write(u8 Register_Address, u8 Word_Data);
u8 max30102_Bus_Read(u8 Register_Address);
void max30102_FIFO_ReadWords(u8 Register_Address,u16  Word_Data[][2],u8 count);
void max30102_FIFO_ReadBytes(u8 Register_Address,u8* Data);

void maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data);
void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data);
void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led);

//心率血氧算法所有函数
void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer ,  int32_t n_ir_buffer_length, uint32_t *pun_red_buffer ,   int32_t *pn_spo2, int8_t *pch_spo2_valid ,  int32_t *pn_heart_rate , int8_t  *pch_hr_valid);
void maxim_find_peaks( int32_t *pn_locs, int32_t *pn_npks,  int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num );
void maxim_peaks_above_min_height( int32_t *pn_locs, int32_t *pn_npks,  int32_t *pn_x, int32_t n_size, int32_t n_min_height );
void maxim_remove_close_peaks( int32_t *pn_locs, int32_t *pn_npks,   int32_t  *pn_x, int32_t n_min_distance );
void maxim_sort_ascend( int32_t *pn_x, int32_t n_size );
void maxim_sort_indices_descend(  int32_t  *pn_x, int32_t *pn_indx, int32_t n_size);

#endif

下面是计算心率和血氧的函数

max30102_calculation.c

复制代码
#include "max30102_calculation.h"
#include "stdio.h"
#include "oled.h"

// 初始化数据结构
MAX30102_Data max30102_data = {
    .buffer_length = BUFFER_LENGTH, // 缓冲区长度
    .min_value = 0x3FFFF,           // 初始最小值
    .max_value = 0,                 // 初始最大值
    .brightness = 0                 // 初始亮度值
};

/********************************** 滤波算法的全局变量 *************************************************/

// 滑动平均滤波器缓存
int hr_buffer[WINDOW_SIZE] = {0};   // 用于心率的滑动窗口
int spo2_buffer[WINDOW_SIZE] = {0}; // 用于血氧的滑动窗口
int hr_index = 0, spo2_index = 0;   // 缓存索引

// 低通滤波器的先前值
int prev_hr = 0, prev_spo2 = 0; // 上一时刻的平滑心率和血氧值

/********************************** 滤波算法的全局变量 *************************************************/

/********************************** 滤波算法 *************************************************/
// 滑动平均滤波函数
int SmoothData(int new_value, int *buffer, int *index)
{
  buffer[*index] = new_value;          // 更新滑动窗口
  *index = (*index + 1) % WINDOW_SIZE; // 循环索引

  int sum = 0;
  for (int i = 0; i < WINDOW_SIZE; i++)
  {
    sum += buffer[i]; // 计算窗口内的平均值
  }

  return sum / WINDOW_SIZE; // 返回平均值
}

// 低通滤波函数
int LowPassFilter(int new_value, int previous_filtered_value)
{
  return (int)(ALPHA * new_value + (1 - ALPHA) * previous_filtered_value); // 按公式进行滤波
}

/********************************** 滤波算法 *************************************************/

// 读取MAX30102传感器数据,并进行心率补偿
void MAX30102_Read_Data(void)
{
  volatile uint32_t un_prev_data;
  uint8_t temp[6];
  // 读取前500个样本,确定信号范围
  for (int i = 0; i < max30102_data.buffer_length; i++)
  {
    while (MAX30102_INT == 1)
      ; // 等待中断信号

    max30102_FIFO_ReadBytes(REG_FIFO_DATA, temp); // 从FIFO读取数据
    max30102_data.red_buffer[i] = (long)((long)((long)temp[0] & 0x03) << 16) | (long)temp[1] << 8 | (long)temp[2];
    max30102_data.ir_buffer[i] = (long)((long)((long)temp[3] & 0x03) << 16) | (long)temp[4] << 8 | (long)temp[5];

    // 更新信号的最小值和最大值
    if (max30102_data.min_value > max30102_data.red_buffer[i])
      max30102_data.min_value = max30102_data.red_buffer[i];
    if (max30102_data.max_value < max30102_data.red_buffer[i])
      max30102_data.max_value = max30102_data.red_buffer[i];
  }

  // 更新上一数据点
  un_prev_data = max30102_data.red_buffer[max30102_data.buffer_length - 1];

  // 调整补偿值:减少补偿并应用在心率计算前
  max30102_data.heart_rate += HEART_RATE_COMPENSATION; // 可减少补偿值
}

// 计算心率和血氧值
void Calculate_Heart_Rate_and_SpO2(void)
{
  maxim_heart_rate_and_oxygen_saturation(max30102_data.ir_buffer, max30102_data.buffer_length,
                                         max30102_data.red_buffer, &max30102_data.spO2, &max30102_data.spO2_valid,
                                         &max30102_data.heart_rate, &max30102_data.heart_rate_valid);
}

// 更新信号的最小值和最大值,并应用滤波
void Update_Signal_Min_Max(void)
{
  uint32_t un_prev_data = max30102_data.red_buffer[max30102_data.buffer_length - 1];

  for (int i = 100; i < max30102_data.buffer_length; i++)
  {
    // 移位数据
    max30102_data.red_buffer[i - 100] = max30102_data.red_buffer[i];
    max30102_data.ir_buffer[i - 100] = max30102_data.ir_buffer[i];

    // 低通滤波处理
    max30102_data.red_buffer[i - 100] = LowPassFilter(max30102_data.red_buffer[i - 100], un_prev_data);
    max30102_data.ir_buffer[i - 100] = LowPassFilter(max30102_data.ir_buffer[i - 100], un_prev_data);

    // 滑动平均滤波
    max30102_data.red_buffer[i - 100] = SmoothData(max30102_data.red_buffer[i - 100], hr_buffer, &hr_index);
    max30102_data.ir_buffer[i - 100] = SmoothData(max30102_data.ir_buffer[i - 100], spo2_buffer, &spo2_index);

    // 更新最小值和最大值
    if (max30102_data.min_value > max30102_data.red_buffer[i - 100])
      max30102_data.min_value = max30102_data.red_buffer[i - 100];
    if (max30102_data.max_value < max30102_data.red_buffer[i - 100])
      max30102_data.max_value = max30102_data.red_buffer[i - 100];
  }
}

// 显示相关字符串
uint8_t dis_hr = 0;   // 显示的心率值
uint8_t dis_spo2 = 0; // 显示的血氧值
char hr_str[20];      // 心率显示字符串
char spo2_str[20];    // 血氧显示字符串

// 数据处理与显示
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();
}

max30102_calculation.h

复制代码
#ifndef __MAX30102_CALCULATION__
#define __MAX30102_CALCULATION__

#include "stm32f1xx_hal.h"
#include "max30102.h"

#define HEART_RATE_COMPENSATION 10 // 心率补偿值,每次补偿固定的心率值
#define WINDOW_SIZE 20 // 滑动窗口大小
#define ALPHA 0.05      // 低通滤波的滤波系数

// 定义常量和数据结构
#define MAX_BRIGHTNESS 255
#define INTERRUPT_REG 0X00
#define BUFFER_LENGTH 500 // 数据缓存长度



// MAX30102 数据结构
typedef struct
{
  uint32_t ir_buffer[BUFFER_LENGTH];  // 红外LED数据(用于血氧计算)
  uint32_t red_buffer[BUFFER_LENGTH]; // 红色LED数据(用于心率计算)
  int32_t spO2;                       // 血氧饱和度
  int8_t spO2_valid;                  // 血氧有效性指示
  int32_t heart_rate;                 // 心率
  int8_t heart_rate_valid;            // 心率有效性指示
  int32_t min_value;                  // 信号最小值
  int32_t max_value;                  // 信号最大值
  int32_t prev_data;                  // 上一数据点
  int32_t brightness;                 // 信号亮度(用于心率计算)
  uint32_t buffer_length;             // 数据缓冲区长度
} MAX30102_Data;

void MAX30102_Read_Data(void);
void Calculate_Heart_Rate_and_SpO2(void);
void Update_Signal_Min_Max(void);
extern MAX30102_Data max30102_data;
void Process_And_Display_Data(void);

extern uint8_t dis_hr;   // 显示的心率值
extern uint8_t dis_spo2; // 显示的血氧值


#endif

注意事项

DS18B20温度传感器

简介

DS18B20 是一款由 ​Dallas Semiconductor(现为 Maxim Integrated)​ ​ 生产的 ​单线数字温度传感器。它因其独特的"一线总线"接口和数字化输出,在各类测温场合中得到广泛应用

  • 独特的单线接口​:与微处理器连接时仅需一条数据线即可实现双向通讯,大大简化了硬件连接。

  • 数字信号输出​:直接输出数字温度信号,抗干扰能力强,提高了系统可靠性。

  • 宽测温范围与高精度 ​:测温范围 ​​-55℃~+125℃ ,在 ​​-10℃~+85℃ ​ 范围内精度可达 ​**±0.5℃**。

  • 可编程分辨率 ​:用户可根据需要选择 ​9~12位 ​ 的分辨率,对应的最小分辨温度分别为 ​**0.5℃、0.25℃、0.125℃ 和 0.0625℃**​

  • 多种供电方式 ​:支持 ​3.0V~5.5V​ 的电源电压范围,可使用数据线寄生供电,也支持外部电源供电

  • 多点组网能力​:多个 DS18B20 可以并联在唯一的三线上,实现组网多点测温,每个器件有唯一的64位序列号

  • 无需外围元件​:在使用中不需要任何外围元件,所有传感元件及转换电路均集成在集成电路内

代码

DS18B20.c

复制代码
#include "ds18b20.h"
#include "stm32f1xx_hal.h" // 根据你的具体STM32系列调整,例如 stm32f4xx_hal.h

/* 私有函数声明 */
static void DS18B20_GPIO_Output(void);
static void DS18B20_GPIO_Input(void);
static uint8_t DS18B20_Reset(void);
static void DS18B20_WriteBit(uint8_t bit);
static uint8_t DS18B20_ReadBit(void);
static void DS18B20_WriteByte(uint8_t byte);
static uint8_t DS18B20_ReadByte(void);

float temperature;


void Delay_us(uint16_t us) {
    us *= (SystemCoreClock / 1000000 / 4); // 粗略计算循环次数(需调整除数校准)
    while (us--) {
        __asm__("nop"); // 执行空操作指令以消耗时间[1,6](@ref)
    }
}

/* 初始化DS18B20 */
uint8_t DS18B20_Init(void) {
    __HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟
    return DS18B20_Reset(); // 复位成功返回0,失败返回1[1,8](@ref)
}

/* 配置数据引脚为推挽输出 */
static void DS18B20_GPIO_Output(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = DS18B20_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出[3](@ref)
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct);
}

/* 配置数据引脚为上拉输入 */
static void DS18B20_GPIO_Input(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = DS18B20_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式
    GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉输入[1](@ref)
    HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct);
}

/* 复位DS18B20 */
static uint8_t DS18B20_Reset(void) {
    uint8_t presence = 0;
    DS18B20_GPIO_Output(); // 设置为输出模式
    
    /* 主机拉低总线至少480us */
    HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET);
    Delay_us(500); // 实际应使用微秒延时,这里简化示意
    
    /* 释放总线,主机设置为输入模式 */
    DS18B20_GPIO_Input();
    Delay_us(100); // 短暂延时等待DS18B20响应
    
    /* 检测DS18B20的存在脉冲(60-240us低电平) */
    if (HAL_GPIO_ReadPin(DS18B20_PORT, DS18B20_PIN) == GPIO_PIN_RESET) {
        presence = 0; // 检测到存在脉冲
    } else {
        presence = 1; // 未检测到存在脉冲
    }
    
    Delay_us(100); // 等待复位完成
    return presence;
}

/* 向DS18B20写入一位 */
static void DS18B20_WriteBit(uint8_t bit) {
    DS18B20_GPIO_Output(); // 设置为输出模式
    
    /* 写时序起始:主机拉低总线至少1us */
    HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET);
    Delay_us(5); // 实际应使用微秒延时
    
    /* 根据要写的位设置总线电平 */
    if (bit) {
        HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET);
    }
    
    Delay_us(100); // 保持至少60us的写时序
    
    /* 释放总线 */
    HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET);
}

/* 从DS18B20读取一位 */
static uint8_t DS18B20_ReadBit(void) {
    uint8_t bit = 0;
    DS18B20_GPIO_Output(); // 设置为输出模式
    
    /* 读时序起始:主机拉低总线至少1us */
    HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET);
    Delay_us(5); // 实际应使用微秒延时
    
    /* 释放总线,设置为输入模式 */
    DS18B20_GPIO_Input();
    Delay_us(10); // 短暂延时后采样
    
    /* 读取总线电平 */
    if (HAL_GPIO_ReadPin(DS18B20_PORT, DS18B20_PIN) == GPIO_PIN_SET) {
        bit = 1;
    }
    
    Delay_us(100); // 完成读时序
    return bit;
}

/* 向DS18B20写入一个字节(低位优先) */
static void DS18B20_WriteByte(uint8_t byte) {
    for (uint8_t i = 0; i < 8; i++) {
        DS18B20_WriteBit(byte & 0x01);
        byte >>= 1; // 移位准备写下一位
    }
}

/* 从DS18B20读取一个字节(低位优先) */
static uint8_t DS18B20_ReadByte(void) {
    uint8_t byte = 0;
    for (uint8_t i = 0; i < 8; i++) {
        byte |= (DS18B20_ReadBit() << i); // 读取位并组合成字节
    }
    return byte;
}

/* 启动温度转换 */
void DS18B20_StartConversion(void) {
    DS18B20_Reset();
    DS18B20_WriteByte(0xCC); // 跳过ROM命令
    DS18B20_WriteByte(0x44); // 启动温度转换命令
}

/* 读取温度值 */
void DS18B20_ReadTemp(void)
{
    uint8_t temp_l, temp_h;
    int16_t temp_raw;
    
    /* 启动温度转换并等待完成 */
    DS18B20_StartConversion();
    HAL_Delay(750); // 等待转换完成(12位分辨率时最多750ms)
    
    /* 重新初始化总线并读取温度值 */
    DS18B20_Reset();
    DS18B20_WriteByte(0xCC); // 跳过ROM命令
    DS18B20_WriteByte(0xBE); // 读取暂存器命令
    
    /* 读取温度值的低字节和高字节 */
    temp_l = DS18B20_ReadByte();
    temp_h = DS18B20_ReadByte();
    
    /* 组合成16位原始温度值 */
    temp_raw = (temp_h << 8) | temp_l;
    
    /* 转换为实际温度值(12位分辨率,0.0625°C/LSB) */
    temperature = (float)temp_raw * 0.0625f;
    
}

DS18B20.h

复制代码
#ifndef __DS18B20_H
#define __DS18B20_H

#include "main.h" // 确保包含必要的HAL库头文件

/* 引脚定义 */
#define DS18B20_PORT GPIOB
#define DS18B20_PIN GPIO_PIN_12

extern float temperature;


/* 函数声明 */
uint8_t DS18B20_Init(void);
void DS18B20_ReadTemp(void);
void DS18B20_StartConversion(void);

#endif

注意事项

相关推荐
lingzhilab4 小时前
零知IDE——基于STM32F407VET6和MCP2515实现CAN通信与数据采集
stm32·单片机·嵌入式硬件
明飞19876 小时前
android-USB-STM32
stm32
GilgameshJSS7 小时前
STM32H743-ARM例程2-UART命令控制LED
arm开发·stm32·单片机·嵌入式硬件
TDengine (老段)11 小时前
TDengine 聚合函数 VAR_POP 用户手册
大数据·数据库·sql·物联网·时序数据库·tdengine·涛思数据
糖糖单片机设计11 小时前
硬件开发_基于STM32单片机的汽车急控系统
stm32·单片机·嵌入式硬件·物联网·汽车·51单片机
柱子jason14 小时前
使用IOT-Tree Server通过MC协议连接三菱Q系列PLC
物联网·自动化·三菱plc·通信
阿华学长单片机设计14 小时前
【开源】基于STM32的智能车尾灯
stm32·单片机·嵌入式硬件
编程墨客16 小时前
STM32与Modbus RTU协议实战开发指南-fc3ab6a453
stm32·单片机·嵌入式硬件
LeenixP16 小时前
STM32的VSCode下开发环境搭建
vscode·stm32·单片机·嵌入式硬件·arm