/**
 * @file    systick.c
 * @author  armfly
 * @author  Myth
 * @version 0.1
 * @date    2021.10.11
 * @brief   stm32f103x6 SysTick 及软件定时器库
 * @note    可在 SysTick_ISR 函数中添加内容，实现全局全生命周期定时调用
 *          例如，在 10ms 调用处添加按键扫描函数，可实现定时按键扫描
 *          添加的函数禁止含有阻塞
 */

#include "systick.h"

static volatile uint32_t s_uiDelayCount = 0;
static volatile uint8_t s_ucTimeOutFlag = 0;
static SOFT_TMR s_tTmr[TMR_COUNT] = {0};          //软件定时器结构体变量
volatile uint32_t g_iRunTime = 0;                 //全局运行时间，单位 ms
static volatile uint8_t g_ucEnableSystickISR = 0; //等待变量初始化

void SysTick_ISR(void);
static void SysTick_SoftTimerDec(SOFT_TMR *_tmr);

/**
  * @brief  配置 SysTick 中断，并初始化软件定时器
  */
void SysTick_Init(void)
{
    //清零所有的软件定时器
    for (uint8_t i = 0; i < TMR_COUNT; i++)
    {
        s_tTmr[i].Count = 0;
        s_tTmr[i].PreLoad = 0;
        s_tTmr[i].Flag = 0;
        s_tTmr[i].Mode = TMR_ONCE_MODE; //缺省是一次性工作模式
    }

    //配置 SysTick 中断周期为 1ms，并启动 SysTick 中断
    SysTick_Config(SystemCoreClock / 1000);

    g_ucEnableSystickISR = 1; //执行systick中断
}

/**
  * @brief  供中断服务程序调用
  */
void SysTick_ISR(void)
{
    static uint8_t s_count = 0;
    static uint16_t l_count = 0;

    //用于 Delay_ms
    if (s_uiDelayCount > 0)
        if (--s_uiDelayCount == 0)
            s_ucTimeOutFlag = 1;

    //软件定时器的计数器减 1
    for (uint8_t i = 0; i < TMR_COUNT; i++)
        SysTick_SoftTimerDec(&s_tTmr[i]);

    //全局运行时间增 1
    g_iRunTime++;
    if (g_iRunTime == UINT32_MAX)
        g_iRunTime = 0;

    //此处每隔 1ms 调用一次

    if (++s_count >= 10)
    {
        s_count = 0;

        //此处每隔 10ms 调用一次
    }

    if (++l_count >= 1000)
    {
        l_count = 0;

        //此处每隔 1000ms 调用一次
    }
}

static void SysTick_SoftTimerDec(SOFT_TMR *_tmr)
{
    if (_tmr->Count > 0)
    {
        //如果定时器变量减到 1 则设置定时器到达标志
        if (--_tmr->Count == 0)
        {
            _tmr->Flag = 1;

            //如果是自动模式，则自动重装计数器
            if (_tmr->Mode == TMR_AUTO_MODE)
                _tmr->Count = _tmr->PreLoad;
        }
    }
}

/**
  * @brief  ms 延时
  * @param  n: 延时数
  */
void Delay_ms(uint32_t n)
{
    if (n == 0)
        return;
    else if (n == 1)
        n = 2;

    DISABLE_INT();

    s_uiDelayCount = n;
    s_ucTimeOutFlag = 0;

    ENABLE_INT();

    while (1)
    {
        if (s_ucTimeOutFlag == 1)
            break;
    }
}

/**
  * @brief  us 延时
  * @param  n: 延时数
  */
void Delay_us(uint32_t n)
{
    uint32_t ticks;
    uint32_t told;
    uint32_t tnow;
    uint32_t tcnt = 0;
    uint32_t reload;

    reload = SysTick->LOAD;
    ticks = n * (SystemCoreClock / 1000000); //需要的节拍数

    tcnt = 0;
    told = SysTick->VAL; //刚进入时的计数器值

    while (1)
    {
        tnow = SysTick->VAL;
        if (tnow != told)
        {
            if (tnow < told)
                tcnt += told - tnow;
            else
                tcnt += reload - tnow + told;
            told = tnow;

            //时间超过、等于要延迟的时间，则退出
            if (tcnt >= ticks)
                break;
        }
    }
}

/**
  * @brief  启动一个定时器，并设置定时周期
  * @param  _id: 定时器 ID
  * @param  _period: 定时周期，单位 ms
  */
void SysTick_StartTimer(uint8_t _id, uint32_t _period)
{
    DISABLE_INT();

    s_tTmr[_id].Count = _period;      //实时计数器初值
    s_tTmr[_id].PreLoad = _period;    //计数器自动重装值，仅自动模式起作用
    s_tTmr[_id].Flag = 0;             //定时时间到标志
    s_tTmr[_id].Mode = TMR_ONCE_MODE; //一次性工作模式

    ENABLE_INT();
}

/**
  * @brief  启动一个自动定时器，并设置定时周期
  * @param  _id: 定时器 ID
  * @param  _period: 定时周期，单位 ms
  */
void SysTick_StartAutoTimer(uint8_t _id, uint32_t _period)
{
    DISABLE_INT();

    s_tTmr[_id].Count = _period;      //实时计数器初值
    s_tTmr[_id].PreLoad = _period;    //计数器自动重装值，仅自动模式起作用
    s_tTmr[_id].Flag = 0;             //定时时间到标志
    s_tTmr[_id].Mode = TMR_AUTO_MODE; //自动工作模式

    ENABLE_INT();
}

/**
  * @brief  停止一个定时器
  * @param  _id: 定时器 ID
  */
void SysTick_StopTimer(uint8_t _id)
{
    DISABLE_INT();

    s_tTmr[_id].Count = 0;            //实时计数器初值
    s_tTmr[_id].Flag = 0;             //定时时间到标志
    s_tTmr[_id].Mode = TMR_ONCE_MODE; //自动工作模式

    ENABLE_INT();
}

/**
  * @brief  检测定时器是否超时
  * @param  _id: 定时器 ID
  * @param  _period: 定时周期
  * @retval 返回 0 表示定时未到，1 表示定时到
  */
uint8_t SysTick_CheckTimer(uint8_t _id)
{
    if (_id >= TMR_COUNT)
        return 0;

    if (s_tTmr[_id].Flag == 1)
    {
        s_tTmr[_id].Flag = 0;
        return 1;
    }
    else
    {
        return 0;
    }
}

/**
  * @brief  获取运行时间，单位 ms。最长可以表示 24.85 天
  * @retval 运行时间
  */
int32_t SysTick_GetRunTime(void)
{
    int32_t runtime;

    DISABLE_INT();

    runtime = g_iRunTime;

    ENABLE_INT();

    return runtime;
}

/**
  * @brief  计算当前运行时间和给定时刻之间的差值。处理了计数器循环
  * @param  _LastTime: 上个时刻
  * @retval 当前时间和过去时间的差值，单位 ms
  */
int32_t SysTick_CheckRunTime(int32_t _LastTime)
{
    int32_t now_time;
    int32_t time_diff;

    DISABLE_INT();

    now_time = g_iRunTime;

    ENABLE_INT();

    if (now_time >= _LastTime)
        time_diff = now_time - _LastTime;
    else
        time_diff = INT32_MAX - _LastTime + now_time;

    return time_diff;
}

/**
  * @brief  SysTick 中断服务程序
  */
void SysTick_Handler(void)
{
    HAL_IncTick();

    if (g_ucEnableSystickISR == 0)
        return;

    SysTick_ISR();
}
