一、主页任务
/* Private includes -----------------------------------------------------------*/
//includes
#include "user_TasksInit.h"
#include "user_ScrRenewTask.h"
#include "main.h"
#include "rtc.h"
#include "lvgl.h"
#include "ui_HomePage.h"
#include "ui_ENVPage.h"/* Private typedef -----------------------------------------------------------*//* Private define ------------------------------------------------------------*//* Private variables ---------------------------------------------------------*//* Private function prototypes -----------------------------------------------*//*** @brief homepage time renew task* @param argument: Not used* @retval None*/
void TimeRenewTask(void *argument)
{uint8_t value_strbuf[10];while(1){if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage){/*lv_obj_set_style_text_opa(ui_TimeColonLabel, 0, LV_PART_MAIN | LV_STATE_DEFAULT);osDelay(500);lv_obj_set_style_text_opa(ui_TimeColonLabel, 255, LV_PART_MAIN | LV_STATE_DEFAULT);*///time get and renew the screenRTC_DateTypeDef nowdate;RTC_TimeTypeDef nowtime;HAL_RTC_GetTime(&hrtc,&nowtime,RTC_FORMAT_BIN);//要先gettime,否则更新不了时间HAL_RTC_GetDate(&hrtc,&nowdate,RTC_FORMAT_BIN);if(ui_TimeMinuteValue != nowtime.Minutes){ui_TimeMinuteValue = nowtime.Minutes;sprintf(value_strbuf,"%02d",ui_TimeMinuteValue);lv_label_set_text(ui_TimeMinuteLabel, value_strbuf);}if(ui_TimeHourValue != nowtime.Hours){ui_TimeHourValue = nowtime.Hours;sprintf(value_strbuf,"%2d",ui_TimeHourValue);lv_label_set_text(ui_TimeHourLabel, value_strbuf);}if(ui_DateDayValue != nowdate.Date){ui_DateDayValue = nowdate.Date;ui_DataWeekdayValue = nowdate.WeekDay;sprintf(value_strbuf,"%2d-%02d",ui_DateMonthValue,ui_DateDayValue);lv_label_set_text(ui_DateLabel, value_strbuf);lv_label_set_text(ui_DayLabel, ui_Days[ui_DataWeekdayValue-1]);}if(ui_DateMonthValue != nowdate.Month){ui_DateMonthValue = nowdate.Month;ui_DateDayValue = nowdate.Date;ui_DataWeekdayValue = nowdate.WeekDay;sprintf(value_strbuf,"%2d-%02d",ui_DateMonthValue,ui_DateDayValue);lv_label_set_text(ui_DateLabel, value_strbuf);lv_label_set_text(ui_DayLabel, ui_Days[ui_DataWeekdayValue-1]);}}osDelay(500);}
}/*** @brief homepage check the battery power and other data* @param argument: Not used* @retval None*/
void HomeUpdata_Task(void *argument)
{while(1){uint8_t HomeUpdataStr;if(osMessageQueueGet(HomeUpdata_MessageQueue,&HomeUpdataStr,NULL,0)==osOK){/*//batuint8_t value_strbuf[5];ui_BatArcValue = PowerCalculate();if(ui_BatArcValue>0 && ui_BatArcValue<=100){}else{ui_BatArcValue=0;}//stepsif(!Sensor_MPU_Erro){unsigned long STEPS = 0;if(!Sensor_MPU_Erro)dmp_get_pedometer_step_count(&STEPS);ui_StepNumValue = (uint16_t)STEPS;}//temp and humiif(!Sensor_AHT21_Erro){//temp and humi messurefloat humi,temp;AHT_Read(&humi,&temp);//checkif(temp>-10 && temp<50 && humi>0 && humi<100){ui_EnvTempValue = (int8_t)temp;ui_EnvHumiValue = (int8_t)humi;}}//set textif(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage){//bat set textlv_arc_set_value(ui_BatArc, ui_BatArcValue);sprintf(value_strbuf,"%2d%%",ui_BatArcValue);lv_label_set_text(ui_BatNumLabel, value_strbuf);//step set textsprintf(value_strbuf,"%d",ui_StepNumValue);lv_label_set_text(ui_StepNumLabel, value_strbuf);//send data save message queueuint8_t Datastr = 3;osMessageQueuePut(DataSave_MessageQueue, &Datastr, 0, 1);//humi and temp set textlv_arc_set_value(ui_TempArc, ui_EnvTempValue);lv_arc_set_value(ui_HumiArc, ui_EnvHumiValue);sprintf(value_strbuf,"%d",ui_EnvTempValue);lv_label_set_text(ui_TempNumLabel, value_strbuf);sprintf(value_strbuf,"%d",ui_EnvHumiValue);lv_label_set_text(ui_HumiNumLabel, value_strbuf);}*/}osDelay(500);}
}
这段 C 语言代码包含了两个函数 TimeRenewTask
和 HomeUpdata_Task
,看起来是一个嵌入式项目中的一部分,基于 STM32 相关硬件(从 HAL_RTC_GetTime
等 HAL 库函数推测)以及 LVGL 图形库进行开发,用于在用户界面的首页(ui_HomePage
)上更新时间、日期以及其他如电量、步数、温湿度等相关数据信息,通过判断页面显示情况以及接收消息队列中的消息来决定是否执行相应的数据更新操作。
观察一下主页的ui可以发现涉及 温度 心率 湿度, 电量,时间,日期,周,步数等信息
1.1 头文件包含部分影响
#include "user_TasksInit.h"
#include "user_ScrRenewTask.h"
#include "main.h"
#include "rtc.h"
#include "lvgl.h"
#include "ui_HomePage.h"
#include "ui_ENVPage.h"
从包含的头文件来看,user_TasksInit.h
可能与任务初始化相关,user_ScrRenewTask.h
或许涉及屏幕更新任务相关的功能支持(虽然在这里未体现出明显关联),main.h
提供主程序通用定义,rtc.h
包含实时时钟操作相关函数声明(用于获取时间日期信息),lvgl.h
是 LVGL 图形库头文件用于界面操作,ui_HomePage.h
则针对首页界面元素等进行定义(例如相关显示标签的定义等)
1.2 TimeRenewTask 刷新时间任务
uint8_t value_strbuf[10];
定义了 uint8_t
类型的数组 value_strbuf
,长度为 10,用于存储格式化后的时间、日期等数据字符串,方便后续设置到对应的 LVGL 标签组件上进行显示更新
if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage)
主循环逻辑(while(1)
循环):
整个函数主体处于一个无限循环中,意味着会持续运行检测是否满足时间更新条件并执行相应操作,不断地检查当前显示页面是否为首页(ui_HomePage
),通过 if(ScrRenewStack.Data[ScrRenewStack.Top_Point - 1] == (long long int)&ui_HomePage)
语句来判断,这里 ScrRenewStack
应该是一个用于记录页面切换等相关信息的栈结构,但当前代码对其 Top_Point
的合法性等没有充分检查。
在屏幕刷新任务中不会使得当前栈为空的,它会把第一个成员变成主页
/*lv_obj_set_style_text_opa(ui_TimeColonLabel, 0, LV_PART_MAIN | LV_STATE_DEFAULT);osDelay(500);lv_obj_set_style_text_opa(ui_TimeColonLabel, 255, LV_PART_MAIN | LV_STATE_DEFAULT);*///time get and renew the screenRTC_DateTypeDef nowdate;RTC_TimeTypeDef nowtime;HAL_RTC_GetTime(&hrtc,&nowtime,RTC_FORMAT_BIN);//要先gettime,否则更新不了时间HAL_RTC_GetDate(&hrtc,&nowdate,RTC_FORMAT_BIN);if(ui_TimeMinuteValue != nowtime.Minutes){ui_TimeMinuteValue = nowtime.Minutes;sprintf(value_strbuf,"%02d",ui_TimeMinuteValue);lv_label_set_text(ui_TimeMinuteLabel, value_strbuf);}if(ui_TimeHourValue != nowtime.Hours){ui_TimeHourValue = nowtime.Hours;sprintf(value_strbuf,"%2d",ui_TimeHourValue);lv_label_set_text(ui_TimeHourLabel, value_strbuf);}if(ui_DateDayValue != nowdate.Date){ui_DateDayValue = nowdate.Date;ui_DataWeekdayValue = nowdate.WeekDay;sprintf(value_strbuf,"%2d-%02d",ui_DateMonthValue,ui_DateDayValue);lv_label_set_text(ui_DateLabel, value_strbuf);lv_label_set_text(ui_DayLabel, ui_Days[ui_DataWeekdayValue-1]);}if(ui_DateMonthValue != nowdate.Month){ui_DateMonthValue = nowdate.Month;ui_DateDayValue = nowdate.Date;ui_DataWeekdayValue = nowdate.WeekDay;sprintf(value_strbuf,"%2d-%02d",ui_DateMonthValue,ui_DateDayValue);lv_label_set_text(ui_DateLabel, value_strbuf);lv_label_set_text(ui_DayLabel, ui_Days[ui_DataWeekdayValue-1]);}
时间更新相关操作(注释部分及实际更新逻辑):
/*lv_obj_set_style_text_opa(ui_TimeColonLabel, 0, LV_PART_MAIN | LV_STATE_DEFAULT);osDelay(500);lv_obj_set_style_text_opa(ui_TimeColonLabel, 255, LV_PART_MAIN | LV_STATE_DEFAULT);*/
注释部分的闪烁效果代码:有一段被注释掉的代码,通过 lv_obj_set_style_text_opa
函数对 ui_TimeColonLabel
(推测是时间显示中用于分隔时、分的冒号标签)的透明度进行设置,先设置为 0(透明),经过 osDelay(500)
延迟后再设置为 255(不透明),这可能原本是想实现类似时间冒号闪烁的效果来增强显示的动态感,但目前未启用该功能。
//time get and renew the screenRTC_DateTypeDef nowdate;RTC_TimeTypeDef nowtime;HAL_RTC_GetTime(&hrtc,&nowtime,RTC_FORMAT_BIN);//要先gettime,否则更新不了时间HAL_RTC_GetDate(&hrtc,&nowdate,RTC_FORMAT_BIN);
实际时间更新逻辑:定义了 RTC_DateTypeDef
类型的 nowdate
和 RTC_TimeTypeDef
类型的 nowtime
结构体变量,用于存储通过 HAL_RTC_GetTime
和 HAL_RTC_GetDate
函数获取到的实时时钟的时间和日期信息(这两个函数应该是基于 STM32 的 HAL 库函数,分别用于获取当前时间和日期,且按二进制格式返回,这里调用顺序很重要,如注释中提到要先获取时间才能正确更新时间,可能涉及到硬件寄存器或相关内部机制的顺序要求)。
如果当前的分钟值和ui界面的标签值不一致才会去更新,不然的话60s内一直做更新 耗费不必要资源。这里就是分别对分钟,小时,月日周检查。
if(ui_TimeMinuteValue != nowtime.Minutes){ui_TimeMinuteValue = nowtime.Minutes;sprintf(value_strbuf,"%02d",ui_TimeMinuteValue);lv_label_set_text(ui_TimeMinuteLabel, value_strbuf);}if(ui_TimeHourValue != nowtime.Hours){ui_TimeHourValue = nowtime.Hours;sprintf(value_strbuf,"%2d",ui_TimeHourValue);lv_label_set_text(ui_TimeHourLabel, value_strbuf);}if(ui_DateDayValue != nowdate.Date){ui_DateDayValue = nowdate.Date;ui_DataWeekdayValue = nowdate.WeekDay;sprintf(value_strbuf,"%2d-%02d",ui_DateMonthValue,ui_DateDayValue);lv_label_set_text(ui_DateLabel, value_strbuf);lv_label_set_text(ui_DayLabel, ui_Days[ui_DataWeekdayValue-1]);}if(ui_DateMonthValue != nowdate.Month){ui_DateMonthValue = nowdate.Month;ui_DateDayValue = nowdate.Date;ui_DataWeekdayValue = nowdate.WeekDay;sprintf(value_strbuf,"%2d-%02d",ui_DateMonthValue,ui_DateDayValue);lv_label_set_text(ui_DateLabel, value_strbuf);lv_label_set_text(ui_DayLabel, ui_Days[ui_DataWeekdayValue-1]);}
1.3 HomeUpdata_Task 更新数据,将其他的组件与时间分离了吗?原因是时间不需要等待消息,传感器等数据需要等待消息
void HomeUpdata_Task(void *argument)
{while(1){uint8_t HomeUpdataStr;if(osMessageQueueGet(HomeUpdata_MessageQueue,&HomeUpdataStr,NULL,0)==osOK){/*//batuint8_t value_strbuf[5];ui_BatArcValue = PowerCalculate();if(ui_BatArcValue>0 && ui_BatArcValue<=100){}else{ui_BatArcValue=0;}//stepsif(!Sensor_MPU_Erro){unsigned long STEPS = 0;if(!Sensor_MPU_Erro)dmp_get_pedometer_step_count(&STEPS);ui_StepNumValue = (uint16_t)STEPS;}//temp and humiif(!Sensor_AHT21_Erro){//temp and humi messurefloat humi,temp;AHT_Read(&humi,&temp);//checkif(temp>-10 && temp<50 && humi>0 && humi<100){ui_EnvTempValue = (int8_t)temp;ui_EnvHumiValue = (int8_t)humi;}}//set textif(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage){//bat set textlv_arc_set_value(ui_BatArc, ui_BatArcValue);sprintf(value_strbuf,"%2d%%",ui_BatArcValue);lv_label_set_text(ui_BatNumLabel, value_strbuf);//step set textsprintf(value_strbuf,"%d",ui_StepNumValue);lv_label_set_text(ui_StepNumLabel, value_strbuf);//send data save message queueuint8_t Datastr = 3;osMessageQueuePut(DataSave_MessageQueue, &Datastr, 0, 1);//humi and temp set textlv_arc_set_value(ui_TempArc, ui_EnvTempValue);lv_arc_set_value(ui_HumiArc, ui_EnvHumiValue);sprintf(value_strbuf,"%d",ui_EnvTempValue);lv_label_set_text(ui_TempNumLabel, value_strbuf);sprintf(value_strbuf,"%d",ui_EnvHumiValue);lv_label_set_text(ui_HumiNumLabel, value_strbuf);}*/}osDelay(500);}
}
if(osMessageQueueGet(HomeUpdata_MessageQueue,&HomeUpdataStr,NULL,0)==osOK)
主循环逻辑(while(1)
循环):
同样是一个无限循环结构,持续运行以检测是否有需要更新首页相关数据的情况发生,通过从 HomeUpdata_MessageQueue
消息队列中获取消息并判断返回值是否为 osOK
(表示成功获取消息)来决定是否执行后续的数据更新操作,这里消息队列机制用于协调不同任务间的通信,通知该任务可以进行首页数据更新了。
像这些需要更新到ui标签上的变量都是全局变量,很方便。直接数据处理传给它就行。
注释部分的数据更新逻辑(暂未启用):
//batuint8_t value_strbuf[5];ui_BatArcValue = PowerCalculate();if(ui_BatArcValue>0 && ui_BatArcValue<=100){}else{ui_BatArcValue=0;}
电量相关更新逻辑:代码中有一段关于电量更新的注释部分逻辑,先是定义了 uint8_t
类型的数组 value_strbuf
,长度为 5,用于存储电量百分比的字符串表示。然后通过调用 PowerCalculate
函数(函数具体实现应在其他地方定义,推测是用于计算当前电量的函数)获取电量值并赋值给 ui_BatArcValue
,接着进行电量值的合法性判断,如果电量值在 0 到 100 之间则保留该值,否则将其设置为 0,确保电量显示值在合理范围。
ui_BatArcValue 就是对应的ui界面的标签值
//stepsif(!Sensor_MPU_Erro){unsigned long STEPS = 0;if(!Sensor_MPU_Erro)dmp_get_pedometer_step_count(&STEPS);ui_StepNumValue = (uint16_t)STEPS;}
步数相关更新逻辑:通过判断 !Sensor_MPU_Erro
(表示传感器无错误,Sensor_MPU_Erro
变量应该在别处定义并维护其状态),在无错误情况下调用 dmp_get_pedometer_step_count
函数(同样函数定义在其他地方,用于获取步数信息)获取步数并赋值给 ui_StepNumValue
,实现步数数据的更新,不过这里有重复判断 !Sensor_MPU_Erro
的情况,可考虑优化。
//temp and humiif(!Sensor_AHT21_Erro){//temp and humi messurefloat humi,temp;AHT_Read(&humi,&temp);//checkif(temp>-10 && temp<50 && humi>0 && humi<100){ui_EnvTempValue = (int8_t)temp;ui_EnvHumiValue = (int8_t)humi;}}
温湿度相关更新逻辑:同样在 !Sensor_AHT21_Erro
(表示温湿度传感器无错误)的条件下,调用 AHT_Read
函数(用于读取温湿度传感器数据,具体实现依赖对应的传感器驱动代码)获取温湿度值存储到临时变量 humi
和 temp
中,再对读取到的温湿度值进行合法性检查(温度范围在 -10 到 50 之间,湿度范围在 0 到 100 之间),如果合法则将其转换为合适的类型(int8_t
)赋值给界面显示相关变量 ui_EnvTempValue
和 ui_EnvHumiValue
,完成温湿度数据的更新。
//set textif(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage){//bat set textlv_arc_set_value(ui_BatArc, ui_BatArcValue);sprintf(value_strbuf,"%2d%%",ui_BatArcValue);lv_label_set_text(ui_BatNumLabel, value_strbuf);//step set textsprintf(value_strbuf,"%d",ui_StepNumValue);lv_label_set_text(ui_StepNumLabel, value_strbuf);//send data save message queueuint8_t Datastr = 3;osMessageQueuePut(DataSave_MessageQueue, &Datastr, 0, 1);//humi and temp set textlv_arc_set_value(ui_TempArc, ui_EnvTempValue);lv_arc_set_value(ui_HumiArc, ui_EnvHumiValue);sprintf(value_strbuf,"%d",ui_EnvTempValue);lv_label_set_text(ui_TempNumLabel, value_strbuf);sprintf(value_strbuf,"%d",ui_EnvHumiValue);lv_label_set_text(ui_HumiNumLabel, value_strbuf);}*/
界面显示更新逻辑(基于页面判断):当确认当前显示页面是首页(通过和 TimeRenewTask
中类似的页面栈判断方式)时,进行一系列界面显示更新操作。对于电量,通过 lv_arc_set_value
函数设置电量弧形进度条(ui_BatArc
)的值为 ui_BatArcValue
,并使用 sprintf
和 lv_label_set_text
函数将电量百分比字符串设置到对应的电量数字显示标签(ui_BatNumLabel
)上。对于步数,也是通过 sprintf
和 lv_label_set_text
函数将步数数值转换为字符串并设置到 ui_StepNumLabel
标签上。接着向 DataSave_MessageQueue
消息队列发送一个值为 3 的消息(具体含义和后续处理逻辑需结合项目其他部分看,可能是用于通知其他任务进行数据保存相关操作)。最后对于温湿度,分别通过 lv_arc_set_value
函数设置温度和湿度弧形进度条(ui_TempArc
、ui_HumiArc
)的值为对应的温湿度界面变量值,并使用 sprintf
和 lv_label_set_text
函数将温湿度数值转换为字符串设置到相应的温度、湿度数字显示标签(ui_TempNumLabel
、ui_HumiNumLabel
)上,实现各数据在首页界面上的完整显示更新。
循环延时操作:在循环末尾同样通过 osDelay(500)
函数让任务暂停 500 个时间单位,作用和 TimeRenewTask
中的延时类似,是为了合理控制任务执行频率,避免资源过度占用以及按照合适的周期来更新首页数据。
二、计算器任务
#include <stdio.h>// 包含标准输入输出库
#include <stdlib.h>// 包含标准库,提供一些常用函数
#include <string.h>// 包含字符串处理库
#include "StrCalculate.h"// 包含自定义的头文件,定义了栈结构和操作// 向字符串栈中压入一个字符
uint8_t strput(StrStack_t * st,char strin)
{if(st->Top_Point == 15 - 1)// 如果栈已满{return -1;}// 返回错误码st->strque[st->Top_Point++] = strin; // 将字符压入栈,并更新栈顶指针return 0; // 成功
}
// 从字符串栈中弹出一个字符
uint8_t strdel(StrStack_t * st)
{if(st->Top_Point == 0)// 如果栈为空{return -1;}// 返回错误码//出栈后原来的位置就指向NULL了st->strque[--st->Top_Point] = NULL;// 将栈顶元素设置为NULL,并更新栈顶指针return 0;// 成功
}
// 检查字符串栈是否为空
uint8_t strstack_isEmpty(StrStack_t* st)
{if(st->Top_Point == 0)// 如果栈顶指针为0,即栈为空{return 1;}// 返回1表示真(非空为0)return 0;// 返回0表示假(栈不为空)
}
// 清空字符串栈
void strclear(StrStack_t* sq)
{while(!strstack_isEmpty(sq))// 只要栈不为空{strdel(sq); // 就一直弹出元素}
}
// 向数字栈中压入一个浮点数
uint8_t NumStackPut(NumStack_t * st, float in)
{if(st->Top_Point == CAL_DEPTH - 1)// 如果栈已满{return -1;}// 返回错误码st->data[st->Top_Point++] = in;// 将数字压入栈,并更新栈顶指针return 0;// 成功
}uint8_t NumStackDel(NumStack_t * st)// 从数字栈中弹出一个浮点数
{if(st->Top_Point == 0) // 如果栈为空{return -1;}// 返回错误码st->data[st->Top_Point--] = 0;// 将栈顶元素设置为0,并更新栈顶指针return 0;// 成功
}
// 检查数字栈是否为空
uint8_t NumStack_isEmpty(NumStack_t* st)// 如果栈顶指针为0,即栈为空
{if(st->Top_Point == 0)// 返回1表示真(非空为0){return 1;}return 0;// 返回0表示假(栈不为空)
}
// 清空数字栈
void NumStackClear(NumStack_t* st)
{while(!NumStack_isEmpty(st))// 只要栈不为空{NumStackDel(st);// 就一直弹出元素}
}
// 向符号栈中压入一个字符
uint8_t SymStackPut(SymStack_t * st, char in)
{if(st->Top_Point == CAL_DEPTH - 1)// 如果栈已满{return -1;}// 返回错误码st->data[st->Top_Point++] = in;// 将符号压入栈,并更新栈顶指针return 0;// 成功
}
// 从符号栈中弹出一个字符
uint8_t SymStackDel(SymStack_t * st)// 从符号栈中弹出一个字符
{if(st->Top_Point == 0)// 如果栈为空{return -1;}// 返回错误码st->data[st->Top_Point--] = 0;// 将栈顶元素设置为0,并更新栈顶指针return 0;// 成功
}
// 检查符号栈是否为空
uint8_t SymStack_isEmpty(SymStack_t* st)
{if(st->Top_Point == 0)// 如果栈顶指针为0,即栈为空{return 1;}// 返回1表示真(非空为0)return 0;// 返回0表示假(栈不为空)
}void SymStackClear(SymStack_t* st)// 清空符号栈
{while(!SymStack_isEmpty(st))// 只要栈不为空{SymStackDel(st);// 就一直弹出元素}
}uint8_t SymisHighPriority(char top, char present)// 判断符号是否具有高优先级
{//乘除的优先级最大if(top == '*' || top == '/'){return 1;// 返回1表示真(高优先级)}else if(top == '+')// '+'和'-'的组合{if(present == '-'){return 1;}// 返回1表示真(高优先级)else{return 0;}// 返回0表示假(低优先级)}else if(top == '-')// '-'和'+'的组合{if(present == '+')// 返回1表示真(高优先级){return 1;}else{return 0;}// 返回0表示假(低优先级)}
}
// 执行一次计算操作
void CalculateOne(NumStack_t * numstack, SymStack_t * symstack)
{caldata_t temp;// 定义临时变量temp.datatype = NUMBER_TYPE;// 设置数据类型为数字temp.symbol = NULL;// 设置符号为空//计算数字栈中的顶部两数,结果存到temp中if(symstack->data[symstack->Top_Point-1] == '+')temp.number = (numstack->data[numstack->Top_Point-2]) + (numstack->data[numstack->Top_Point-1]);else if(symstack->data[symstack->Top_Point-1] == '-')temp.number = (numstack->data[numstack->Top_Point-2]) - (numstack->data[numstack->Top_Point-1]);else if(symstack->data[symstack->Top_Point-1] == '*')temp.number = (numstack->data[numstack->Top_Point-2]) * (numstack->data[numstack->Top_Point-1]);else if(symstack->data[symstack->Top_Point-1] == '/')temp.number = (numstack->data[numstack->Top_Point-2]) / (numstack->data[numstack->Top_Point-1]);//运算前两数出栈,运算结果数入栈NumStackDel(numstack); // 弹出第一个数字NumStackDel(numstack);// 弹出第二个数字NumStackPut(numstack,temp.number); // 将结果压入数字栈SymStackDel(symstack);// 弹出符号栈顶元素}
// 分离数字和符号,并进行计算
uint8_t NumSymSeparate(char * str, uint8_t strlen, NumStack_t * NumStack, SymStack_t * SymStack)
{NumStackClear(NumStack);// 清空数字栈SymStackClear(SymStack);// 清空符号栈caldata_t temp,temp_pre;// 定义临时变量char NumBehindPoint_Flag = 0;//数字是否在小数点后,后多少位temp.datatype = NUMBER_TYPE;// 设置数据类型为数字temp.number = 0;// 设置数字为0temp.symbol = NULL;// 设置符号为空temp_pre = temp;// 将temp赋值给temp_pretemp_pre.datatype = SYMBOL_TYPE;if(str[0]>'9' || str[0]<'0')// 如果第一个字符不是数字return 1;//erro //返回错误码1int i; // 定义循环变量for(i=0;i<strlen;i++)// 遍历字符串{if(str[i]=='.')// 如果遇到小数点{temp.datatype = POINT_TYPE;// 设置数据类型为小数点if(temp_pre.datatype == NUMBER_TYPE)// 如果前一个字符是数字{} else{return 2;}// 返回错误码2temp_pre = temp;// 更新temp_pre}if(str[i]<='9' && str[i]>='0') // 如果字符是数字{//溢出报错if(NumStack->Top_Point>CAL_DEPTH || SymStack->Top_Point>CAL_DEPTH){return 3;} // 如果数字栈或符号栈溢出,返回错误码3//读取当前的字符到temp中temp.datatype = NUMBER_TYPE;// 设置数据类型为数字temp.number = (str[i] - '0');// 将字符转换为对应的数字,并存储在temp.number中temp.symbol = NULL;// 设置符号为NULL//如果为连续数字,需要进行进位,将数字栈顶读出进位,再加上现在位,再入栈if(temp_pre.datatype == NUMBER_TYPE){if(!NumBehindPoint_Flag) // 如果不在小数点后,则直接进行十进制进位{temp.number += NumStack->data[NumStack->Top_Point-1] * 10;}else{NumBehindPoint_Flag += 1;// 如果在小数点后,则进行小数进位char i = NumBehindPoint_Flag;while(i--){temp.number /= 10;}// 将之前的数值除以10,为新的小数位腾出位置temp.number += NumStack->data[NumStack->Top_Point-1];// 加上当前位的数值 }NumStackDel(NumStack);// 弹出数字栈顶元素NumStackPut(NumStack,temp.number); // 将新的数值压入数字栈}//当前数字刚好是小数点后一位else if(temp_pre.datatype == POINT_TYPE){ // 当前数字刚好是小数点后一位NumBehindPoint_Flag = 1;// 标记现在位于小数点后temp.number /= 10;// 将数值除以10,为小数点后第一位腾出位置temp.number += NumStack->data[NumStack->Top_Point-1];// 加上当前位的数值NumStackDel(NumStack);// 弹出数字栈顶元素NumStackPut(NumStack,temp.number);// 将新的数值压入数字栈}//前一位不是数字或小数点,现在读取的这一位是数字,直接入栈 else{NumStackPut(NumStack,temp.number);// 将数值直接压入数字栈}temp_pre = temp; // 更新temp_pre为当前temp的值}// 如果字符是运算符else if(str[i] == '+' || str[i] == '-' || str[i] == '*' || str[i] == '/') {//溢出报错if(NumStack->Top_Point>CAL_DEPTH || SymStack->Top_Point>CAL_DEPTH){return 4;}// 如果数字栈或符号栈溢出,返回错误码4//读取当前的字符到temp中temp.datatype = SYMBOL_TYPE; // 设置数据类型为符号temp.symbol = str[i];// 将当前字符设置为符号temp.number = 0; // 设置数值为0NumBehindPoint_Flag = 0;//小数点计算已经结束//重复输入了运算符号if(temp_pre.datatype == SYMBOL_TYPE)// 重复输入了运算符号{ return 5 ;//erro// 返回错误码5} else{ // 检查是否需要先计算栈顶的表达式if((!SymStack_isEmpty(SymStack)) && SymisHighPriority(SymStack->data[SymStack->Top_Point-1],temp.symbol)){CalculateOne(NumStack, SymStack);// 执行一次计算操作SymStackPut(SymStack,temp.symbol);// 将当前符号压入符号栈}else{//符号压入符号栈SymStackPut(SymStack,temp.symbol);// 将当前符号压入符号栈}temp_pre = temp;// 更新temp_pre为当前temp的值}}}// 完成字符串的遍历后,返回0表示成功return 0;
}
// 计算字符串表达式的函数
uint8_t StrCalculate(char * str,NumStack_t * NumStack, SymStack_t * SymStack)
{ // 调用NumSymSeparate函数分离数字和符号,并尝试计算表达式if(NumSymSeparate(str,strlen(str),NumStack,SymStack)){ // 如果发生错误,清空数字栈和符号栈//erro, clear allNumStackClear(NumStack);SymStackClear(SymStack);return -1;// 返回-1表示计算失败}else{// 如果没有错误,继续计算直到符号栈为空while(!SymStack_isEmpty(SymStack)){CalculateOne(NumStack,SymStack); // 执行一次计算操作}}return 0; // 所有计算完成后返回0表示成功
}
// 判断一个浮点数是否为整数的函数
uint8_t isIntNumber(float number)
{ // 通过比较浮点数与其转换为整数后的值是否相等来判断是否为整数if(number == (int)number){return 1;}// 如果相等,返回1表示这是一个整数return 0; // 如果不相等,返回0表示这不是一个整数
}
这段 C 语言代码实现了一个简单的表达式计算功能,通过定义字符串栈、数字栈和符号栈等数据结构,并配合相应的栈操作函数,实现了对包含四则运算(加、减、乘、除)的字符串表达式的解析、计算以及结果输出,同时还包含了一些辅助函数用于判断栈状态、数字类型以及结果是否为整数等操作
2.1 头文件包含部分
#include <stdio.h> // 包含标准输入输出库
#include <stdlib.h> // 包含标准库,提供一些常用函数
#include <string.h> // 包含字符串处理库
#include "StrCalculate.h" // 包含自定义的头文件,定义了栈结构和操作
标准库头文件:<stdio.h>
:提供了如 printf
、scanf
等标准输入输出函数相关的声明,虽然在当前代码中未直接看到其使用,但可能在调试或者后续扩展功能时会用于输出信息、接收用户输入等操作。<stdlib.h>
:包含了一些通用的标准库函数,例如内存分配相关的 malloc
、free
函数等,同样在现有代码中未明显体现其使用,但可能在进一步完善代码功能(比如动态分配栈空间等情况)时会用到。<string.h>
:提供了字符串处理相关的函数声明,像 strlen
(在代码中用于获取输入字符串长度)等函数,对字符串操作提供了支持。
自定义头文件:StrCalculate.h
应该是项目中自定义的头文件,从代码中对栈结构的操作推测,它里面可能定义了 StrStack_t
、NumStack_t
、SymStack_t
等栈相关的结构体类型以及对应的操作函数声明(虽然部分函数在当前代码文件中已经实现,但头文件中可能会包含完整的声明供其他源文件使用),用于统一管理和操作不同类型的栈,实现表达式计算逻辑。
计算栈深度为5 根据前面的数字栈 最多操作4个数字
#include "main.h"#define NUMBER_TYPE 0
#define SYMBOL_TYPE 1
#define POINT_TYPE 3
#define CAL_DEPTH 5typedef struct
{char strque[10];uint8_t Top_Point;}StrStack_t;uint8_t strput(StrStack_t * st,char strin);
uint8_t strdel(StrStack_t * st);
uint8_t strstack_isEmpty(StrStack_t* st);
void strclear(StrStack_t* sq);typedef struct
{char datatype;float number;char symbol;
}caldata_t;typedef struct
{float data[CAL_DEPTH];uint8_t Top_Point;}NumStack_t;typedef struct
{char data[CAL_DEPTH];uint8_t Top_Point;}SymStack_t;void NumStackClear(NumStack_t* st);
void SymStackClear(SymStack_t* st);
uint8_t NumSymSeparate(char * str, uint8_t strlen, NumStack_t * NumStack, SymStack_t * SymStack);
uint8_t StrCalculate(char * str, NumStack_t * NumStack, SymStack_t * SymStack);
uint8_t isIntNumber(float number);
2.2 字符串栈操作函数(strput
、strdel
、strstack_isEmpty
、strclear
)
压栈strput
uint8_t strput(StrStack_t * st, char strin)
{if (st->Top_Point == 15 - 1) // 如果栈已满{return -1; // 返回错误码}st->strque[st->Top_Point++] = strin; // 将字符压入栈,并更新栈顶指针return 0; // 成功
}
最多压入14个字符。与之前的ui栈没有区别 向上增长 初始栈索引为0
其他几个函数也是一样,就换了个名字
出栈strdel
uint8_t strdel(StrStack_t * st)
{if (st->Top_Point == 0) // 如果栈为空{return -1; // 返回错误码}st->strque[--st->Top_Point] = NULL; // 将栈顶元素设置为 NULL,并更新栈顶指针return 0; // 成功
}
栈是否为空 strstack_isEmpty
uint8_t strstack_isEmpty(StrStack_t * st)
{if (st->Top_Point == 0) // 如果栈顶指针为 0,即栈为空{return 1; // 返回 1 表示真(非空为 0)}return 0; // 返回 0 表示假(栈不为空)
}
清空栈strclear
void strclear(StrStack_t * sq)
{while (!strstack_isEmpty(sq)) // 只要栈不为空{strdel(sq); // 就一直弹出元素}
}
2.3 数字栈操作函数(NumStackPut
、NumStackDel
、NumStack_isEmpty
、NumStackClear
)
这些函数的功能和逻辑与字符串栈操作函数类似,只是操作对象变为数字栈 NumStack_t
,用于向数字栈中压入浮点数、弹出浮点数、判断数字栈是否为空以及清空数字栈
NumStackPut
函数
uint8_t NumStackPut(NumStack_t * st, float in)
{if (st->Top_Point == CAL_DEPTH - 1) // 如果栈已满{return -1; // 返回错误码}st->data[st->Top_Point++] = in; // 将数字压入栈,并更新栈顶指针return 0; // 成功
}
最多压入CAL_DEPTH-1个数
NumStackDel
函数:
uint8_t NumStackDel(NumStack_t * st)
{if (st->Top_Point == 0) // 如果栈为空{return -1; // 返回错误码}st->data[st->Top_Point--] = 0; // 将栈顶元素设置为 0,并更新栈顶指针return 0; // 成功
}
NumStack_isEmpty
函数
uint8_t NumStack_isEmpty(NumStack_t * st)
{if (st->Top_Point == 0) // 如果栈顶指针为 0,即栈为空{return 1; // 返回 1 表示真(非空为 0)}return 0; // 返回 0 表示假(栈不为空)
}
NumStackClear
函数
void NumStackClear(NumStack_t * st)
{while (!NumStack_isEmpty(st)) // 只要栈不为空{NumStackDel(st); // 就一直弹出元素}
}
2.4 符号栈也是类似
// 向符号栈中压入一个字符
uint8_t SymStackPut(SymStack_t * st, char in)
{if(st->Top_Point == CAL_DEPTH - 1)// 如果栈已满{return -1;}// 返回错误码st->data[st->Top_Point++] = in;// 将符号压入栈,并更新栈顶指针return 0;// 成功
}
// 从符号栈中弹出一个字符
uint8_t SymStackDel(SymStack_t * st)// 从符号栈中弹出一个字符
{if(st->Top_Point == 0)// 如果栈为空{return -1;}// 返回错误码st->data[st->Top_Point--] = 0;// 将栈顶元素设置为0,并更新栈顶指针return 0;// 成功
}
// 检查符号栈是否为空
uint8_t SymStack_isEmpty(SymStack_t* st)
{if(st->Top_Point == 0)// 如果栈顶指针为0,即栈为空{return 1;}// 返回1表示真(非空为0)return 0;// 返回0表示假(栈不为空)
}void SymStackClear(SymStack_t* st)// 清空符号栈
{while(!SymStack_isEmpty(st))// 只要栈不为空{SymStackDel(st);// 就一直弹出元素}
}
2.5 SymisHighPriority
uint8_t SymisHighPriority(char top, char present)
{//乘除的优先级最大if (top == '*' || top == '/'){return 1; // 返回1表示真(高优先级)}else if (top == '+') // '+'和'-'的组合{if (present == '-'){return 1; // 返回1表示真(高优先级)}else{return 0; // 返回0表示假(低优先级)}}else if (top == '-') // '-'和'+'的组合{if (present == '+') // 返回1表示真(高优先级){return 1; // 返回1表示真(高优先级)}else{return 0; // 返回0表示假(低优先级)}}
}
该函数用于比较两个运算符的优先级,判断传入的 top
运算符是否比 present
运算符具有更高的优先级。按照常见的数学运算优先级规则,乘除(*
、/
)运算符的优先级高于加减(+
、-
)运算符。在加减运算符之间比较时,根据传入的具体组合情况判断优先级,例如 +
与 -
相邻时,按照特定规则确定先后顺序。
首先判断 top
是否为乘除运算符,如果是,则直接返回 1
表示其优先级高;若 top
是 +
或 -
,则再根据 present
的值来进一步判断,符合特定组合(如 top
为 +
且 present
为 -
,或者 top
为 -
且 present
为 +
)时返回 1
,表示 top
的优先级高,否则返回 0
。
遵循*/自左向右高于加减的自左向右
如果top是乘除那无论present是啥,top都是最高优先级
但是如果top是+- present是乘除就不行了。top是加减 present必须也是加减才能满足top是最高优先级。
2.6 CalculateOne
函数
void CalculateOne(NumStack_t * numstack, SymStack_t * symstack)
{caldata_t temp; // 定义临时变量temp.datatype = NUMBER_TYPE; // 设置数据类型为数字temp.symbol = NULL; // 设置符号为空//计算数字栈中的顶部两数,结果存到temp中if (symstack->data[symstack->Top_Point - 1] == '+')temp.number = (numstack->data[numstack->Top_Point - 2]) + (numstack->data[numstack->Top_Point - 1]);else if (symstack->data[symstack->Top_Point - 1] == '-')temp.number = (numstack->data[numstack->Top_Point - 2]) - (numstack->data[numstack->Top_Point - 1]);else if (symstack->data[symstack->Top_Point - 1] == '*')temp.number = (numstack->data[numstack->Top_Point - 2]) * (numstack->data[numstack->Top_Point - 1]);else if (symstack->data[symstack->Top_Point - 1] == '/')temp.number = (numstack->data[numstack->Top_Point - 2]) / (numstack->data[numstack->Top_Point - 1]);//运算前两数出栈,运算结果数入栈NumStackDel(numstack); // 弹出第一个数字NumStackDel(numstack); // 弹出第二个数字NumStackPut(numstack, temp.number); // 将结果压入数字栈SymStackDel(symstack); // 弹出符号栈顶元素
}
功能:此函数实现了基于栈的一次四则运算操作。它从数字栈 numstack
和符号栈 symstack
中取出相应的元素,根据符号栈顶的运算符对数字栈顶的两个数字进行运算,然后将运算结果放回数字栈,并弹出参与运算的数字和对应的运算符,模拟了表达式计算过程中按顺序执行运算的步骤。
逻辑:
typedef struct
{char datatype;float number;char symbol;
}caldata_t;
首先定义一个 caldata_t
类型的临时变量 temp
,并初始化其数据类型为数字、符号为空。
根据符号栈栈顶的运算符(通过 symstack->data[symstack->Top_Point - 1]
获取)进行判断,对数字栈顶的两个数字(通过 numstack->data[numstack->Top_Point - 2]
和 numstack->data[numstack->Top_Point - 1]
获取)执行相应的四则运算(加、减、乘、除),将结果存储在 temp.number
中。接着调用 NumStackDel
函数两次,依次弹出参与运算的两个数字,再使用 NumStackPut
函数将运算结果压入数字栈,最后调用 SymStackDel
函数弹出符号栈顶的运算符,完成一次完整的运算操作及栈状态更新。
2.7 NumSymSeparate
函数
uint8_t NumSymSeparate(char * str, uint8_t strlen, NumStack_t * NumStack, SymStack_t * SymStack)
{NumStackClear(NumStack); // 清空数字栈SymStackClear(SymStack); // 清空符号栈caldata_t temp, temp_pre; // 定义临时变量char NumBehindPoint_Flag = 0; // 数字是否在小数点后,后多少位temp.datatype = NUMBER_TYPE; // 设置数据类型为数字temp.number = 0; // 设置数字为 0temp.symbol = NULL; // 设置符号为空temp_pre = temp;temp_pre.datatype = SYMBOL_TYPE;if (str[0] > '9' || str[0] < '0') // 如果第一个字符不是数字return 1; // erro //返回错误码1int i; // 定义循环变量for (i = 0; i < strlen; i++) // 遍历字符串{if (str[i] == '.') // 如果遇到小数点{temp.datatype = POINT_TYPE; // 设置数据类型为小数点if (temp_pre.datatype == NUMBER_TYPE) // 如果前一个字符是数字{}else{return 2; // 返回错误码2}temp_pre = temp;}if (str[i] <= '9' && str[i] >= '0') // 如果字符是数字{//溢出报错if (NumStack->Top_Point > CAL_DEPTH || SymStack->Top_Point > CAL_DEPTH){return 3; // 如果数字栈或符号栈溢出,返回错误码3}//读取当前的字符到temp中temp.datatype = NUMBER_TYPE; // 设置数据类型为数字temp.number = (str[i] - '0'); // 将字符转换为对应的数字,并存储在temp.number中temp.symbol = NULL; // 设置符号为 NULL//如果为连续数字,需要进行进位,将数字栈顶读出进位,再加上现在位,再入栈if (temp_pre.datatype == NUMBER_TYPE){if (!NumBehindPoint_Flag) // 如果不在小数点后,则直接进行十进制进位{temp.number += NumStack->data[NumStack->Top_Point - 1] * 10;}else{NumBehindPoint_Flag += 1; // 如果在小数点后,则进行小数进位char i = NumBehindPoint_Flag;while (i--){temp.number /= 10;} // 将之前的数值除以 10,为新的小数位腾出位置temp.number += NumStack->data[NumStack->Top_Point - 1]; // 加上当前位的数值}NumStackDel(NumStack); // 弹出数字栈顶元素NumStackPut(NumStack, temp.number); // 将新的数值压入数字栈}//当前数字刚好是小数点后一位else if (temp_pre.datatype == POINT_TYPE){// 当前数字刚好是小数点后一位NumBehindPoint_Flag = 1; // 标记现在位于小数点后temp.number /= 10; // 将数值除以 10,为小数点后第一位腾出位置temp.number += NumStack->data[NumStack->Top_Point - 1]; // 加上当前位的数值NumStackDel(NumStack); // 弹出数字栈顶元素NumStackPut(NumStack, temp.number); // 将新的数值压入数字栈}//前一位不是数字或小数点,现在读取的这一位是数字,直接入栈else{NumStackPut(NumStack, temp.number); // 将数值直接压入数字栈}temp_pre = temp; // 更新temp_pre为当前temp的值}// 如果字符是运算符else if (str[i] == '+' || str[i] == '-' || str[i] == '*' || str[i] == '/'){//溢出报错if (NumStack->Top_Point > CAL_DEPTH || SymStack->Top_Point > CAL_DEPTH){return 4; // 如果数字栈或符号栈溢出,返回错误码4}//读取当前的字符到temp中temp.datatype = SYMBOL_TYPE; // 设置数据类型为符号temp.symbol = str[i]; // 将当前字符设置为符号temp.number = 0; // 设置数值为0NumBehindPoint_Flag = 0; // 小数点计算已经结束//重复输入了运算符号if (temp_pre.datatype == SYMBOL_TYPE) // 重复输入了运算符号{return 5; // erro// 返回错误码5}else{//检查是否需要先计算栈顶的表达式if ((!SymStack_isEmpty(SymStack)) && SymisHighPriority(SymStack->data[SymStack->Top_Point - 1], temp.symbol)){CalculateOne(NumStack, SymStack); // 执行一次计算操作SymStackPut(SymStack, temp.symbol); // 将当前符号压入符号栈}else{//符号压入符号栈SymStackPut(SymStack, temp.symbol); // 将当前符号压入符号栈}temp_pre = temp; // 更新temp_pre为当前temp的值}}}// 完成字符串的遍历后,返回0表示成功return 0;
}
功能:这个函数负责将输入的字符串表达式进行解析,分离出其中的数字和符号,并按照运算优先级规则将数字压入数字栈 NumStack
,符号压入符号栈 SymStack
,同时处理诸如小数点、连续数字进位、运算符优先级等各种情况,在遇到不符合规则的情况时返回相应的错误码。
逻辑:
首先清空数字栈和符号栈,为解析新的表达式做准备。然后初始化一些用于临时存储数据和状态的变量,如 temp
、temp_pre
和 NumBehindPoint_Flag
等。
对输入字符串的第一个字符进行判断,如果不是数字则直接返回错误码 1
。
接着通过循环遍历整个字符串,针对不同类型的字符(小数点、数字、运算符)进行不同的处理:当遇到小数点时,检查前一个字符是否为数字,如果不是则返回错误码 2
,若是则更新相关临时变量的状态。若字符是数字,先检查数字栈和符号栈是否溢出,若溢出则返回相应错误码。然后根据数字所处的位置(是否连续数字、是否在小数点后等情况)进行不同的进位和入栈操作,确保数字能正确地存储到数字栈中,并更新相关临时变量记录状态。当字符是运算符时,同样先检查栈溢出情况,然后根据当前运算符以及前一个符号(通过 temp_pre
记录)的情况进行处理。如果是重复输入运算符则返回错误码 5
;否则,通过调用 SymisHighPriority
函数判断是否需要先执行栈顶已有的高优先级运算(如果符号栈非空且当前运算符优先级低于栈顶运算符优先级),若是则先执行一次 CalculateOne
操作,之后再将当前运算符压入符号栈,更新临时变量记录当前符号状态。
也就是在数据符号分离的时候就进行着数字的计算了,就是如果栈内存的是12 符号栈存的是+ 如果再存一个数字是3 符号是* 的话此刻就不会进行+的运算
如果当前的运算符是+ 那之前栈内存的符号+ 就是最高优先级 ,此时就执行一次计算 1+2 = 3,再把3压入栈内,新的+号也压入栈内。
就简化了后续的处理流程。
2.8 StrCalculate 函数 // 计算字符串表达式的函数
uint8_t StrCalculate(char * str,NumStack_t * NumStack, SymStack_t * SymStack)
{ // 调用NumSymSeparate函数分离数字和符号,并尝试计算表达式if(NumSymSeparate(str,strlen(str),NumStack,SymStack)){ // 如果发生错误,清空数字栈和符号栈//erro, clear allNumStackClear(NumStack);SymStackClear(SymStack);return -1;// 返回-1表示计算失败}else{// 如果没有错误,继续计算直到符号栈为空while(!SymStack_isEmpty(SymStack)){CalculateOne(NumStack,SymStack); // 执行一次计算操作}}return 0; // 所有计算完成后返回0表示成功
}
功能:该函数是整个表达式计算的入口函数,它首先调用 NumSymSeparate
函数对输入的字符串表达式进行数字和符号的分离及初步处理,如果这个过程出现错误则清空栈并返回错误标识;若没有错误,则通过循环不断调用 CalculateOne
函数,按照运算符的顺序依次进行计算,直到符号栈为空,表示表达式计算完成,最后返回相应的成功或失败标识。
逻辑:先调用 NumSymSeparate
函数,根据其返回值判断是否有错误发生,若返回非零值(表示有错误),则执行清空数字栈和符号栈的操作,并返回 -1
表示计算失败;若返回 0
(表示分离数字和符号成功),则进入循环,只要符号栈不为空,就持续调用 CalculateOne
函数进行计算操作,当符号栈为空时,意味着表达式计算完毕,返回 0
表示计算成功。
也就是经过分离处理后的表达式内最多只剩下加减法,不存在乘除运算。因为在分离的时候假如第一个是+第二个是* 那此次不计算并且压栈,第三个是+ 第二个会被弹出并运算一次。此刻符号栈变成++,如果第三个是*第二个依旧最高优先级 依旧弹出。会把最高优先级的在分离的时候就消耗掉。
2.9 isIntNumber
函数
uint8_t isIntNumber(float number)
{// 通过比较浮点数与其转换为整数后的值是否相等来判断是否为整数if (number == (int)number){return 1; // 如果相等,返回1表示这是一个整数}return 0; // 如果不相等,返回0表示这不是一个整数
}
功能:用于判断给定的浮点数是否为整数,通过将浮点数强制转换为整数后与原浮点数进行比较,如果二者相等,则说明该浮点数实际上是一个整数,返回 1
,否则返回 0
逻辑:直接使用 (int)number
将输入的浮点数 number
转换为整数类型,然后比较转换后的整数与原浮点数是否相等,根据比较结果返回相应的 1
或 0
值来表示是否为整数。
三、user_TasksInit 初始化任务
#ifndef __USER_TASKSINIT_H__
#define __USER_TASKSINIT_H__#ifdef __cplusplus
extern "C" {
#endif#include "FreeRTOS.h"
#include "cmsis_os.h"#define SCRRENEW_DEPTH 5extern osMessageQueueId_t Key_MessageQueue;
extern osMessageQueueId_t Idle_MessageQueue;
extern osMessageQueueId_t Stop_MessageQueue;
extern osMessageQueueId_t IdleBreak_MessageQueue;
extern osMessageQueueId_t HomeUpdata_MessageQueue;
extern osMessageQueueId_t DataSave_MessageQueue;void User_Tasks_Init(void);
void TaskTickHook(void);#ifdef __cplusplus
}
#endif#endif
/* Private includes -----------------------------------------------------------*/
//includes包含用户任务初始化头文件
#include "user_TasksInit.h"
//sys// 包含系统相关头文件
#include "sys.h"
#include "stdio.h"
#include "lcd.h"
//gui// 包含GUI相关头文件
#include "lvgl.h"
#include "ui_TimerPage.h"
//tasks// 包含任务相关头文件
#include "user_StopEnterTask.h"
#include "user_KeyTask.h"
#include "user_ScrRenewTask.h"
#include "user_HomePageTask.h"
#include "user_SensorPageTask.h"
#include "user_ChargPageTask.h"
#include "user_MessageSendTask.h"
#include "user_MPUCheckTask.h"
#include "user_DataSaveTask.h"/* Private typedef -----------------------------------------------------------*//* Private define ------------------------------------------------------------*//* Private variables ---------------------------------------------------------*/// 定义一个osTimerId_t类型的定时器句柄
/* Timers --------------------------------------------------------------------*/
osTimerId_t IdleTimerHandle;/* Tasks ---------------------------------------------------------------------*/
//LVGL Handler task// 定义LVGL处理任务的线程句柄和属性
osThreadId_t LvHandlerTaskHandle;
const osThreadAttr_t LvHandlerTask_attributes = {.name = "LvHandlerTask",.stack_size = 128 * 24,// 堆栈大小为128*24字节.priority = (osPriority_t) osPriorityLow,// 任务优先级为低
};//WDOG Feed task// 定义看门狗喂养任务的线程句柄和属性
osThreadId_t WDOGFeedTaskHandle;
const osThreadAttr_t WDOGFeedTask_attributes = {.name = "WDOGFeedTask",.stack_size = 128 * 1,// 堆栈大小为128字节.priority = (osPriority_t) osPriorityHigh2,// 任务优先级为高
};//Idle Enter Task// 定义空闲进入任务的线程句柄和属性
osThreadId_t IdleEnterTaskHandle;
const osThreadAttr_t IdleEnterTask_attributes = {.name = "IdleEnterTask",.stack_size = 128 * 1,// 堆栈大小为128字节.priority = (osPriority_t) osPriorityHigh,// 任务优先级为高
};//Stop Enter Task // 定义停止进入任务的线程句柄和属性
osThreadId_t StopEnterTaskHandle;
const osThreadAttr_t StopEnterTask_attributes = {.name = "StopEnterTask",.stack_size = 128 * 16,// 堆栈大小为128*16字节.priority = (osPriority_t) osPriorityHigh1, // 任务优先级为较高
};//Key task// 定义按键任务的线程句柄和属性
osThreadId_t KeyTaskHandle;
const osThreadAttr_t KeyTask_attributes = {.name = "KeyTask",.stack_size = 128 * 1,// 堆栈大小为128字节.priority = (osPriority_t) osPriorityNormal,// 任务优先级为普通
};//ScrRenew task// 定义屏幕刷新任务的线程句柄和属性
osThreadId_t ScrRenewTaskHandle;
const osThreadAttr_t ScrRenewTask_attributes = {.name = "ScrRenewTask",.stack_size = 128 * 10,// 堆栈大小为128*10字节.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//TimeRenew task // 定义时间刷新任务的线程句柄和属性
osThreadId_t TimeRenewTaskHandle;
const osThreadAttr_t TimeRenewTask_attributes = {.name = "TimeRenewTask",.stack_size = 128 * 5,.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};
// 定义首页更新任务的线程句柄和属性
//HomeUpdata task
osThreadId_t HomeUpdataTaskHandle;
const osThreadAttr_t HomeUpdataTask_attributes = {.name = "HomeUpdataTask",.stack_size = 128 * 5,// 堆栈大小为128*5字节// 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//SensorDataRenew task// 定义传感器数据刷新任务的线程句柄和属性
osThreadId_t SensorDataTaskHandle;
const osThreadAttr_t SensorDataTask_attributes = {.name = "SensorDataTask",.stack_size = 128 * 5,.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//HRDataRenew task // 定义心率数据刷新任务的线程句柄和属性
osThreadId_t HRDataTaskHandle;
const osThreadAttr_t HRDataTask_attributes = {.name = "HRDataTask",.stack_size = 128 * 5,// 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//ChargPageEnterTask// 定义充电页面进入任务的线程句柄和属性
osThreadId_t ChargPageEnterTaskHandle;
const osThreadAttr_t ChargPageEnterTask_attributes = {.name = "ChargPageEnterTask",.stack_size = 128 * 10,.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//ChargPageRenewTask // 定义充电页面刷新任务的线程句柄和属性
osThreadId_t ChargPageRenewTaskHandle;
const osThreadAttr_t ChargPageRenewTask_attributes = {.name = "ChargPageRenewTask",.stack_size = 128 * 5,// 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow1, // 任务优先级为较低
};//messagesendtask // 定义消息发送任务的线程句柄和属性
osThreadId_t MessageSendTaskHandle;
const osThreadAttr_t MessageSendTask_attributes = {.name = "MessageSendTask",.stack_size = 128 * 5, // 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow1, // 任务优先级为较低
};//MPUCheckTask // 定义MPU检查任务的线程句柄和属性
osThreadId_t MPUCheckTaskHandle;
const osThreadAttr_t MPUCheckTask_attributes = {.name = "MPUCheckTask",.stack_size = 128 * 3, // 堆栈大小为128*3字节.priority = (osPriority_t) osPriorityLow2,// 任务优先级为最低
};//DataSaveTask
osThreadId_t DataSaveTaskHandle; // 定义数据保存任务的线程句柄和属性
const osThreadAttr_t DataSaveTask_attributes = {.name = "DataSaveTask",.stack_size = 128 * 5, // 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow2, // 任务优先级为最低
};/* Message queues ------------------------------------------------------------*/
//Key message // 定义消息队列
osMessageQueueId_t Key_MessageQueue;
osMessageQueueId_t Idle_MessageQueue;
osMessageQueueId_t Stop_MessageQueue;
osMessageQueueId_t IdleBreak_MessageQueue;
osMessageQueueId_t HomeUpdata_MessageQueue;
osMessageQueueId_t DataSave_MessageQueue;
// 声明私有函数原型
/* Private function prototypes -----------------------------------------------*/
void LvHandlerTask(void *argument);
void WDOGFeedTask(void *argument);/*** @brief FreeRTOS initialization* @param None* @retval None*/
void User_Tasks_Init(void)
{/* add mutexes, ... *//* add semaphores, ... *//* start timers, add new ones, ... */// 创建空闲定时器IdleTimerHandle = osTimerNew(IdleTimerCallback, osTimerPeriodic, NULL, NULL);osTimerStart(IdleTimerHandle,100);//100ms/* add queues, ... */// 创建按键消息队列Key_MessageQueue = osMessageQueueNew(1, 1, NULL);Idle_MessageQueue = osMessageQueueNew(1, 1, NULL);Stop_MessageQueue = osMessageQueueNew(1, 1, NULL);IdleBreak_MessageQueue = osMessageQueueNew(1, 1, NULL);HomeUpdata_MessageQueue = osMessageQueueNew(1, 1, NULL);DataSave_MessageQueue = osMessageQueueNew(2, 1, NULL);/* add threads, ... */ // 创建LVGL处理线程LvHandlerTaskHandle = osThreadNew(LvHandlerTask, NULL, &LvHandlerTask_attributes);//WDOGFeedTaskHandle = osThreadNew(WDOGFeedTask, NULL, &WDOGFeedTask_attributes);//IdleEnterTaskHandle = osThreadNew(IdleEnterTask, NULL, &IdleEnterTask_attributes);//StopEnterTaskHandle = osThreadNew(StopEnterTask, NULL, &StopEnterTask_attributes);//创建KeyTask任务,这是一个处理按键的任务线程KeyTaskHandle = osThreadNew(KeyTask, NULL, &KeyTask_attributes);// 创建ScrRenewTask任务,这是一个刷新屏幕的任务线程ScrRenewTaskHandle = osThreadNew(ScrRenewTask, NULL, &ScrRenewTask_attributes);//创建TimeRenewTask任务,这是一个刷新时间的任务线程TimeRenewTaskHandle = osThreadNew(TimeRenewTask, NULL, &TimeRenewTask_attributes);// 创建HomeUpdataTask任务,这是一个更新首页数据的任务线程HomeUpdataTaskHandle = osThreadNew(HomeUpdata_Task, NULL, &HomeUpdataTask_attributes);//SensorDataTaskHandle = osThreadNew(SensorDataRenewTask, NULL, &SensorDataTask_attributes);//HRDataTaskHandle = osThreadNew(HRDataRenewTask, NULL, &HRDataTask_attributes);//ChargPageEnterTaskHandle = osThreadNew(ChargPageEnterTask, NULL, &ChargPageEnterTask_attributes);//ChargPageRenewTaskHandle = osThreadNew(ChargPageRenewTask, NULL, &ChargPageRenewTask_attributes);// 创建MessageSendTask任务,这是一个发送消息的任务线程MessageSendTaskHandle = osThreadNew(MessageSendTask, NULL, &MessageSendTask_attributes);//MPUCheckTaskHandle = osThreadNew(MPUCheckTask, NULL, &MPUCheckTask_attributes);//DataSaveTaskHandle = osThreadNew(DataSaveTask, NULL, &DataSaveTask_attributes);/* add events, ... *//* add others ... */uint8_t HomeUpdataStr;osMessageQueuePut(HomeUpdata_MessageQueue, &HomeUpdataStr, 0, 1);}/*** @brief FreeRTOS Tick Hook, to increase the LVGL tick* @param None* @retval None*/
void TaskTickHook(void)
{//to increase the LVGL ticklv_tick_inc(1);//to increase the timerpage's timer(put in here is to ensure the Real Time)if(ui_TimerPageFlag){ui_TimerPage_ms+=1;if(ui_TimerPage_ms>=10){ui_TimerPage_ms=0;ui_TimerPage_10ms+=1;}if(ui_TimerPage_10ms>=100){ui_TimerPage_10ms=0;ui_TimerPage_sec+=1;uint8_t IdleBreakstr = 0;osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 0);}if(ui_TimerPage_sec>=60){ui_TimerPage_sec=0;ui_TimerPage_min+=1;}if(ui_TimerPage_min>=60){ui_TimerPage_min=0;}}
}/*** @brief LVGL Handler task, to run the lvgl* @param argument: Not used* @retval None*/
void LvHandlerTask(void *argument)
{uint8_t IdleBreakstr=0;while(1){if(lv_disp_get_inactive_time(NULL)<1000){//Idle time break, set to 0osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 0);}lv_task_handler();osDelay(1);}
}/*** @brief Watch Dog Feed task* @param argument: Not used* @retval None*/
void WDOGFeedTask(void *argument)
{
// //owdg
// WDOG_Port_Init();
// while(1)
// {
// WDOG_Feed();
// WDOG_Enable();
// osDelay(100);
// }
}
这段代码基于 FreeRTOS 实时操作系统框架,主要用于嵌入式系统中多任务的初始化与管理,涉及到多个不同功能任务的定义、创建以及相关资源(如定时器、消息队列等)的配置,同时还包含了一些任务执行逻辑相关的函数实现,用于处理诸如界面显示(LVGL 相关)、按键处理、数据更新、定时器操作以及看门狗相关(部分代码被注释掉)等功能,整体构建起一个具备多任务协同工作能力的嵌入式软件系统基础架构。
3.1 头文件包含部分
#include "user_TasksInit.h"
#include "sys.h"
#include "stdio.h"
#include "lcd.h"
#include "lvgl.h"
#include "ui_TimerPage.h"
#include "user_StopEnterTask.h"
#include "user_KeyTask.h"
#include "user_ScrRenewTask.h"
#include "user_HomePageTask.h"
#include "user_SensorPageTask.h"
#include "user_ChargPageTask.h"
#include "user_MessageSendTask.h"
#include "user_MPUCheckTask.h"
#include "user_DataSaveTask.h"
user_TasksInit.h
:自定义的头文件,推测用于整个任务初始化相关的通用定义、函数声明等,为后续任务相关配置提供基础支持。
sys.h
:可能包含了系统层面的一些配置、宏定义以及与底层硬件或系统相关的函数声明等,例如系统时钟配置、中断相关设置等内容(具体依赖项目实际情况)。其实就是一些变量的别名命名
stdio.h
:标准输入输出库头文件,用于支持像 printf
等输入输出函数,方便在代码中进行调试信息输出或者与外部设备进行简单的文本交互(如果有需求的话)。
lcd.h
:针对液晶显示屏(LCD)操作的头文件,应该定义了与 LCD 初始化、显示控制等相关的函数声明、数据结构等内容,为后续在任务中实现屏幕显示相关功能做准备。
lvgl.h
:LVGL(Light and Versatile Graphics Library)图形库的头文件,LVGL 用于创建嵌入式系统中的图形化用户界面,通过它可以实现各种界面元素的显示、交互等功能,代码中多个任务都与 LVGL 相关界面操作有关。
其他以 user_
开头的头文件(如 user_StopEnterTask.h
、user_KeyTask.h
等):这些都是自定义的任务相关头文件,各自对应不同功能任务(停止进入任务、按键任务等)的函数声明、结构体定义等内容,用于实现具体业务逻辑的模块化组织
3.2 变量定义部分
定时器相关变量(IdleTimerHandle
):
osTimerId_t IdleTimerHandle;
定义了一个 osTimerId_t
类型的变量 IdleTimerHandle
,用于标识一个定时器,从后续代码来看,这个定时器应该与空闲状态相关的定时操作有联系,例如在系统空闲一段时间后触发某些特定的处理逻辑等。
就是用来处理空闲时间超时降低亮度和 息屏的
3.3 任务相关线程句柄和属性变量(多个 osThreadId_t
类型变量及对应的 osThreadAttr_t
结构体变量):
/* Tasks ---------------------------------------------------------------------*/
//LVGL Handler task// 定义LVGL处理任务的线程句柄和属性
osThreadId_t LvHandlerTaskHandle;
const osThreadAttr_t LvHandlerTask_attributes = {.name = "LvHandlerTask",.stack_size = 128 * 24,// 堆栈大小为128*24字节.priority = (osPriority_t) osPriorityLow,// 任务优先级为低
};//WDOG Feed task// 定义看门狗喂养任务的线程句柄和属性
osThreadId_t WDOGFeedTaskHandle;
const osThreadAttr_t WDOGFeedTask_attributes = {.name = "WDOGFeedTask",.stack_size = 128 * 1,// 堆栈大小为128字节.priority = (osPriority_t) osPriorityHigh2,// 任务优先级为高
};//Idle Enter Task// 定义空闲进入任务的线程句柄和属性
osThreadId_t IdleEnterTaskHandle;
const osThreadAttr_t IdleEnterTask_attributes = {.name = "IdleEnterTask",.stack_size = 128 * 1,// 堆栈大小为128字节.priority = (osPriority_t) osPriorityHigh,// 任务优先级为高
};//Stop Enter Task // 定义停止进入任务的线程句柄和属性
osThreadId_t StopEnterTaskHandle;
const osThreadAttr_t StopEnterTask_attributes = {.name = "StopEnterTask",.stack_size = 128 * 16,// 堆栈大小为128*16字节.priority = (osPriority_t) osPriorityHigh1, // 任务优先级为较高
};//Key task// 定义按键任务的线程句柄和属性
osThreadId_t KeyTaskHandle;
const osThreadAttr_t KeyTask_attributes = {.name = "KeyTask",.stack_size = 128 * 1,// 堆栈大小为128字节.priority = (osPriority_t) osPriorityNormal,// 任务优先级为普通
};//ScrRenew task// 定义屏幕刷新任务的线程句柄和属性
osThreadId_t ScrRenewTaskHandle;
const osThreadAttr_t ScrRenewTask_attributes = {.name = "ScrRenewTask",.stack_size = 128 * 10,// 堆栈大小为128*10字节.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//TimeRenew task // 定义时间刷新任务的线程句柄和属性
osThreadId_t TimeRenewTaskHandle;
const osThreadAttr_t TimeRenewTask_attributes = {.name = "TimeRenewTask",.stack_size = 128 * 5,.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};
// 定义首页更新任务的线程句柄和属性
//HomeUpdata task
osThreadId_t HomeUpdataTaskHandle;
const osThreadAttr_t HomeUpdataTask_attributes = {.name = "HomeUpdataTask",.stack_size = 128 * 5,// 堆栈大小为128*5字节// 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//SensorDataRenew task// 定义传感器数据刷新任务的线程句柄和属性
osThreadId_t SensorDataTaskHandle;
const osThreadAttr_t SensorDataTask_attributes = {.name = "SensorDataTask",.stack_size = 128 * 5,.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//HRDataRenew task // 定义心率数据刷新任务的线程句柄和属性
osThreadId_t HRDataTaskHandle;
const osThreadAttr_t HRDataTask_attributes = {.name = "HRDataTask",.stack_size = 128 * 5,// 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//ChargPageEnterTask// 定义充电页面进入任务的线程句柄和属性
osThreadId_t ChargPageEnterTaskHandle;
const osThreadAttr_t ChargPageEnterTask_attributes = {.name = "ChargPageEnterTask",.stack_size = 128 * 10,.priority = (osPriority_t) osPriorityLow1,// 任务优先级为较低
};//ChargPageRenewTask // 定义充电页面刷新任务的线程句柄和属性
osThreadId_t ChargPageRenewTaskHandle;
const osThreadAttr_t ChargPageRenewTask_attributes = {.name = "ChargPageRenewTask",.stack_size = 128 * 5,// 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow1, // 任务优先级为较低
};//messagesendtask // 定义消息发送任务的线程句柄和属性
osThreadId_t MessageSendTaskHandle;
const osThreadAttr_t MessageSendTask_attributes = {.name = "MessageSendTask",.stack_size = 128 * 5, // 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow1, // 任务优先级为较低
};//MPUCheckTask // 定义MPU检查任务的线程句柄和属性
osThreadId_t MPUCheckTaskHandle;
const osThreadAttr_t MPUCheckTask_attributes = {.name = "MPUCheckTask",.stack_size = 128 * 3, // 堆栈大小为128*3字节.priority = (osPriority_t) osPriorityLow2,// 任务优先级为最低
};//DataSaveTask
osThreadId_t DataSaveTaskHandle; // 定义数据保存任务的线程句柄和属性
const osThreadAttr_t DataSaveTask_attributes = {.name = "DataSaveTask",.stack_size = 128 * 5, // 堆栈大小为128*5字节.priority = (osPriority_t) osPriorityLow2, // 任务优先级为最低
};
这里定义了多个任务的线程句柄(如 LvHandlerTaskHandle
、WDOGFeedTaskHandle
等)以及对应的任务属性结构体(如 LvHandlerTask_attributes
、WDOGFeedTask_attributes
等)。每个任务属性结构体中指定了任务的名称(方便调试和识别任务)、堆栈大小(决定了任务运行时可使用的栈空间大小,用于存储局部变量、函数调用上下文等信息,不同任务根据其功能复杂程度需求分配不同大小的栈空间)和任务优先级(如低、高、较高等不同级别,用于操作系统进行任务调度,决定哪个任务先获取 CPU 资源执行),通过这种方式对各个任务的基本属性进行了明确配置,为后续创建任务做准备。
3.4 消息队列相关变量(多个 osMessageQueueId_t
类型变量)
osMessageQueueId_t Key_MessageQueue;
osMessageQueueId_t Idle_MessageQueue;
osMessageQueueId_t Stop_MessageQueue;
osMessageQueueId_t IdleBreak_MessageQueue;
osMessageQueueId_t HomeUpdata_MessageQueue;
osMessageQueueId_t DataSave_MessageQueue;
定义了多个消息队列的标识符变量,消息队列在多任务系统中用于不同任务间的通信,各个任务可以通过向消息队列发送消息或者从消息队列获取消息来传递数据、通知事件等,不同的消息队列在代码中应该服务于不同的功能模块间的交互(比如 Key_MessageQueue
可能用于按键任务相关的消息传递,HomeUpdata_MessageQueue
用于首页数据更新相关任务间的通信等)。
按键消息(触发返回主界面,返回上一界面,亮屏等),空闲消息(触发息屏),停止消息(触发低功耗),空闲打断(触发亮屏),主页更新消息(触发主页各个组件的更新),数据保存(没用到)。
3.5 User_Tasks_Init
函数
void User_Tasks_Init(void)
{/* add mutexes,... *//* add semaphores,... *//* start timers, add new ones,... */// 创建空闲定时器IdleTimerHandle = osTimerNew(IdleTimerCallback, osTimerPeriodic, NULL, NULL);osTimerStart(IdleTimerHandle, 100); // 100ms/* add queues,... */// 创建按键消息队列Key_MessageQueue = osMessageQueueNew(1, 1, NULL);Idle_MessageQueue = osMessageQueueNew(1, 1, NULL);Stop_MessageQueue = osMessageQueueNew(1, 1, NULL);IdleBreak_MessageQueue = osMessageQueueNew(1, 1, NULL);HomeUpdata_MessageQueue = osMessageQueueNew(1, 1, NULL);DataSave_MessageQueue = osMessageQueueNew(2, 1, NULL);/* add threads,... */// 创建LVGL处理线程LvHandlerTaskHandle = osThreadNew(LvHandlerTask, NULL, &LvHandlerTask_attributes);// WDOGFeedTaskHandle = osThreadNew(WDOGFeedTask, NULL, &WDOGFeedTask_attributes);// IdleEnterTaskHandle = osThreadNew(IdleEnterTask, NULL, &IdleEnterTask_attributes);// StopEnterTaskHandle = osThreadNew(StopEnterTask, NULL, &StopEnterTask_attributes);// 创建KeyTask任务,这是一个处理按键的任务线程KeyTaskHandle = osThreadNew(KeyTask, NULL, &KeyTask_attributes);// 创建ScrRenewTask任务,这是一个刷新屏幕的任务线程ScrRenewTaskHandle = osThreadNew(ScrRenewTask, NULL, &ScrRenewTask_attributes);// 创建TimeRenewTask任务,这是一个刷新时间的任务线程TimeRenewTaskHandle = osThreadNew(TimeRenewTask, NULL, &TimeRenewTask_attributes);// 创建HomeUpdataTask任务,这是一个更新首页数据的任务线程HomeUpdataTaskHandle = osThreadNew(HomeUpdata_Task, NULL, &HomeUpdataTask_attributes);// SensorDataTaskHandle = osThreadNew(SensorDataRenewTask, NULL, &SensorDataTask_attributes);// HRDataTaskHandle = osThreadNew(HRDataRenewTask, NULL, &HRDataTask_attributes);// ChargPageEnterTaskHandle = osThreadNew(ChargPageEnterTask, NULL, &ChargPageEnterTask_attributes);// ChargPageRenewTaskHandle = osThreadNew(ChargPageRenewTask, NULL, &ChargPageRenewTask_attributes);// 创建MessageSendTask任务,这是一个发送消息的任务线程MessageSendTaskHandle = osThreadNew(MessageSendTask, NULL, &MessageSendTask_attributes);// MPUCheckTaskHandle = osThreadNew(MPUCheckTask, NULL, &MPUCheckTask_attributes);// DataSaveTaskHandle = osThreadNew(DataSaveTask, NULL, &DataSaveTask_attributes);/* add events,... *//* add others... */uint8_t HomeUpdataStr;osMessageQueuePut(HomeUpdata_MessageQueue, &HomeUpdataStr, 0, 1);
}
定时器创建与启动:首先通过 osTimerNew
函数创建一个定时器,传入 IdleTimerCallback
函数指针(这个函数应该是定时器到期时要执行的回调函数,不过代码中未展示其具体实现)、osTimerPeriodic
表示定时器是周期性触发的类型、NULL
作为定时器的参数(如果有需要传递给回调函数的额外参数可以在这里指定)以及 NULL
用于指定定时器所属的对象(如果有的话),然后使用 osTimerStart
函数启动该定时器,设置其定时周期为 100ms,意味着每隔 100ms 定时器到期会触发相应的回调操作(通过 IdleTimerCallback
函数执行)。每隔100ms空闲计数值+1 10s降低亮度 15s息屏
消息队列创建:使用 osMessageQueueNew
函数分别创建多个消息队列,函数的参数指定了消息队列的一些属性,例如第一个参数表示消息队列的长度(即能容纳的最大消息数量),第二个参数表示每条消息的大小(以字节为单位),后面的 NULL
参数用于指定消息队列的一些其他属性(比如消息队列的名称等,如果支持的话,这里未使用)。不同的消息队列根据其预期用途设置了不同的属性,为后续任务间通信提供了基础支持。
任务创建:通过 osThreadNew
函数创建多个任务线程,每个函数调用传入对应的任务函数指针(如 LvHandlerTask
、KeyTask
等,这些函数定义了各个任务具体要执行的逻辑,在后续代码中有部分实现展示)、NULL
作为任务的参数(如果任务执行需要外部传入参数可以在这里指定)以及对应的任务属性结构体指针(如 &LvHandlerTask_attributes
等,前面定义的任务属性结构体用于配置任务的名称、堆栈大小和优先级等信息),按照这样的方式依次创建了多个不同功能的任务线程,使它们能够在操作系统的调度下独立运行,实现各自的业务逻辑。
消息队列消息发送:最后定义了一个 uint8_t
类型的变量 HomeUpdataStr
,并通过 osMessageQueuePut
函数向 HomeUpdata_MessageQueue
消息队列发送该变量对应的消息,发送操作的参数中,最后一个参数 1
表示如果消息队列已满则阻塞等待,直到能够成功将消息放入队列中,这一步操作可能是为了触发首页数据更新相关任务的初始执行,向对应的任务发送一个初始消息来启动数据更新流程。
3.6 TaskTickHook
函数(灵魂所在)
这就是我为什么移植LVGL失败的原因了,他把LVGL的时钟加到任务的时基钩子函数里了。
任务每触发一次滴答,钩子函数运行一次
void TaskTickHook(void)
{// to increase the LVGL ticklv_tick_inc(1);// to increase the timerpage's timer(put in here is to ensure the Real Time)if (ui_TimerPageFlag){ui_TimerPage_ms += 1;if (ui_TimerPage_ms >= 10){ui_TimerPage_ms = 0;ui_TimerPage_10ms += 1;}if (ui_TimerPage_10ms >= 100){ui_TimerPage_10ms = 0;ui_TimerPage_sec += 1;uint8_t IdleBreakstr = 0;osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 0);}if (ui_TimerPage_sec >= 60){ui_TimerPage_sec = 0;ui_TimerPage_min += 1;}if (ui_TimerPage_min >= 60){ui_TimerPage_min = 0;}}
}
LVGL 定时器相关操作:首先调用 lv_tick_inc(1)
函数,这个函数应该是用于给 LVGL 图形库的内部定时器递增计数,为 LVGL 的界面刷新、动画等基于时间的操作提供时间基准,确保 LVGL 相关功能能够按照合适的时间节奏运行,比如界面元素的定时更新、动画效果的平滑过渡等都依赖这个定时器的计数来驱动。
特定页面定时器逻辑(ui_TimerPage
相关):通过判断 ui_TimerPageFlag
变量(这个变量应该在其他地方定义并根据具体情况被设置为真或假,用于控制是否进入该定时器逻辑)来决定是否执行后续的定时器计数操作。如果条件满足,对 ui_TimerPage
相关的时间变量(ui_TimerPage_ms
、ui_TimerPage_10ms
、ui_TimerPage_sec
、ui_TimerPage_min
)进行递增操作,模拟了一个时间计数的逻辑,并且当时间达到一定阈值(如 ui_TimerPage_ms
满 10 则向高位进位,ui_TimerPage_10ms
满 100 也向更高位进位等)时进行相应的处理,当 ui_TimerPage_10ms
满 100 时,还会创建一个 uint8_t
类型的变量 IdleBreakstr
并初始化为 0,然后通过 osMessageQueuePut
函数将其发送到 IdleBreak_MessageQueue
消息队列中,这可能是用于通知其他任务系统在该定时器相关的时间维度上进入了某个特定状态或者触发其他相关的业务逻辑(比如涉及空闲状态打破等相关操作,具体依赖整个系统设计)。
1s触发一次UI的时间刷新消息。
3.7 LvHandlerTask
函数
void LvHandlerTask(void *argument)
{uint8_t IdleBreakstr = 0;while (1){if (lv_disp_get_inactive_time(NULL) < 1000){// Idle time break, set to 0osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 0);}lv_task_handler();osDelay(1);}
}
空闲状态相关消息发送判断:在一个无限循环中,首先通过 lv_disp_get_inactive_time(NULL)
函数获取显示设备(由 LVGL 管理的显示屏)的空闲时间(单位可能是系统默认的时间单位,比如毫秒,这里传入 NULL
表示获取默认显示设备的空闲时间信息),然后判断这个空闲时间是否小于 1000(单位推测为毫秒),如果小于该值,意味着显示设备在较短时间内有操作,不算处于长时间空闲状态,此时通过 osMessageQueuePut
函数向 IdleBreak_MessageQueue
消息队列发送一个 IdleBreakstr
变量对应的消息(这里发送操作的最后一个参数 0
表示如果消息队列已满则直接返回,不阻塞等待,避免任务因为消息队列满而长时间阻塞无法继续执行其他操作),可以推测这个操作是用于通知其他任务系统当前显示相关操作比较活跃,打破可能存在的空闲状态相关逻辑判断。打破计时息屏的过程 起来重新计时
LVGL 任务处理与延时:调用 lv_task_handler()
函数,这个函数是 LVGL 图形库用于处理各种内部任务的关键函数,例如界面元素的更新、事件处理等操作都会在这个函数调用中被执行,确保 LVGL 界面能够正常响应各种交互、动态更新显示内容等。最后通过 osDelay(1)
让任务暂停执行 1 个时间单位(通常是毫秒),这样既可以避免任务一直占用 CPU 资源不断循环执行(给其他任务留出执行机会),又能以一定的频率(每秒 1000 次左右,取决于系统时钟配置)持续检查显示设备空闲时间以及处理 LVGL 相关任务,保证系统整体的流畅运行和响应性能。
这个函数是必须加的
3.8 WDOGFeedTask
函数(部分注释代码)、
void WDOGFeedTask(void *argument)
{// owdg// WDOG_Port_Init();while (1){// WDOG_Feed();// WDOG_Enable();osDelay(100);}
}
功能说明(基于未注释代码推测):从函数名和部分代码注释可以推测,这个任务原本应该是用于处理看门狗(Watch Dog,WDOG)相关操作的。WDOG_Port_Init()
函数(虽然被注释掉,但推测其功能)可能是用于初始化看门狗相关的硬件端口配置,WDOG_Feed()
函数应该是用于 “喂狗” 操作(即在看门狗定时器到期前重置定时器,防止系统复位,是看门狗机制中保持系统正常运行的关键操作),WDOG_Enable()
函数则是用于启用看门狗功能。然而当前代码中这些关键操作都被注释掉了,只剩下 osDelay(100)
语句,使得该任务目前只是每隔 100ms 暂停一次(通过延时操作让出 CPU 资源),没有实际执行看门狗相关的核心功能。
防止程序跑飞,每100ms喂一次狗。
接下来就是解析 各种UI界面了。我估计ui界面的函数是死的,初始化一下保留一个页面指针,把所有组件都扩展给别的任务使用,有改变就刷新 没改变就不动。比如主界面静态的效果应该宏定义,然后就是根据实际任务处理的逻辑来修改。只不过我们以前写裸机的时候就一个任务,更新中断的时候将数据写入lcd,这里是写入一个应用lvgl,我们不需要直接操作lcd的寄存器。以前我么更新都是按顺序,或者中断优先级 ,现在有了freertos帮我们管理,只要发生了某件事,发送一个消息,任务去获取消息就可以实现同样的功能。