栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

ARM裸机开发:输入中断

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ARM裸机开发:输入中断

文章目录
  • ARM裸机开发:输入中断
    • 一、硬件平台:
    • 二、原理图分析
    • 三、程序编写
      • 3.1 移植相关文件
      • 3.2 编写启动文件
      • 3.3 中断处理程序
      • 3.4 开启输入中断
      • 3.5 按键中断编写
      • 3.6 编写Makefile脚本
      • 四、实验现象

ARM裸机开发:输入中断 一、硬件平台:

正点原子I.MX6U阿尔法开发板

二、原理图分析

输入中断是配置GPIO作为输入IO口,检测按键引脚电平,当目标电平来到时产生中断,进入中断服务函数处理程序,I.MX6U的按键引脚如下:

可以看到按键引脚接到 GPIO1_IO18 口,按键的原理就是默认接一个上拉电阻,按键按下接地,可以有效控制 IO 电平

三、程序编写

程序编写前先复制上一节按键输入的工程作为本小节的开始工程

3.1 移植相关文件

在 NXP 提供的 SDK 包内 core_ca7.h 有相关的定义文件,为了节省开发时间,我们将其移植到本地工程目录;注意该文件要做一些修改,删除一些不必要的内容,不然会保存,这里我直接复制正点原子修改后的文件到工程目录下:

该文件下面我们只需要注意 10 个API函数,函数如下:

函数描述
GIC_Init初始化 GIC
GIC_EnableIRQ使能指定的外设中断
GIC_DisableIRQ关闭指定的外设中断
GIC_AcknowledgeIRQ返回中断号
GIC_DeactivateIRQ无效化指定中断
GIC_GetRunningPriority获取当前正在运行的中断优先级
GIC_SetPriorityGrouping设置抢占优先级位数
GIC_GetPriorityGrouping获取抢占优先级位数
GIC_SetPriority设置指定中断的优先级
GIC_GetPriority获取指定中断的优先级

文件添加后使用如下头文件调用

#include "core_ca7.h"
3.2 编写启动文件

SDK 添加完成之后就是修改启动文件,定义系统中断服务函数,修改 IRQ 中断,判断中断类型,进入不同的中断服务函数,启动文件编写如下:

首先编写全局标号,进入 _start 函数,在里面创建中断向量表

.global _start  				


_start:
	ldr pc, =Reset_Handler			
	ldr pc, =Undefined_Handler	
	ldr pc, =SVC_Handler		
	ldr pc, =PrefAbort_Handler	
	ldr pc, =DataAbort_Handler	
	ldr	pc, =NotUsed_Handler	
	ldr pc, =IRQ_Handler		
	ldr pc, =FIQ_Handler		

编写对应的中断服务函数,这里除了 Reset_Handler 和 IRQ_Handler 我们需要关注一下,其他的都暂时先编写为死循环:

Undefined_Handler:
	ldr r0, =Undefined_Handler
	bx r0

SVC_Handler:
	ldr r0, =SVC_Handler
	bx r0

PrefAbort_Handler:
	ldr r0, =PrefAbort_Handler	
	bx r0

DataAbort_Handler:
	ldr r0, =DataAbort_Handler
	bx r0

NotUsed_Handler:
	ldr r0, =NotUsed_Handler
	bx r0

FIQ_Handler:
	ldr r0, =FIQ_Handler	
	bx r0									

这些中断服务函数是可以编写一些处理代码,方便用户判断错误的来源的,暂时先不研究

下面编写复位中断服务函数:

	
Reset_Handler:
	
	cpsid i						
	
    
	mrc     p15, 0, r0, c1, c0, 0    
	
    bic     r0,  r0, #(0x1 << 12)     
    
    bic     r0,  r0, #(0x1 <<  2)     
	
    bic     r0,  r0, #0x2             
	
    bic     r0,  r0, #(0x1 << 11)     
	
    bic     r0,  r0, #0x1             
	
    mcr     p15, 0, r0, c1, c0, 0     
#if 0
	
	ldr r0, =0x87800000
	dsb
	isb
	mcr p15, 0, r0, c12, c0, 0
	dsb
	isb
