废话部分
摸了两天,感觉看着教程很多都明白,越看越困
事实证明要让学习这件事维持下去是需要融入一点自己的想法的
正文
看到定时器里面为了调试塞的一堆东西,心情就莫名的烦躁,再想到移植的问题,就更烦躁了
之前在公司里面尝试过把它封装的好看一点,现在靠记忆复原一下也是很简单的
主体部分
**typedef struct
{
void (*VoidFunctionPointArray[MAX_VOID_FUNCTION_POINT_ARRAY_VALUE])();
uint16_t VoidFunctionPointArrayNextMember;
uint16_t* VariablePointArray[MAX_VARIABLE_POINT_ARRAY_VALUE];
uint16_t VariablePointArrayNextMember;
}S_TIMER;
S_TIMER s_Timer;
void timer_Auto_Execute(void)
{
uint16_t i = 0;
for(i = 0;i<s_Timer.VariablePointArrayNextMember;i++)
{
if(*(s_Timer.VariablePointArray[i])<MAX_VARIABLE_OVERFLOW_VALUE)
{
(*(s_Timer.VariablePointArray[i]))++;
}
}
for(i = 0;i<s_Timer.VoidFunctionPointArrayNextMember;i++)
{
s_Timer.VoidFunctionPointArray[i]();
}
}
定义了一个包含两个数组的结构体,一个数组用于存储需要放在定时器中自加的变量的地址,另外一个数组存储需要在每次定时器中断中自动执行的函数地址。
下面的函数则是放在定时器中断中,每次产生定时器中断,即将所有的需要自加一次的变量自加一遍,再把所有需要执行的函数执行一遍
这样处理,其他模块需要使用到定时器时,可以直接调用相关的接口函数解决
比如跑马灯程序,需要一个用于计时的在定时器中自加的变量,即可调用这个函数,将这个变量地址存放进数组
E_RESULT timer_Add_Variable_Into_Timer(uint16_t* pVariable)
{
uint16_t i = 0;
if(pVariable == NULL)
{
return E_RESULT_ERROR;
}
if(s_Timer.VariablePointArrayNextMember>=MAX_VARIABLE_POINT_ARRAY_VALUE)
{
return E_RESULT_ERROR;
}
for(i = 0;i<s_Timer.VariablePointArrayNextMember;i++)
{
if(pVariable == s_Timer.VariablePointArray[i])
{
return E_RESULT_ERROR;
}
}
s_Timer.VariablePointArray[s_Timer.VariablePointArrayNextMember] = pVariable;
s_Timer.VariablePointArrayNextMember++;
return E_RESULT_OK;
}
实际使用时类似这种效果
uint16_t LedChangeTime = 0;
...
timer_Add_Variable_Into_Timer(&LedChangeTime);
while(1)
{
if(LedChangeTime >=500)
{
LedChangeTime = 0;
LED0 = ~LED0;
}
}
那这样处理有什么好处,我为什么不直接把需要自加的变量丢定时器里面去?
好看啊!
这样你定时器中断里面永远只有一个函数timer_Auto_Execute()
除了好看呢?
这。。总归是有一些比较复杂的控制逻辑或者细节的考虑的
比如你希望控制资源,让一些变量(和后面提到的在定时器中调用的函数)是有寿命的,用完即删,不在某种情况下不使用
我寻思这些立一个标志位判断不就行了
。。。
它好看啊QAQ!
所以这么改的作用其实除了上层程序可以脱离底层(毕竟不需要把自己的变量和函数那么明显的放到另外一个和底层息息相关的模块里面),节省了移植的麻烦以外,也就是好看了
上面说的如果控制逻辑比较复杂,有可能需要自加的变量是有寿命的话,可以在不需要这个变量的时候调用反函数
E_RESULT timer_Del_Variable_In_Timer(uint16_t* pVariable)
{
uint16_t i = 0;
__HAL_RCC_TIM3_CLK_DISABLE(); //为防止出现极限状况下最后添加的变量被跳过一次自加,这里非能计时器
if(pVariable == NULL)
{
__HAL_RCC_TIM3_CLK_ENABLE();
return E_RESULT_ERROR;
}
for(i = 0;i<s_Timer.VariablePointArrayNextMember;i++)
{
if(pVariable == s_Timer.VariablePointArray[i])
{
s_Timer.VariablePointArrayNextMember--; //不非能计时器,这里先减可能导致最后添加的变量被跳过一次自加,后减会导致最后添加的变量自加两次
s_Timer.VariablePointArray[i] = s_Timer.VariablePointArray[s_Timer.VariablePointArrayNextMember];
__HAL_RCC_TIM3_CLK_ENABLE();
return E_RESULT_OK;
}
}
__HAL_RCC_TIM3_CLK_ENABLE();
return E_RESULT_ERROR;
}
如果相对来说不在意注释里面的问题,更在乎整体时钟的精准,或者出于对移植的考虑(其实也可以把使能和非能的语句自己再封装成一个宏定义或者函数,这样移植起来方便一点),去除时钟使能和非能的部分也是可以的
同理,需要在定时器中执行的函数也可以这么使用
E_RESULT timer_Add_Void_Function_Into_Timer(void (*pVoidFunction)())
{
uint16_t i = 0;
if(pVoidFunction == NULL)
{
return E_RESULT_ERROR;
}
if(s_Timer.VoidFunctionPointArrayNextMember>=MAX_VOID_FUNCTION_POINT_ARRAY_VALUE)
{
return E_RESULT_ERROR;
}
for(i = 0;i<s_Timer.VoidFunctionPointArrayNextMember;i++)
{
if(pVoidFunction == s_Timer.VoidFunctionPointArray[i])
{
return E_RESULT_ERROR;
}
}
s_Timer.VoidFunctionPointArray[s_Timer.VoidFunctionPointArrayNextMember] = pVoidFunction;
s_Timer.VoidFunctionPointArrayNextMember++;
return E_RESULT_OK;
}
E_RESULT timer_Del_Void_Function_In_Timer(void (*pVoidFunction)())
{
uint16_t i = 0;
__HAL_RCC_TIM3_CLK_DISABLE(); //为防止出现极限状况下最后添加的函数被跳过一次执行,这里非能计时器
if(pVoidFunction == NULL)
{
__HAL_RCC_TIM3_CLK_ENABLE();
return E_RESULT_ERROR;
}
for(i = 0;i<s_Timer.VoidFunctionPointArrayNextMember;i++)
{
if(pVoidFunction == s_Timer.VoidFunctionPointArray[i])
{
s_Timer.VoidFunctionPointArrayNextMember--;//不非能计时器,这里先减可能导致最后添加的函数被跳过一次执行,后减会导致最后添加的函数被执行两次
s_Timer.VoidFunctionPointArray[i] = s_Timer.VoidFunctionPointArray[s_Timer.VoidFunctionPointArrayNextMember];
__HAL_RCC_TIM3_CLK_ENABLE();
return E_RESULT_OK;
}
}
__HAL_RCC_TIM3_CLK_ENABLE();
return E_RESULT_ERROR;
}
这里还有另外一种处理方式,即在每次定时器中断中立起标志,再在主循环中检查对应标志再执行,毕竟定时器时间蛮宝贵的
(但是这样不就失去了定时器能插队的优势了嘛)