迪文屏幕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注意事项