#endif
	
	
	mrs r0, cpsr
	bic r0, r0, #0x1f 	
	orr r0, r0, #0x12 	
	msr cpsr, r0		
	ldr sp, =0x80600000	
	
	mrs r0, cpsr
	bic r0, r0, #0x1f 	
	orr r0, r0, #0x1f 	
	msr cpsr, r0		
	ldr sp, =0x80400000	
	
	mrs r0, cpsr
	bic r0, r0, #0x1f 	
	orr r0, r0, #0x13 	
	msr cpsr, r0		
	ldr sp, =0x80200000	
	cpsie i				
#if 0
	
	mrs r0, cpsr		
	bic r0, r0, #0x80	
	msr cpsr, r0		
#endif
	b main				

IRQ 中断服务函数,进入中断服务函数后,先进行现场保护,然后获取 GIC 的基地址,偏移后操作其寄存器,获取当前中断号,保存到寄存器 r0 和 r1,接着调用一个c语言中断处理函数,将参数从 r0-r3 四个寄存器传入函数

汇编调用 C 函数的时候建议形参不要超过 4 个,形参可以由 r0~r3 这四个寄存器来传递,如果形参大于 4 个,那么大于 4 个的部分要使用堆栈进行传递。

所以 r0 寄存器写入中断号就可以了传入到函数 system_irqhandler;接着该函数进行对应中断的调用和处理,处理完成后向 GICC_EOIR 寄存器写入其中断号表示中断处理完成;

IRQ_Handler:

	# 现场保护
	push {lr}					
	push {r0-r3, r12}			
	mrs r0, spsr				
	push {r0}					
	
	
	mrc p15, 4, r1, c15, c0, 0 						
	add r1, r1, #0x2000			
	ldr r0, [r1, #0XC]			
								
								
								
	push {r0, r1}				
	cps #0x13					
	push {lr}					
	ldr r2, =system_irqhandler	
	blx r2						
	pop {lr}					
	
	cps #0x12					
	pop {r0, r1}		
    # 向 GICC_EOIR 寄存器写入刚刚处理完成的中断号,
    # 当一个中断处理完成以后必须向 GICC_EOIR 寄存器
    # 写入其中断号表示中断处理完成
	str r0, [r1, #0x10]			
	pop {r0}						
	msr spsr_cxsf, r0			
	pop {r0-r3, r12}			
	pop {lr}					
	subs pc, lr, #4				

之后就是进行现场恢复,返回到中断位置!注意,此处恢复现场传递的是 lr - 4 的寄存器值,而不是pc,因为 ARM 的指令是三级流水线:取指、译指、执 行,pc 指向的是正在取值的地址,比如下面一段代码

0x2000 MOV R1, R0 ;执行
0x2004 MOV R2, R3 ;译指
0x2008 MOV R4, R5 ;取值 PC

当前正在执行 0x2000 地址处的指令 “MOV R1, R0” ,但 PC 里面已经保存了 0x2008 地址处的指令“MOV R4, R5”。若发生中断,中断发生的时候保存在 lr 中的是 pc 的值,即地址 0x2008。当中断处理完成如果直接跳转到 lr 里面保存的地址处(0x2008) 开始运行,那么就有一个指令没有执行,所以就需要将 lr-4 赋值给 pc,即 pc=0x2004,从第二级正在译指的指令 “MOV R2, R3” 开始执行

3.3 中断处理程序

我们在中断服务函数 IRQ_Handler 中调用了 C 函数 system_irqhandler 来处理具体的中断,该函数的具体细节需要我们自己实现,所以要编写中断处理程序来实现,同时因为中断数量较多,所以我们引入一些其他的数据结构单元辅助管理中断服务函数,编写如下:

新建一个新的模块文件

头文件插入如下代码

#ifndef __BSP_INT_H
#define __BSP_INT_H
#include "imx6ul.h"

typedef void (* system_irq_handler_t) (unsigned int giccIar,void *param);

typedef struct _sys_irq_handle
{
    
    system_irq_handler_t irqHandler;
    void *userParam;
} sys_irq_handle_t;

void int_init(void);
void system_irqtable_init(void);
void system_register_irqhandler(IRQn_Type irq,
                                system_irq_handler_t handler,
                                void *userParam);

void system_irqhandler(unsigned int giccIar);
void default_irqhandler(unsigned int giccIar,void *userParam);

#endif

代码解释:

typedef void (* system_irq_handler_t) (unsigned int giccIar,void *param);

创建一个函数指针,用 typedef 定义修饰别名为 system_irq_handler_t

typedef struct _sys_irq_handle
{
    
    system_irq_handler_t irqHandler;
    void *userParam;
} sys_irq_handle_t;

创建一个结构体,其有两个参数,一个是函数指针的入口指针,另外一个则是一个用户参数,创建这个结构体用于保存中断的信息,保存其中断处理函数入口因为有160个中断源,所以我们在.c文件中可以定义一个结构体数组用于存储所有中断的信息

其他的就是一些函数声明了:

// 中断系统(GIC)初始化
void int_init(void);
// 中断信息结构体数组初始化
void system_irqtable_init(void);
// 注册中断,修改目标中断的结构体的信息
//要使用某个外设中断,那就必须调用此函数来给这个中断注册一个中断处理函数
void system_register_irqhandler(IRQn_Type irq,
                                system_irq_handler_t handler,
                                void *userParam);
// _start 文件中调用的的中断号处理函数
void system_irqhandler(unsigned int giccIar);
// 默认中断处理函数
void default_irqhandler(unsigned int giccIar,void *userParam);

.c 模块文件代码如下,具体功能注释写在代码中:

#include "bsp_int.h"


static unsigned int irqNesting;


static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];


void int_init(void)
{
	GIC_Init(); 						
	system_irqtable_init();				
	__set_VBAR((uint32_t)0x87800000); 	
}


void system_irqtable_init(void)
{
	unsigned int i = 0;
	irqNesting = 0;
	
	
	for(i = 0; i < NUMBER_OF_INT_VECTORS; i++)
	{
        //给每个中断的数组改变传入参数和数值
		system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);
	}
}


