/**
  *****************************************************************************
  *                            系统延时相关的函数
  * 详细说明请参考《Cortex-M3权威指南(中文)》第133 ~ 134页 第8章 SysTick定时器介绍
  *
  *                       (C) Copyright 2000-2020, ***
  *                            All Rights Reserved
  *****************************************************************************
  *
  * @File    : delay.c
  * @By      : 陈桂东
  * @Version : V1.0
  * @Date    : 2012 / 10 / 20
  *
  *****************************************************************************
  *                                    Update
  * @Version : V1.0.1
  * @By      : 陈桂东
  * @Date    : 2014 / 02 / 26
  * @Brief   : 增加另外一种延时计算方法
  *
  * @Version : V1.1
  * @By      : 陈桂东
  * @Date    : 2014 / 05 / 24
  * @Brief   : A、增加对C++环境支持
  *            B、修改在跑ucos时初始化嘀嗒定时器、延时us和ms函数
  *
  * @Version : V1.2
  * @By      : 陈桂东
  * @Date    : 2015 / 10 / 03
  * @Brief   : 优化延时函数
  *
  *****************************************************************************
**/

#include "delay.h"


/******************************************************************************

* @ SysTick定时器 相关控制寄存器说明

@ 1、SysTick控制及状态寄存器（地址：0xE000_E010）复位值为0

	bit16 COUNTFLAG(R）  -> 如果在上次读取本寄存器后，SysTick已经数到了0，则该位为1。如果读取该位，该位将自动清零
	bit2  CLKSOURCE(R/W) -> 0=外部时钟源(STCLK)。1=内核时钟(FCLK)
	bit1  TICKINT(R/W)   -> 1=SysTick倒数到0时产生SysTick异常请求,0=数到0时无动作
	bit0  ENABLE(R/W)    -> SysTick定时器的使能位

@ 2、SysTick重装载数值寄存器（地址：0xE000_E014）复位值为0

	[23:0] RELOAD(R/W) -> 当倒数至零时，将被重装载的值

@ 3、SysTick当前数值寄存器（地址：0xE000_E018） 复位值为0

	[23:0] CURRENT(R/Wc) -> 读取时返回当前倒计数的值，写它则使之清零，同时还会清除在SysTick 控制及状态寄存器中的COUNTFLAG标志

@ 4、SysTick校准数值寄存器（地址：0xE000_E01C）复位值: bit31未知。bit30未知。[23:0]为0

	bit32 NOREF(R)    -> 1=没有外部参考时钟（STCLK不可用）。0=外部参考时钟可用
	bit30 SKEW(R)     -> 1=校准值不是准确的10ms。0=校准值是准确的10ms
	[23:0] TENMS(R/W) -> 10ms的时间内倒计数的格数。芯片设计者应该通过Cortex‐M3的输入信号提供该数值。若该值读回零，则表示无法使用校准功能

******************************************************************************/

#if _SYSTEM_SUPPORT_ROTS == 1

#if _RTOS_Type_Support == 1  //RAW-OS
#include "raw_api.h"
#else  //uCOS
#include "includes.h"
#endif

/**
  *****************************************************************************
  * @Name   : SysTick定时器中断服务函数
  *
  * @Brief  : 在此编写了，则stm32f10x_it.c中就不需要编写，否则stm32f10x_it.c中就要编写
  *
  * @Input  : none
  *
  * @Output : none
  *
  * @Return : none
  *****************************************************************************
**/
void SysTick_Handler ( void )
{
    /* 编写与SysTick定时器中断操作相关的API函数调用 */

#if _RTOS_Type_Support == 1  //RAW-OS

    raw_enter_interrupt();
#if (CONFIG_RAW_TASK_0 > 0)		// 开启了TASK_0特性	    
    task_0_tick_post();
#else
    raw_time_tick();
#endif
    raw_finish_int();

#else  //uCOS

    OSIntEnter();  //ucos进入中断
    OSTimeTick();  //调用ucos的时钟服务函数
    OSIntExit();  //ucos退出中断

#endif
}

#endif  /* end _SYSTEM_SUPPORT_ROTS */



#if _USER_OtherDelay == 0  //使用嘀嗒定时器做延时时基

/******************************************************************************
                               定义计算变量
******************************************************************************/

static uint8_t  fac_us = 0;	//us延时倍乘数
static uint16_t fac_ms = 0;	//ms延时倍乘数

#endif

