定时器中断实验
S3C2440提供了3种时钟:FCLK用于CPU核;HCLK用于AHB总线上的设备,比如存储器控制器、中断控制器、LCD控制器、DMA和USB主机模块,同时也可以在特殊情况下用于CPU核;而PCLK则用于APB总线上的设备,比如看门狗、IIS、IIC、PWM定时器、ADC、UART、GPIO、RTC和SPI等。
总的来说,AHB总线主要用于高性能模块,APB总线主要用于低带宽的周边外设之间的连接。
S3C2440定时器的总时钟源为PCLK,首先通过两个8位的预分频器降低频率,定时器0、1共用第一个预分频器,定时器2、3、4共用第二个预分频器;之后预分频器的输出将被第二级的五路选择分频器处理,之后供定时器各自选择。
原理介绍
时钟频率选择
S3C2440 SoC内部含有5个16位的定时器,分别是timer0、timer1、timer2、timer3、timer4。前四个定时器同时还具有PWM(Pulse Width Modulation)功能,而因为第5个定时器timer4没有外部输出引脚,所以不具备PWM功能。同时需要说明,由于timer0定时器还具有一个死区发生器,所以它可以被用来驱动大电流设备。
由上图可知,timer0和timer1共用一个8位的预分频器以降低频率;timer2、timer3和timer4共用另外一个预分频器。从预分频器出来之后,会分别进入第二级的5路选择分频器。5路选择分频器可以选择2分频、4分频、8分频、16分频或者直接使用外部时钟TCLK0/TCLK1。每个定时器都可以从第二级分出来的5种频率中选择各自使用的时钟频率。
对于预分频器,我们可以通过TCFG0寄存器来进行设置。而对于第二级时钟频率选择,则是通过TCFG1寄存器来选择的。
定时器内部控制逻辑
当时钟分频设置好之后,我们开始研究一下定时器本身的设置。
首先,我们需要设置初始计数缓存寄存器TCNTBn和初始比较值寄存器TCMPBn,用以分别表示定时器n的初始计数值和初始比较值。
第二,我们需要设置TCON寄存器使得计数器和比较器的值被加载进内部寄存器TCMPn和TCNTn。
第三,当启动定时器之后,每来一个时钟周期,TCNTn的值就递减1,同时我们可以通过TCNTOn这个观察寄存器来对当前数值进行观察。
第四,当TCNTn等于TCMPn时,定时器n的输出管脚TOUTn就会电平反转。
第五,当TCNTn的值到达0时,TOUTn的输出管脚电平会再次反转;同时如果使能了中断的话,并会触发定时器中断。
第六,如果通过TCON寄存器设置了自动加载计数值的话,当TCNTn递减到0时,TCMPBn和TCNTBn寄存器的值会被自动装入内部寄存器,从而开始新一轮的定时周期。如果没有设置为自动加载的话,定时器就是一次性的。
通过上述分析我们可知,我们可以设置TCMPBn和TCNTBn的值,来调整TOUTn管脚输出信号的占空比,从而输出PWM方波,因而这几个可以输出PWM的定时器又叫做PWM定时器。所谓PWM,就是可调制脉冲(Pulse Width Modulation)。
定时器设置
TCFG0寄存器
我们可以看到,TCFG0[7:0]和TCFG0[15:8]分别控制Prescaler0和Prescaler1这两个一级预分频器,其值域为闭区间[0, 255],分频后的输出值为PCLK / (prescaler value + 1)。由于我们之前设置的PCLK频率为50MHz,所以我们将prescaler value设置为99,方便后续计算,即:
TCFG0 = (99 << 0) | (99 << 8);
TCFG1寄存器
如上图所示,经过一级预分频器后的时钟接下来会被二级五路选择分频器所分频。具体的五路选择则由TCFG1寄存器来配置。这里我们随便选择1/4分频,即:
TCFG1 = (1 << 16) | (1 << 12) | (1 << 8) | (1 << 4) | (1 << 0);
由于这5个定时器操作都是类似的,下面为简便起见,我们只是用定时器0来进行实验。
由于一级分频为1/100,二级分频为1/4,所以定时器0的时钟频率为50MHz/400 = 125000 Hz。所以我们下面的计数器就要设置为该值,从而1秒产生一次IRQ中断。
哈哈,希望是美好的,现实是残酷的。
我们通过下一张图片可知计数器TCNTB0只能使用16位,所以它可设置的最大计数值为65535,比我们希望设置的值12500小了一个数量级。所以如果我们想1秒产生一次中断,那么就要修改分频系数,我们二级分频系数采用1/8,这样计数值就变为62500,符合要求。
TCFG1 = (2 << 16) | (2 << 12) | (2 << 8) | (2 << 4) | (2 << 0);
TCNTB0和TCMPB0寄存器
通过上图可知,我们只用到了TCNTB0和TCMPB0 这两个寄存器的低16位。TCNTB0寄存器中保存的是定时器0的计数初始值,TCMPB0寄存器中保存的是定时器0的比较值。
这里需要强调的一点是,由于定时器4没有输出引脚,即没有PWM功能,所以对应定时器4来说,它没有TCMPB4和TCMP4寄存器。
在定时器工作时,我们可以通过TCNTO0这个观察寄存器,对当前定时器内部TCNT0寄存器中的值进行观察。
我们将计数值设置为62500,比较值设置为其一般,即31250。
TCNTB0 = 62500;
TCMPB0 = 31250;
TCON寄存器
这里需要强调几点内容:
第一次启动定时器前,一定要手动将TCNTBn和TCMPBn寄存器的值加载到定时器内部寄存器TCNTn和TCMPn中。那么怎么手动加载呢?以定时器0来举例,就是将TCON寄存器的第1位置1即可。之后在后面启动定时器的时候,还需要将这一位清零。
还要设置当本次计数完毕后,是否自动加载计数值,从而开始下一个计数周期。
还要设置定时器TOUTn管脚的输出电平是否反转。
我们设置定时器0为自动加载,从而周期性的产生中断。代码如下所示:
//停止定时器0
TCON &= (~(0x1F << 0));
//将计数器缓冲和比较器缓冲中的值加载到定时器0内
TCON |= (1 << 1);
TCON &= (~(1 << 1));
//启动自动加载,并启动定时器0
TCON = (1 << 3) | (1 << 0);
这样定时器0就开始工作了,每个1秒产生一次IRQ中断。
中断控制器设置
通过查看中断源我们发现,定时器0对应的INT_TIMER0中断源没有子中断源, 所以我们只需设置INTMSK寄存器即可。
通过上图可知,INT_TIMER0对应的硬件中断号是10。
INTMSK &= (~(1 << 10));
定时器0的中断处理函数
我们在中断处理函数中只打印一句话,表明定时器中断发生了。
void timer0_irq_handler(void){
printf("nrtimer0 irq is handled.nr");
}
void irqExpHandler(void) {
//现在处于SVC模式
//我们先获取硬件中断号
unsigned int interruptNum = INTOFFSET;
int subInterruptNum = 0;
printf("nrIRQ Interrupt Number: %dnr", interruptNum);
//按键 按下后为低电平
unsigned int isKeyUp = 0;
int ledNum = -1;
switch(interruptNum){
case 0:
//EINT0中断 s2按键
isKeyUp = (GPFDAT & 0x1);
ledNum = LED1;
break;
case 2:
//EINT2 s3按键
isKeyUp = (GPFDAT & (0x1 << 2));
ledNum = LED2;
break;
case 5:
//EINT8_23 s4按键
isKeyUp = (GPGDAT & (0x1 << 3));
ledNum = LED4;
break;
case 10:
timer0_irq_handler();
break;
case 28:
subInterruptNum = SUBSRCPND & 0x7;
if(SUBSRCPND & 0x1) {
//INT_RXD0
uart0_handle_irq(UART0_RX_IRQ);
}
if(SUBSRCPND & 0x2) {
//INT_RXD0
uart0_handle_irq(UART0_TX_IRQ);
}
if(SUBSRCPND & 0x4) {
//INT_RXD0
uart0_handle_irq(UART0_ERR_IRQ);
}
break;
default:
break;
}
if(ledNum > 0) {
if(isKeyUp) {
//熄灭LED1
led_off(ledNum);
printf("Key is Up.LED%d is turn off.nr", ledNum);
}else {
led_on(ledNum);
printf("Key is Down.LED%d is turn on.nr", ledNum);
}
}
//处理完毕,从源头开始清理中断标志
//注意是写1清零
if(interruptNum == 5) {
EINTPEND = (0x1 << 11);
}
SUBSRCPND = subInterruptNum;
SRCPND = (0x1 << interruptNum);
INTPND = (0x1 << interruptNum);
printf("IRQ Interrupt is Handlednr");
}
char gCh = 'A';
int main(void) {
leds_init();
keys_init_irq();
printf("%snr", "IRQ Test.");
dump_norFlash(80);
timer_init();
timer_start();
while(1) {
printf("%c(0x%02x) ", gCh, gCh);
gCh++;
wait(888888);
}
return 0;
}
实验结果
我们可以看到,每个1秒钟产生一次定时器0 IRQ中断,如下图所示:
实验相关源文件
timer.c
//timer.c
#include "s3c2440_soc.h"
#include "timer.h"
#include "myprintf.h"
void timer0_irq_handler(void){
printf("nrtimer0 irq is handled.nr");
}
void timer_init(void) {
TCFG0 = (99 << 0) | (99 << 8);
TCFG1 = (2 << 16) | (2 << 12) | (2 << 8) | (2 << 4) | (2 << 0);
TCNTB0 = 62500;
TCMPB0 = 31250;
INTMSK &= (~(1 << 10));
}
void timer_start(void) {
//停止定时器0
TCON &= (~(0x1F << 0));
//将计数器缓冲和比较器缓冲中的值加载到定时器0内
TCON |= (1 << 1);
TCON &= (~(1 << 1));
//启动自动加载,并启动定时器0
TCON = (1 << 3) | (1 << 0);
}
intException.c
//intException.c
#include "myprintf.h"
#include "s3c2440_soc.h"
#include "led.h"
#include "uart.h"
#include "timer.h"
void undExpHandler(unsigned int* undAddr) {
printf("Undefined Instruction(0x%08x) at address(0x%08x) is found!nr",
*undAddr, undAddr);
}
void swiExpHandler(unsigned int swiNum) {
printf("Software Exception is Checked with Number %d(0x%x)nr", swiNum, swiNum);
}
void abortPrefetchExpHandler(unsigned int* abtPrefetchAddr) {
printf("Fetch instruction from address(0x%08x) failed.nr", abtPrefetchAddr);
}
void abortDataExpHandler(unsigned int* abtDataAddr) {
printf("Fetch data failed at address(0x%08x).nr",
abtDataAddr);
}
void irqExpHandler(void) {
//现在处于SVC模式
//我们先获取硬件中断号
unsigned int interruptNum = INTOFFSET;
int subInterruptNum = 0;
printf("nrIRQ Interrupt Number: %dnr", interruptNum);
//按键 按下后为低电平
unsigned int isKeyUp = 0;
int ledNum = -1;
switch(interruptNum){
case 0:
//EINT0中断 s2按键
isKeyUp = (GPFDAT & 0x1);
ledNum = LED1;
break;
case 2:
//EINT2 s3按键
isKeyUp = (GPFDAT & (0x1 << 2));
ledNum = LED2;
break;
case 5:
//EINT8_23 s4按键
isKeyUp = (GPGDAT & (0x1 << 3));
ledNum = LED4;
break;
case 10:
timer0_irq_handler();
break;
case 28:
subInterruptNum = SUBSRCPND & 0x7;
if(SUBSRCPND & 0x1) {
//INT_RXD0
uart0_handle_irq(UART0_RX_IRQ);
}
if(SUBSRCPND & 0x2) {
//INT_RXD0
uart0_handle_irq(UART0_TX_IRQ);
}
if(SUBSRCPND & 0x4) {
//INT_RXD0
uart0_handle_irq(UART0_ERR_IRQ);
}
break;
default:
break;
}
if(ledNum > 0) {
if(isKeyUp) {
//熄灭LED1
led_off(ledNum);
printf("Key is Up.LED%d is turn off.nr", ledNum);
}else {
led_on(ledNum);
printf("Key is Down.LED%d is turn on.nr", ledNum);
}
}
//处理完毕,从源头开始清理中断标志
//注意是写1清零
if(interruptNum == 5) {
EINTPEND = (0x1 << 11);
}
SUBSRCPND = subInterruptNum;
SRCPND = (0x1 << interruptNum);
INTPND = (0x1 << interruptNum);
printf("IRQ Interrupt is Handlednr");
}
void fiqExpHandler(void) {
printf("FIQ is Detected!nr");
unsigned int isKeyUp = 0;
int offset = -1;
int hardwareIntNum = -1;
printf("EINTPEND(0x%08x)nr", EINTPEND);
printf("SRCPND(0x%08x)nr", SRCPND);
if(EINTPEND & (0x1 << 11)) {
//S4按下或松开 EINT11
isKeyUp = (GPGDAT & (0x1 << 3));
if(isKeyUp) {
//熄灭LED4
led_off(LED4);
printf("Key is Up.LED4 is turn off.nr");
}else {
led_on(LED4);
printf("Key is Down.LED4 is turn on.nr");
}
offset = 11;
//EINT11对应的硬件中断号是5
hardwareIntNum = 5;
}else if(EINTPEND & (0x1 << 19)) {
//S5按下或松开 EINT19
isKeyUp = (GPGDAT & (0x1 << 11));
if(isKeyUp) {
//熄灭LED4
led_off(LED1);
led_off(LED2);
led_off(LED4);
printf("Key is Up.LED1 LED2 LED4 is turn off.nr");
}else {
led_on(LED1);
led_on(LED2);
led_on(LED4);
printf("Key is Down.LED1 LED2 LED4 is turn on.nr");
}
offset = 19;
//EINT19对应的硬件中断号是5
hardwareIntNum = 5;
}else {
printf("Unknown FIQ Interrupt!nr");
}
//从源头开始清理中断标志
//注意是写1清零
if(offset >= 0) {
EINTPEND = (1 << offset);
SRCPND = (1 << hardwareIntNum);
}
printf("FIQ is Handled.nr");
}
void keys_init_irq(void) {
//按键S2对应的GPIO是GPF0
//S3对应的GPIO是GPF2
//S4对应的GPIO是GPG3
//初始化GPF0 GPF2为中断功能,对应EINT2 EINT0
GPFCON &= (~(0x33));
GPFCON |= (0x2 << 4) | (0x2 << 0);
//初始化GPG3为中断功能,对应EINT11
GPGCON &= (~(0x3 << 6));
GPGCON |= (0x2 << 6);
//按键S5对应EINT19, GPG11
GPGCON &= (~(0x3 << 22));
GPGCON |= (0x2 << 22);
//设置GPIO中断触发方式为双边沿触发
EXTINT0 |= (0x7 | (0x7 << 8));
EXTINT1 |= (0x7 << 12);
//设置EINT19的触发方式为双边沿触发
EXTINT2 |= (0x7 << 12);
//打开GPIO中断屏蔽开关
//对于EINT0~EINT3来说,GPIO控制器这里是始终没有屏蔽中断的。
//我们只需打开EINT11中断屏蔽即可。
EINTMASK &= (~(0x1 << 11));
//打开EINT9中断屏蔽开关
EINTMASK &= (~(1 << 19));
//设置EINT19对应的中断方式为FIQ
//当我们将中断方式设置为FIQ时,中断发生时不会去设置INTPND INTOFFSET
INTMOD |= (1 << 5);
//打开中断控制器对应的中断开关
INTMSK &= (~((1 << 0) | (1 << 2) | (1 << 5)));
}
main.c
//main.c
#include "myprintf.h"
#include "nand.h"
#include "nor.h"
#include "uart.h"
上一篇:【S3C2440】第14课、异常与中断之学习笔记
下一篇:11.S3C2440 中断实验(一)und和swi实验
推荐阅读最新更新时间:2024-11-13 10:24
推荐帖子
- 麻醉安全度智能监护仪研制成功
- 从重庆市信息产业局获悉,一项用于提高临床麻醉监护安全性的麻醉安全度智能监护仪日前在重庆工学院研制成功,这一成果有望提高外科手术中使用麻醉手段的安全程度。麻醉是众多外科手术中必须的条件和前提,在全身麻醉手术中,病人在手术中发生知晓意识的比率为1%至2%,麻醉程度不够会造成病人心理不愉快,产生手术恐惧情绪,还会对医生产生不信任感和逆反心理,但麻醉用药过量则更危险。因此,手术麻醉操作既要使病人在手术过程中的意识处于完全消失状态,又要防止麻醉用药过量,降低医疗费用和减少并发症等。重
- 黑衣人 工控电子
- 物联网项目 集锦 2:看看你想动手做哪个?
- 十五.使用ArduinoIDE的ESP8266气象站十六.土壤湿度传感器和Arduino十七.汽车倒车雷达电路十八.Arduino金属探测器十九.数字电容式触摸传感器arduino接口二十.水流量传感器YF-S201Arduino接口二十一.颜色传感器TCS3200Arduino接口二十二.Myoware肌肉传感器与Arduino接口二十三.Arduino的自动风扇速度控制电路二十四.室内空气质量传感器Ren
- Jacktang DigiKey得捷技术专区
- AD采集分压电阻的选择
- 单片机AD采集测量范围0~2.56V,现在要测量一个24V~30V的锂电池电压,打算分压,然后AD采集,请问分压电阻如何选择呢?试了好几组电阻值,测出的电压值都比实际电压偏高好多差到0.4~0.5V了,不知该如何选择?程序没有问题,单片机也没有问题,使用精准电压源芯片提供2.5V电压,AD采集值计算结果很准,只是用电阻分压AD采集值就会差很多。还有就是如果是和电阻精度有关,为什么用万用表测出来也和理论计算值接近,唯独AD采集结果偏高好多?AD采集分压电阻的选择 直接用AD测一个不分压的电压
- gxp790953623 单片机
- 为什么需要多寄存器访问指令?
- ldr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ld举例(ubootstart.S537行)stmiasp,{r0-r12}将r0存入sp指向的内存处(假设为0x30001000);然后地址+4(即指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008),将r2存入该地址······直到r12内容放入(0x3000130),指令完成。一个访存周期同时完成13个寄
- 灞波儿奔 微控制器 MCU
- 51的例程
- 本帖最后由leo121于2014-5-1021:21编辑 不知道有没有人发过,从keil下载的51的例程顶一下吧。支持下~~
- leo121 51单片机
- 请教反激开关电源的问题
- 各位老师,请教反激开关电源的问题UC3842主控芯片的反激开关电源,这个红框内的起什么作用?箭头指的是MOS管么,参数怎么选型?请教反激开关电源的问题软起动,芯片的数据手册上有。。【箭头指的是MOS管么,参数怎么选型?】箭头所指是小功率双极型三极管,上面一个是PNP型,下面一个是NPN型。这两个小功率双极型三极管,差不多所有小功率管都可以用,要求不高。例如8550和8050就可以。【这个红框内的起什么作用?】要想弄清楚红框内电路作用,必须结合该
- Knight97538 电源技术
设计资源 培训 开发板 精华推荐
- 使用 MaxLinear, Inc 的 SPX385S-1.2/TR 的参考设计
- 使用 Panasonic 的 NN30332A 的参考设计
- QorIQ® T1023参考设计板
- 【核心板】STM32F103C8T6
- DC944A,用于 LT5571EUF、900MHz I/Q 调制器的演示板,具有 Hi-Z/0.5VDC 偏置
- 8路光耦隔离继电器驱动模块
- LM2902VDR2G 双四路滤波器运算放大器的典型应用
- LT6654AHS6-2.048、16 位 ADC 电压基准的典型应用
- LTC3723EGN-1 240W、42Vin 至 56Vin 至 12V/20A 隔离式转换器的典型应用电路
- LT1086CM-3.6 低压差稳压器与基准的典型应用