任务函数分析
一、页面存储栈 PageStack
1、头文件
#include "ui.h"
#define MAX_DEPTH 6
typedef long long int StackData_t;
typedef struct
{
StackData_t Data[MAX_DEPTH];
uint8_t Top_Point;
}user_Stack_T;
uint8_t user_Stack_Push(user_Stack_T* stack, StackData_t datain);
uint8_t user_Stack_Pop(user_Stack_T* stack);
uint8_t user_Stack_isEmpty(user_Stack_T* stack);
void user_Stack_Clear(user_Stack_T* stack);
首先用typedef定义了一种数据类型, long long int
我在想32位单片机最大的数据类型长度不就是int型 的 4个字节吗,好奇怪,只有在PC机上 long long int才是64位吧,也就是8个字节
去串口验证了一下,确实是8个字节
这个数组长度是6 。一看到这个我就想起来了任务的堆栈空间,寄存器和栈之间保存上下文的时候。
后面定义了一个1个字节的变量 看名字是栈顶指针的意思(其实是表示栈中元素个数的意思吧)。
后面声明了4个函数,在C文件里再进行解析
2、c文件
#include "PageStack.h"
uint8_t user_Stack_Push(user_Stack_T* stack, StackData_t datain)
{
if(stack->Top_Point == MAX_DEPTH - 1)
{return -1;}
stack->Data[stack->Top_Point++] = datain;
return 0;
}
uint8_t user_Stack_Pop(user_Stack_T* stack)
{
if(stack->Top_Point == 0)
{return -1;}
stack->Data[--stack->Top_Point] = NULL;
return 0;
}
uint8_t user_Stack_isEmpty(user_Stack_T* stack)
{
if(stack->Top_Point == 0)
{return 1;}
return 0;
}
void user_Stack_Clear(user_Stack_T* stack)
{
while(!user_Stack_isEmpty(stack))
{
user_Stack_Pop(stack);
}
}
2.1 user_Stack_Push 入栈
uint8_t user_Stack_Push(user_Stack_T* stack, StackData_t datain)
{
if(stack->Top_Point == MAX_DEPTH - 1)
{return -1;}
stack->Data[stack->Top_Point++] = datain;
return 0;
}
这个名字一听就懂,压栈 。栈这个数据结构遵循先进后出的顺序。(ARM)栈底指针指向地址最高位,栈是向下增长的。栈顶指针就指向第一个无元素的位置。可能栈的类型也不全一样,我记得还有栈指针指向-1的,空栈的时候。
这里的栈明显是低地址增长的。有些许不太一样,而且是数组构成的栈
如果栈顶指针指向最高地址也就是 5 这时候栈内已经填满了。这个数组是个a[6]类型,地址最高到5,逻辑应该是这样的。栈顶指针永远指向最后一个入栈的元素的下一个位置,开始指向0的话是不满足的。我猜测是这样的
这里确实对我来说有点绕,但是栈内最多存到5又是没错的,因为6已经超出数组的范围了。它是现存再自增的,存到第5个元素后,Top_Point已经为5了,假如从零开始初始化。第六个元素存不进去啊?第一个if就推出了。只有当初始化为-1,且++在左边我认为才正常。
索引先自增为0 压入第一个元素
最后一次入栈,索引等于4,先自增索引为5.压入最后一个元素。下次判断也是栈满的情况。
我还是问问GPT吧,文心一言的判断与我相同,豆包和kimi的判断一致。
让文心一言给我修改过后是这样
if(stack->Top_Point == MAX_DEPTH)
{
return -1; // 栈已满
}
stack->Data[stack->Top_Point++] = datain;
return 0; // 成功推入
2.2 user_Stack_Pop 出栈
uint8_t user_Stack_Pop(user_Stack_T* stack)
{
if(stack->Top_Point == 0)
{return -1;}
stack->Data[--stack->Top_Point] = NULL;
return 0;
}
出栈的话,跟入栈是相反的过程,如果栈索引到 最低地址(也就是数组首地址)退出。意思也就是初始化的时候,栈索引使指向数组首地址的。
那上面的解答了,最大栈存的元素个数 是深度-1 然后这里弹出栈的逻辑好像有点奇怪。先自减再赋值,这不是等于栈索引得指向第6个才能生效,这样自减后索引到数组的第五个,赋值NULL意思就是出栈吧,一般出栈有人来取,这里可能没人取吧。
我们就假设入栈判断是 最大深度。刚好就是6 这样二者才能联系起来。不然意义就是少存一个(也不能算错),申请M个元素大小的栈空间,却只利用了M-1个,土豪 可以这么做。
2.3 user_Stack_isEmpty 栈是否为空
uint8_t user_Stack_isEmpty(user_Stack_T* stack)
{
if(stack->Top_Point == 0)
{return 1;}
return 0;
}
这里就是判断索引位置来判断,很简单。
2.4 user_Stack_Clear 清空栈
void user_Stack_Clear(user_Stack_T* stack)
{
while(!user_Stack_isEmpty(stack))
{
user_Stack_Pop(stack);
}
}
也就是逐个出栈的步骤,将整个数组的元素清空
二、user_KeyTask 按键任务
/* Private includes -----------------------------------------------------------*/
//includes
#include "user_TasksInit.h"
#include "user_StopEnterTask.h"
#include "user_ScrRenewTask.h"
#include "ui_HomePage.h"
#include "main.h"
#include "key.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/**
* @brief Key press check task
* @param argument: Not used
* @retval None
*/
void KeyTask(void *argument)
{
uint8_t keystr=0;
uint8_t Stopstr=0;
uint8_t IdleBreakstr=0;
while(1)
{
switch(KeyScan(0))
{
case 1:
keystr = 1;
osMessageQueuePut(Key_MessageQueue, &keystr, 0, 1);
osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 1);
break;
case 2:
break;
}
osDelay(1);
}
}
从按键任务引用的头文件可以 了解到 涉及到停止、进入某些特定任务,屏幕刷新任务, ui的主界面相关。
按键任务 首先定义了三个 uint8_t
类型的局部变量 keystr
、Stopstr
和 IdleBreakstr
,初始值都设为 0
。keystr
可能用于后续构建要发送到消息队列中的按键相关消息内容,Stopstr
和 IdleBreakstr
目前从代码看暂未在其他地方使用
任务死循环内 执行按键扫描,不支持连按
如果按下就把keystr这个键值赋值为1 ,freertos的队列是地址传递节省了值传递的复制的开销
分别向两个消息队列 发送消息 优先级为 0 普通消息 超时时间1ms,基本不会阻塞
扫描期间也加入1ms阻塞延时。
三、user_MessageSendTask 消息发送任务
/* Private includes -----------------------------------------------------------*/
//includes
#include "string.h"
#include "stdio.h"
#include "main.h"
#include "stm32f4xx_it.h"
#include "rtc.h"
#include "user_TasksInit.h"
#include "user_MessageSendTask.h"
#include "ui.h"
#include "ui_EnvPage.h"
#include "ui_HRPage.h"
#include "ui_SPO2Page.h"
#include "ui_HomePage.h"
#include "ui_DateTimeSetPage.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
struct
{
RTC_DateTypeDef nowdate;
RTC_TimeTypeDef nowtime;
int8_t humi;
int8_t temp;
uint8_t HR;
uint8_t SPO2;
uint16_t stepNum;
}BLEMessage;
struct
{
RTC_DateTypeDef nowdate;
RTC_TimeTypeDef nowtime;
}TimeSetMessage;
/* Private function prototypes -----------------------------------------------*/
void StrCMD_Get(uint8_t * str,uint8_t * cmd)
{
uint8_t i=0;
while(str[i]!='=')
{
cmd[i] = str[i];
i++;
}
}
//set time//OV+ST=20230629125555
uint8_t TimeFormat_Get(uint8_t * str)
{
TimeSetMessage.nowdate.Year = (str[8]-'0')*10+str[9]-'0';
TimeSetMessage.nowdate.Month = (str[10]-'0')*10+str[11]-'0';
TimeSetMessage.nowdate.Date = (str[12]-'0')*10+str[13]-'0';
TimeSetMessage.nowtime.Hours = (str[14]-'0')*10+str[15]-'0';
TimeSetMessage.nowtime.Minutes = (str[16]-'0')*10+str[17]-'0';
TimeSetMessage.nowtime.Seconds = (str[18]-'0')*10+str[19]-'0';
if(TimeSetMessage.nowdate.Year>0 && TimeSetMessage.nowdate.Year<99
&& TimeSetMessage.nowdate.Month>0 && TimeSetMessage.nowdate.Month<=12
&& TimeSetMessage.nowdate.Date>0 && TimeSetMessage.nowdate.Date<=31
&& TimeSetMessage.nowtime.Hours>=0 && TimeSetMessage.nowtime.Hours<=23
&& TimeSetMessage.nowtime.Minutes>=0 && TimeSetMessage.nowtime.Minutes<=59
&& TimeSetMessage.nowtime.Seconds>=0 && TimeSetMessage.nowtime.Seconds<=59)
{
RTC_SetDate(TimeSetMessage.nowdate.Year, TimeSetMessage.nowdate.Month,TimeSetMessage.nowdate.Date);
RTC_SetTime(TimeSetMessage.nowtime.Hours,TimeSetMessage.nowtime.Minutes,TimeSetMessage.nowtime.Seconds);
printf("TIMESETOK\r\n");
}
}
/**
* @brief send the message via BLE, use uart
* @param argument: Not used
* @retval None
*/
void MessageSendTask(void *argument)
{
while(1)
{
if(HardInt_uart_flag)
{
HardInt_uart_flag = 0;
uint8_t IdleBreakstr=0;
osMessageQueuePut(IdleBreak_MessageQueue,&IdleBreakstr,NULL,1);
printf("RecStr:%s\r\n",HardInt_receive_str);
if(!strcmp(HardInt_receive_str,"OV"))
{
printf("OK\r\n");
}
else if(!strcmp(HardInt_receive_str,"OV+VERSION"))
{
printf("VERSION=V2.3\r\n");
}
else if(!strcmp(HardInt_receive_str,"OV+SEND"))
{
HAL_RTC_GetTime(&hrtc,&(BLEMessage.nowtime),RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&BLEMessage.nowdate,RTC_FORMAT_BIN);
BLEMessage.humi = ui_EnvHumiValue;
BLEMessage.temp = ui_EnvTempValue;
BLEMessage.HR = ui_HRValue;
BLEMessage.SPO2 = ui_SPO2Value;
BLEMessage.stepNum = ui_StepNumValue;
printf("data:%2d-%02d\r\n",BLEMessage.nowdate.Month,BLEMessage.nowdate.Date);
printf("time:%02d:%02d:%02d\r\n",BLEMessage.nowtime.Hours,BLEMessage.nowtime.Minutes,BLEMessage.nowtime.Seconds);
printf("humidity:%d%%\r\n",BLEMessage.humi);
printf("temperature:%d\r\n",BLEMessage.temp);
printf("Heart Rate:%d%%\r\n",BLEMessage.HR);
printf("SPO2:%d%%\r\n",BLEMessage.SPO2);
printf("Step today:%d\r\n",BLEMessage.stepNum);
}
//set time//OV+ST=20230629125555
else if(strlen(HardInt_receive_str)==20)
{
uint8_t cmd[10];
memset(cmd,0,sizeof(cmd));
StrCMD_Get(HardInt_receive_str,cmd);
if(user_APPSy_EN && !strcmp(cmd,"OV+ST"))
{
TimeFormat_Get(HardInt_receive_str);
}
}
memset(HardInt_receive_str,0,sizeof(HardInt_receive_str));
}
osDelay(1000);
}
}
string.h
和 stdio.h
是 C 语言标准库头文件,分别提供了字符串处理函数(如 strcpy
、strcmp
、memset
等)和输入输出函数(如 printf
)相关的声明,在这里用于字符串操作以及向控制台(可能是通过调试接口)输出信息
main.h
通常包含主程序模块相关的一些通用定义,例如全局变量声明、主函数中使用到的宏等。
stm32f4xx_it.h
一般是 STM32F4 系列单片机的中断相关的头文件,可能包含了中断服务函数的声明以及一些与中断处理相关的宏定义等内容,用于处理单片机的各种中断事件。
rtc.h
应该是与实时时钟(RTC)模块相关的头文件,里面会有实时时钟操作函数(如 RTC_SetDate
、RTC_SetTime
等函数的声明),用于获取和设置系统时间日期。
user_TasksInit.h
包含了对freertos的引用 还有一个屏幕刷新深度。
user_MessageSendTask.h
里面没有东西
struct
{
RTC_DateTypeDef nowdate;
RTC_TimeTypeDef nowtime;
int8_t humi;
int8_t temp;
uint8_t HR;
uint8_t SPO2;
uint16_t stepNum;
}BLEMessage;
struct
{
RTC_DateTypeDef nowdate;
RTC_TimeTypeDef nowtime;
}TimeSetMessage;
定义了两个匿名结构体类型的变量 BLEMessage
和 TimeSetMessage
BLEMessage
结构体包含了实时时钟的日期和时间信息(nowdate
和 nowtime
),以及环境湿度(humi
)、温度(temp
)、心率(HR
)、血氧饱和度(SPO2
)和步数(stepNum
)等信息。这个结构体可能用于封装要通过蓝牙(BLE,从变量名推测)发送给外部设备的各种数据,方便统一管理和传输。
TimeSetMessage
结构体只包含了实时时钟的日期和时间信息,主要用于在设置系统时间的操作中临时存储解析出来的日期和时间数据,以便后续调用 RTC_SetDate
和 RTC_SetTime
函数来更新系统时间。
取出字符串的一部分
void StrCMD_Get(uint8_t * str,uint8_t * cmd)
{
uint8_t i=0;
while(str[i]!='=')
{
cmd[i] = str[i];
i++;
}
}
估计是将一个字符串 等于号左边的取到cmd里
时间格式获取
//set time//OV+ST=20230629125555
uint8_t TimeFormat_Get(uint8_t * str)
{
TimeSetMessage.nowdate.Year = (str[8]-'0')*10+str[9]-'0';
TimeSetMessage.nowdate.Month = (str[10]-'0')*10+str[11]-'0';
TimeSetMessage.nowdate.Date = (str[12]-'0')*10+str[13]-'0';
TimeSetMessage.nowtime.Hours = (str[14]-'0')*10+str[15]-'0';
TimeSetMessage.nowtime.Minutes = (str[16]-'0')*10+str[17]-'0';
TimeSetMessage.nowtime.Seconds = (str[18]-'0')*10+str[19]-'0';
if(TimeSetMessage.nowdate.Year>0 && TimeSetMessage.nowdate.Year<99
&& TimeSetMessage.nowdate.Month>0 && TimeSetMessage.nowdate.Month<=12
&& TimeSetMessage.nowdate.Date>0 && TimeSetMessage.nowdate.Date<=31
&& TimeSetMessage.nowtime.Hours>=0 && TimeSetMessage.nowtime.Hours<=23
&& TimeSetMessage.nowtime.Minutes>=0 && TimeSetMessage.nowtime.Minutes<=59
&& TimeSetMessage.nowtime.Seconds>=0 && TimeSetMessage.nowtime.Seconds<=59)
{
RTC_SetDate(TimeSetMessage.nowdate.Year, TimeSetMessage.nowdate.Month,TimeSetMessage.nowdate.Date);
RTC_SetTime(TimeSetMessage.nowtime.Hours,TimeSetMessage.nowtime.Minutes,TimeSetMessage.nowtime.Seconds);
printf("TIMESETOK\r\n");
}
}
就是把字符串解析成时间并进行设置时间消息,如果解析的数据无误,消息设置完还要对RTC的时间寄存器更新。这些消息应该是时间任务在用 。
消息发送任务
void MessageSendTask(void *argument)
{
while (1)
{
if (HardInt_uart_flag)
{
HardInt_uart_flag = 0;
uint8_t IdleBreakstr = 0;
osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, NULL, 1);
printf("RecStr:%s\r\n", HardInt_receive_str);
if (!strcmp(HardInt_receive_str, "OV"))
{
printf("OK\r\n");
}
else if (!strcmp(HardInt_receive_str, "OV+VERSION"))
{
printf("VERSION=V2.3\r\n");
}
else if (!strcmp(HardInt_receive_str, "OV+SEND"))
{
HAL_RTC_GetTime(&hrtc, &(BLEMessage.nowtime), RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &BLEMessage.nowdate, RTC_FORMAT_BIN);
BLEMessage.humi = ui_EnvHumiValue;
BLEMessage.temp = ui_EnvTempValue;
BLEMessage.HR = ui_HRValue;
BLEMessage.SPO2 = ui_SPO2Value;
BLEMessage.stepNum = ui_StepNumValue;
printf("data:%2d-%02d\r\n", BLEMessage.nowdate.Month, BLEMessage.nowdate.Date);
printf("time:%02d:%02d:%02d\r\n", BLEMessage.nowtime.Hours, BLEMessage.nowtime.Minutes, BLEMessage.nowtime.Seconds);
printf("humidity:%d%%\r\n", BLEMessage.humi);
printf("temperature:%d\r\n", BLEMessage.temp);
printf("Heart Rate:%d%%\r\n", BLEMessage.HR);
printf("SPO2:%d%%\r\n", BLEMessage.SPO2);
printf("Step today:%d\r\n", BLEMessage.stepNum);
}
//set time//OV+ST=20230629125555
else if (strlen(HardInt_receive_str) == 20)
{
uint8_t cmd[10];
memset(cmd, 0, sizeof(cmd));
StrCMD_Get(HardInt_receive_str, cmd);
if (user_APPSy_EN &&!strcmp(cmd, "OV+ST"))
{
TimeFormat_Get(HardInt_receive_str);
}
}
memset(HardInt_receive_str, 0, sizeof(HardInt_receive_str));
}
osDelay(1000);
}
}
通过判断 HardInt_uart_flag
的值来确定是否接收到了新的 UART 数据。当这个标志为 1
时,表示有新数据到达,进入处理分支
产生一次串口数据中断进入这个任务的if分支
没有中断这个任务就一直阻塞。
HardInt_uart_flag = 0;
uint8_t IdleBreakstr=0;
osMessageQueuePut(IdleBreak_MessageQueue,&IdleBreakstr,NULL,1);
printf("RecStr:%s\r\n",HardInt_receive_str);
首先将 HardInt_uart_flag
清零,准备下一次数据接收判断,然后定义一个 uint8_t
类型的变量 IdleBreakstr
并初始化为 0
,接着通过 osMessageQueuePut
函数将 IdleBreakstr
的地址放入 IdleBreak_MessageQueue
消息队列中,具体这个消息队列的作用和后续接收处理它的任务需要参考项目的其他部分代码,但从命名推测可能和任务空闲状态打破等相关操作有关
但是空闲任务一般优先级不是最低吗
if(!strcmp(HardInt_receive_str,"OV"))
{
printf("OK\r\n");
}
else if(!strcmp(HardInt_receive_str,"OV+VERSION"))
{
printf("VERSION=V2.3\r\n");
}
else if(!strcmp(HardInt_receive_str,"OV+SEND"))
{
HAL_RTC_GetTime(&hrtc,&(BLEMessage.nowtime),RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&BLEMessage.nowdate,RTC_FORMAT_BIN);
BLEMessage.humi = ui_EnvHumiValue;
BLEMessage.temp = ui_EnvTempValue;
BLEMessage.HR = ui_HRValue;
BLEMessage.SPO2 = ui_SPO2Value;
BLEMessage.stepNum = ui_StepNumValue;
printf("data:%2d-%02d\r\n",BLEMessage.nowdate.Month,BLEMessage.nowdate.Date);
printf("time:%02d:%02d:%02d\r\n",BLEMessage.nowtime.Hours,BLEMessage.nowtime.Minutes,BLEMessage.nowtime.Seconds);
printf("humidity:%d%%\r\n",BLEMessage.humi);
printf("temperature:%d\r\n",BLEMessage.temp);
printf("Heart Rate:%d%%\r\n",BLEMessage.HR);
printf("SPO2:%d%%\r\n",BLEMessage.SPO2);
printf("Step today:%d\r\n",BLEMessage.stepNum);
}
//set time//OV+ST=20230629125555
else if(strlen(HardInt_receive_str)==20)
{
uint8_t cmd[10];
memset(cmd,0,sizeof(cmd));
StrCMD_Get(HardInt_receive_str,cmd);
if(user_APPSy_EN && !strcmp(cmd,"OV+ST"))
{
TimeFormat_Get(HardInt_receive_str);
}
}
命令解析及对应操作逻辑(多个 if - else if
分支)
OV"
命令处理:当接收到的字符串是 "OV"
时,简单地输出 "OK"
"OV+VERSION"
命令处理:如果接收到的字符串是 "OV+VERSION"
,则输出软件版本信息 "VERSION=V2.3"
,说明该命令用于查询设备的软件版本情况
"OV+SEND"
命令处理:当接收到 "OV+SEND"
命令时,会通过 HAL_RTC_GetTime
和 HAL_RTC_GetDate
函数(这两个函数应该是 STM32 相关的 HAL 库函数,用于获取实时时钟的时间和日期信息)分别获取当前的时间和日期,并存储到 BLEMessage
结构体的相应成员中。然后将 ui_EnvHumiValue
、ui_EnvTempValue
、ui_HRValue
、ui_SPO2Value
、ui_StepNumValue
这些来自用户界面不同页面显示的传感器数据(从变量名推测)赋值给 BLEMessage
结构体中的湿度、温度、心率、血氧饱和度和步数成员。最后通过一系列 printf
函数将 BLEMessage
结构体中的各项数据输出显示,可能用于向外部设备(比如通过蓝牙发送时先在控制台显示确认一下内容)展示当前系统的状态信息
类似于蓝牙模块的指令吧?
长度为 20 的字符串命令处理(设置时间相关):当接收到的字符串长度为 20 时(从注释 //set time//OV+ST=20230629125555
可知,这可能是用于设置时间的特定格式字符串),首先定义一个长度为 10 的 uint8_t
类型数组 cmd
,并通过 memset
函数将其内容初始化为全 0
。然后调用 StrCMD_Get
函数从接收到的完整字符串中提取命令部分(存储到 cmd
数组中),接着判断 user_APPSy_EN
(这个变量应该是一个用于控制是否允许进行相关系统操作的标志,比如是否允许设置时间功能开启)是否为真,并且提取出来的命令字符串是否为 "OV+ST"
,如果都满足条件,则调用 TimeFormat_Get
函数来解析并设置系统时间,这个函数内部会对解析出来的时间日期数据进行合法性检查,并通过 RTC_SetDate
和 RTC_SetTime
函数更新实时时钟的时间和日期信息
uint8_t user_APPSy_EN=0;这里设置的是不支持串口进行设置时间。
最后把缓冲区全部清零。