从51到ARM裸机开发实验(009)LPC2138 中断实验

一、场景设计

中断的概念在《从51到ARM裸机开发实验(007) AT89C51 中断实验》中已经介绍过,LPC2138的Keil工程创建在《从51到ARM裸机开发实验(005)LPC2138 GPIO实验》中已经介绍过。本次使用LPC2138来实现一个这样的场景:四个LED依次亮灭,时间间隔最小0.1秒,最大1秒,要求精确延时。使用两个按键分别控制间隔时间的增减,每按一次增或减0.1秒。精确延时用定时器中断实现,按键响应使用外部中断实现。(Protues仿真的时间和现实时间差距较大,可以注意Protues左下方的仿真时间或者接示波器观察曲线变化时间间隔)

二、LPC2138时钟系统

从芯片手册可以看到,PCLK时钟**(记住这个时钟,它是定时器发挥作用的前置条件)**是由CCLK时钟经过分频而来,分频多少由寄存器VPBDIV决定。PCLK可以等CCLK或1/2CCLK或1/4CCLK。那么CCLK又是怎么来的呢?它是晶振或外部时钟源经PLL锁相环配置而来。

关于寄存器更详细的解释见文末附件中的芯片数据手册。
本实验中的采用的晶振频率为10MHz,将系统配置为 Fosc = 10MHz , CCLK= 60MHz 。根据芯片手册的描述计算如下:

M=cclk/Fosc = 60MHz/10MHz = 6 。因此, M-1 = 5 写入 PLLCFG4:0 。

P 值可由 P=Fcco/(cclk*2) 得出, Fcco 必须在 156MHz~320MHz 内。假设 Fcco 取最低频率 156MHz ,则P=156MHz/(2*60MHz) = 1.3 。 Fcco 取最高频率可得出 P=2.67 。因此,同时满足 Fcco 最低和最高频率要求的 P 值只能为 2 。所以, PLLCFG=6:5=1 。

接下来配置VPBDIV,本实验中将PCLK配置为CCLK的1/4,即15MHz

三、LPC2138中断系统

本实验中使用EINT0和EINT1两个中断。
EINT0使用P0.16 配置PINSEL1寄存器1:0位为01
EINT0使用P0.14 配置PINSEL0寄存器29:28位为10

通过以下两个寄存器配置中断的触发方式:

重点内容:LPC2138不支持中断嵌套。LPC2138为ARM7TDMI内核。是一种ARM处理器架构,具体来说是ARMv4T微架构。在这种处理器中,如果当前处于一个高优先级中断处理程序中,而此时又发生了一个同级别或更低优先级的中断,那么处理器将不会立即进入这个新的中断处理程序,即不支持中断嵌套。这意味着如果一个中断服务程序(ISR)正在执行,并且在该ISR完成之前又发生了一个中断,这第二个中断将被忽略,直至当前的ISR执行完毕。

问题解决方法:① 优化中断处理程序:确保每个中断处理程序尽可能短小精悍,以减少中断处理时间,从而减少对中断嵌套的需求。② 使用轮询方式:如果确实需要在一个中断处理程序执行期间处理另一个中断,可以在ISR中设置一个标志,然后在ISR返回前将控制权交还给处理器,然后在主循环中或者一个低优先级中断中处理这个标志。③ 优先级重新配置:在系统设计中,可以为某些中断设置高的优先级,以确保在处理低优先级中断时不会被高优先级中断打断。④ 使用专用的嵌套向量中断控制器(NVIC):如果硬件和ARM核支持中断嵌套,可以配置NVIC来允许中断嵌套。

在实际应用中,通常会根据实际需求和系统资源,选择最合适的解决方案。如果系统对中断响应有严格的实时要求,且对中断处理时间有严格控制,那么可能需要重新评估系统设计,避免不必要的中断嵌套,或采取措施减少每个中断的处理时间。

LPC2138的中断实现方式:

①、通过VICVectAddrX指定某中断(此时还不知道是哪个中断)发生时的执行函数。

②、通过VICVectCntlX控制寄存器,启用编号为X的中断,并将某VIC通道的中断(指定哪个VIC通道号就是哪个中断)和X中断进行关联。

③、通过VICIntEnable配置启用某中断(VICIntEnable中的位和X存在对应关系)。

四、仿真电路

注意仿真时间线和现实时间并不一致,比如现实中可能过去三五秒了,仿真时间才过去1秒。仿真时间线注意仿真软件左下角即可。比如本实验中最长延时LED 1秒切换一次,是指Protues的仿真时间线过去1秒才会切换,与现实时间无关。

五、程序设计

1、驱动程序

interrupt.h

cpp 复制代码
#ifndef _INTERRUPT_H_
#define _INTERRUPT_H_
#include "lpc2138.h"

void set_interrupt_callback(int interrupt_num, void* isr_callback);
void init_timer0_isr();
void init_external_isr();
#endif

lpc2138.h 里面主要是寄存器到内存地址的映射配置,也可以不使用此文件,自己根据芯片手册配置寄存器。

interrupt.c

