硬件连接如下:


先来看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真是太方便了

我要赚赏金