/**
  *****************************************************************************
  * @Name   : 初始化延时函数
  *
  * @Brief  : 主要Cortex-M3内核对系统时钟计数单元
  *           详细见《Cortex-M3权威指南(中文)》第216页 a)  时钟周期（CYCCNT） 的内容
  *           周立功《CM3计数参考手册》第28、29页、第110、125页
  *
  * @Input  : none
  *
  * @Output : none
  *
  * @Return : none
  *****************************************************************************
**/
void delay_init ( void )
{
#if _USER_OtherDelay == 1  //使用其他资源做延时时基

    DEM_CTRL |= 1 << 24; //该位必须为1，使能跟踪和调试模块的使用。详细见：周立功《CM3计数参考手册》第115页介绍
    //在没有使用跟踪时，该位使能对功率使用的控制。它能够由应用程序或调试器使能，供ITM使用
    //在DWT能够使用之前，调试异常和监控控制寄存器的TRCENA(bit24)位必须置位

    DWT_CTRL |= 1 << 0; //使能DWT_CYCCNT计数器。
    //如果不使能，则计数器不执行计数操作，因此不会产生PC采样或CYCCNTENA事件。
    //在正常使用时，CYCCNT计数器应由调试器初始化为0。

#else  //使用嘀嗒定时器做延时时基

#if _SYSTEM_SUPPORT_ROTS == 1  //运行了OS
    uint32_t RELOAD = 0;  //挡计数器倒数到0时的重装值，有效位:0 ~ 23
#endif

    /* 根据SysTick定时器的时钟分频来确定重装值 */
    /* 8分频时除以8000‘000，1分频时除以1000’000 */
    SysTick_CLKSourceConfig ( SysTick_CLKSource_HCLK_Div8 ); //选择外部时钟 HCLK / 8

    fac_us = SystemCoreClock / 8000000;  //系统时钟的 1/8

#if _SYSTEM_SUPPORT_ROTS == 1  //运行了OS

    RELOAD = SystemCoreClock / 8000000;	//每秒钟的计数次数，单位Hz

#if _RTOS_Type_Support == 1  // RAW-OS

    RELOAD *= 1000000 / RAW_TICKS_PER_SECOND;  //根据操作系统的心跳时长来计算溢出时间，单位：KHz
    //RELOAD为24位计数器，最大值为:16777216
    fac_ms = 1000 / RAW_TICKS_PER_SECOND;

#else  // uCOS

    RELOAD *= 1000000 / OS_TICKS_PER_SEC;	//根据操作系统的心跳时长来计算溢出时间，单位：KHz
    //RELOAD为24位计数器，最大值为:16777216
    fac_ms = 1000 / OS_TICKS_PER_SEC;

#endif

    SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;	//开启SysTick定时器中断请求
    SysTick->LOAD = RELOAD;	//溢出计数值，每1/TICKINT_CNT秒中断一次
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;      //开始倒数

#else  //没有运行OS

    fac_ms = ( uint16_t ) fac_us * 1000;	//ms需要的SysTick时钟数

#endif

#endif  //end _USER_OtherDelay
}

