对于“裸奔”的系统而言,使用中断是无法避免的,Stellaris外设驱动库的中断操作函数都封装在相应外设的.c/h文件和interrupt.c/h文件中,使用起来非常方便。我比较支持使用固件库,固件库出错的可能性绝对要比自己去直接操作寄存器要小的多,而且使用固件库可以将驱动层和应用层相分离,符合软件分层开发的思想。将固件库的维护交给厂商去做,我们坐享其成,何乐而不为呢。
利用《Stellaris 外设驱动库》编写一个中断程序的基本方法如下:
1. 使能相关片内外设,并进行基本的配置
对于中断源所涉及的片内外设必须要首先使能,使能的方法是调用头文件<sysctl.h>中的函数SysCtlPeripheralEnable( )。使能该片内外设以后,还要进行必要的基本配置。
2. 设置具体中断的类型或触发方式
不同片内外设具体中断的类型或触发方式也各不相同。在使能中断之前,必须对其进行正确的设置。以GPIO 为例,分为边沿触发、电平触发两大类,共 5 种,这要通过调用函数GPIOIntTypeSet( )来进行设置。
3. 使能中断
对于 Stellaris 系列ARM,使能一个片内外设的具体中断,通常要采取分 3 步走的方法:
a.调用片内外设具体中断的使能函数
b.调用函数IntEnable( ),使能片内外设的总中断
c.调用函数IntMasterEnable( ),使能处理器总中断
4. 编写中断服务函数
中断服务函数从形式上跟普通函数类似,但在命名及具体的处理上有所不同。中断服务函数的名称可以由程序员自行指定,但是为了提高程序的可移植性,我们还是建议采用标准的中断服务函数名称。
a.中断状态查询
一个具体的片内外设可能存在多个子中断源,但是都共用同一个中断向量。例如GPIOA 有8个管脚,每个管脚都可以产生中断,但是都共用同一个中断向量号16,任一管脚发生中断时都会进入同一个中断服务函数。为了能够准确区分每一个子中断源,就需要利用中断状态查询函数,例如GPIO 的中断状态查询函数是 GPIOPinIntStatus() 。
b.中断清除
对于 Stellaris 系列ARM 的所有片内外设,在进入其中断服务函数后,中断状态并不能自动清除,而必须采用软件清除。如果中断未被及时清除,则在退出中断服务函数时会立即再次触发中断而造成混乱。清除中断的方法是调用相应片内外设的中断清除函数。例如,GPIO 端口的中断清除函数是GPIOPinIntClear( ) 。
5. 注册中断服务函数
中断服务函数虽然已经编写完成,但是当中断事件产生时程序还无法找到它,因为还缺少最后一个步骤:注册中断服务函数。注册方法有两种,一是直接利用中断注册函数,操作上跟C程序中的回掉函数一样。这种方法的好处是操作简单、可移植性好。
void IntRegister(unsigned long ulInterrupt, void (*pfnHandler)(void)) //注册一个中断出现时被调用的函数;
void IntUnregister(unsigned long ulInterrupt) //注销一个中断出现时被调用的函数;
论坛上还有人用另一种方法,通过修改启动文件来注册中断服务函数。以Port_F中断为例,在Keil 开发环境下的启动文件“Startup.s”里找到“Vectors”表格,在其前面插入声明EXTERN GPIO_Port_F_ISR, 再根据“Vectors”表格的注释内容找到外设GPIO_Port_F的位置,把相应的“IntDefaultHandler”替换为“GPIO_Port_F”。 个人认为这种方法比较繁琐,移植性不好。
在上述几个步骤完成后,就可以等待中断事件的到来了。当中断事件产生时,程序就会自动跳转到对应的中断服务函数去处理。
举个简单的例子,用lm3s8962评估板上的SELECT按键产生中断,控制LED的状态,每次按下按键会改变LED的点亮/熄灭状态。其它外设的中断照猫画虎即可。
//main
int main(void)
{
SysCtlClockSet(SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_8MHZ | SYSCTL_SYSDIV_4 ); //50MHz
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); //这里是给F口时钟
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, LED_PIN); //设F0为输出
//设F1为输入
GPIODirModeSet(GPIO_PORTF_BASE, KEY_SELECT, GPIO_DIR_MODE_IN);
//推挽弱上拉,2mA
GPIOPadConfigSet(GPIO_PORTF_BASE, KEY_SELECT, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
GPIOPortIntRegister(GPIO_PORTF_BASE,GPIO_Port_F_ISR); //注册中断函数
GPIOIntTypeSet(GPIO_PORTF_BASE, KEY_SELECT, GPIO_LOW_LEVEL); //低电平触发中断
GPIOPinIntEnable(GPIO_PORTF_BASE, KEY_SELECT); //使能KEY中断
IntEnable(INT_GPIOF); //使能PF中断
IntMasterEnable(); //使能处理器中断
//等待中断发生
while(1)
{
}
}
//PortE中断服务
void GPIO_Port_F_ISR(void)
{
unsigned char ucVal;
unsigned long ulStatus;
ulStatus = GPIOPinIntStatus(GPIO_PORTF_BASE, true); //读取中断状态
GPIOPinIntClear(GPIO_PORTF_BASE, ulStatus); //清除中断状态
if (ulStatus & KEY_SELECT) //如果KEY的中断状态有效
{
ucVal = GPIOPinRead(GPIO_PORTF_BASE, LED_PIN);
GPIOPinWrite(GPIO_PORTF_BASE, LED_PIN, ~ucVal); //翻转LED
Delay_nMS(5); //延时约5ms,消除按键抖动
while (GPIOPinRead(GPIO_PORTF_BASE, KEY_SELECT) == 0x00); //等待KEY抬起
Delay_nMS(5); //延时约5ms,消除松键抖动
}
}