ARM体系结构及编程 实验二 外部中断实验

一、实验目的

      1. 深入理解GPIO外部中断处理机制;

      2. 掌握STM32外部中断工作原理和配置方式;

      3. 掌握中断固件库函数使用方法;

      4. 掌握外部中断应用程序及中断服务程序的编写方法。

二、实验内容

      使用库函数开发模版编写GPIO外部中断应用程序,能够通过按键K1控制LED灯的点亮和熄灭,通过按键K2控制蜂鸣器的鸣叫。

三、实验设备仪器及材料

      硬件:STM32开发板, PC 机;

      软件:MDK5集成开发环境,Windows 操作系统;

四、实验原理及接线

1. 中断相关概念

      中断就是当CPU执行程序时,由于发生了某种随机的事件(内部或外部),引起CPU暂时中断正在运行的程序,转去执行一段特殊的服务程序(中断服务子程序或中断处理程序),以处理该事件,该事件处理完后又返回被中断的程序继续执行,这一过程就被称为中断,引发中断的称为中断源。

2. NVIC介绍

      NVIC是嵌套向量中断控制器,控制着整个STM32芯片中断相关的功能。在配置中断时,一般使用ISER、ICER和IP这三个寄存器,ISER是中断使能寄存器,ICER是中断清除寄存器,IP是中断优先级寄存器。

      固件库头文件core_cm3.h中,提供了NVIC的一些函数,这些函数遵循CMSIS规则,只要是Cortex-M3的处理器都可以使用,具体如下:

NVIC库函数描述
void NVIC_EnableIRQ(IRQn_Type IRQn)使能中断
void NVIC_DisableRQ(IRQn_Type IRQn)失能中断
void NVIC_SetPendingIRQ(IRQn_Type IRQn)设置中断悬起位
void NVIC_ClearPendingIRQ(IRQn_Type IRQn)清楚中断悬起位
unit32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)获取悬起中断编号
void NVIC_SetPriority(IRQn_Type IRQn,unit32_t priority)设置中断优先级
unit32_t NVIC_GetPriority(IRQn_Type IRQn)获取中断优先级
void NVIC_SystemReset(void)系统复位
3. 中断优先级

      具有高抢占式优先级的中断可以在具有低抢占式欧县级的中断服务程序执行过程中被相应,即中断嵌套(高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行)

      在抢占式优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被相应的低子优先级中断执行结束后才能得到相应,即子优先级不支持中断嵌套。

4. EXIT介绍

    (1)STM32F4外部中断/时间控制器(EXIT)包含23个用于产生事件/中断请求的边沿检测器。EXTI可监测指定GPIO口的电平信号,当GPIO口的电平变化时,EXTI就立刻向NVIC发出中断申请,经过NVIC裁决后,让CPU执行中断程序

    (2)触发方式:上升沿(低变高),下降沿(高变低),双边沿(前两个都可以)

    (3)GPIO:支持所有GPIO口,但是相同的Pin不能触发中断

    (4)通道数 :16个Pin(外加PVD输出,RTC闹钟,USB唤醒,以太网唤醒)

    (5)触发响应方式:中断响应,事件响应

五、实验操作步骤

1. 编写led.c文件
#include "led.h"
void LED_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量,该语句的作用是将GPIO_InitTypeDef结构体命名为GPIO_InitStructure
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); //使能端口F时钟,
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//设置输出模式
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_10;//管脚设置为F9和F10
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//IO口速度为100M
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
	GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化结构体,初始化PF口
	GPIO_SetBits(GPIOF,GPIO_Pin_9|GPIO_Pin_10);//输出高电平
}
2. 编写key.c文件
#include "key.h"
#include "SysTick.h"
void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOA,ENABLE); //使能端口PORTE、PORTA时钟
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN; //设置GPIO工作模式为输入
	GPIO_InitStructure.GPIO_Pin=KEY_LEFT_Pin|KEY_DOWN_Pin|KEY_RIGHT_Pin;//设置管脚
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
	GPIO_Init(KEY_Port,&GPIO_InitStructure); //初始化结构体
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN; //设置GPIO工作模式为输入
	GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin;//设置管脚
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//下拉
	GPIO_Init(KEY_UP_Port,&GPIO_InitStructure); //初始化结构体
}

KEY_Scan函数用于按键的扫描检测,mode为0时表示不支持连续按键,mode为1时可支持连续按键,按键K_UP是高电频有效,而K_DOWN、K_LEFT、K_RIGHT都是低电频有效,所以当K_UP为1时表示按下K_UP,而其它按键为0表示按下,由此可以完成按键的扫描检测,具体代码如下:

