迪文屏幕T5L平台学习笔记三:定时器使用

Posted 无痕幽雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了迪文屏幕T5L平台学习笔记三:定时器使用相关的知识,希望对你有一定的参考价值。

上篇博客介绍了第一个C51程序Demo,在文本上显示一个(中英文混合)字符串,这篇博客介绍下定时器2的使用(定时器0和1类似)。

一、先看定时器2的寄存器

在其他章节,有如下说明:

 

 意思就是,如果在main函数里面操作了DGUS变量或者扩展SFR变量,同时在中断里面也要操作这类变量,那么就会存在一个问题“嵌套”,比如main函数正在操作扩展SFR变量,中断来了,传去执行中断,在中断里面又操作了扩展SFR变量,那么这就是“嵌套操作”,文档里面明令禁止这种操作,可能引发意想不到的后果,这就是所谓的“临界区”或者叫“原子操作”(不明白的请自行百度),那操作SFR需要保护吗?手册没说,但是为了安全起见,我还是做了临界区保护。

二、临界区保护方法

保护方法很简单,如果知道哪几个中断操作了这类变量,在main函数里面操作变量之前关闭相应中断,操作完成后,再打开相应中断;为了减少操作复杂度和临界代码一致性,我们也可以在main函数操作变量之前关闭总中断,操作完成后再打开总中断。直接这么简单粗暴操作,一般情况下没事,但是当操作变量存在嵌套时候,就有问题了,因此我们要求原子性保护具有嵌套属性,大意就是:main在操作变量之前,保存总中断控制位状态,然后关闭,操作完成后,恢复总中断控制位状态,而不是简单粗暴的打开。

这里方法有两类:

1、定义一个全局变量,记录每次嵌套操作层数或者叫关闭总中断次数,进入时候每次加一,退出时候减一,判断是否为0,当为0时候,打开总中断位。

2、利用属性,每次进去之前把总中断位保存在一个临时变量里面,退出时候还原,缺点就是增大栈(实际使用,也不会占用多少)。

我采用方法2:

原子宏实现如下:

//保证原子性访问
//保存总中断状态,用以恢复现场
#ifdef __C51__

    typedef uint8_t  istate_t;

    #define	DIS_INT()                           EA =0;
    #define	EN_INT()                            EA =1;

    #define GET_GLOBAL_INTERRUPT_STATE()         (EA)
    #define SET_GLOBAL_INTERRUPT_STATE(__STATE)  EA = __STATE;
    #define DISABLE_GLOBAL_INTERRUPT()           DIS_INT()
    #define EXIT_GLOBAL_INTERRUPT()              SET_GLOBAL_INTERRUPT_STATE(tState)


    #define SAFE_ATOM_CODE(__CODE)                         \\
            istate_t tState = GET_GLOBAL_INTERRUPT_STATE(); \\
            DISABLE_GLOBAL_INTERRUPT();                     \\
                                                           \\
                __CODE;                                     \\
                                                           \\
            SET_GLOBAL_INTERRUPT_STATE(tState);             \\
        
#else
    /*================================MDK 宏定义======================================*/
    #pragma anon_unions
    
    typedef uint32_t  istate_t;
    
    //#define __USER_SELECT_FAULTMASK__
    #if     defined(__USER_SELECT_FAULTMASK__)
    
        #define	DIS_INT()                           __set_FAULTMASK(1);
        #define	EN_INT()                            __set_FAULTMASK(0);

        #define GET_GLOBAL_INTERRUPT_STATE()         __get_FAULTMASK()
        #define SET_GLOBAL_INTERRUPT_STATE(__STATE)  __set_FAULTMASK(__STATE);
        #define DISABLE_GLOBAL_INTERRUPT()           DIS_INT()
        #define EXIT_GLOBAL_INTERRUPT()              SET_GLOBAL_INTERRUPT_STATE(tState) 
        
    #else
        
        #define	DIS_INT()                           __set_PRIMASK(1);
        #define	EN_INT()                            __set_PRIMASK(0);

        #define GET_GLOBAL_INTERRUPT_STATE()         __get_PRIMASK()
        #define SET_GLOBAL_INTERRUPT_STATE(__STATE)  __set_PRIMASK(__STATE);
        #define DISABLE_GLOBAL_INTERRUPT()           DIS_INT()
        #define EXIT_GLOBAL_INTERRUPT()              SET_GLOBAL_INTERRUPT_STATE(tState)     
        
    #endif

    #define SAFE_ATOM_CODE(...)                            \\
            istate_t tState = GET_GLOBAL_INTERRUPT_STATE(); \\
            DISABLE_GLOBAL_INTERRUPT();                     \\
                                                           \\
                __VA_ARGS__;                                \\
                                                           \\
            SET_GLOBAL_INTERRUPT_STATE(tState);             \\
        

