keil下基于arm9的TX2440开发板的外部中断编写
S3c2440可以说是学习arm9最常用的芯片了,这方面有很多开发板,例如TQ2440,MINI2440,JZ2440。
当时学习了业界很火的郭天祥老师的51单片机教程,书写的很长很好。因此在学习arm上也想学习郭老师的教程,在他的天祥淘宝店铺上,花了很贵的价钱买了TX2440开发板。满心欢喜的进行学习。
然鹅,我大失所望,和五一单片机完全不一样,这个arm9的开发板看不到一点郭老师的影子,完全是另一个人在讲,而且讲的也是十分的粗糙。
基本上的讲法就是:这部分内容很多,不可能一个个说,好了给你们推荐一些资料哈,下去自己看吧。。。。(省略10000字)(说话还漏口水)。
这里还是先劝劝大伙学arm不要入郭老师的坑了,人家重点根本不放在arm教学上。所以没有详细的指导给你。推荐去看看韦东山老师的书和视频吧,讲的很细。
好了不吐槽了。
首先说一下我的开发板是TX2440的板子。这次目的是用按键实现外部中断,从而控制led灯的亮灭。还有我的程序是完全裸机下的程序。
首先我们编写最开始的启动代码,这部分需要汇编完成(我也不喜欢汇编)。
我们随便建立个.s文件,输入如下代码
IMPORT main
IMPORT disable_watch_dog
IMPORT init_irq
IMPORT EINT_Handle
AREA RESET,code,readonly
arm
entry
最开始是一些声明,import是表示声明一个本文件之外而在本文件被引用的标号。申明的这些标号会在后边用到,arm表示现在是arm模式,entry表示这里是所有程序的最开始的起点。
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
上面是设置中断(异常)向量表,在我的芯片受到外部中断或者内部中断时,会通过硬件将pc(程序指针)指向最开始的一些特定地址,比如我们的复位中断,在开发板最开始的上电的时候就会进入复位中断。这时pc自动指向的0x0的地址上,我们就应该在0x0的地址出写入一个处理复位中断的程序。
上面的程序是最开始的启始程序,第一句是b reset,这就是处理中断的语句,是一个跳转指令,类似于c语言的jump,(后面我们会把生成的整个代码烧到nor flash中起始地址为0x0的地方,所以b reset这条语句对应的地址就是0x0,也就是处理复位中断的位置)
上面共设定了有7,8条中断向量表,每个表都是一条指令“ b xxx”意思是
跳转的特定的中断处理函数中。
但这里我们只使用IRQ中断,因为键盘的中断就是IRQ中断。所以在在上面程序中对于其他中断我们都是原地循环跳转(不处理),对于IRQ和复位reset中断我们设置的单独的处理函数
注意:在正规的BootLoader代码中需要对所有的中断向量表都设置处理函数,下面是复位中断的处理函数:
Reset
ldr sp, =4096 ;设置栈指针,以下都是C函数,调用前需要设好栈
bl disable_watch_dog ; 关闭WATCHDOG,否则CPU会不断重启
msr cpsr_c, #0xd2 ; 进入中断模式
ldr sp, =3072 ; 设置中断模式栈指针
msr cpsr_c, #0xd3 ; 进入管理模式
ldr sp, =4096 ; 设置管理模式栈指针,
; 其实复位之后,CPU就处于管理模式,
; 前面的“ldr sp, =4096”完成同样的功能,此句可省略
; 初始化LED的GPIO管脚
bl init_irq ; 调用中断初始化函数,在init.c中
msr cpsr_c, #0x53 ;@ 设置I-bit=0,开IRQ中断
ldr lr, =halt_loop ;@ 设置返回地址
ldr pc, =main ;@ 调用main函数
halt_loop
b halt_loop
disable_watch_dog,init_irq这些是在其他的.c文件中定义的c语言函数。.c文件后面会给出来,这里是用bl跳转过去执行c函数,执行完成后,会自动返回原来的汇编程序的地址的继续执行程序
刚开始关看门狗中断,是所有程序必须执行的一个步骤,不管多简单的程序都需要先关看门狗。
c语言的执行必须要使用栈,所以我们给sp装入一个合适的值。这里栈的大小和位置自己确定,只要合理即可,但是注意使用nandflash的同学sp的值不能超过4096,这个后面再说。
栈是很重要的,在子程序的调用,使用C语言的地方都要使用栈,在正规的BootLoader引导代码中刚开始会设置中断向量和关口狗,随后会为各种中断模式设定合理的栈,但这里我们只用到了正常模式和IRQ中断模式,所以只设定了这两个栈的指针:
msr cpsr_c, #0xd2 ; 进入中断模式
ldr sp, =3072 ; 设置中断模式栈指针
msr cpsr_c, #0xd3 ; 进入管理模式
ldr sp, =4096 ; 设置管理模式栈指针
下面是中断处理的函数:在按键时会产生IRQ中断,系统pc的指针会由硬件自动指向0x18的地址处,观察前面的中断向量表可以看出0x18处有指令:
b HandleIRQ
这样就会自动跳转到下面的handleirq处理函数了,这个函数主要写的是将寄存器压栈,保护现场,随后跳转到我们用C语言写的的中断处理函数EINT_Handle中去,EINT_Handle中会判断是哪个按键被按下,随后点亮对应的led。执行完中断后会出栈,恢复现场,然后进入c语言写的main函数内无限循环,等待按键中断的再次到来。
HandleIRQ
sub lr, lr, #4 ;@ 计算返回地址
stmdb sp!, { r0-r12,lr } ;@ 保存使用到的寄存器
;@ 注意,此时的sp是中断模式的sp
; @ 初始值是上面设置的3072
ldr lr, =int_return ;@ 设置调用ISR即EINT_Handle函数后的返回地址
ldr pc, =EINT_Handle ; @ 调用中断服务函数,在interrupt.c中
int_return
ldmia sp!, { r0-r12,pc }^ ; @ 中断返回, ^表示将spsr的值复制到cpsr
end
上面是一套汇编代码,按顺序复制粘贴就可以了,下面是C语言代码
第一个是初始化GPIO口和打开中断的代码,具体就是配置一下寄存器
寄存器的使用可以看看s3c2440手册的 I/Oports和中断控制 章节,TX2440开发板使用GPF的8 个端口,前4个连接led,后四个连接按键,所以我们需要设置控制GPF的一系列寄存器
/*
* init.c: 进行一些初始化
*/
#include "s3c24xx.h"
/*
*/
#define GPF0_out (1<<(0*2))
#define GPF1_out (1<<(1*2))
#define GPF2_out (1<<(2*2))
#define GPF3_out (1<<(3*2))
#define GPF0_msk (3<<(0*2))///00 11
#define GPF1_msk (3<<(1*2))///00 11
#define GPF2_msk (3<<(2*2))///1100
#define GPF3_msk (3<<(3*2))
#define GPF4_msk (3<<(4*2))
#define GPF5_msk (3<<(5*2))
#define GPF6_msk (3<<(6*2))
#define GPF7_msk (3<<(7*2))
/*
*/
#define GPF4_eint (0x2<<(4*2))
#define GPF5_eint (0x2<<(5*2))
#define GPF6_eint (0x2<<(6*2))
#define GPF7_eint (0x2<<(7*2))
/*
* 关闭WATCHDOG,否则CPU会不断重启
*/
void disable_watch_dog(void)
{
WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可
}
/*
* 初始化GPIO引脚为外部中断
* GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
*/
void init_irq( )
{
// S2,S3对应的2根引脚设为中断引脚 EINT0,ENT2
// GPFCON &= ~(GPF4_msk | GPF5_msk | GPF6_msk | GPF7_msk); //把相应的位先清0
// GPFCON |= GPF4_eint | GPF5_eint | GPF6_eint | GPF7_eint;
GPFCON = (GPFCON|0xFFFF)&0xFFFFaa55; //GPF4-7设置为EINT4-7,GPF0-3为输出
GPFUP &= 0xFF00; //打开上拉功能
EXTINT0 &= ~(7<<16 | 7<<20 | 7<<24 | 7<<28);
EXTINT0 |= (2<<16 | 2<<20 | 2<<24 | 2<<28) ; //设置外部中断4_7下降沿触发
EINTPEND |= (1<<4|1<<5|1<<6|1<<7); //clear eint 4
EINTMASK &= ~(1<<4|1<<5|1<<6|1<<7); //enable eint 4
// 对于EINT4 5 6 7,需要在EINTMASK寄存器中使能它
/*
* 设定优先级:这里简单起见我们不设置优先级
* ARB_SEL0 = 00b, ARB_MODE0 = 0: REQ1 > REQ3,即EINT0 > EINT2
* 仲裁器1、6无需设置
*/
// EINT4-7中断使能
INTMSK &= (~(1<<4)); //写0是使能中断
}
下面是中断处理函数
interrupt.c
#include "s3c24xx.h"
void EINT_Handle()
{
unsigned long oft = INTOFFSET;
unsigned long val;
if(oft==4) {
val= EINTPEND;
if(val & (1<<4)){ //判断是哪个按键按下
GPFDAT |= 0x0f; // 所有LED熄灭
GPFDAT &=~(1<<0);//点亮对应的led
}
if(val & (1<<5)) {
GPFDAT |= 0x0f; // 所有LED熄灭
GPFDAT &=~(1<<1);
}
if(val & (1<<6)) {
GPFDAT |= 0x0f; // 所有LED熄灭
GPFDAT &=~(1<<2);
}
if(val & (1<<7)) {
GPFDAT |= 0x0f; // 所有LED熄灭
GPFDAT &=~(1<<3);
}
}
下面是一个C语言的头文件,定义了一下寄存器的物理地址,
s3c24xx.h
/* WOTCH DOG register */
#define WTCON (*(volatile unsigned long *)0x53000000)
/* SDRAM regisers */
#define MEM_CTL_BASE 0x48000000
#define SDRAM_BASE 0x30000000
/* NAND Flash registers */
#define NFCONF (*(volatile unsigned int *)0x4e000000)
#define NFCMD (*(volatile unsigned char *)0x4e000004)
#define NFADDR (*(volatile unsigned char *)0x4e000008)
#define NFDATA (*(volatile unsigned char *)0x4e00000c)
#define NFSTAT (*(volatile unsigned char *)0x4e000010)
/*GPIO registers*/
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#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 GPHCON (*(volatile unsigned long *)0x56000070)
#define GPHDAT (*(volatile unsigned long *)0x56000074)
#define GPHUP (*(volatile unsigned long *)0x56000078)
/*UART registers*/
#define ULCON0 (*(volatile unsigned long *)0x50000000)
#define UCON0 (*(volatile unsigned long *)0x50000004)
#define UFCON0 (*(volatile unsigned long *)0x50000008)
#define UMCON0 (*(volatile unsigned long *)0x5000000c)
#define UTRSTAT0 (*(volatile unsigned long *)0x50000010)
#define UTXH0 (*(volatile unsigned char *)0x50000020)
#define URXH0 (*(volatile unsigned char *)0x50000024)
#define UBRDIV0 (*(volatile unsigned long *)0x50000028)
/*interrupt registes*/
#define SRCPND (*(volatile unsigned long *)0x4A000000)
#define INTMOD (*(volatile unsigned long *)0x4A000004)
#define INTMSK (*(volatile unsigned long *)0x4A000008)
#define PRIORITY (*(volatile unsigned long *)0x4A00000c)
#define INTPND (*(volatile unsigned long *)0x4A000010)
#define INTOFFSET (*(volatile unsigned long *)0x4A000014)
#define SUBSRCPND (*(volatile unsigned long *)0x4A000018)
#define INTSUBMSK (*(volatile unsigned long *)0x4A00001c)
/*external interrupt registers*/
#define EINTMASK (*(volatile unsigned long *)0x560000a4)
#define EINTPEND (*(volatile unsigned long *)0x560000a8)
#define EXTINT0 (*(volatile unsigned long *)0x56000088)
下面是main函数,很简单,只需无限循环等待中断到来就可以了
main.c :
int main()
{
while(1);
return 0;
}
以上就是所以的程序了,我们建立.c和.h .s文件然后把相应的代码复制进去。建立keil工程步骤如下
选择合适的芯片s3c2440,不要加入启动代码。
把这些文件都加进去
设置空间,这里只设置这一个,这个为后面的链接生成规则文件,在这个程序其实没多大的用,只不过是告诉编译器你的偏移地址是0x00,这样在汇编中的引用绝对地址的地方就会以0x0为起点,有了参考依据。
下面这个命令很重要,使用keil中的fro0melf文件生成.bin 文件,可能你需要小小的调整一下地址和名称才能让这个命令才能在你的工程中使用。
keil会编译你的文件在你的project旁边生成axf文件,但是在arm里的可执行文件必须是.bin文件,所以用fromelf命令来通过axf文件生成.bin文件。
因为我们不调试和烧写flash,所以其他的debug,之类的选项暂时不用调了,就设置这么些,我们按编译就可以生成.bin文件。
下面就是使用jlink或者jtag将bin文件从0地址开始烧写到nand或者norflash里面。重启开发板就可以运行啦。
生成可在arm中执行的文件,可以使用keil,也可以使用ads,ads现在太老了,还总是出故障,所以建议在keil中编写。
也可以在Linux系统下编写,使用特定的arm-Linux-gcc交叉编译工具也可以生成.bin文件,这也是现在最主流的方法。
希望对你有帮助,谢谢