奇葩代码:实现个灯闪烁函数200行代码---开什么玩笑!

为什么要写这篇

程序需要移植,移植一些底层函数调用接口就需要修改,其中遇到个灯闪烁函数,本以为三下五除二就能搞定,小case。没想到结果让我自己都惊讶。

什么闪烁函数这么牛

函数接口如下:设置灯闪烁行为的函数

有四个参数:idx(LED的下标) blink_time_ms (LED闪烁时间,亮时间 = 灭时间 = blink_time_ms/2)  rest_ms (最后等待时间) times(LED闪烁次数, 如果为0意味着一直)

举例来说blink_time_ms = 400ms , rest_ms = 500ms, times = 5,  那么200ms后灯亮,再经过200ms灯灭,等待500ms ,为一个周期。下个周期仍为200ms后灯亮,再经过200ms灯灭,等待500ms。执行五个周期,灯不再闪烁。

我的分析,我要用定时器实现,不阻塞其它任务功能。idx LED的下标,没有实际意义,砍掉,返回值,没啥实际意义,砍掉。然后就成了这样

工欲善其事必先利其器,什么器?定时器,我之前用过的软件定时器MultiTimer ,为了用起来更顺手一些,更方便移植一些,给其封装了一下。大概用了36行。

typedef struct Multi_Timer  Soft_Timer;
typedef void (*Timeout_Cb)(void);

/**
 * software_timer_set
 * @brief   软件定时器设置
 * @param   handle 参数描述: 定时器句柄标识
 * @param   callback_func 参数描述: 回调函数指针
 * @param   timeout_ms 参数描述: 定时timeout_ms 毫秒
 * @param   is_repeat 参数描述: 是否重复开启定时
 * @note    由于底层没有留出回调函数参数传入,导致无法实现数据传入
 * 
 */
void 
software_timer_set(Soft_Timer* handle, Timeout_Cb callback_func, uint32_t timeout_ms, _Bool is_repeat)
{
    if(is_repeat)
    {
        multi_timer_init(handle, callback_func, timeout_ms, timeout_ms);
        multi_timer_start(handle);
    }else
    {
        multi_timer_init(handle, callback_func, timeout_ms, 0);
        multi_timer_start(handle);
    }
}

/**
 * software_timer_stop
 * @brief 	软件定时器停止
 * @param	handle 参数描述: 定时器句柄标识
 */
void
software_timer_stop(Soft_Timer* handle)
{
	multi_timer_stop(handle);
}

1. 为什么要封装?为了方便移植,每个芯片底层函数都有定时器功能实现,但不见得有软件定时器函数,没有的话就用Multi_Timer替代,有的话,直接将底层函数函数替换掉就好了。

2. 为什么要写注释?因为我没有谜之自信,函数介绍一下功能,说明一下参数,变量说明下作用,添加点儿备注都是给人看的,真正移植别人写的代码时就能体会到要是有些注释那该有多好哇。而且尽最大可能不要在函数内部写单条语句的注释, 失之毫厘,差之千里。没有功能介绍,零散的注释,一旦注释未来得及修正,让移植的人更加头大。

进入正题,如何实现

1. 对于该函数来说,每次执行都该是一次重新开始,如何重新开始,停止闪烁(关闭定时器),指示灯设置为灭。

扫描二维码关注公众号,回复: 12660389 查看本文章

2. 进入定时回调函数的时间如何确定?定时回调函数的首要功能是计时,然后才是执行功能。时间难道不是一个固定值吗?no no no ... 应该随着输入的变化而变化,还是假设blink_time_ms = 400ms , rest_ms = 500ms, 设置什么值最合适呢?100ms, 因为400/2 和 500能被整除的最大数为100,即最大公约数。此处用去66行代码

/**
 * pow_func
 * @brief   幂函数
 * @param   num1 参数描述: 无
 * @param   num2 参数描述: 无
 * @retval  uint16_t 返回值描述: 指数值
 */
uint16_t pow_func(uint16_t num1, uint8_t num2)
{
    uint16_t need_val = 1;

    for (uint8_t i = 0; i < num2; i++)
    {
        need_val *= num1;
    }
}

/**
 * greatest_common_divisor
 * @brief   获取两数的最大公约数
 * @param   num1 参数描述: 无
 * @param   num2 参数描述: 无
 * @retval  uint16_t 返回值描述: 获取两数的最大公约数 
 * @note    1.两数是否有特殊关系,相等或成倍数关系
 * @note    2.若都是10的倍数,化简
 * @note    3.从两数中较小的数开始递减,直到被两数整除
 */
uint16_t greatest_common_divisor(uint16_t num1, uint16_t num2)
{
    uint16_t max_val, min_val;
    uint8_t index = 0;

    if ((num1 == num2) || (num2 % num1 == 0))
    {
        return num1;
    }
    if (num1 % num2 == 0)
    {
        return num2;
    }

    while ((num1 % 10 == 0) && (num2 % 10 == 0))
    {
        num1 /= 10;
        num2 /= 10;
        index++;
    }

    if (num1 < num2)
    {
        min_val = num1;
        max_val = num2;
    }
    else
    {
        min_val = num2;
        max_val = num1;
    }

    for (uint16_t i = min_val; i > 0; i--)
    {
        if ((max_val % i == 0) && (min_val % i == 0))
        {
            return i * pow_func(10, index);
        }
    }
}

