项目简介
基于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, ®, 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