#endif

为了方便在库函数里面一次关闭所有临界区保护,我又定义了如下宏:

#define T5LIB_ATOM_CODE_ENABLE        //T5L的库函数原子性保护
#ifdef  T5LIB_ATOM_CODE_ENABLE
#define T5LIB_ATOM_CODE(__CODE)       SAFE_ATOM_CODE(__CODE)
#else
#define T5LIB_ATOM_CODE(__CODE)       __CODE
#endif

三、时间计算

定时器定时周期计算如下:

定时时间(单位:S) = 计时数*计一次数时间;

计一次数时间=1/定时器的频率(Hz)

定时器频率(Hz)=CPU频率(Hz)/分频系数(12或者24)

因此:

定时时间(单位:S)=计时数*分频系数/CPU频率(Hz);

两边同时乘以1000000,变换单位

定时时间(单位:uS)=计时数*分频系数/CPU频率(MHz);

因为C51的Timer是向上增长技术,并且是溢出中断,及:当计数值为65536时候出发中断,因此

计数寄存器初值=65536-计时数

定时时间(单位:uS)=(65536-计时数)*分频系数/CPU频率(MHz);为手册给出公式

CPU频率=晶振频率*56/3;

晶振频率11.0592M  CPU=206.4384M,最大定时时间=65536*24/206.4384=7619uS=7.619mS

如果超过了这个时间,我们是没有办法用定时间隔实现的,只能定义一个变量,定时中断进行累加,间接达到我们要的效果。

四、驱动

驱动用了两种方法实现,宏函数和函数,宏函数主要为了提高效率。

/************************************定时器2***********************************/
#define TIMER2_ENABLE()         T5LIB_ATOM_CODE(TR2 = 0x01;)
#define TIMER2_DISENABLE()      T5LIB_ATOM_CODE(TR2 = 0x00;)
#define TIMER2_ENABLE_INT()     T5LIB_ATOM_CODE(ET2 = 0x01;)
#define TIMER2_DISENABLE_INT()  T5LIB_ATOM_CODE(ET2 = 0x00;)
//#define TIMER2_READ_INT_FLAG()  T5LIB_ATOM_CODE(TF2)
extern  bool timer2_read_int_flag(void);
#define TIMER2_CLEAR_INT_FLAG() T5LIB_ATOM_CODE(TF2 = 0x00;)
extern  bool timer2_set_cycle(uint32_t wCycleUs);
bool timer2_read_int_flag(void)

    bool bFlag = 0;

    T5LIB_ATOM_CODE(
        bFlag = TF2;
    )
    return bFlag;