1. 最大公倍数怎么得来的?最大公倍数不可能比两数中较小的那个数大。因此从较小的那个数递减开始,直到找到第一个可以被两数都整除的数,即最大公倍数。根据设定时间的常规操作,对执行做一些简化,避免求最大公倍数时间过长。

2. 为什么要写一个幂函数?芯片对标准函数库中的函数 srand 和 rand都能重写,那我还会觉得math.h中的函数还是原装原味的吗。干脆自己写一个。

 

 用到的变量不少,封装成一个结构体。

flag_invoke_blink_func 调用func_LED_blink 函数时,重新开始执行,那么回调函数中的静态变量就都该清0,还不是每次进入回调函数就清零,因此需要一个变量。

flag_finish_blink 只有在执行完闪烁后才会计时rest_ms时间,因此需要一个变量。

blink_times 闪烁时间是进入回调时间的整数倍,需要变量来确定第几次进入回调函数时执行操作。

rest_times 同理。

blink_cycle_times 要求执行的闪烁次数。占用8行代码

struct led_indicate
{
    uint8_t flag_invoke_blink_func;   //调用aiqi_LED_blink 函数标志
    uint8_t flag_finish_blink;        //完成一次闪烁标志
    uint16_t blink_times;             //完成闪烁时间需要进入回调函数的次数
    uint16_t rest_times;              //完成间隔时间需要进入回调函数的次数
    uint16_t blink_cycle_times;       //需要执行闪烁的次数
} twinkle;

设置结构体中变量的函数如下,占用30行代码

/**
 * func_LED_blink
 * @brief   重写该函数,之前是调用工程底层接口实现,为移植改成仅用定时器就能实现
 * @param   blink_time_ms 参数描述: 闪烁周期,亮灭总时间
 * @param   rest_ms 参数描述: 间隔时间
 * @param   times 参数描述: 闪烁次数,设置为0则一直闪烁
 */
void func_LED_blink(uint16_t blink_time_ms, uint16_t rest_ms, uint8_t times)
{
  uint16_t need_value;    //进入回调函数的时间

  software_timer_stop(&timer_indicate_led);
  indicator_led_off();

  if (rest_ms != 0)
  {
      need_value = greatest_common_divisor(blink_time_ms / 2, rest_ms);
  }
  else
  {
      need_value = blink_time_ms / 2;
  }

  twinkle.blink_times = blink_time_ms / 2 / need_value;
  twinkle.rest_times = rest_ms / need_value;
  twinkle.blink_cycle_times = times;
  twinkle.flag_invoke_blink_func = 1;
  twinkle.flag_finish_blink = 0;

  software_timer_set(&timer_indicate_led, indicate_led_callback, need_value, true);
}

回调函数中有哪些变量?into_times 记录是第几次进入回调函数 ; cycle_times 记录是第几个周期。 占用64行代码

代码逻辑和注释条理感觉还是比较清晰的。

/**
 * indicate_led_callback
 * @brief   指示灯回调函数
 * @note    1.针对间隔时间为0,进入回调函数的时间为blink_time_ms / 2,直到执行完闪烁次数后关闭定时器
 * @note    2.间隔时间不为0,通过最大公约数求得进入回调函数的时间,直到执行完闪烁次数后关闭定时器
 */
void indicate_led_callback(void)
{
    static uint16_t into_times = 0;     //进入该回调函数次数
    static uint16_t cycle_times = 0;    //闪烁循环次数

    if (twinkle.flag_invoke_blink_func)
    {
        twinkle.flag_invoke_blink_func = 0;
        into_times = 0;
        cycle_times = 0;
    }

    into_times++;
    if (twinkle.rest_times == 0)
    {
        if (into_times % 2)
        {
            indicator_led_toggle();   //indicator_led_on();
        }
        else
        {
            into_times = 0;
            cycle_times++;
            indicator_led_toggle();   //indicator_led_off();

            if ((twinkle.blink_cycle_times > 0) && (cycle_times >= twinkle.blink_cycle_times))
            {
                cycle_times = 0;
                software_timer_stop(&timer_indicate_led);
            }
        }
    }
    else
    {
        if (into_times == twinkle.blink_times)
        {
            indicator_led_toggle();     //indicator_led_on();
        }
        else if (into_times == 2 * twinkle.blink_times)
        {
            indicator_led_toggle();     //indicator_led_off();
            twinkle.flag_finish_blink = 1;
        }
        else if (twinkle.flag_finish_blink)
        {
            if (into_times >= 2 * twinkle.blink_times + twinkle.rest_times)
            {
                into_times = 0;
                twinkle.flag_finish_blink = 0;
                cycle_times++;
                if ((twinkle.blink_cycle_times > 0) && (cycle_times >= twinkle.blink_cycle_times))
                {
                    cycle_times = 0;
                    software_timer_stop(&timer_indicate_led);
                }
            }
        }
    }
}

代码总行数:36 + 66 + 8 + 30 + 64 = 204。 对了,其中还没算上indicator_led_on  , indicator_led_off  和  indicator_led_toggle 的实现代码。

是不是还觉得不可思议,但不觉得我是在开玩笑了吧?

 

猜你喜欢

转载自blog.csdn.net/quanquanxiaobu/article/details/112852977