/**
  *****************************************************************************
  * @Name   : 延时n个us
  *
  * @Brief  : none
  *
  * @Input  : nus：要延时的us数
  *
  * @Output : none
  *
  * @Return : none
  *****************************************************************************
**/
void delay_us ( uint32_t nus )
{
#if _USER_OtherDelay == 1  //使用其他资源做延时时基

    uint32_t savecount, endcnt, CPU_cnt;

    savecount = DWT_CYCCNT;	//保存计数器当前数值
    CPU_cnt = nus * ( SystemCoreClock / ( 1000 * 1000 ) );	//计算达到所需延时值的CPU时钟数。即多少个系统时钟计数
    //得到更精确延时时间，减去前面代码运行的时间即可

    endcnt = savecount + CPU_cnt;	//计算所需延时时间DWT_CYCCNT的计数值，在溢出时返回到0

    if ( endcnt > savecount )	//所需延时值大于当前计数值
    {
        while ( DWT_CYCCNT < endcnt );	//循环等待所需要的延时时间的CPU时钟计数值
    }
    else	//小于当前计数值
    {
        while ( DWT_CYCCNT > endcnt );	//等待计数器溢出翻转

        while ( DWT_CYCCNT < endcnt );	//等待所需延时时间到达
    }

#else  //使用嘀嗒定时器做延时时基

    uint32_t temp = 0;

#if _SYSTEM_SUPPORT_ROTS == 1  //运行了OS

    uint32_t VAL_Prev = 0; //开始计时之前的值
    uint32_t VAL_Now = 0; //当前计时值
    uint32_t VAL_cnt = 0; //计数
    uint32_t Reload = SysTick->LOAD; //获取到LOAD的值

    temp = nus * fac_us; //得到延时的节拍数

#if _RTOS_Type_Support == 1  // RAW-OS

    raw_disable_sche();  //阻止RAW-OS调度，防止打断us延时

#else  // uCOS

    OSSchedLock();  //阻止ucos调度，防止打断us延时

#endif

    VAL_Prev = SysTick->VAL;  //保存当前的计数值

    while ( 1 )
    {
        VAL_Now = SysTick->VAL;  //读取数值

        if ( VAL_Now != VAL_Prev )
        {
            if ( VAL_Now < VAL_Prev )  VAL_cnt += VAL_Prev - VAL_Now; //因为SysTick是一个递减的定时器
            else                      VAL_cnt += Reload - VAL_Now + VAL_Prev;

            VAL_Prev = VAL_Now;  //刷新

            if ( VAL_cnt >= temp )  break; //超过/等于需要的延时值了，则退出循环
        }
    };

#if _RTOS_Type_Support == 1  // RAW-OS

    raw_enable_sche();  //开启RAW-OS调度

#else  // uCOS

    OSSchedUnlock();  //开启ucos调度

#endif

#else  //没有运行os
    SysTick->LOAD = nus * fac_us; //时间加载

    SysTick->VAL = 0x00;        //清空计数器

    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;      //开始倒数

    do
    {
        temp = SysTick->CTRL;
    }
    while ( temp & 0x01 && ! ( temp & ( 1 << 16 ) ) );	//等待时间到达

    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
    SysTick->VAL = 0x00;	//清空计数器

#endif  //end _SYSTEM_SUPPORT_ROTS

#endif  //end _USER_OtherDelay
}

/**
  *****************************************************************************
  * @Name   : 延时n个ms
  *
  * @Brief  : 1、延时ms级定义，延时范围：1 ~ 65535ms。延时最大值可变，不爆机uint32_t/1000范围即可
  *           2、SysTick->LOAD为24位寄存器,所以,最大延时为:
  *              nms <= 0xffffff*8*1000/SYSCLK
  *              SYSCLK单位为Hz,nms单位为ms
  *              注意nms的范围 0 ~ 1864(72M情况下)
  *
  * @Input  : nms：要延时的ms数
  *
  * @Output : none
  *
  * @Return : none
  *****************************************************************************
**/
void delay_ms ( uint16_t nms )
{
#if _USER_OtherDelay == 1  //使用其他资源做延时时基

    delay_us ( ( uint32_t ) ( nms * 1000 ) ); //采用普通的延时

#else  //使用嘀嗒定时器做延时时基


#if _SYSTEM_SUPPORT_ROTS == 1  //运行了OS

#if _RTOS_Type_Support == 1  // RAW-OS

    if ( raw_os_active == RAW_OS_RUNNING && raw_int_nesting == 0 )

#else  //uCOS

    if ( OSRunning == OS_TRUE && OSLockNesting == 0 ) //ucos已经在跑了

#endif
    {
        if ( nms > fac_ms ) //延时大于ucos基数
        {
#if _RTOS_Type_Support == 1  // RAW-OS

            raw_sleep ( nms / fac_ms );

#else  // uCOS

            OSTimeDly ( nms / fac_ms ); //采用ucos延时

#endif
        }

        nms %= fac_ms;  //ucos无法提供小于节拍的延时了
    }

    if ( nms == 0 )  return; //没意义了，直接退出，不加这句，在运行RAW-OS时会死机

    delay_us ( ( uint32_t ) ( nms * 1000 ) ); //采用普通的延时

#else  //没有运行os

    uint32_t temp;

    SysTick->LOAD = ( uint32_t ) nms * fac_ms;	//时间加载(SysTick->LOAD为24bit)
    SysTick->VAL = 0x00;           //清空计数器
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;      //开始倒数

    do
    {
        temp = SysTick->CTRL;
    }
    while ( temp & 0x01 && ! ( temp & ( 1 << 16 ) ) );	//等待时间到达

    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;	//关闭计数器
    SysTick->VAL = 0x00;	//清空计数器

#endif  //end _SYSTEM_SUPPORT_ROTS

#endif  //end _USER_OtherDelay
}