void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) 
{
	irqTable[irq].irqHandler = handler;
  	irqTable[irq].userParam = userParam;
}


void system_irqhandler(unsigned int giccIar) 
{

   uint32_t intNum = giccIar & 0x3FFUL;
   
   
   if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
   {
	 	return;
   }
 
   irqNesting++;	

   
   irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
   
   irqNesting--;	

}


void default_irqhandler(unsigned int giccIar, void *userParam) 
{
	while(1) ;
}
3.4 开启输入中断

这里 GPIO 配置代码直接使用正点原子的驱动方案,有关的注释我写在代码内

bsp_gpio.h

#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#define _BSP_KEY_H
#include "imx6ul.h"



typedef enum _gpio_pin_direction
{
    kGPIO_DigitalInput = 0U,  		
    kGPIO_DigitalOutput = 1U, 		
} gpio_pin_direction_t;


typedef enum _gpio_interrupt_mode
{
    kGPIO_NoIntmode = 0U, 				
    kGPIO_IntLowLevel = 1U, 			
    kGPIO_IntHighLevel = 2U, 			
    kGPIO_IntRisingEdge = 3U, 			
    kGPIO_IntFallingEdge = 4U, 			
    kGPIO_IntRisingOrFallingEdge = 5U, 	
} gpio_interrupt_mode_t;	

	
typedef struct _gpio_pin_config
{
    gpio_pin_direction_t direction; 		
    uint8_t outputLogic;            		
	gpio_interrupt_mode_t interruptMode;	
} gpio_pin_config_t;


// GPIO 初始化
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
// GPIO 读IO电平
int gpio_pinread(GPIO_Type *base, int pin);
// GPIO 写GPIO电平
void gpio_pinwrite(GPIO_Type *base, int pin, int value);
// GPIO 中断配置
void gpio_intconfig(GPIO_Type* base, unsigned int pin, 
                    gpio_interrupt_mode_t pinInterruptMode);
// 使能 GPIO 中断
void gpio_enableint(GPIO_Type* base, unsigned int pin);
// 失能 GPIO 中断
void gpio_disableint(GPIO_Type* base, unsigned int pin);
// 清除中断标志
void gpio_clearintflags(GPIO_Type* base, unsigned int pin);
#endif