cpp 复制代码
#include "interrupt.h"
#include "delay.h"

void (*callback0)(); // 声明一个指向同样参数、返回值的函数指针类型
void (*callback1)(); // 声明一个指向同样参数、返回值的函数指针类型
void (*callback2)(); // 声明一个指向同样参数、返回值的函数指针类型

//设置中断回调函数
void set_interrupt_callback(int interrupt_num, void* isr_callback){
	if(interrupt_num == 0){
		callback0 = isr_callback;
	}else if(interrupt_num == 1){
		callback1 = isr_callback;
	}else if(interrupt_num == 2){
		callback2 = isr_callback;
	}
}

//定时器0中断事件处理
void timer0_isr(void) __irq {
		//清除中断
    T0IR = (1<<0);
		//中断应答
    VICVectAddr = 0;
		callback2();
}

//cclk = 60MHz
void initPLL() {
    // 设置 PLL0CFG 寄存器,选择合适的 M 和 P 值
    PLLCFG |= ((1 << 5) |(1 << 2) | (1 << 0));
    // 启动 PLL
    PLLCON |= 0x01;
    // 等待 PLL 锁定
    while (!(PLLSTAT & (1 << 10)));
    // 选择 PLL 为系统时钟源
    PLLCON |= (1 << 1);
    PLLFEED = 0xAA;
    PLLFEED = 0x55;
    // 等待 PLL 切换完成
    while (!(PLLSTAT & (1 << 9)));
}

//pclk = 15MHz
void initVPBdivider() {
    // 设置 VPBDIV 寄存器,选择 VPB 时钟分频比
    VPBDIV &= ~(0x03);
}


//定时器中断初始化
void init_timer0_isr(){
		initPLL();
		initVPBdivider();
    // 定时器模式:每上升一次PCLK边
		T0CTCR = 0x00;
	  // 预刻度寄存器:15 MHz PCLK, 15000-1得到毫秒
    T0PR = 14999;
	  // 匹配寄存器:计时100毫秒即0.1秒
		T0MR0 = 100;
		// 每经过T0PR+1个PCLK周期,T0TC值增加1
		T0TC = 0;
	  // 在MR0上中断和复位(T0TC值 = T0MR0值时触发中断和复位)
    T0MCR = (1<<0) | (1<<1);
		// 定时器0 ISR地址
    VICVectAddr3 = (unsigned long)timer0_isr;
		// 启用定时器0中断,使用槽位4
		VICVectCntl3 = (1<<5) | 4;
    // 在VIC中使能定时器0中断
		VICIntEnable = (1<<4);
    // 启动计时器0
		T0TCR = 0x01;
}


// 定义外部中断处理函数0
void external_interrupt_handler0(void) __irq {
		delayms(10);
    EXTINT = 1 << 0; // 清除外部中断0的中断标志位
		VICVectAddr = 0;
		callback0();
}


// 定义外部中断处理函数1
void external_interrupt_handler1(void) __irq {
		delayms(10);
    EXTINT = 1 << 1; // 清除外部中断1的中断标志位
		VICVectAddr = 0;
		callback1();
}

//按键中断初始化
void init_external_isr(){
    // 配置外部中断引脚
		// 配置P0.14为ENIT1
		PINSEL0 |= (1 << 29);		//第29位配置为1
		PINSEL0 &= ~(0x01<<28); //第28位配置为0
		// 配置P0.16为ENIT0
		PINSEL1 |= (1 << 0);		//第0位配置为1
		PINSEL1 &= ~(0x01<<1);  //第1位配置为0
		IODIR0 &= ~(1<<16);
    
    // 配置外部中断触发方式
    EXTMODE |= (1 << 0) | (1 << 1); // 设置外部中断0和1为边沿触发模式
    EXTPOLAR |= (1 << 0) | (1 << 1); // 设置外部中断0和1为上升沿触发
    
    // 启用外部中断中断
    VICVectAddr0 = (unsigned)external_interrupt_handler0; // 设置中断处理函数0
	  VICVectCntl0 |= (1 << 5) | 0x0E; 											// 设置为外部中断0并启用,EINT0中断编号为14
    VICIntEnable |= (1 << 14); 														// 启用外部中断0
    
    VICVectAddr1 = (unsigned)external_interrupt_handler1; // 设置中断处理函数1
    VICVectCntl1 |= (1 << 5) | 0x0F; 											// 设置为外部中断1并启用,EINT1中断编号为15
    VICIntEnable |= (1 << 15); 														// 启用外部中断1
}

led.h

cpp 复制代码
#ifndef _LED_H_
#define _LED_H_
	#define PINSEL0 (*(volatile unsigned long *)0xE002C000)
	#define IO0PIN (*(volatile unsigned long *)0xE0028000)
	#define IO0DIR  (*(volatile unsigned long *)0xE0028008)
		
	void led_init();
	void led_on(unsigned char site);
	void led_off(unsigned char site);
	char get_led_status(unsigned char site);
	void led_operate(unsigned char site,unsigned char on_off);
#endif

led.c

