定时器是每一个芯片必备的外设,它的作用就如它的名字一样,是用来计时的。当然它不会直接告诉你具体的时间值,而是一个特定时间间隔的计数,通过这个数乘以时间间隔就是这段时间的长度。定时器在计时的基础上可以实现很多其他功能,比如定时中断、pwm输出、输入捕获、正交译码等等。下面这张图是gd32vf103所有定时器的分类和功能汇总。

我会按照功能分别予以介绍,本文先介绍最基本的定时、计数功能,以定时器0为例。

1. 打开定时器时钟源;

2. 设置定时器时钟的预分频;

3. 设置计数方向、对齐模式、时钟分频;

4. 设置自动重载值和重复计数值;

5. 触发软件更新事件更新寄存器的值;

6. 使能更新中断(由于用到了中断,注意中断控制器的配置);

7. 使能计数器;

8. 编写中断服务程序。

1. 打开定时器时钟源

定时器的时钟源可以是内部的也可以是外部的,这里以内部时钟源为例。内部时钟源是CK_TIMER,通过时钟树我们可以看到定时器0的CK_TIMER是挂在APB2上的,第一步先打开它。配置的寄存器是APB2使能寄存器,bit11使能定时器0。手册中介绍说这个位是定时器0复位,感觉有点奇怪。代码如下所示:

```c

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIMER0, ENABLE);

```

2. 设置定时器时钟的预分频

可以对CK_TIMER的时钟频率进行预分频,降低时钟源频率,预分频器是16位的,可以将时钟源进行1-65536的分频。分频后得到PSC_CLK时钟,通过它来驱动计数器计数。设置预分频系数的寄存器是预分频寄存器(TIMERx_PSC):

```c

uint32_t prescaler = 107; // 设置PSC预分频值为107

TIMER0->PSC = prescaler; // 将prescaler值写入PSC寄存器

```

代码如下所示,宏TIMER0_BASE是0x40012C00。APB2的时钟是108M,CK_TIMER的也是108M,这里我们想要一个1M的时钟,所以设置PSC预分频值107,最终将CK_TIMER(107+1)分频。

3. 设置计数方向、对齐模式、时钟分频

定时器0有三种计数方式:向上计数、向下计数和中央对齐计数。向上计数就是从0加计数到自动重载值,然后再从0开始。向下计数是从自动重载值开始减计数到0,然后再从自动重载值开始。中央对齐是交替从0开始向上计数到自动重载值,然后再向下计数到0。这里的分频是用来产生DTS时钟。

```c

TIMER_CTL0_CLEAR(TIMER0_CTL0_CTIE); // 清除CTIE标志位,关闭上溢中断

TIMER_CTL0_CLEAR(TIMER0_CTL0_DCIE); // 清除DCIE标志位,关闭下溢中断

TIMER_CTL0_CLEAR(TIMER0_CTL0_UBE); // 清除UBE标志位,关闭中央对齐溢出中断

TIMER_CTL0_CLEAR(TIMER0_CTL0_PRDLD); // 清除PRDLD标志位,禁止预装载值加载

TIMER_CTL0_CLEAR(TIMER0_CTL0_CSS); // 清除CSS标志位,禁止周期性自启动

TIMER_CTL0_CLEAR(TIMER0_CTL0_COUNTMODE); // 清除COUNTMODE标志位,设置为向上计数模式

TIMER_CTL0_SET(TIMER0_CTL0_PRESCALER); // 设置预分频系数为prescaler值

```

4. 设置自动重载值和重复计数值

自动重载值是在每次溢出后重新加载的值,重复计数值表示在达到自动重载值后再次从零开始计数。这些值可以通过寄存器TCON来设置:

```c

uint32_t autoreload = 9999; // 设置自动重载值为9999

uint32_t repeatcount = 1; // 设置重复计数值为1

TCON = (TCON & ~TCON_ARRH) | (autoreload >> 8); // 将ARRH位置为autoreload的高8位

TCON = (TCON & ~TCON_ARRL) | (autoreload & 0xFF); // 将ARRL位置为autoreload的低8位

TCON = (TCON & ~TCON_RC) | (repeatcount << 16); // 将RC位置为repeatcount的高16位,低16位为0

```

