今天我们用定时器做一个us/ms延时,并且保留其中断,us/ms延时均用查询的方式实现,故不影响中断额实时性。
上次用MPLAB+MCC构建了简单的点灯例程。虽然固件包里面已经帮我们做了一个软件的软延时(__delay_us()和__delay_ms()),具体在pic.h里面
#ifdef __PICCPRO__
/****************************************************************/
/* Built-in delay routines */
/****************************************************************/
// NOTE: To use the macros below, YOU must have previously defined _XTAL_FREQ
#define __delay_us(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000000.0)))
#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))
#endif
IDE帮我们其实做了很多工作,包括头文件的包含,一些常见的标志头定义等,好处就是易上手,不要考虑那么多,但有点小麻烦就是想查看那些头文件及源文件还是有点烦的。
下面我们看下我们的PIC16F15244的固件包里面具体有什么:
扯远了,我们开始配置定时器并且开启中断:
生成代码。我们对代码进行修改,现在它300ms的中断里面,让LED闪烁一次:
对timr0.c添加:
<font color="#ff0000">#include "pin_manager.h"</font>
......
void TMR0_CallBack(void)
{
// Add your custom callback code here
<font color="#ff0000"> LED_Toggle(); </font>
if(TMR0_InterruptHandler)
{
TMR0_InterruptHandler();
}
}
下面我们新建我们的延时函数:
delay.h:
#ifndef DELAY_H
#define DELAY_H
#ifdef __cplusplus
extern "C" {
#endif
#include <xc.h>
#include "mcc_generated_files/tmr0.h"
void delay_us(uint32_t nus);
void delay_ms(uint32_t nms);
#ifdef __cplusplus
}
#endif
#endif /* DELAY_H */
delay.c:
#include "delay.h"
extern volatile uint16_t timer0ReloadVal16bit;
void delay_us(uint32_t nus)
{
uint32_t ticks=0;
uint32_t told,tnow,tcnt=0;
uint32_t reload=timer0ReloadVal16bit;
//7999cnt=1ms
//8cnt=1us
ticks=nus*8;
tcnt=0;
told=TMR0_ReadTimer();
while(1)
{
tnow=TMR0_ReadTimer();
if(tnow!=told)
{
//not flowover
if(tnow>told)
{
tcnt+=tnow-told;
}
else //flow over tnow<told
{
tcnt+=(tnow-reload)+(0xffff-told);
}
told=tnow;
if(tcnt>=ticks)break; //达到设定值,跳出
}
}
}
void delay_ms(uint32_t nms)
{
uint32_t ticks=0;
uint32_t told,tnow,tcnt=0;
uint32_t reload=timer0ReloadVal16bit;
//7999cnt=1ms
//8cnt=1us
ticks=nms*7999;
tcnt=0;
told=TMR0_ReadTimer();
while(1)
{
tnow=TMR0_ReadTimer();
if(tnow!=told)
{
//not flowover
if(tnow>told)
{
tcnt+=tnow-told;
}
else //flow over tnow<told
{
tcnt+=(tnow-reload)+(0xffff-told);
}
told=tnow;
if(tcnt>=ticks)break; //达到设定值,跳出
}
}
}
怎么得知,定时器1cnt代表多长时间,这里有2个办法,就是一知道时钟的时钟,然后算出,我们这里timer0时钟选的是:
(对照MCC的配置截图)
FOSC/4 通过查数据书册得知为FOSC为系统时钟,这里我们的系统时钟为32M,那么定时器0的时钟为8M,即1cnt=1/8us;
反过来就是说1us=8cnt,这样我们就把1cnt和时间的对应关系找到了。那么以此类推,1ms=8000cnt。
下面讲第二种懒人的推断方式,MCC不是配置好了1ms的reload值了么,我们知道定时器0为往上累加1计算,我们来计时
一下reload到溢出值0XFFFF到底有多少个cnt,找到定时器0的初始化配置代码:
void TMR0_Initialize(void)
{
// Set TMR0 to the options selected in the User Interface
// T0CS FOSC/4; T0CKPS 1:1; T0ASYNC synchronised;
T0CON1 = 0x40;
<font color="#ff0000"> // TMR0H 224;
TMR0H = 0xE0;
// TMR0L 192;
TMR0L = 0xC0;</font>
// Load TMR0 value to the 16-bit reload variable
timer0ReloadVal16bit = (TMR0H << 8) | TMR0L;
// Clear Interrupt flag before enabling the interrupt
PIR0bits.TMR0IF = 0;
// Enabling TMR0 interrupt.
PIE0bits.TMR0IE = 1;
// Set Default Interrupt Handler
TMR0_SetInterruptHandler(TMR0_DefaultInterruptHandler);
// T0OUTPS 1:1; T0EN enabled; T016BIT 16-bit;
T0CON0 = 0x90;
}
红色部分的 数值,即为我们需要的reload值:
0xE0C0=57536,溢出值65535-57536+1=8000,即1ms需要8000cnt,那么1us需要8cnt,与我们上面计时出的完全相符。如果不想理清
定时器的时钟,就用第二种,顶多会少算多算1cnt,无伤大雅。
void delay_us(uint32_t nus)与void delay_ms(uint32_t nms)原理一样,我们就取us延时讲解:
第一步、我们将需要的延时转换成需要的cnt数,由前面的得知:nus需要的tick数为:tick=nus*8;
第二步、我们先取出timer0的初始值作为旧值told,然后取出当前值tnow,然后计算出当前值tnow与旧值told之间的tick数,并赋给一个累加的记录总tick的变量tcnt;
然后新值赋给旧值,进行下一轮循环,当总tick数cnt大于等于我们需求的tick数时,即达到延时,然后跳出。
这里新值与就值的对应位置有2总可能,一直就是未溢出的状态,那么新值是大于旧值的,还有一种可能就是溢出了,旧值大于新值。
下面用一个图表示:
这样就清楚多了。
我们在main函数里,让他每隔500ms,打印一次。
while (1)
{
//LED_Toggle();
printf("Hello world!\r\n");
// __delay_ms(500);
delay_ms(500);
}
下载观察现象,LED是否会300ms闪烁,和串口是否会500ms打印一次:
好了定时器就到这里了,小白文,大神勿喷~~
|