定时器与计数器在硬件上的实现都是相同的,都是一个二进制计数器
计数器——记录外部脉冲的个数(一般为随机脉冲)
定时器—— 由系统提供计数脉冲(频率固定)
核心都是计数器,对脉冲信号进行计数,不过一个是对外部脉冲,一个是对系统时钟脉冲,由于系统脉冲的频率固定,所以周期也固定,即达到相应计数次数的时间是固定的,因此作为定时器使用。
通用定时器模块(GPTM)
6个16/32位GPTM——2路16位定时计数器(A和B)
可独立使用:16位定时/计数器
或级联使用:32位定时/计数器
作为16位定时计数器时,可使用8位预分频器将位数扩展到24位
6个32/64位GPTM ——2路32位定时计数器(A和B)
可独立使用:32位定时/计数器
或级联使用:64位定时/计数器
作为32位定时计数器时,可使用16位预分频器将位数扩展到48位
不同位数的计数器结构功能类似,位数不同
每个GPTM有两个Capture/Compare/PWM(CCP)引脚,具有捕获、比较、PWM输出等功能
1,如作为输入引脚,具有捕获Capture功能,即对于输入信号捕捉他的边沿信息,上升沿计数或下降沿计数。
2.如作为输出引脚,具有比较Compare和PWN功能,这两种功能相似,都是将计数器的值与某个值进行比较,根据结果输出相应的矩形波。
每个模块都有6种运行模式:
单次计数(One-shot)、周期性计数(Periodic)、RTC实时计时、输入边沿计数(Input Edge Count)、输入边沿计时(Input Edge Time),PWM模式
六种运行模式:1.单次计数 one-shot
2.周期性计数 periodic
3.RTC实时计数 (real-time clock 实时时钟)
4.输入边沿计数 input edge count
5.输入边沿计时 input edge time
6.PWN模式(pulse width modulation 脉冲宽度调制)
GPTMTn(A/B)R 定时计数器A/B的寄存器
GPTMTn(A/B)V 定时计数器A/B的影子寄存器—从中读取*值(当前时间的计数器数值)
GPTMTnMR模式寄存器—通过修改这三个寄存器中的值,可以配置定时器的具体工作模式
GPTMCTL 控制寄存器————|
下面这个寄存器中的值,在运行过程中是不变的
GPTMTnILR 间隔装载寄存器(递增模式—计数最大值;递减模式—计数预设值)|
在单独模式下,他和下面这个寄存器合起来就是实际最大值或预设值
GPTMTnPR 预分频寄存器(分频系数-1的值,8-bit or 16-bit)———————|
GPTMTnPS 预分频快照寄存器——预分频寄存器的当前值
GPTMTnPV 预分频数值寄存器——预分频寄存器的当前*运行值
以下两个寄存器是中断时使用的,由于在单独模式下,定时计数器的实际计数数值是定时计数器寄存器中的数值“加上”预分频寄存器的当前值,所以除了定时计数器的匹配寄存器,还需要一个预分频匹配寄存器。
GPTMTnMATCHR 定时计数器匹配寄存器(当计数值与匹配值相同时,产生中断)
GPTMTnPMR 预分频匹配寄存器(对GPTMTnMATCHR匹配值的位数进行扩展)
GPTMTnR(实际是GPTMTnV)与GPTMTnILR和GPTMTnMATCHR中的值相同或者说是达到时(因为递增是从0到寄存器中的值,而递减是从寄存器的值到0,但是计数次数是定的,无论是999-0还是0-999都是1000次),均会产生中断信号,发出中断请求,区别是计数次数达到间隔重载寄存器中的数值后,定时计数器的数值会重载,即递增从0开始加,递减从间隔重载寄存器中的数值开始减,而计数次数达到定时计数器匹配寄存器中的数值时,只会产生中断请求,但不会改变计数次数。
这些寄存器的数据都存在哪儿?
对与每一个定时计数器A/B,每一路都有一个基准地址[即每一个GPTM的TA(Timer A)和TB(Timer B)都有一个基准地址],而每一个定时计数器都有相同种类的多个寄存器(CFG,ILR等),这些寄存器都有一个偏移地址,同种寄存器的偏移地址是相同的(即TA的CFG的偏移地址和TB的CFG的偏移地址是相同的),但要注意,偏移地址不是一个实际的地址,他只是表示这个寄存器所存储的数据的首地址相对与基准地址的偏移量。GPTM1的TA的CFG寄存器的数据存储的首地址(一个实际地址),是GPTM1的TA的基准地址加上CFG的偏移地址,在PPT中,根据书上给出的基准地址和偏移地址,GPTM1的TA的CFG寄存器的数据存储的首地址是0x4003.0000加上0x000,即0x4003.0000(”.”不是小数点,只是高四位与低四位的分隔表示而已)
为什么偏移地址都是0x000,0x00c这种三位的十六进制,这种表示感觉很奇怪啊!!!
这是基准地址的锅,因为,基准地址,如GPTM1TB的基准地址是0x4003.1000。相对于GPTM1TA 0x4003.0000来讲,他只在低四位的第四位上加了一,也就是说,一路定时计数器,我只给他三位十六进制的地址空间,也就是2的3x4=12次方的二进制空间,如果说我的偏移地址大于三位十六进制,比如是0x1001,那如果我把他和TA的基准地址一加,这不就跑到TB的地址空间里去了吗,所以偏移地址只有三位,因为再多就跑到其他路的定时计数器的地址里去了。当然,我也可以把偏移地址0x000写成0x0000,反正只要我保证第四位一直是0,我就不会跑到其他路的定时计数器的地址里去,但这样很蠢啊!我明知道自己只能变三位,还要非加上一位让他一直为0,为什么不直接省了来的更省事呢。从另一方面来说,一个定时计数器的一路,如GPTM1TA的所有寄存器的数据加起来,也没有达到0xFFF,所以三位十六进制的地址空间足够耐用啦~
感觉定时计数器就像是一类定长的数组,而GPTM1TA就是他的数组名,不同寄存器中存的数就是不同的元素,如CFG中的数就是元素[0]中的数。
Timer定时器中断程序实例-循环闪烁的三色LED灯
main.c中的部分
#include <stdint.h>
#include <stdbool.h>
#include <inc/tm4c123gh6pm.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/interrupt.h"
#include "driverlib/timer.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
int main(void)
{
//SysCtlClockSet(SYSCTL_SYSDIV_1|SYSCTL_USE_OSC|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);
//SysCtlClockSet(SYSCTL_SYSDIV_1|SYSCTL_USE_OSC|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_INT);
SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
TimerConfigure(TIMER0_BASE,TIMER_CFG_PERIODIC);
TimerLoadSet(TIMER0_BASE,TIMER_A,SysCtlClockGet()/2-1);
IntEnable(INT_TIMER0A);
TimerIntEnable(TIMER0_BASE,TIMER_TIMA_TIMEOUT);
IntMasterEnable();
TimerEnable(TIMER0_BASE,TIMER_A);
GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1,2);
while(1)
{
}
}
//中断函数
void Timer0IntHander(void){
TimerIntClear(TIMER0_BASE,TIMER_TIMA_TIMEOUT);
if(GPIOPinRead(GPIO_PORTF_BASE,GPIO_PIN_2))
{
GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3,8);
}
else if(GPIOPinRead(GPIO_PORTF_BASE,GPIO_PIN_3))
{
GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3,2);
}
else if(GPIOPinRead(GPIO_PORTF_BASE,GPIO_PIN_1))
{
GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3,4);
}
}
下面来一一介绍各个函数作用
- SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);
系统时钟设置为40MHz。
- SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
使能GPIO F。
- GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
设置GPIO F的PIN1,PIN2,PIN3为输出状态。
- SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
使能定时器模块Timer0。
- TimerConfigure(TIMER0_BASE,TIMER_CFG_PERIODIC);
配置定时器模块Timer0为Periodic周期性计数模式。
- TimerLoadSet(TIMER0_BASE,TIMER_A,SysCtlClockGet()/2-1);
设定定时器模块Timer0的Load重装载值为系统时钟频率的一半再减一(为0.5秒)。
- IntEnable(INT_TIMER0A);
使能定时器模块Timer0的定时器A的中断。
- TimerIntEnable(TIMER0_BASE,TIMER_TIMA_TIMEOUT);
使能单独的定时器中断源,第一个TIMER0_BASE为Timer0的基地址,第二个是中断源启用的中断事件的标识码,TIMER_TIMA_TIMEOUT的意思是定时器A(TimerA)溢出(重装载),以此为标志位,当TimerA重装载时就会触发中断。
- IntMasterEnable();
使能处理器中断,使处理器能够响应中断。
- TimerEnable(TIMER0_BASE,TIMER_A);
使能定时器TimerA。
- GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1,2);
向特定引脚写入一个值,向GPIO F PIN_1写入2,即0000.0010,第一个为GPIOF的基地址,第二个为把数据位相关联的地址位置位,即把PIN_1数据位相关联的地址位写为1,使我能对PIN_1的数据位写入数据,第三个为写入数据,0000.0010,对应PIN_1的写入数据为1,详细内容看基本接口与外设中GPIO数据寄存器操作。
- TimerIntClear(TIMER0_BASE,TIMER_TIMA_TIMEOUT);
清除定时器中断,第一个为基地址,第二个为要清除的中断源标识码,在中断处理程序中调用,防止中断重复触发,与之前的TimerIntEnable()相对应。
- GPIOPinRead(GPIO_PORTF_BASE,GPIO_PIN_2)
读取GPIOPin口的值,高电平为1,低电平为0,第一个为GPIO F的基地址,第二个为要读取的Pin口为Pin_2。
startup_ccs.c 中的部分(根据型号不同该文件全称不同,我的是tm4c123gh6pm_startup_ccs.c)
找到定时器模块Timer0的TA对应的中断函数,将其改为你的中断函数名称
startup中都是TI CCS的各种启动代码,具体每个部分的用途可以看文件内的注释