bsp_gpio.c

#include "bsp_gpio.h"



void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
	base->IMR &= ~(1U << pin);
	
	if(config->direction == kGPIO_DigitalInput) 
	{
		base->GDIR &= ~( 1 << pin);
	}
	else										
	{
		base->GDIR |= 1 << pin;
		gpio_pinwrite(base,pin, config->outputLogic);	
	}
	gpio_intconfig(base, pin, config->interruptMode);	
}


 int gpio_pinread(GPIO_Type *base, int pin)
 {
	 return (((base->DR) >> pin) & 0x1);
 }


void gpio_pinwrite(GPIO_Type *base, int pin, int value)
{
	 if (value == 0U)
	 {
		 base->DR &= ~(1U << pin); 
	 }
	 else
	 {
		 base->DR |= (1U << pin); 
	 }
}


void gpio_intconfig(GPIO_Type* base, unsigned int pin, 
                    gpio_interrupt_mode_t pin_int_mode)
{
	volatile uint32_t *icr;
	uint32_t icrShift;

	icrShift = pin;
	
	base->EDGE_SEL &= ~(1U << pin);

	if(pin < 16) 	
	{
		icr = &(base->ICR1);
	}
	else			
	{
		icr = &(base->ICR2);
		icrShift -= 16;
	}
	switch(pin_int_mode)
	{
		case(kGPIO_IntLowLevel):
			*icr &= ~(3U << (2 * icrShift));
			break;
		case(kGPIO_IntHighLevel):
			*icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift));
			break;
		case(kGPIO_IntRisingEdge):
			*icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));
			break;
		case(kGPIO_IntFallingEdge):
			*icr |= (3U << (2 * icrShift));
			break;
		case(kGPIO_IntRisingOrFallingEdge):
			base->EDGE_SEL |= (1U << pin);
			break;
		default:
			break;
	}
}


void gpio_enableint(GPIO_Type* base, unsigned int pin)
{ 
    base->IMR |= (1 << pin);
}


void gpio_disableint(GPIO_Type* base, unsigned int pin)
{ 
    base->IMR &= ~(1 << pin);
}


void gpio_clearintflags(GPIO_Type* base, unsigned int pin)
{
    base->ISR |= (1 << pin);
}
3.5 按键中断编写

有了 GPIO 驱动代码后,我们就可以新建一个新的模块代码,用于配置外部触发中断,新建模块如下:

bsp_exit.h 代码:

#ifndef __BSP_EXIT_H
#define __BSP_EXIT_H
#include "imx6ul.h"
// 外部中断初始化
void exit_init(void);
// 外部中断回调函数
void gpio1_io18_irqhandler(void);
#endif

bsp_exit.c 代码如下:

#include "bsp_exit.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_delay.h"
#include "bsp_beep.h"

void exit_init(void)
{
    //设定GPIO模式
    gpio_pin_config_t key_config;
    IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
	//设定按键中断
    key_config.direction=kGPIO_DigitalInput;
    key_config.interruptMode=kGPIO_IntFallingEdge;
    key_config.outputLogic=1;
    gpio_init(GPIO1,18,&key_config);
	//使能GIC中断,注册按键触发中断
    GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
    system_register_irqhandler(GPIO1_Combined_16_31_IRQn,
                               (system_irq_handler_t)gpio1_io18_irqhandler,
                               NULL);
    //使能按键触发中断
    gpio_enableint(GPIO1, 18);
}

void gpio1_io18_irqhandler(void)
{
    static unsigned char state = 0;
    //延时消抖(中断中严禁使用死延时,这里是为了IO稳定)
    delay(10);
    if(gpio_pinread(GPIO1,18) == 0)
    {
        state = !state;
        beep_switch(state);
    }
    //清除中断标志
    gpio_clearintflags(GPIO1,18);
}

以上代码准备完成后,我们在 main.c 中分别调用代码进行初始化

3.6 编写Makefile脚本

在 Makefile 里面添加上对应文件的文件夹就可以完成编译,添加位置如下:


编译一下,成功通过:

四、实验现象

按下按键 LED 的灯光效果切换

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/511257.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号