硬件连接如下:
先来看OLED驱动
OLED是比较常用的器件,网上的驱动程序很多,直接COPY一下,P0_16和P0_17引脚
#include "oled.h" #include "stdlib.h" #include "string.h" #include <stdint.h> #include "board.h" #include "mxc_device.h" #include "i2c.h" #define SSD1306_ADDR 0x3C // SSD1306 I2C 地址 #define SSD1306_WIDTH 128 #define SSD1306_HEIGHT 64 //OLED显存总共分为4页 //每页8行,一行128个像素点 //OLED的显存 //存放格式如下. //[0]0 1 2 3 ... 127 (0~7)行 //[1]0 1 2 3 ... 127 (8~15)行 //[2]0 1 2 3 ... 127 (16~23)行 //[3]0 1 2 3 ... 127 (24~31)行 //数组每个bit存储OLED每个像素点的颜色值(1-亮(白色),0-灭(黑色)) //每个数组元素表示1列8个像素点,一共128列 uint8_t Flash_OLED_Buffer[1025] = {0}; uint8_t * OLED_buffer = Flash_OLED_Buffer+1 ; #define I2C_MASTER MXC_I2C1 // SCL P0_16; SDA P0_17 #define I2C_SCL_PIN 16 #define I2C_SDA_PIN 17 #define I2C_FREQ 400000 // 100kHZ mxc_i2c_req_t reqMaster; //声明结构体 void i2cm_Init(void) { int error; //Setup the I2CM error = MXC_I2C_Init(I2C_MASTER, 1, 0); if (error != E_NO_ERROR) { printf("-->I2C Master Initialization failed, error:%d\n", error); return; } else { printf("\n-->I2C Master Initialization Complete\n"); } printf("-->Scanning started\n"); MXC_I2C_SetFrequency(I2C_MASTER, I2C_FREQ); reqMaster.i2c = I2C_MASTER; reqMaster.addr = 0; reqMaster.tx_buf = NULL; reqMaster.tx_len = 0; reqMaster.rx_buf = NULL; reqMaster.rx_len = 0; reqMaster.restart = 0; reqMaster.callback = NULL; } /******************************************************************* * @name :void OLED_WR_Byte(unsigned dat,unsigned cmd) * @date :2018-08-27 * @function :Write a byte of content to the OLED screen * @parameters :dat:Content to be written cmd:0-write command 1-write data * @retvalue :None ********************************************************************/ void OLED_WR_Byte(unsigned char dat,unsigned char cmd) { unsigned char i2c_tx_buff[2]; int error; if(cmd) { i2c_tx_buff[0] = 0x40; } else { i2c_tx_buff[0] = 0x00; } i2c_tx_buff[1] = dat; reqMaster.tx_buf = i2c_tx_buff; reqMaster.addr = SSD1306_ADDR; reqMaster.tx_len = 2; error = MXC_I2C_MasterTransaction(&reqMaster); if (error != E_NO_ERROR) { printf("-->I2C send failed, error:%d\n", error); return; } } /******************************************************************* * @name :void OLED_Set_Pos(unsigned char x, unsigned char y) * @date :2018-08-27 * @function :Set coordinates in the OLED screen * @parameters :x:x coordinates y:y coordinates * @retvalue :None ********************************************************************/ void OLED_Set_Pos(unsigned char x, unsigned char y) { OLED_WR_Byte(YLevel+y/OLED_PAGE_SIZE,OLED_CMD); OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD); OLED_WR_Byte((x&0x0f),OLED_CMD); } /******************************************************************* * @name :void OLED_Display_On(void) * @date :2018-08-27 * @function :Turn on OLED display * @parameters :None * @retvalue :None ********************************************************************/ void OLED_Display_On(void) { OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON } /******************************************************************* * @name :void OLED_Display_Off(void) * @date :2018-08-27 * @function :Turn off OLED display * @parameters :None * @retvalue :None ********************************************************************/ void OLED_Display_Off(void) { OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令 OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF } /******************************************************************* * @name :void OLED_Set_Pixel(unsigned char x, unsigned char y,unsigned char color) * @date :2018-08-27 * @function :set the value of pixel to RAM * @parameters :x:the x coordinates of pixel y:the y coordinates of pixel color:the color value of the point 1-white 0-black * @retvalue :None ********************************************************************/ void OLED_Set_Pixel(unsigned char x, unsigned char y,unsigned char color) { if(color) { OLED_buffer[(y/OLED_PAGE_SIZE)*WIDTH+x]|= (1<<(y%OLED_PAGE_SIZE))&0xff; } else { OLED_buffer[(y/OLED_PAGE_SIZE)*WIDTH+x]&= ~((1<<(y%OLED_PAGE_SIZE))&0xff); } } /******************************************************************* * @name :void OLED_Display(void) * @date :2018-08-27 * @function :Display in OLED screen * @parameters :None * @retvalue :None ********************************************************************/ void OLED_Display(void) { OLED_WR_Byte(0x21, 0); // 设置列地址 OLED_WR_Byte(0x00, 0); // 起始列地址 OLED_WR_Byte(SSD1306_WIDTH - 1, 0); // 结束列地址 OLED_WR_Byte(0x22, 0); // 设置页地址 OLED_WR_Byte(0x00, 0); // 起始页地址 OLED_WR_Byte(OLED_PAGE_SIZE - 1, 0); // 结束页地址 Flash_OLED_Buffer[0] = 0x40; reqMaster.tx_buf = Flash_OLED_Buffer; reqMaster.tx_len = 1025; reqMaster.addr = SSD1306_ADDR; MXC_I2C_MasterTransaction(&reqMaster); } /******************************************************************* * @name :void OLED_Clear(unsigned dat) * @date :2018-08-27 * @function :clear OLED screen * @parameters :dat:0-Display full black 1-Display full white * @retvalue :None ********************************************************************/ void OLED_Clear(unsigned char dat) { if(dat) { memset(OLED_buffer,0xff,SSD1306_WIDTH * OLED_PAGE_SIZE); } else { memset(OLED_buffer,0,SSD1306_WIDTH * OLED_PAGE_SIZE); } } void OLED_Init(void) { // delay_ms(200); i2cm_Init(); ///**************初始化SSD1306*****************/ OLED_WR_Byte(0xAE, 0); // 关闭显示 OLED_WR_Byte(0x20, 0); // 设置内存寻址模式 OLED_WR_Byte(0x00, 0); // 水平寻址模式 OLED_WR_Byte(0xB0, 0); // 设置页地址 OLED_WR_Byte(0xC8, 0); // 设置扫描方向 OLED_WR_Byte(0x00, 0); // 设置列地址低 4 位 OLED_WR_Byte(0x10, 0); // 设置列地址高 4 位 OLED_WR_Byte(0x40, 0); // 设置显示起始行 OLED_WR_Byte(0x81, 0); // 设置对比度 OLED_WR_Byte(0xFF, 0); // 最大对比度 OLED_WR_Byte(0xA1, 0); // 设置段重映射 OLED_WR_Byte(0xA6, 0); // 设置正常显示 OLED_WR_Byte(0xA8, 0); // 设置多路复用率 OLED_WR_Byte(0x3F, 0); // 1/64 多路复用 OLED_WR_Byte(0xA4, 0); // 恢复整体显示 OLED_WR_Byte(0xD3, 0); // 设置显示偏移 OLED_WR_Byte(0x00, 0); // 无偏移 OLED_WR_Byte(0xD5, 0); // 设置时钟分频比/振荡器频率 OLED_WR_Byte(0xF0, 0); // 设置分频比 OLED_WR_Byte(0xD9, 0); // 设置预充电周期 OLED_WR_Byte(0x22, 0); // 设置预充电周期 OLED_WR_Byte(0xDA, 0); // 设置 COM 引脚硬件配置 OLED_WR_Byte(0x12, 0); // 设置 COM 引脚硬件配置 OLED_WR_Byte(0xDB, 0); // 设置 VCOMH 取消选择级别 OLED_WR_Byte(0x20, 0); // 设置 VCOMH 取消选择级别 OLED_WR_Byte(0x8D, 0); // 设置电荷泵 OLED_WR_Byte(0x14, 0); // 启用电荷泵 OLED_WR_Byte(0xAF, 0); // 开启显示 }
MAX30102使用模拟I2C,引脚使用P2_6和P2_7。
#include "max30102.h" #include "mxc_device.h" #include "xiic.h" #include "stdlib.h" #include "stdio.h" #include "board.h" #include "max78000.h" /*define ---------------------------------------------------------------------*/ #define max30100_WR_address 0x57<<1 void hardIIC_init(void) { IIC_GPIO_INIT(); } uint8_t max30102_Bus_Write(uint8_t Register_Address, uint8_t Word_Data) { uint8_t data[1]; data[0] = Word_Data; return IIC_Write_Array(max30100_WR_address,Register_Address,data,1); } uint8_t max30102_Bus_Read(uint8_t Register_Address) { return IIC_Read_Byte(max30100_WR_address,Register_Address); } void max30102_init(void) { hardIIC_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 } void max30102_reset(void) { max30102_Bus_Write(REG_MODE_CONFIG, 0x40); // max30102_Bus_Write(REG_MODE_CONFIG, 0x40); } void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led) { uint32_t un_temp; unsigned char uch_temp; unsigned char ach_i2c_data[6]; *pun_red_led = 0; *pun_ir_led = 0; // read and clear status register max30102_Bus_Read(REG_INTR_STATUS_1); max30102_Bus_Read(REG_INTR_STATUS_2); IIC_Read_Array(max30100_WR_address,REG_FIFO_DATA,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] if(*pun_red_led<=10000) { *pun_red_led = 0; } if(*pun_ir_led<=10000) { *pun_ir_led = 0; } }
再来就是心率读取和显示
#include "algorithm.h" #include "max30102.h" #include "stdio.h" #include "gui.h" #include "oled.h" #include "blood.h" #include "math.h" #include "FreeRTOS.h" #include "task.h" #include "semphr.h" uint16_t g_fft_index = 0; //fft输入输出下标 struct compx s1[FFT_N+16]; //FFT输入和输出:从S[1]开始存放,根据大小自己定义 struct compx s2[FFT_N+16]; //FFT输入和输出:从S[1]开始存放,根据大小自己定义 static uint16_t sample_index = 0; struct { float Hp ; //血红蛋白 float HpO2; //氧合血红蛋白 }g_BloodWave;//血液波形数据 BloodData g_blooddata = {0}; //血液数据存储 #define CORRECTED_VALUE 47 //标定血液氧气含量 // 同步机制 SemaphoreHandle_t xBufferMutex; TaskHandle_t xDataProcessingHandle; // 新增:保存数据处理任务句柄 // 数据处理函数声明 void process_buffer(); // =============== 数据采集任务 =============== void vTaskDataCollection(void *pvParameters) { uint32_t fifo_red, fifo_ir; int num_samples = 0; max30102_reset(); max30102_init(); printf("start max30102\n"); for (;;) { num_samples = max30102_Bus_Read(REG_OVF_COUNTER)& 0x0F; if (num_samples == 0) { vTaskDelay(50); // 控制采样率 } else { for(int i = 0; i < num_samples; i++) { maxim_max30102_read_fifo(&fifo_red, &fifo_ir); // 写入当前缓冲区 s1[sample_index].real = fifo_red; s2[sample_index].real = fifo_ir; s1[sample_index].imag = 0; s2[sample_index].imag = 0; sample_index++; // 缓冲满时切换并通知 if (sample_index >= FFT_N) { sample_index = 0; // xTaskNotifyGive(xDataProcessingHandle); process_buffer(); } } } } } // 数据处理函数 - 适配原有血液信息转换逻辑 void process_buffer() { float n_denom; uint16_t i; float dc_red =0; float dc_ir =0; float ac_red =0; float ac_ir =0; for (i=0 ; i<FFT_N ; i++ ) { dc_red += s1[i].real ; dc_ir += s2[i].real ; } dc_red =dc_red/FFT_N ; dc_ir =dc_ir/FFT_N ; for (i=0 ; i<FFT_N ; i++ ) { s1[i].real = s1[i].real - dc_red ; s2[i].real = s2[i].real - dc_ir ; } for(i = 1;i < FFT_N-1;i++) { n_denom= ( s1[i-1].real + 2*s1[i].real + s1[i+1].real); s1[i].real= n_denom/4.00; n_denom= ( s2[i-1].real + 2*s2[i].real + s2[i+1].real); s2[i].real= n_denom/4.00; } //八点平均滤波 for(i = 0;i < FFT_N-8;i++) { n_denom= ( s1[i].real+s1[i+1].real+ s1[i+2].real+ s1[i+3].real+ s1[i+4].real+ s1[i+5].real+ s1[i+6].real+ s1[i+7].real); s1[i].real= n_denom/8.00; n_denom= ( s2[i].real+s2[i+1].real+ s2[i+2].real+ s2[i+3].real+ s2[i+4].real+ s2[i+5].real+ s2[i+6].real+ s2[i+7].real); s2[i].real= n_denom/8.00; //UsartPrintf(USART_DEBUG,"%f\r\n",s1[i].real); } g_fft_index = 0; FFT(s1); FFT(s2); //解平方 //UsartPrintf(USART_DEBUG,"开始FFT算法****************************************************************************************************\r\n"); for(i = 0;i < FFT_N;i++) { s1[i].real=sqrtf(s1[i].real*s1[i].real+s1[i].imag*s1[i].imag); s1[i].real=sqrtf(s2[i].real*s2[i].real+s2[i].imag*s2[i].imag); } //计算交流分量 for (i=1 ; i<FFT_N ; i++ ) { ac_red += s1[i].real ; ac_ir += s2[i].real ; } int s1_max_index = find_max_num_index(s1, 30); int s2_max_index = find_max_num_index(s2, 30); float Heart_Rate = 60.00 * ((100.0 * s1_max_index )/ 512.00)+20; g_blooddata.heart = Heart_Rate; float R = (ac_ir*dc_red)/(ac_red*dc_ir); float sp02_num =-45.060*R*R+ 30.354 *R + 94.845; g_blooddata.SpO2 = sp02_num; printf("Heart:%d\r\n",(int)Heart_Rate) ; printf("SpO2:%.2f\r\n",sp02_num); } // =============== 初始化函数(需在main中调用) =============== void Blood_Init(void) { // 创建互斥锁 xTaskCreate( vTaskDataCollection, "DataCollection", 4096, NULL, // 直接传递句柄 tskIDLE_PRIORITY + 1, NULL); }
相关内容整合后在main中调用就可以正常显示结果了
#include <stdio.h> #include <stdint.h> #include <string.h> #include "FreeRTOSConfig.h" #include "FreeRTOS.h" #include "task.h" #include "semphr.h" #include "board.h" #include "mxc_device.h" #include "mxc_delay.h" #include "nvic_table.h" #include "i2c.h" #include "oled.h" #include "gui.h" #include "rtc.h" #include "max30102.h" #include "blood.h" /***** Definitions *****/ #define MSEC_TO_RSSA(x) \ (0 - ((x * 4096) / \ 1000)) /* Converts a time in milleseconds to the equivalent RSSA register value. */ #define SECS_PER_MIN 60 #define SECS_PER_HR (60 * SECS_PER_MIN) #define SECS_PER_DAY (24 * SECS_PER_HR) TaskHandle_t xTask1Handle = NULL; extern BloodData g_blooddata ; /* 任务函数声明 */ void vTask1(void *pvParameters); // 判断是否为闰年 int isLeapYear(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } // 获取指定年份中每个月的天数 void getDaysInMonth(int year, int daysInMonth[]) { daysInMonth[0] = 0; // 不使用索引0 daysInMonth[1] = 31; daysInMonth[2] = isLeapYear(year) ? 29 : 28; // 闰年2月29天 daysInMonth[3] = 31; daysInMonth[4] = 30; daysInMonth[5] = 31; daysInMonth[6] = 30; daysInMonth[7] = 31; daysInMonth[8] = 31; daysInMonth[9] = 30; daysInMonth[10] = 31; daysInMonth[11] = 30; daysInMonth[12] = 31; } // 将从2000年1月1日起的时间戳转换为日期时间 void timestampToDateTime(long timestamp, int *year, int *month, int *day, int *hour, int *minute, int *second) { long sec = timestamp; // 计算时、分、秒(已有代码) // 计算天数(从2000年1月1日起) long dayCount = sec / SECS_PER_DAY; sec -= dayCount * SECS_PER_DAY; // 剩余秒数用于计算时、分、秒 // 计算时、分、秒 *hour = sec / SECS_PER_HR; sec -= *hour * SECS_PER_HR; *minute = sec / SECS_PER_MIN; sec -= *minute * SECS_PER_MIN; *second = sec; // 计算天数(从2000年1月1日起) // 计算年份 *year = 2000; while (1) { int daysInYear = isLeapYear(*year) ? 366 : 365; if (dayCount < daysInYear) break; dayCount -= daysInYear; (*year)++; } // 计算月份和日期 int daysInMonth[13]; getDaysInMonth(*year, daysInMonth); *month = 1; while (*month <= 12) { if (dayCount < daysInMonth[*month]) break; dayCount -= daysInMonth[*month]; (*month)++; } *day = (int)dayCount + 1; // +1是因为天数从0开始计数,而日期从1开始 } // 计算从2000年1月1日起的天数 int daysSince2000(int year, int month, int day) { int days = 0; int i; // 计算完整年份的天数 for (i = 2000; i < year; i++) { days += isLeapYear(i) ? 366 : 365; } // 计算当年到目标月份的天数 int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (isLeapYear(year)) { daysInMonth[2] = 29; // 闰年2月29天 } for (i = 1; i < month; i++) { days += daysInMonth[i]; } // 加上当月的天数 days += day; return days; } void printTime(void) { int year, month, day, hour, minute, second,err; uint32_t rtc_readout; uint8_t buff[24]; do { err = MXC_RTC_GetSeconds(&rtc_readout); } while (err != E_NO_ERROR); // 转换时间戳为日期时间 timestampToDateTime(rtc_readout, &year, &month, &day, &hour, &minute, &second); printf("timestamp %ld datetime: %04d-%02d-%02d %02d:%02d:%02d\n", rtc_readout, year, month, day, hour, minute, second); sprintf(buff, "%02d:%02d:%02d",hour, minute, second); OLED_Clear(0); GUI_ShowString(16,0,buff,16,0); sprintf(buff, "HpO2:%.2f", g_blooddata.SpO2); GUI_ShowString(16,16,buff,16,0); sprintf(buff, "Hear::%d", (int)g_blooddata.heart); GUI_ShowString(16,32,buff,16,0); OLED_Display(); } /* 任务1的实现 */ void vTask1(void *pvParameters) { OLED_Clear(0); GUI_ShowString(0,0,(uint8_t *)"OLED show",16,0); OLED_Display(); Blood_Init(); /* 任务循环 */ for(;;) { /* 任务要执行的代码 */ LED_On(LED1); /* 延时一段时间 */ printTime(); vTaskDelay(pdMS_TO_TICKS(1000)); /* 延时1秒 */ LED_Off(LED1); vTaskDelay(pdMS_TO_TICKS(1000)); /* 延时1秒 */ } /* 注意:任务函数不应该返回,如果返回则必须调用vTaskDelete(NULL) */ vTaskDelete(NULL); } void show_task(void *pvParameters) ; // ***************************************************************************** int main(void) { printf("start...!\n"); OLED_Init(); // 目标日期:2025年10月05日16:07:00 int year = 2025; int month = 10; int day = 5; int hour = 16; int minute = 7; int second = 0; // 计算从2000年1月1日起的总秒数 int totalDays = daysSince2000(year, month, day); long timestamp = (long)totalDays * SECS_PER_DAY + hour * SECS_PER_HR + minute * SECS_PER_MIN + second; printf("从2000年1月1日00:00:00到%04d-%02d-%02d %02d:%02d:%02d的时间戳为: %ld\n", year, month, day, hour, minute, second, timestamp); if (MXC_RTC_Init(timestamp, 0) != E_NO_ERROR) { printf("Failed RTC Initialization\n"); printf("Example Failed\n"); while (1) {} } if (MXC_RTC_Start() != E_NO_ERROR) { printf("Failed RTC_Start\n"); printf("Example Failed\n"); while (1) {} } printf("RTC started\n"); xTaskCreate( vTask1, /* 任务函数 */ "Task1", /* 任务名称 */ 2048, /* 栈大小 */ NULL, /* 传递给任务函数的参数 */ 1, /* 任务优先级 */ &xTask1Handle /* 任务句柄 */ ); /* 启动调度器 */ printf("start scheduler...!\n"); vTaskStartScheduler(); printf("ERROR: FreeRTOS did not start due to above error!\n"); while (1) { LED_On(LED1); MXC_Delay(500000); LED_Off(LED1); MXC_Delay(500000); } return 0; } //OLED显示任务函数 void show_task(void *pvParameters) { char buff[40]; OLED_Init(); OLED_Clear(0); GUI_ShowString(0,0,(uint8_t *)"OLED show",16,0); OLED_Display(); while(1) { printf("start show\n"); vTaskDelay(600); } }
最终显示效果如下:
TIPS:
所有的程序官方的老师已经都分享出来了,跟着学习就一定可以成功。如果程序看不懂,可以直接在VSCODE进行询问,让他帮忙加加注释。AI真是太方便了