代码如下所示。宏TIMER0_BASE是0x40012C00。APB2的时钟是108M,CK_TIMER的也是108M,这里我们想要一个1M的时钟,所以设置PSC预分频值107,最终将CK_TIMER(107+1)分频。

5. 触发软件更新事件更新寄存器的值;使能更新中断(由于用到了中断,注意中断控制器的配置);使能计数器;编写中断服务程序。

以下是重构后的内容:

1. 控制寄存器0(TIMERx_CTL0)的操作寄存器包括:

- bit4:设置计数方向

- bit[5:6]:设置模式

- bit[8:9]:设置DTS分频系数

2. 设置自动重载值和重复计数值:

- 计数器自动重载值决定了计数器的计数范围,即计数周期。寄存器为TIMERx_CAR,是一个16位寄存器。

- 重复计数决定计数器经过多少(N+1)个计数周期后触发更新事件。寄存器为TIMERx_CREP,是一个8位寄存器。

3. 触发软件更新事件:

- 通过向软件事件产生寄存器TIMERx_SWEVG的指定位写1可以产生一些软件事件。这里我们要让各种寄存器恢复到计数初始状态,所以设置更新事件bit0写1产生更新事件。

4. 使能更新中断:

- 使用定时器中断。中断相关的配置寄存器为DMA和中断使能寄存器TIMERx_DMAINTEN,其中bit0就是更新中断使能。

5. 使能计数器:

- 在控制寄存器0(TIMERx_CTL0)中,将bit0置1以启用计数器。

6. 编写中断服务程序:

- 定时器更新中断服务程序判断中断是否为更新中断,然后清除标志,并让变量update自加以统计中断次数。

在实际应用中,可以根据需要调整代码中的参数和配置。

以下是使用中断实现my_delay延时毫秒函数的代码,其中update变量一定要加volatile关键字,否则编译器会优化这里的time1,认为它是多余的。或者可以实现一个获取update值的函数。

```c

#include

typedef unsigned int uint;

typedef unsigned char uchar;

uchar count = 0; //计数器

uint time1 = 0; //时间计数器

uint time0 = 0;

bit flag = 0; //标志位

void init_timer0()

{

TMOD |= 0x01; //定时器模式设置

TH0 = (65536 - 5000) / 256; //定时器高8位初值设定

TL0 = (65536 - 5000) % 256; //定时器低8位初值设定

EA = 1; //开总中断

ET0 = 1; //开定时器中断

TR0 = 1; //启动定时器

}

void timer0() interrupt 1

{

TH0 = (65536 - 5000) / 256; //定时器高8位初值设定

TL0 = (65536 - 5000) % 256; //定时器低8位初值设定

count++; //计数器自增

if(count == 20) //每ms中断一次

{

time1++; //时间计数器自增

if(time1 >= 10) //当时间为1秒时,停止计时并将标志位设为1

{

TH0 = (65536 - 5000) / 256; //定时器高8位初值设定

TL0 = (65536 - 5000) % 256; //定时器低8位初值设定

time1 = 0; //时间计数器清零

TR0 = 0; //停止定时器

while(!flag); //等待标志位清零

}

}

}

void delay_ms(uint xms)//毫秒级延时函数,xms为毫秒数。如delay_ms(10):延时约1s左右。注意:此函数在单片机上运行速度较慢。建议用delay_us代替。

{

uint i, j;

i = xms * 478; /* i 为要循环次数 */

j = xms * 249; /* j为实际每次循环占用CPU的时间*/

TH0 = (65536-5000)/256; /*装初值 */

TL0 = (65536-5000)%256; /*装初值*/

/*此处加入死循环*/while(i--) /*执行要执行的语句*/ while (j--); /*空循环*/}

```