一、前言
定时器可以说是任何单片机中的标配外设了,学过那么多种MCU,定时器模块非常简单,几乎都是一样的工作流程。用一个寄存器不断的计数来标记经过的时间,这个计数寄存器溢出后可以触发中断等事件,定时器模块一般都捆绑有PWM功能,就是再加一个比较寄存器,当比较寄存器中的值与计数器值相等时改变IO的电平,实现PWM控制。当然,S3C2440定时器模块也是一样的。
二、实验目标
采用定时器0实现定时器中断,每隔1秒改变1次开发板上LED的亮灭状态。
三、分析
S3C2440定时器模块方框图如下:
S3C2440A 有 5 个 16 位定时器。其中定时器 0、1、2 和 3 具有脉宽调制(PWM)功能。定时器 4 是一个无输出引脚的内部定时器。定时器 0 还包含用于大电流驱动的死区发生器。定时器 0 和 1 共用一个 8 位预分频器,定时器 2、3 和 4 共用另外的 8 位预分频器。每个定时器都有一个可以生成 5 种不同分频信号(1/2,1/4,1/8,1/16 和 TCLK)的时钟分频器。每个定时器模块从相应 8 位预分频器得到时钟的时钟分频器中得到其自己的时钟信号。8 位预分频器是可编程的,并且按存储在 TCFG0 和 TCFG1 寄存器中的加载值来分频 PCLK。定时计数缓冲寄存器(TCNTBn)包含了一个当使能了定时器时的被加载到递减计数器中的初始值。定时比较缓冲寄存器(TCMPBn)包含了一个被加载到比较寄存器中的与递减计数器相比较的初始值。这种 TCNTBn 和TCMPBn 的双缓冲特征保证了改变频率和占空比时定时器产生稳定的输出。
每个定时器有它自己的由定时器时钟驱动的 16 位递减计数器。当递减计数器到达零时,产生定时器中断请求通知 CPU 定时器操作已经完成。当定时器计数器到达零时,相应的 TCNTBn 的值将自动被加载到递减计数器以继续下一次操作。然而,如果定时器停止了,例如,在定时器运行模式期间清除 TCONn 的定时器使能位,TCNTBn的值将不会被重新加载到计数器中。
TCMPBn 的值是用于脉宽调制(PWM)。当递减计数器的值与定时器控制逻辑中的比较寄存器的值相匹配时定时器控制逻辑改变输出电平。因此,比较寄存器决定 PWM 输出的开启时间(或关闭时间)。
TCFG0设置对PCLK的分频:
TCFG1设置MUX的分频:
TCON可以设置定时器的开、关、使能自动重装载等功能。
设置TCON的时候有个注意点,就是要先设置手动更新位,然后再清除手动更新位。设置手动更新位的时候预装载值进入到TCNTO0,再清除手动更新位后下次定时器计数值到0后自动取TCNTB0中的值装载入TCNTO0中进行下一次递减计数。所以想要循环定时中断的话设置了手动更新位后要立刻再清除掉。
TCNTB0可以设置初始计数值,注意想要读取当前计数值不是读取TCNTB0,而是要去读取TCNTO0。
四、代码编写
代码分为以下几个文件:
head.S:启动文件。
main.c:各种C函数。
Makefile:编译代码。
文件内容分别如下:
head.S
@*************************************************************************
@ File:head.S
@*************************************************************************
.text
.global _start
_start:
@******************************************************************************
@ 中断向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用
@******************************************************************************
b Reset
@ 0x04: 未定义指令中止模式的向量地址
HandleUndef:
b HandleUndef
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
HandleSWI:
b HandleSWI
@ 0x0c: 指令预取终止导致的异常的向量地址
HandlePrefetchAbort:
b HandlePrefetchAbort
@ 0x10: 数据访问终止导致的异常的向量地址
HandleDataAbort:
b HandleDataAbort
@ 0x14: 保留
HandleNotUsed:
b HandleNotUsed
@ 0x18: 中断模式的向量地址
b HandleIRQ
@ 0x1c: 快中断模式的向量地址
HandleFIQ:
b HandleFIQ
Reset:
ldr sp, =4096 @设置堆栈,因为要调用C语言函数
bl disable_watch_dog @关WATCH DOG
bl init_system_clk @初始化FCLK到400MHz,PCLK到50MHz
msr cpsr_c, #0xd2 @ 进入中断模式
ldr sp, =3072 @ 设置中断模式栈指针
msr cpsr_c, #0xd3 @ 进入管理模式
ldr sp, =4096 @ 设置管理模式栈指针,
@ 其实复位之后,CPU就处于管理模式,
@ 前面的“ldr sp, =4096”完成同样的功能,此句可省略
msr cpsr_c, #0x53 @ 设置I-bit=0,开IRQ中断
ldr lr, =halt_loop @ 设置返回地址
ldr pc, =main @ 调用main函数
halt_loop:
b halt_loop
HandleIRQ:
sub lr, lr, #4 @ 计算返回地址
stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器
@ 注意,此时的sp是中断模式的sp
@ 初始值是上面设置的3072
ldr lr, =int_return @ 设置调用ISR即EINT_Handle函数后的返回地址
ldr pc, =Timer0_Handle @ 调用中断服务函数
int_return:
ldmia sp!, { r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
main.c
#define BYTE unsigned char
#define WORD unsigned short
#define DWORD unsigned int
/* WOTCH DOG register */
#define REG_WTCON (*(volatile unsigned long *)0x53000000)
/* Sys Clk Config */
#define REG_CLKDIVN (*(volatile unsigned long *)0x4C000014)
#define REG_CAMDIVN (*(volatile unsigned long *)0x4C000018)
#define REG_MPLLCON (*(volatile unsigned long *)0x4C000004)
/* GPIO Configure*/
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPFUP (*(volatile unsigned long *)0x56000058)
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define GPGDAT (*(volatile unsigned long *)0x56000064)
#define GPGUP (*(volatile unsigned long *)0x56000068)
#define REG_EXTINT0 (*(volatile unsigned long *)0x56000088)
#define REG_EXTINT1 (*(volatile unsigned long *)0x5600008C)
/* interrupt Configure */
#define REG_EINTMASK (*(volatile unsigned long *)0x560000A4)
#define REG_INTMSK (*(volatile unsigned long *)0X4A000008)
#define REG_INTOFFSET (*(volatile unsigned long *)0x4A000014)
#define REG_EINTPEND (*(volatile unsigned long *)0x560000A8)
#define REG_SRCPND (*(volatile unsigned long *)0X4A000000)
#define REG_INTPND (*(volatile unsigned long *)0X4A000010)
// #define REG_INTSUBMSK (*(volatile unsigned long *)0x560000A8)
// #define REG_SUBSRCPND (*(volatile unsigned long *)0X4A000018)
/* timer Configure */
#define REG_TCFG0 (*(volatile unsigned long *)0x51000000)
#define REG_TCFG1 (*(volatile unsigned long *)0x51000004)
#define REG_TCON (*(volatile unsigned long *)0x51000008)
#define REG_TCNTB0 (*(volatile unsigned long *)0x5100000C)
void disable_watch_dog();
void init_system_clk();
void init_led();
void init_timer0();
void Timer0_Handle();
/*上电后,WATCH DOG默认是开着的,要把它关掉 */
void disable_watch_dog()
{
REG_WTCON = 0;
}
void init_system_clk()
{
//HCLK = FCLK/4, 当 CAMDIVN[9] = 0 时
//PCLK 设置为 HCLK/2
//完成配置FCLK : HCLK : PCLK = 1 : 1/4 : 1/8,DIVN_UPLL是USB的时钟不用管
REG_CLKDIVN = (2 << 1) | (1 << 0);
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
__asm__(
"mrc p15, 0, r1, c1, c0, 0n" /* 读出控制寄存器 */
"orr r1, r1, #0xc0000000n" /* 设置为“asynchronous bus mode” */
"mcr p15, 0, r1, c1, c0, 0n" /* 写入控制寄存器 */
);
//m=MDIV+8, p=PDIV+2, s=SDIV, Mpll = ( 2 × m × Fin ) / ( p × 2^s )
//FCLK = (2 * (92 + 8) * 12000000) / ((1 + 2) * 2) = 400000000 = 400MHz
//配置完MPLL后时钟停振,CPU停止运行等待时钟输出稳定,之后FCLK=400MHz,HCLK=100MHz,PCLK=50MHz */
REG_MPLLCON = (92<<12)|(1<<4)|(1<<0);
}
void init_led()
{
GPFCON &= ~((DWORD)(3 << (2 * 4)) | (3 << (2 * 5)) | (3 << (2 * 6)));
GPFCON |= ((DWORD)(1 << (2 * 4)) | (1 << (2 * 5)) | (1 << (2 * 6))); //GPF4、GPF5、GPF6输出模式
GPFDAT |= (1 << 4) | (1 << 5) | (1 << 6); //输出高电平,LED全灭
}
void init_timer0()
{
//Prescaler 0设置250分频
REG_TCFG0 &= ~((DWORD)0xFF << 0);
REG_TCFG0 |= ((DWORD)(250 - 1) << 0);
//MUX 0 设置4分频
REG_TCFG1 &= ~((DWORD)0xF << 0);
REG_TCFG1 |= ((DWORD)1 << 0);
//设置定时器0自动重装载值
REG_TCNTB0 = 50000;
//手动更新 TCNTB0 和 TCMPB0
REG_TCON |= ((DWORD)1 << 1);
//清除手动更新 TCNTB0 和 TCMPB0,不加这句定时器不运行
REG_TCON &= ~((DWORD)1 << 1);
//开定时器0中断
REG_INTMSK &= ~((DWORD)1 << 10);
//启动定时器0,使能自动重装载
REG_TCON |= ((DWORD)9 << 0);
}
void Timer0_Handle()
{
BYTE bIntOffset = REG_INTOFFSET;
switch(bIntOffset)
{
//定时器0中断
case 10:
GPFDAT ^= ((DWORD)1 << 4); //电平翻转
break;
default:
break;
}
//清中断
REG_SRCPND = (DWORD)1 << bIntOffset;
REG_INTPND = REG_INTPND;
}
int main()
{
init_led();
init_timer0();
while(1);
return 0;
}
Makefile
objs := head.o main.o
timer.bin: $(objs)
arm-linux-ld -Ttext 0x0000000 -g -o timer_elf $^
arm-linux-objcopy -O binary -S timer_elf $@
arm-linux-objdump -D -m arm timer_elf > timer.dis
%.o:%.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean:
rm -f timer.bin timer_elf timer.dis *.o
执行make命令后将生成的bin文件烧写到JZ2440开发板的NandFlash中,然后设置Nand启动,可以看到LED1每隔1秒改变1次亮灭状态。
五、实验总结
定时器的配置方式和寄存器基本都和单片机的一样,也就那些基本的操作。
上一篇:【JZ2440笔记】DMA
下一篇:【JZ2440笔记】串口通信(中断方式)
推荐阅读最新更新时间:2024-11-17 11:45