嵌入式程序发开思路
嵌入式程序发开思路
一. 状态机思想(State Machine)
可以理解为用一个队列去做周期性任务,这个队列中每个任务都有对应的周期时间,且扫描队列通过定时器中断中去扫描
注意:这边定时器中断只是用作了扫描任务时间(也可以理解为时间到了就把对应的标志位置1),并没有去执行任务,然后真正的扫描任务的程序放到主程序中去做,主程序扫描到对应程序的标志位就去执行该任务。
所以这边需要引入一个队列任务的特点结构体:
typedef struct
{uint32_t TaskTimeCount[TASK_QUEUE_SIZE];//每个任务的时间计数器uint8_t TaskCount[TASK_QUEUE_SIZE];//每个任务执行次数uint32_t TaskTimeValue[TASK_QUEUE_SIZE];//任务执行时间void (*TaskFunction[TASK_QUEUE_SIZE])(void *);//每个带有参数的任务函数指针void (*TaskFunctionNoParam[TASK_QUEUE_SIZE])(void);//每个不带参数的任务函数指针uint8_t TaskState[TASK_QUEUE_SIZE];//每个任务的状态,0表示未启用,1表示启用...
}TaskQueueStruct;
任务相关的一些状态值:
#define TASK_QUEUE_SIZE 128//用于存储任务的数量,根据实际需求进行调整
#define TASK_ADD_OK 1//任务添加成功
#define TASK_ADD_ERROR 0//任务添加失败#define TASK_REMOVE_OK 1//任务移除成功
#define TASK_REMOVE_ERROR 0//任务移除失败//任务状态枚举类型
enum
{TASK_NOT_ENABLED = 0,//任务未启用TASK_ENABLED,//任务已启用TASK_RUNNING,//任务正在运行TASK_COMPLETED,//任务已完成TASK_ERROR//任务出错
};
1.1 周期时间控制
用到的参数:
#define TASK_QUEUE_SIZE 128//用于存储任务的数量,根据实际需求进行调整//任务状态枚举类型
enum
{TASK_NOT_ENABLED = 0,//任务未启用TASK_ENABLED,//任务已启用TASK_RUNNING,//任务正在运行TASK_COMPLETED,//任务已完成TASK_ERROR//任务出错
};uint32_t TaskTimeCount[TASK_QUEUE_SIZE];//每个任务的时间计数器
uint8_t TaskCount[TASK_QUEUE_SIZE];//每个任务执行次数
uint32_t TaskTimeValue[TASK_QUEUE_SIZE];//任务执行时间
uint8_t TaskState[TASK_QUEUE_SIZE];//每个任务的状态,0表示未启用,1表示启用...
这几个变量用来控制程序执行的次数以及循环的周期,我这边暂时只是做了一个周期性循环任务的程序扫描示例代码如下,如果需要单步执行或者次数执行可以用TaskQueue.TaskTimeCount[i]
这个来进行控制。
void TaskQueueCycleTimeScan(void)
{for(uint8_t i = 0 ;i < TASK_QUEUE_SIZE;i++)//任务遍历{if(TaskQueue.TaskTimeCount[i]!=0 && TaskQueue.TaskState[i] == TASK_ENABLED){TaskQueue.TaskTimeCount[i]--;//任务计数器开始递减if(TaskQueue.TaskTimeCount[i] == 0)//对应任务的计数值为0时{TaskQueue.TaskCount[i]=1;//需要任务执行次数=1,当任务扫描时扫到这个变量就表示需要执行一次对应的任务了TaskQueue.TaskTimeCount[i] = TaskQueue.TaskTimeValue[i];//任务计数器重新赋值}}}
}
然后这个程序因为需要严格控制时间周期,所以我把它丢到了定时器中断中去做,定时器的周期我定的时1ms进一次中断,因为用的HAL库编程所以是1ms调用一次回调函数。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM2)//判断是不是TIM2进的中断{TaskQueueCycleTimeScan();//扫描队列中的函数的时间}
}
1.2 周期任务控制
用到的参数:
#define TASK_QUEUE_SIZE 128//用于存储任务的数量,根据实际需求进行调整//任务状态枚举类型
enum
{TASK_NOT_ENABLED = 0,//任务未启用TASK_ENABLED,//任务已启用TASK_RUNNING,//任务正在运行TASK_COMPLETED,//任务已完成TASK_ERROR//任务出错
};uint8_t TaskCount[TASK_QUEUE_SIZE];//每个任务执行次数
void (*TaskFunction[TASK_QUEUE_SIZE])(void *);//每个带有参数的任务函数指针
void (*TaskFunctionNoParam[TASK_QUEUE_SIZE])(void);//每个不带参数的任务函数指针
uint8_t TaskState[TASK_QUEUE_SIZE];//每个任务的状态,0表示未启用,1表示启用...
这些变量用来任务队列的周期执行,我这边只是做了无参数的任务执行方式,如果任务需要传入参数,可以使用 void (*TaskFunction[TASK_QUEUE_SIZE])(void *);//每个带有参数的任务函数指针
这个来做任务,传入的参数这边虽然是一个无符号的指针作为参数,但是可以用强制类型转换来变,或者你要加入多个参数,就可以使用结构体来定义参数,然后把这个void*
强制转换成结构体类型的参数这样就可以传入多个参数了。
void TaskQueueCycleScan(void)
{for(uint8_t i = 0 ;i < TASK_QUEUE_SIZE;i++)//遍历任务{if(TaskQueue.TaskCount[i] !=0 && TaskQueue.TaskState[i] == TASK_ENABLED)//找到要执行的任务{TaskQueue.TaskCount[i] = 0;//重新把执行次数清零if(TaskQueue.TaskFunctionNoParam[i] != NULL){TaskQueue.TaskFunctionNoParam[i]();//执行任务}}}
}
上面这部分任务队列的周期执行程序需要放到主程序或死循环中去做
while (1){TaskQueueCycleScan();/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}
这样主程序中只需要去扫描任务就好了。
1.3 任务添加
我这边暂时只是做了无形参的函数用作任务的添加,有形参的需要额外去写,这边传入的参数uint16_t TaskTime
表示任务执行的周期时间,因为定时器中断我采用了1ms进一次的操作,所以这边给多少就是多少ms
执行一次
uint8_t TaskQueueAddTask_NoParam(void (*TaskFunction)(void),uint16_t TaskTime,uint8_t TaskState)
{//遍历任务队列,查找空闲位置for(uint8_t i = 0;i<TASK_QUEUE_SIZE;i++){if(TaskQueue.TaskFunction[i] == NULL && TaskQueue.TaskFunctionNoParam[i] == NULL && TaskQueue.TaskState[i] == TASK_NOT_ENABLED){TaskQueue.TaskFunctionNoParam[i] = TaskFunction;//将任务函数指针赋值给任务队列TaskQueue.TaskTimeCount[i] = TaskTime;//给与任务时间TaskQueue.TaskTimeValue[i] = TaskTime;//记录任务所需要的时间方便下次使用TaskQueue.TaskState[i] = TaskState;//任务状态置为启用return TASK_ADD_OK;//任务添加成功}}return TASK_ADD_ERROR;//任务添加失败
}
1.4 任务删除
任务删除其实和初始化有点类似,只不过这边需要找到对应的程序,我这边还是只做了无形参的任务删除函数
uint8_t TaskQueueRemoveTask_NoParam(void (*TaskFunction)(void))
{//遍历任务队列,查找要删除的任务for(uint8_t i = 0;i<TASK_QUEUE_SIZE;i++){if(TaskQueue.TaskFunctionNoParam[i] == TaskFunction && TaskQueue.TaskState[i] == TASK_ENABLED){TaskQueue.TaskTimeCount[i] = 0;//任务时间计数器清零TaskQueue.TaskCount[i] = 0;//任务执行次数清零TaskQueue.TaskTimeValue[i] = 0;//任务时间值清零TaskQueue.TaskFunction[i] = NULL;//清除当前位置的带参数任务TaskQueue.TaskFunctionNoParam[i] = NULL;//清除当前位置的不带参数任务TaskQueue.TaskState[i] = TASK_NOT_ENABLED;//任务状态置为未启用return TASK_REMOVE_OK;//任务删除成功}}return TASK_REMOVE_ERROR;//任务删除失败
}
1.5 简单示例
我这边做了一个简单的示例,让两个等进行不同时间间隔的闪烁
void TaskInit(void)
{TaskQueueInit();//任务队列初始化TaskQueueAddTask_NoParam(LED_Control2,1000,TASK_ENABLED);//添加LED控制任务TaskQueueAddTask_NoParam(LED_Control3,300,TASK_ENABLED);//添加LED控制任务
}void LED_Control2(void)//LED切换电平任务
{HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);
}void LED_Control3(void)
{HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
}