bool timer2_set_cycle(uint32_t wCycleUs)

    union _union_uint32_t tTemp   = 0;
    bool     bResult = false;

    if(wCycleUs > ((65536*24)/(CPU_SYSCLK/1000000.0)))//11.0592M  7619
        return false;
    

    T5LIB_ATOM_CODE(
        tTemp.wTemp = (wCycleUs * (CPU_SYSCLK/1000000.0))/12;
        if(tTemp.wTemp <= 65536)
            tTemp.wTemp = 65536 - tTemp.wTemp;
            T2CON = T2CON & 0x7F; 
            bResult =  true;
        else
            tTemp.wTemp = (wCycleUs * (CPU_SYSCLK/1000000.0))/24;
            if(tTemp.wTemp <= 65536)
                tTemp.wTemp = 65536 - tTemp.wTemp;
                T2CON = T2CON | 0x80; 
                bResult = true;
            
        

        if(bResult)
            TRL2H = tTemp.chTemp[2];
            TRL2L = tTemp.chTemp[3];
        
    )

    return bResult;

其中CPU频率的宏如下:

#ifndef HSE_VALUE
#define HSE_VALUE               11059200
#endif

#define LOG_UART                RS485_4_UART

#define CPU_SYSCLK              ((HSE_VALUE*56)/3)      //系统周期
#define SYS_TICK_CYCLE          10                      //心跳周期(ms)

 

定时器2初始化:

static void timer2_init(void)

    TIMER2_DISENABLE_INT();
    TIMER2_DISENABLE();
    TIMER2_CLEAR_INT_FLAG();

    timer2_set_cycle(5000);
    
    TIMER2_ENABLE_INT();
    TIMER2_ENABLE();

timer2中断:

void TIMER2_ISR(void)	    interrupt 5

    extern  void sys_tick_irq(void);
    sys_tick_irq();
    TIMER2_CLEAR_INT_FLAG();

注意:定时器2需要手动清除中断标志位,否则会连续进中断,定时器0和1是自动清除中断标志位。

上面初始化是5ms,但是我想要10ms的定时,中断相应函数这里处理:

void sys_tick_irq(void)

    static uint8_t  s_chTimer = 0;

    s_chTimer++;
    if(s_chTimer >= 2)
        s_chTimer = 0;

        //心跳时钟
         s_tTimeTick++;

        //软件定时器服务
        //SYS_CALL_SOFTWARE_TIMER_SERVICE();

        //LED服务
        //led_services();

    #ifndef __DEBUG
        //看门狗
        wdg();
    #endif
    

五、用定时器累加实现跑秒

为了测试定时器,把一个变量每1S累加,并且显示,达到计时效果,测试代码如下:

void test(void)

    static enum
        FSM_TEST_START = 0,
        FSM_TEST_WAIT,
        FSM_TEST_SET,
    s_tState = FSM_TEST_START;

    static struct timer s_tTimer = 0;
    static uint32_t     s_wTimer = 0;

    switch(s_tState)
        case FSM_TEST_START:
            s_tState = FSM_TEST_WAIT;
            s_wTimer = 0;
            timer_set(&s_tTimer,DELAY_TIMERS(1000));
            //break;
        case FSM_TEST_WAIT:
            if(!timer_expired(&s_tTimer))
                break;
            
            timer_set(&s_tTimer,DELAY_TIMERS(1000));
            s_tState = FSM_TEST_SET;
            s_wTimer++;
            //break;
        case FSM_TEST_SET:
            
                static int8_t    cData[10];
                int        i = 0;
                i = sprintf(cData,"%ld\\0",s_wTimer);
                write_dgusii_vp(0x1000,cData,10);
            
            s_tState = FSM_TEST_WAIT;
            break;
        default:
            FSM_DEFAULT_ACTION();
    

显示效果:

 这里说个点,就是sprintf开始是乱码,调试过程下篇博客再分析。

以上是关于迪文屏幕T5L平台学习笔记三:定时器使用的主要内容,如果未能解决你的问题,请参考以下文章

迪文屏幕T5L平台学习笔记四:C51使用printf或者sprintf注意事项

迪文屏幕T5L平台学习笔记五:C51使用UART2打印log

迪文屏幕T5L平台学习笔记二:第一个C51C程序Demo

迪文屏幕T5L平台学习笔记七:RS485测试

迪文屏幕T5L平台学习笔记零:KEIL环境搭建

迪文屏幕T5L平台学习笔记一:开发环境搭建注意事项