u8 KEY_Scan(u8 mode)
{
	static u8 key=1;
	if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0)) //任意一个按键按下
	{
		delay_ms(10);  //减少抖动
		key=0;
		if(K_UP==1)
		{
			return KEY_UP; 
		}
		else if(K_DOWN==0)
		{
			return KEY_DOWN; 
		}
		else if(K_LEFT==0)
		{
			return KEY_LEFT; 
		}
		else
		{
			return KEY_RIGHT;
		}
	}
	else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1)    //无按键按下
	{
		key=1;
	}
	if(mode==1) //连续按键按下
	{
		key=1;
	}
	return 0;
}
3. 编写keep.c文件
#include "beep.h"
void BEEP_Init(void) 
{
	GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); //使能端口F时钟
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //输出模式
	GPIO_InitStructure.GPIO_Pin=BEEP_Pin;//管脚设置F8
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度为100M
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
	GPIO_Init(BEEP_Port,&GPIO_InitStructure); //初始化结构体
	
	GPIO_SetBits(BEEP_Port,BEEP_Pin); //关闭蜂鸣器
	
}
4. 编写exit.c文件
#include "exti.h"
#include "led.h"
#include "SysTick.h"
#include "beep.h"
#include "key.h"
void My_EXTI_Init(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;//定义结构体变量
	EXTI_InitTypeDef  EXTI_InitStructure;//定义外部中断的结构体变量
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);//PA0与EXTI0进行连接
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource2);//PE2管脚
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource3);//PE3管脚
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource4);//PE4管脚
	
	
	//EXTI0 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//设置通道:EXTIO中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器	
	
	//EXTI2 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;//EXTI2中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;		//子优先级(相应优先级)
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
	
	
	//EXTI3 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//EXTI3中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;		//子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
	
	//EXTI4 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;//EXTI4中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;		//子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
	
	
	EXTI_InitStructure.EXTI_Line=EXTI_Line0;//中断线0
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//设置外部中断模式
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发方式
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能
	EXTI_Init(&EXTI_InitStructure);//初始化外部中断结构体
	
	EXTI_InitStructure.EXTI_Line=EXTI_Line2|EXTI_Line3|EXTI_Line4; //中断线2 3 4
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//设置外部中断模式
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿触发模式
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能
	EXTI_Init(&EXTI_InitStructure);
	
}
//外部中断0函数
void EXTI0_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line0)==1)
	{
		delay_ms(10);
		if(K_UP==1)
		{
			led2=0;
		}	
	}
	EXTI_ClearITPendingBit(EXTI_Line0);
}
//外部中断3函数
void EXTI3_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line3)==1)
	{
		delay_ms(10);
		if(K_DOWN==0)
		{	
			beep = !beep;
		}
		
	}
	EXTI_ClearITPendingBit(EXTI_Line3);
}
//外部中断2函数
void EXTI2_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line2)==1)
	{
		delay_ms(10);
		if(K_RIGHT==0)
		{
			led2 = !led2;
		}
		
	}
	EXTI_ClearITPendingBit(EXTI_Line2);
}
//外部中断4函数
void EXTI4_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line4)==1)
	{
		delay_ms(10);
		if(K_LEFT==0)
		{
			beep=1;
		}
		
	}
	EXTI_ClearITPendingBit(EXTI_Line4);
}
5. 编写main.c文件
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "exti.h"

int main()
{
	u8 key,i=0;
	SysTick_Init(168);
	LED_Init();
	BEEP_Init();
	KEY_Init();
	while(1)
	{
		key=KEY_Scan(0);   //扫描按键
		switch(key)
		{
			//GPIO_ResetBits(GPIOF,GPIO_Pin_9);//复位F9,点亮D1
			case KEY_UP: 
				while(1){
				led1=!led1;
			  delay_ms(500);
			  led2=!led2;
			  delay_ms(500);}
			break;      //按下K_UP按键   点亮D1指示灯
			case KEY_DOWN: 
				led2=1;
			break;    //按下K_DOWN按键,点亮D2指示灯
			case KEY_LEFT: beep=1;break;    //按下K_LEFT按键,关闭蜂鸣器
			case KEY_RIGHT: beep=0;break;   //按下K_RIGHT按键,打开蜂鸣器
		}
		i++;
		if(i%20==0)
		{
			led1=!led1;      //LED1状态取反
		}
		delay_ms(10);
	}
}

六、实验结果

      未按下任何键(K_DOWN和K_RIGHT):

      按下K1(K_RIGHT)键:

      可以看到led2常亮,再按一次K1键,led2熄灭,led1灯设置为一直闪烁状态,当按下K2(K_DOWN)键,可以听到蜂鸣器声响。