cpp 复制代码
#include "led.h"

void led_init(){
	PINSEL0 = PINSEL0 & 0xffffff00;
	IO0DIR = IO0DIR | 0x0f;
}

void led_on(unsigned char site){
	led_init();
	switch(site){
			case 0: 
				IO0PIN &= ~(0x01);
				break;
			case 1:
				IO0PIN &= ~(0x01<<1);
				break;
			case 2: 
				IO0PIN &= ~(0x01<<2);
				break;
			case 3: 
				IO0PIN &= ~(0x01<<3);
				break;
			default:
				break;
	}
}

void led_off(unsigned char site){
	led_init();
	switch(site){
			case 0: 
				IO0PIN |= (0x01); 	 
				break;
			case 1:
				IO0PIN |= (0x01<<1); 
				break;
			case 2: 
				IO0PIN |= (0x01<<2); 
				break;
			case 3: 
				IO0PIN |= (0x01<<3); 
				break;
			default:
				break;
	}
}

char get_led_status(unsigned char site){
		switch(site){
			case 0: 
				return (IO0PIN >> 0) & (0x01);
			case 1:
				return (IO0PIN >> 1) & (0x01);
			case 2: 
				return (IO0PIN >> 2) & (0x01);
			case 3: 
				return (IO0PIN >> 3) & (0x01);
			default:
				return -1;
	}
}
//on_off 0:on 1:off
void led_operate(unsigned char site,unsigned char on_off){
	if(on_off == 0){
		led_on(site);
	}else if(on_off == 1){
		led_off(site);
	}
}

delay.h

cpp 复制代码
#ifndef _DELAY_H_
#define _DELAY_H_
	void delayms(unsigned int xms);
#endif

delay.c

cpp 复制代码
#include "delay.h"

void delayms(unsigned int xms){
	unsigned int i,j;
	for(i=xms;i>0;i--){
		for(j=2500;j>0;j--);
	}
}

2、应用程序

application.h

cpp 复制代码
#ifndef _APPLICATION_H_
#define _APPLICATION_H_
	void timer0_callback();
	void external0_callback();
	void external1_callback();
#endif

application.c

cpp 复制代码
#include "application.h"
#include "interrupt.h"

#define uchar unsigned char
#define uint unsigned int
#define MAX_CYCLE 10
#define MIN_CYCLE 1

uchar num = 0;
uchar time_cycle = 1;

int main(void){
	led_operate(0,0);
	led_operate(1,1);
	led_operate(2,1);
	led_operate(3,1);
	init_timer0_isr();
	init_external_isr();
	set_interrupt_callback(0,external0_callback);
	set_interrupt_callback(1,external1_callback);
	set_interrupt_callback(2,timer0_callback);
	while(1);
}

//外部中断0回调函数
void external0_callback(){
	if(time_cycle > MIN_CYCLE){
		time_cycle = time_cycle - 1;
	}
}

//外部中断1回调函数
void external1_callback(){
	if(time_cycle < MAX_CYCLE){
		time_cycle = time_cycle + 1;
	}
}

//定时器0中断回调函数
void timer0_callback(){
		num++;
		if(num>=time_cycle){
			num=0;
			if(get_led_status(0) == 0){
				led_operate(0,1);
				led_operate(1,0);
			}else if(get_led_status(1) == 0){
				led_operate(1,1);
				led_operate(2,0);
			}else if(get_led_status(2) == 0){
				led_operate(2,1);
				led_operate(3,0);
			}else if(get_led_status(3) == 0){
				led_operate(3,1);
				led_operate(0,0);
			}
		}
}

六、资料下载

源码&仿真电路&芯片手册下载地址:https://download.csdn.net/download/qq_54140018/89142182

相关推荐
代码游侠6 小时前
ARM开发——阶段问题综述(二)
运维·arm开发·笔记·单片机·嵌入式硬件·学习
DLGXY6 小时前
STM32——旋转编码器计次(七)
stm32·单片机·嵌入式硬件
羽获飞7 小时前
从零开始学嵌入式之STM32——3.使用寄存器点亮一盏LED灯
单片机·嵌入式硬件
浩子智控8 小时前
商业航天计算机抗辐射设计
单片机·嵌入式硬件
独处东汉11 小时前
freertos开发空气检测仪之输入子系统结构体设计
数据结构·人工智能·stm32·单片机·嵌入式硬件·算法
czy878747512 小时前
机智云 MCU OTA可以对MCU程序进行无线远程升级。
单片机·嵌入式硬件
A9better14 小时前
嵌入式开发学习日志52——二值与计数信号量
单片机·嵌入式硬件·学习
_chirs14 小时前
编译不依赖动态库的FFMPEG(麒麟国防 V10)
arm开发·ffmpeg
日更嵌入式的打工仔15 小时前
(实用向)中断服务程序(ISR)的优化方向
笔记·单片机
想放学的刺客16 小时前
单片机嵌入式试题(第25)嵌入式系统可靠性设计与外设驱动异常处理
stm32·单片机·嵌入式硬件·mcu·物联网