jz2440裸机开发与分析:S3c2440ARM异常与中断体系详解8---定时器中断程序示例
这节课我们来写一个定时器的中断服务程序
使用定时器来实现点灯计数
查考资料就是第10章PWM TIMER
我们先把这个结构图展示出来
这个图的结构很好
这里面肯定有一个clk(时钟),
1 、每来一个clk(时钟)这个TCNTn减去1
2、 当TCNTn == TCMPn时,可以产生中断,也可以让对应的SPWM引脚反转,(比如说原来是高电平,发生之后电平转换成低电平)
3、 TCNTn继续减1,当TCNTn == 0时,可以产生中断,pwm引脚再次反转 TCMPn 和 TCNTn的初始值来自TCMPBn,TCNTBn
4 、TCNTn == 0时,可自动加载初始
怎么使用定时器?
1、 设置时钟
2 、设置初值
3、 加载初始,启动Timer
4、 设置为自动加载
5 、中断相关
由于2440没有引出pwm引脚,所以pwm功能无法使用,也就无法做pwm相关实验,所谓pwm是指可调制脉冲
T1高脉冲和T2低脉冲它的时间T1, T2可调整,可以输出不同频率不同占控比的波形,在控制电机时特别有用
我们这个程序只做一个实验,当TCNTn这个计数器到0的时候,就产生中断,在这个中断服务程序里我们点灯
写代码
打开我们的main函数
int main(void)
{led_init();interrupt_init(); /* 初始化中断控制器 */
//我们初始化了中断源,同样的,我们初始化timerkey_eint_init(); /* 初始化按键, 设为中断源 */
//初始化定时器timer_init();
我们需要实现定时器初始化函数
新建一个timer.c
,我们肯定需要操作一堆寄存器,添加头文件
#include "s3c2440_soc.h"void timer_init(void)
- 设置TIMER0的时钟
- 设置TIMER0的初值
- 加载初值, 启动timer0
- 设置为自动加载并启动(值到0以后会自动加载)
- 设置中断,显然我们需要提供一个中断处理函数
void timer_irq(void)
在这里面我们需要点灯
打开芯片手册,我们想设置timer0的话
- 首先设置8-Bit Prescaler
- 设置5.1 MUX(选择一个时钟分频)
- 设置TCMPB0和TCNTB0
- 设置TCONn寄存器
看手册上写如何初始化timer
- 把初始值写到TCNTBn 和TCMPBn寄存器
- 设置手动更新位
- 设置启动位
往下看到时钟配置寄存器
有个计算公式
Timer clk = PCLK / {(预分频数)prescaler value+1} / {divider value(5.1MUX值)}
PCLK是50M
= 50000000/(99+1)/16
= 31250
也就是说我们得TCON是31250的话,从这个值一直减到0
Prescaler0等于99TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */
TCFG1 MUX多路复用器的意思,他有多路输入,我们可以通过MUX选择其中一路作为输出
根据上面mux的值,我们要把MUX0 设置成0011
只需要设置这4位即可,先清零
再或上 0011 就是3
TCFG1 &= ~0xf;
TCFG1 |= 3; /* MUX0 : 1/16 */
再来看看初始值控制寄存器
一秒钟点灯太慢了 ,就让0.5秒
TCNTB0 = 15625; /* 0.5s中断一次 */
这个寄存器是用来观察里面的计数值的,不需要设置
现在可以设置TCON来设置这个寄存器
现在需要设置Timer0
开始需要手工更新
TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */
把这两个值放到TCNTB0 和 TCMPB0中
注意:这一位必须清楚才能写下一位
设置为自动加载并启动,先清掉手动更新位,再或上bit0 bit3
TCON &=~ (1<<1);
TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */
设置中断,显然我们需要提供一个中断处理函数void timer_irq(void)
在Timer里没有看到中断相关的控制器,我们需要回到中断章节去看看中断控制器,看看有没有定时器相关的中断
我们没有看到更加细致的Timer0寄存器
当TCNTn=TCMPn
时,他不会产生中断,会发生脉冲的反转,只有当TCNTn
等于0的时候才可以产生中断,我们之前以为这个定时器可以产生两种中断,那么肯定有寄存器中断或者禁止两种寄存器其中之一,那现在只有一种中断的话,就相对简单些
设置中断的话,我们只需要设置中断控制器
设置interrupu.c
中断控制器
*初始化中断控制器 void interrupt_init(void)
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
*把定时器相应的位清零就可以了,哪一位呢?
INTPND的哪一位?
INT_TIMER0第10位即可
INTMSK &= ~(1<<10); /* enable timer0 int */
当定时器减到0的时候就会产生中断,就会进到start.s这里一路执行do_irq
do_irq:/* 执行到这里之前:* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址* 2. SPSR_irq保存有被中断模式的CPSR* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式* 4. 跳到0x18的地方执行程序 *//* sp_irq未设置, 先设置它 */ldr sp, =0x33d00000/* 保存现场 *//* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 *//* lr-4是异常处理完后的返回地址, 也要保存 */sub lr, lr, #4stmdb sp!, {r0-r12, lr} /* 处理irq异常 */bl handle_irq_c/* 恢复现场 */ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */让后进入irq处理函数中处理,处理这个irq
void handle_irq_c(void)
{/* 分辨中断源 */int bit = INTOFFSET;/* 调用对应的处理函数 */if(bit ==0 || bit == 2 || bit == 5)/*eint0,2,rint8_23*/
{key_eint_irq(bit);/*处理中断,清中断源EINTPEND*/
}else if(bit == 10)//如果等于10的话说明发生的是定时器中断,这时候就调用我们得timer_irq
{timer_irq();
}/* 清中断 : 从源头开始清 */SRCPND = (1<<bit);INTPND = (1<<bit);
}回到timer.c文件中,在这个定时器处理函数中我们需要点灯
void timer_irq(void)
{/* 点灯计数 循环点灯*/static int cnt = 0;int tmp;cnt++;tmp = ~cnt;tmp &= 7;GPFDAT &= ~(7<<4);GPFDAT |= (tmp<<4);
}
代码写完我们实验一下,上传代码,在Makefile中添加timer.o
,进行编译
编译后进行烧写
发现灯已经开始闪
对程序进行改进
进入main函数中执行 timer_init();
还需要修改interrupt.c
初始化函数
void interrupt_init(void)
还需要调用中断处理函数
void handle_irq_c(void)
每次添加一个中断我都需要修改handle_irq这个函数,这样太麻烦,我能不能保证这个interrupt文件不变,只需要在timer.c中引用即可,这里我们使用指针数组
在interrupt.c
中定义函数指针数组
typedef void(*irq_func)(int);
定义一个数组,我们来卡看下这里有多少项,一共32位,我们想把每一个中断的处理函数都放在这个数组里面来,当发生中断时,我们可以得到这个中断号,让后我从数组里面调用对应的中断号就可以了
irq_func irq_array[32];
那么我们得提供一个注册函数
void register_irq (int irq, irq_func fp)
{irq_array[irq] = fp;INTMASK &= ~(1 << irq)
}
以后我直接调用对应的处理函数
void handle_irq_c(void)
{/* 分辨中断源 */int bit = INTOFFSET;/* 调用对应的处理函数 */
irq_array[bit](bit);/* 清中断 : 从源头开始清 */SRCPND = (1<<bit);INTPND = (1<<bit);
}//按键中断初始化函数需要注册/* 初始化按键, 设为中断源 */void key_eint_init(void){/* 配置GPIO为中断引脚 */GPFCON &= ~((3<<0) | (3<<4));GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */GPGCON &= ~((3<<6) | (3<<22));GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 *//* 设置中断触发方式: 双边沿触发 */EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */EXTINT1 |= (7<<12); /* S4 */EXTINT2 |= (7<<12); /* S5 *//* 设置EINTMASK使能eint11,19 */EINTMASK &= ~((1<<11) | (1<<19));register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);}
在timer.c中也需要设置中断
void timer_init(void){/* 设置TIMER0的时钟 *//* Timer clk = PCLK / {prescaler value+1} / {divider value} = 50000000/(99+1)/16= 31250*/TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */TCFG1 &= ~0xf;TCFG1 |= 3; /* MUX0 : 1/16 *//* 设置TIMER0的初值 */TCNTB0 = 15625; /* 0.5s中断一次 *//* 加载初值, 启动timer0 */TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 *//* 设置为自动加载并启动 */TCON &= ~(1<<1);TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload *//* 设置中断 */register_irq(10, timer_irq);}
把interrupt.c中按键的初始化放在最后面
我们来看看我们做了什么事情,
<1>我们定义了一个指针数组
typedef void(*irq_func)(int);
注:这里看不懂请参考typedef函数指针用法
这个指针数组里面放有各个指针的处理函数
irq_func irq_array[32];
当我们去初始化按键中断时,我们给这按键注册中断函数
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);
这个注册函数会做什么事情,他会把这个数组放在注册函数里面,同时使能中断
void register_irq(int irq, irq_func fp)
{irq_array[irq] = fp;INTMSK &= ~(1<<irq);
}
//我们的timer.c中timer_init();
//也会注册这个函数/* 设置中断 */register_irq(10, timer_irq);
把这个中断irq放在第10项里同时使能中断,以后我们只需要添加中断号,和处理函数即可,再也不需要修改函数
烧写执行
我们从start.s
开始看,
一上电从 b reset
运行做一列初始化
.text
.global _start_start:b reset /* vector 0 : reset */ldr pc, und_addr /* vector 4 : und */ldr pc, swi_addr /* vector 8 : swi */b halt /* vector 0x0c : prefetch aboot */b halt /* vector 0x10 : data abort */b halt /* vector 0x14 : reserved */reset:/* 关闭看门狗 */ldr r0, =0x53000000ldr r1, =0str r1, [r0]/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m *//* LOCKTIME(0x4C000000) = 0xFFFFFFFF */ldr r0, =0x4C000000ldr r1, =0xFFFFFFFFstr r1, [r0]/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */ldr r0, =0x4C000014ldr r1, =0x5str r1, [r0]/* 设置CPU工作于异步模式 */mrc p15,0,r0,c1,c0,0orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iAmcr p15,0,r0,c1,c0,0/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) * m = MDIV+8 = 92+8=100* p = PDIV+2 = 1+2 = 3* s = SDIV = 1* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M*/ldr r0, =0x4C000004ldr r1, =(92<<12)|(1<<4)|(1<<0)str r1, [r0]/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定* 然后CPU工作于新的频率FCLK*//* 设置内存: sp 栈 *//* 分辨是nor/nand启动* 写0到0地址, 再读出来* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动* 否则就是nor启动*/mov r1, #0ldr r0, [r1] /* 读出原来的值备份 */str r1, [r1] /* 0->[0] */ ldr r2, [r1] /* r2=[0] */cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */ldr sp, =0x40000000+4096 /* 先假设是nor启动 */moveq sp, #4096 /* nand启动 */streq r0, [r1] /* 恢复原来的值 */bl sdram_init//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 *//* 重定位text, rodata, data段整个程序 */bl copy2sdram/* 清除BSS段 */bl clean_bss/* 复位之后, cpu处于svc模式* 现在, 切换到usr模式*/mrs r0, cpsr /* 读出cpsr */bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */bic r0, r0, #(1<<7) /* 清除I位, 使能中断 */msr cpsr, r0/* 设置 sp_usr */ldr sp, =0x33f00000ldr pc, =sdram
sdram:bl uart0_initbl print1/* 故意加入一条未定义指令 */
und_code:.word 0xdeadc0de /* 未定义指令 */bl print2swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 *//***最后执行main函数***///bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ldr pc, =main /* 绝对跳转, 跳到SDRAM */halt:b halt
进入main.c做一系列初始化
int main(void)
{led_init();//interrupt_init(); /* 初始化中断控制器 */key_eint_init(); /* 初始化按键, 设为中断源 */timer_init();puts("\n\rg_A = ");printHex(g_A);puts("\n\r");
}
进入按键初始化程序 interrupt.c
初始化按键, 设为中断源
void key_eint_init(void)
{/* 配置GPIO为中断引脚 */GPFCON &= ~((3<<0) | (3<<4));GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */GPGCON &= ~((3<<6) | (3<<22));GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 *//* 设置中断触发方式: 双边沿触发 */EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */EXTINT1 |= (7<<12); /* S4 */EXTINT2 |= (7<<12); /* S5 *//* 设置EINTMASK使能eint11,19 */EINTMASK &= ~((1<<11) | (1<<19));/*注册中断控制器*/register_irq(0, key_eint_irq);register_irq(2, key_eint_irq);register_irq(5, key_eint_irq);
}时钟初始化程序 <code>timer_init();</code>void timer_init(void)
{/* 设置TIMER0的时钟 *//* Timer clk = PCLK / {prescaler value+1} / {divider value} = 50000000/(99+1)/16= 31250*/TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */TCFG1 &= ~0xf;TCFG1 |= 3; /* MUX0 : 1/16 *//* 设置TIMER0的初值 */TCNTB0 = 15625; /* 0.5s中断一次 *//* 加载初值, 启动timer0 */TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 *//* 设置为自动加载并启动 */TCON &= ~(1<<1);TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload *//* 设置中断 */register_irq(10, timer_irq);
}
让后main.c函数一直循环执行
输出串口信息
while (1){putchar(g_Char);g_Char++;putchar(g_Char3);g_Char3++;delay(1000000);//printHex(TCNTO0);}
定时器减到0的时候就会产生中断,start.S
跳到 0x18
的地方执行
ldr pc, irq_addr /* vector 0x18 : irq */b halt /* vector 0x1c : fiq */
.align 4do_irq:/* 执行到这里之前:* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址* 2. SPSR_irq保存有被中断模式的CPSR* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式* 4. 跳到0x18的地方执行程序 *//* sp_irq未设置, 先设置它 */ldr sp, =0x33d00000/* 保存现场 *//* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 *//* lr-4是异常处理完后的返回地址, 也要保存 */sub lr, lr, #4stmdb sp!, {r0-r12, lr} /* 处理irq异常 */bl handle_irq_c/* 恢复现场 */ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
ldr pc, irq_addr /* vector 0x18 : irq */
b halt /* vector 0x1c : fiq */
.align 4
do_irq:
/* 执行到这里之前:
* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_irq保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
* 4. 跳到0x18的地方执行程序
*/
/* sp_irq未设置, 先设置它 */
ldr sp, =0x33d00000/* 保存现场 */
/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr-4是异常处理完后的返回地址, 也要保存 */
sub lr, lr, #4
stmdb sp!, {r0-r12, lr} /* 处理irq异常 */
bl handle_irq_c/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
看看怎么处理irq
void handle_irq_c(void)
{/* 分辨中断源 */int bit = INTOFFSET;/* 调用对应的处理函数执行 */irq_array[bit](bit);/* 清中断 : 从源头开始清 */SRCPND = (1<<bit);INTPND = (1<<bit);
}
jz2440裸机开发与分析:S3c2440ARM异常与中断体系详解8---定时器中断程序示例相关推荐
- Mysql高手系列 - 第20篇:异常捕获及处理详解(实战经验)
Mysql高手系列 - 第20篇:异常捕获及处理详解(实战经验) 参考文章: (1)Mysql高手系列 - 第20篇:异常捕获及处理详解(实战经验) (2)https://www.cnblogs.co ...
- Android开发详解之App升级程序一点通
Android开发详解之App升级程序一点通 结束语 UpdateManager.java import java.io.File; import java.io.FileOutputStream; ...
- iOS开发——使用Charles进行https网络抓包详解
我在前面两篇博客中<网络抓包工具Charles的介绍与使用><iOS开发--使用Charles进行http网络抓包详解>对Charles的http抓包进行了详细的讲解.今天我们 ...
- Joomla远程代码执行漏洞分析小白版(小宇特详解)
Joomla远程代码执行漏洞分析小白版(小宇特详解) 今天看了一下2021陇原战役WP,在看web方向的时候,看到pop链,想了解一下,后来又看到了p师傅在15年的一篇文章,在这里记录一下.这里主要是 ...
- iOS 开发 Certificate和Code Signing--证书与签名--详解
iOS 开发 Certificate和Code Signing–证书与签名–详解 1.Certificate-证书 iOS 开发 证书–根证书/申请证书/开发(发布)证书–详解 2.Code Sign ...
- Python异常重试解决方案 Python中异常重试的解决方案详解
想了解Python中异常重试的解决方案详解的相关内容吗,标点符在本文为您仔细讲解Python异常重试解决方案的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:python,重试,python ...
- 《微信小程序开发》 页面导航最强详解 | 如何对小程序页面进行跳转?
<微信小程序开发> 页面导航最强详解 | 如何对小程序页面进行跳转? 文章目录 <微信小程序开发> 页面导航最强详解 | 如何对小程序页面进行跳转? 一.微信小程序导航 二.命 ...
- wxml报错原因_微信小程序开发教程(八)视图层——.wxml详解
框架的视图层由WXMKL(WeiXin Markup language)与WXSS(WeiXin Style Sheet)编写,由组件进行展示. 对于微信小程序而言,视图层就是所有.wxml文件与.w ...
- python脚本运行时网络异常_Python中异常重试的解决方案详解
前言 大家在做数据抓取的时候,经常遇到由于网络问题导致的程序保存,先前只是记录了错误内容,并对错误内容进行后期处理. 原先的流程: def crawl_page(url): pass def log_ ...
- S5PV210 Uboot开发与移植03:Uboot启动流程详解
目录 1. start.S解析 1.1 uboot入口分析 1.2 头文件包含 1.2.1 config.h 1.2.2 version.h 1.2.3 asm/proc/domain.h 1.2.4 ...
最新文章
- 全球大国人工智能实力大比拼与技术发展趋势
- 黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级)
- Android数据存储之SQLite的操作
- C语言学生成绩简单,C语言实现简单学生成绩管理系统.pdf
- 浅谈计算机硬件维护 论文,浅谈计算机硬件维护的论文(2)
- 功能强大的Windows PowerShell
- 如何制定有价值的目标
- 26 | 红黑树(下):掌握这些技巧,你也可以实现一个红黑树
- php中获取本月第二天,php第二天
- plotloss记录
- 默认帐户生成器帐户来源
- mui + php,GitHub - alphaphp/mui-kidApp: 基于 MUI 构建一个具有 90 +页面的APP应用
- Elasticsearch 参考指南(引导检查)
- 中国计算机学会推荐国际学术会议和期刊目录
- jq ui datepicker添加时分秒
- 解决刷GApps时的Error 70报错或64
- 基于微信小程序的鲜花销售系统毕业设计源码
- “企业级零代码黑客马拉松大赛”决赛名单公布
- 7-4 计算e的近似值 (10 分)
- 游戏攻略资料收集,制作技巧经验分享-游戏编辑2
热门文章
- JavaWeb学习方法
- 重磅发布:《AI产品经理的实操手册(2021版)——AI产品经理大本营的4年1000篇干货合辑》(PDF)...
- openssl生成key和pem文件
- 既能被2又能被5整C语言,2012年国研究生统一考试心理学专业试题与答案
- 【院校信息】2021北京航空航天大学计算机考研数据汇总
- 为什么有了二叉搜索树和二叉平衡树之后还需要红黑树?
- xposed框架定位修改怎么用_硬核!教你三种方法,实现微信自定义修改地区!
- 4g模块Linux拨号ppp脚本,在ARM-linux上实现4G模块PPP拨号上网【转】
- Python3网络爬虫——爬虫基本原理
- Gif动图体积过大如何缩小?仅需三步教你在线压缩gif