总结:

      使用中断一般需要先使能外设某个中断,然后设置中断优先级,初始化NVIC_InitTypeDef结构体,并且设置抢占优先级和响应优先级,使能中断请求,然后再编写中断函数,当有中断发生的时候,会进入到中断函数中实现函数功能。而外部中断EXTI配置的一般步骤是先对IO口进行使能,将IO口设置为输入模式,然后开启时钟,设置IO口语中断线的映射关系,然后配置中断分组(NVIC),使能中断,接着再初始化EXTI,选择对应的触发方式,最后编写中断服务函数。

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在 Linux ARM 平台上,要访问 PCI 总线设备,通常需要进行以下几个步骤: 首先,要先检查 Linux 版本以确保所使用的内核版本是否支持 ARM 平台上的 PCI 访问。如果是较新的版本,一般会已经自带了对 ARM 的 PCI 支持。 接下来,需要加载适当的内核模块来启用 PCI 的支持。在 ARM 平台上,常见的模块包括 pci, pcie, pci-mvebu, 等等。可以使用 `lsmod` 命令来查看当前已加载的模块,并使用 `modprobe` 命令加载需要的模块。 加载了适当的模块后,可以使用 `lspci` 命令来列出 PCI 设备的信息。该命令会显示出系统中已发现的所有 PCI 设备的详细信息,如设备 ID,厂商 ID,PCI 类型等。 要访问特定的 PCI 设备,需要知道它的总线号、设备号和功能号。可以使用 `lspci -t` 命令来查看系统中各个设备的拓扑结构,并找到需要访问的设备的 PCI 地址。 一旦确定了需要访问的设备的 PCI 地址,可以使用一些工具和库函数来进行访问。例如,可以使用 `pciutils` 软件包的工具 `lspci`, `setpci` 等来获取和设置 PCI 设备的配置寄存器的值。 此外,还可以使用一些编程接口来进行更灵活的访问。一种常见的接口是 `libpciaccess` 库,它提供了一组函数来访问 PCI 配置空间、I/O 空间和内存映射空间。 当程序需要访问 PCI 设备时,可以使用这些接口函数来打开设备、映射和操作设备的寄存器等。 总的来说,在 Linux ARM 平台上访问 PCI 设备需要检查内核支持、加载适当的模块、确定设备的 PCI 地址,使用工具和库函数或编程接口进行访问。 ### 回答2: 在Linux ARM平台上,要访问PCI(Peripheral Component Interconnect,外设组件互联)设备,需要进行一些特定的步骤。 首先,要理解在ARM架构上操作PCI的基本知识。PCI是一种标准化的总线架构,用于连接外部设备和主机系统。它允许主机系统与各种不同类型的设备进行通信和交互。在Linux系统中,访问PCI设备需要使用相关的驱动程序和库函数来实现。 其次,要了解系统上的PCI设备信息。可以使用命令`lspci`来列出系统中存在的PCI设备,包括设备ID、厂商ID、以及设备的地址和IRQ等信息。这些信息将有助于编写驱动程序并进行设备访问。 然后,需要编写相应的驱动程序。在Linux内核中,有一种叫做PCI驱动的模块,用于管理和控制PCI设备。驱动程序需要使用相关的函数和数据结构,如`pci_register_driver()`和`pci_dev`等,来与PCI设备进行通信。在编写驱动程序时,需要注意设备的地址映射、中断处理和数据传输等方面的实现。 最后,进行设备的访问和操作。根据设备的需求,可以通过读写设备的寄存器、发送命令和接收数据等方式来与设备进行交互。在ARM平台上,可能需要使用特定的函数或指令来进行设备的操作。 总之,在Linux ARM平台上访问PCI设备需要了解相关的基本知识,并编写相应的驱动程序来实现与设备的交互。这些驱动程序需要使用特定的函数和数据结构,并考虑设备映射、中断处理和数据传输等方面的实现。这样才能正确地访问和操作PCI设备。 ### 回答3: 在Linux ARM平台上,要访问PCI(Peripheral Component Interconnect)设备,我们需要遵循以下步骤: 1. 确认系统内核是否已配置PCI驱动程序,可通过运行命令`lspci`检查是否能识别到PCI设备。 2. 根据PCI设备的Vendor ID和Device ID,编写相应的驱动程序。可以使用内核提供的通用PCI驱动程序,或是根据具体的PCI设备编写特定的驱动程序。 3. 在设备树(device tree)中添加PCI设备的描述信息,描述该设备的资源分配和连接关系。 4. 在驱动程序中实现适当的操作,如初始化PCI设备、进行内存或IO映射等。 5. 在用户空间中,通过访问`/sys/bus/pci/devices`目录下的相应文件,获取PCI设备的资源信息,并进行访问控制。 6. 可以使用`lspci`命令来验证是否成功访问了PCI设备。 需要注意的是,在ARM平台上,由于其体系结构与x86平台有所不同,因此在编写驱动程序时可能需要考虑一些特殊因素。此外,还需要注意PCI设备所需的特定驱动程序是否可用,并确保内核版本与所需驱动程序的兼容性。 总之,在Linux ARM平台上访问PCI设备,需要编写相应的驱动程序,并进行适当的配置和操作,以实现对PCI设备的访问和控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布零酱

阿里嘎多!谢谢打赏嗷喵~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值