17.3.3通用中断驱动文件编写
在start.S文件中我们在中断服务函数IRQ_Handler中调用了C函数system_irqhandler来处理具体的中断。此函数有一个参数,参数是中断号,但是函数system_irqhandler的具体内容还没有实现,所以需要实现函数system_irqhandler的具体内容。不同的中断源对应不同的中断处理函数,I.MX6U有160个中断源,所以需要160个中断处理函数,我们可以将这些中断处理函数放到一个数组里面,中断处理函数在数组中的标号就是其对应的中断号。当中断发生以后函数system_irqhandler根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。
在bsp目录下新建名为“int”的文件夹,在bsp/int文件夹里面创建bsp_int.c和bsp_int.h这两个文件。在bsp_int.h文件里面输入如下内容:
示例代码17.3.3.1 bsp_int.h文件代码
1 #ifndef _BSP_INT_H
2 #define _BSP_INT_H
3 #include "imx6ul.h"
15/* 中断处理函数形式 */
typedefvoid(*system_irq_handler_t)(unsignedint giccIar,void*param);
18/* 中断处理函数结构体*/
19typedefstruct _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(unsignedint giccIar);
void default_irqhandler(unsignedint giccIar,void*userParam);
#endif
第16~23行是中断处理结构体,结构体sys_irq_handle_t包含一个中断处理函数和中断处理函数的用户参数。一个中断源就需要一个sys_irq_handle_t变量,I.MX6U有160个中断源,因此需要160个sys_irq_handle_t组成中断处理数组。
在bsp_int.c中输入如下所示代码:
示例代码17.3.3.2 bsp_int.c文件代码
1 #include "bsp_int.h"
13/* 中断嵌套计数器 */
14staticunsignedint irqNesting;
16/* 中断服务函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
19/*
20 * @description : 中断初始化函数
21 * @param : 无
22 * @return : 无
23 */
24void int_init(void)
25{
GIC_Init(); /* 初始化GIC */
system_irqtable_init(); /* 初始化中断表 */
__set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移 */
}
30
31/*
32 * @description : 初始化中断服务函数表
33 * @param : 无
34 * @return : 无
35 */
void system_irqtable_init(void)
{
unsignedint i =0;
irqNesting =0;
/* 先将所有的中断服务函数设置为默认值 */
for(i =0; i < NUMBER_OF_INT_VECTORS; i++)
{
system_register_irqhandler( (IRQn_Type)i,default_irqhandler,NULL);
}
}
48/*
49 * @description : 给指定的中断号注册中断服务函数
50 * @param - irq : 要注册的中断号
51 * @param - handler : 要注册的中断处理函数
52 * @param - usrParam : 中断服务处理函数参数
53 * @return : 无
54 */
void system_register_irqhandler(IRQn_Type irq,system_irq_handler_t handler,void*userParam)
{
irqTable[irq].irqHandler = handler;
irqTable[irq].userParam = userParam;
}
60
61/*
62 * @description : C语言中断服务函数,irq汇编中断服务函数会
63 调用此函数,此函数通过在中断服务列表中查
64 找指定中断号所对应的中断处理函数并执行。
65 * @param - giccIar : 中断号
66 * @return : 无
67 */
void system_irqhandler(unsignedint giccIar)
{
uint32_t intNum = giccIar &0x3FFUL;
/* 检查中断号是否符合要求 */
if((intNum ==1020)||(intNum >= NUMBER_OF_INT_VECTORS))
{
return;
}
irqNesting++;/* 中断嵌套计数器加一 */
/* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/
irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
irqNesting--;/* 中断执行完成,中断嵌套寄存器减一 */
}
89 * @description : 默认中断服务函数
90 * @param - giccIar : 中断号
91 * @param - usrParam : 中断服务处理函数参数
92 * @return : 无
93 */
void default_irqhandler(unsignedint giccIar,void*userParam)
{
while(1)
{
}
}
第14行定义了一个变量irqNesting,此变量作为中断嵌套计数器。
第17行定了中断服务函数数组irqTable,这是一个sys_irq_handle_t类型的结构体数组,数组大小为I.MX6U的中断源个数,即160个。
第24~28行是中断初始化函数int_init,在此函数中首先初始化了GIC,然后初始化了中断服务函数表,最终设置了中断向量表偏移。
第36~46行是中断服务函数表初始化函数system_irqtable_init,初始化irqTable,给其赋初值。
第55~59行是注册中断处理函数system_register_irqhandler,此函数用来给指定的中断号注册中断处理函数。如果要使用某个外设中断,那就必须调用此函数来给这个中断注册一个中断处理函数。
第68~86行就是前面在start.S中调用的system_irqhandler函数,此函数根据中断号在中断处理函数表irqTable中取出对应的中断处理函数并执行。
第94~99行是默认中断处理函数default_irqhandler,这是一个空函数,主要用来给初始化中断函数处理表。
17.3.4修改GPIO驱动文件
在前几章节试验中我们只是使用到了GPIO最基本的输入输出功能,本章我们需要使用GPIO的中断功能。所以需要修改文件GPIO的驱动文件bsp_gpio.c和bsp_gpio.h,加上中断相关函数。关于GPIO中断内容已经在8.1.5小节进行了详细的讲解,这里就不赘述了。打开bsp_gpio.h文件,重新输入如下内容:
示例代码17.3.4.1 bsp_gpio.h文件代码
1 #ifndef _BSP_GPIO_H
2 #define _BSP_GPIO_H
3 #include "imx6ul.h"
4/***************************************************************
5 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
6文件名 : bsp_gpio.h
7作者 : 左忠凯
8版本 : V1.0
9描述 : GPIO操作文件头文件。
10其他 : 无
11论坛 : www.openedv.com
12日志 : 初版V1.0 2019/1/4 左忠凯创建
13 V2.0 2019/1/4 左忠凯修改
14 添加GPIO中断相关定义
15
16 ***************************************************************/
17
18/*
19 * 枚举类型和结构体定义
20 */
21typedefenum _gpio_pin_direction
22{
23 kGPIO_DigitalInput =0U,/* 输入 */
24 kGPIO_DigitalOutput =1U,/* 输出 */
25} gpio_pin_direction_t;
26
27/*
28 * GPIO中断触发类型枚举
29 */
30typedefenum _gpio_interrupt_mode
31{
32 kGPIO_NoIntmode =0U, /* 无中断功能 */
33 kGPIO_IntLowLevel =1U, /* 低电平触发 */
34 kGPIO_IntHighLevel =2U, /* 高电平触发 */
35 kGPIO_IntRisingEdge =3U, /* 上升沿触发 */
36 kGPIO_IntFallingEdge =4U, /* 下降沿触发 */
37 kGPIO_IntRisingOrFallingEdge =5U, /* 上升沿和下降沿都触发 */
38} gpio_interrupt_mode_t;
39
40/*
41 * GPIO配置结构体
42 */
43typedefstruct _gpio_pin_config
44{
45 gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */
46uint8_t outputLogic; /* 如果是输出的话,默认输出电平 */
47 gpio_interrupt_mode_t interruptMode; /* 中断方式 */
48} gpio_pin_config_t;
49
50
51/* 函数声明 */
52void gpio_init(GPIO_Type *base,int pin, gpio_pin_config_t *config);
53int gpio_pinread(GPIO_Type *base,int pin);
54void gpio_pinwrite(GPIO_Type *base,int pin,int value);
55void gpio_intconfig(GPIO_Type* base,unsignedint pin,
gpio_interrupt_mode_t pinInterruptMode);
56void gpio_enableint(GPIO_Type* base,unsignedint pin);
57void gpio_disableint(GPIO_Type* base,unsignedint pin);
58void gpio_clearintflags(GPIO_Type* base,unsignedint pin);
59
60 #endif
相比前面试验的bsp_gpio.h文件,“示例代码17.3.3.2”中添加了一个新枚举类型:gpio_interrupt_mode_t,枚举出了GPIO所有的中断触发类型。还修改了结构体gpio_pin_config_t,在,在里面加入了interruptMode成员变量。最后就是添加了一些跟中断有关的函数声明,bsp_gpio.h文件的内容总体还是比较简单的。
打开bsp_gpio.c文件,重新输入如下代码:
示例代码17.3.4.2 bsp_gpio.c文件代码
1 #include "bsp_gpio.h"
2/***************************************************************
3 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
4文件名 : bsp_gpio.c
5作者 : 左忠凯
6版本 : V1.0
7描述 : GPIO操作文件。
8其他 : 无
9论坛 : www.openedv.com
10日志 : 初版V1.0 2019/1/4 左忠凯创建
11 V2.0 2019/1/4 左忠凯修改:
12修改gpio_init()函数,支持中断配置.
13添加gpio_intconfig()函数,初始化中断
14添加gpio_enableint()函数,使能中断
15 添加gpio_clearintflags()函数,清除中断标志位
16
17 ***************************************************************/
18
19/*
20 * @description : GPIO初始化。
21 * @param - base : 要初始化的GPIO组。
22 * @param - pin : 要初始化GPIO在组内的编号。
23 * @param - config : GPIO配置结构体。
24 * @return : 无
25 */
26void gpio_init(GPIO_Type *base,int pin, gpio_pin_config_t *config)
27{
28 base->IMR &=~(1U<< pin);
29
30if(config->direction == kGPIO_DigitalInput) /* GPIO作为输入 */
31{
32 base->GDIR &=~(1<< pin);
33}
34else /* 输出 */
35{
36 base->GDIR |=1<< pin;
37 gpio_pinwrite(base,pin, config->outputLogic);/* 设置默认电平 */
38}
39 gpio_intconfig(base, pin, config->interruptMode);/* 中断功能配置 */
40}
41
42/*
43 * @description : 读取指定GPIO的电平值。
44 * @param – base : 要读取的GPIO组。
45 * @param - pin : 要读取的GPIO脚号。
46 * @return : 无
47 */
48int gpio_pinread(GPIO_Type *base,int pin)
49{
50return(((base->DR)>> pin)&0x1);
51}
52
53/*
54 * @description : 指定GPIO输出高或者低电平。
55 * @param - base : 要输出的的GPIO组。
56 * @param - pin : 要输出的GPIO脚号。
57 * @param – value : 要输出的电平,1 输出高电平, 0 输出低低电平
58 * @return : 无
59 */
60void gpio_pinwrite(GPIO_Type *base,int pin,int value)
61{
62if(value ==0U)
63{
64 base->DR &=~(1U<< pin);/* 输出低电平 */
65}
66else
67{
68 base->DR |=(1U<< pin);/* 输出高电平 */
69}
70}
71
72/*
73 * @description : 设置GPIO的中断配置功能
74 * @param - base : 要配置的IO所在的GPIO组。
75 * @param - pin : 要配置的GPIO脚号。
76 * @param – pinInterruptMode: 中断模式,参考gpio_interrupt_mode_t
77 * @return : 无
78 */
79void gpio_intconfig(GPIO_Type* base,unsignedint pin,
gpio_interrupt_mode_t pin_int_mode)
80{
81volatileuint32_t*icr;
82uint32_t icrShift;
83
84 icrShift = pin;
85
86 base->EDGE_SEL &=~(1U<< pin);
87
88if(pin <16)/* 低16位 */
89{
90 icr =&(base->ICR1);
91}
92else/* 高16位 */
93{
94 icr =&(base->ICR2);
95 icrShift -=16;
96}
97switch(pin_int_mode)
98{
99case(kGPIO_IntLowLevel):
100*icr &=~(3U<<(2* icrShift));
101break;
102case(kGPIO_IntHighLevel):
103*icr =(*icr &(~(3U<<(2* icrShift))))|
(1U<<(2* icrShift));
104break;
105case(kGPIO_IntRisingEdge):
106*icr =(*icr &(~(3U<<(2* icrShift))))|
(2U<<(2* icrShift));
107break;
108case(kGPIO_IntFallingEdge):
109*icr |=(3U<<(2* icrShift));
110break;
111case(kGPIO_IntRisingOrFallingEdge):
112 base->EDGE_SEL |=(1U<< pin);
113break;
114default:
115break;
116}
117}
118
119/*
120 * @description : 使能GPIO的中断功能
121 * @param - base : 要使能的IO所在的GPIO组。
122 * @param - pin : 要使能的GPIO在组内的编号。
123 * @return : 无
124 */
125void gpio_enableint(GPIO_Type* base,unsignedint pin)
126{
127 base->IMR |=(1<< pin);
128}
129
130/*
131 * @description : 禁止GPIO的中断功能
132 * @param - base : 要禁止的IO所在的GPIO组。
133 * @param - pin : 要禁止的GPIO在组内的编号。
134 * @return : 无
135 */
136void gpio_disableint(GPIO_Type* base,unsignedint pin)
137{
138 base->IMR &=~(1<< pin);
139}
140
141/*
142 * @description : 清除中断标志位(写1清除)
143 * @param - base : 要清除的IO所在的GPIO组。
144 * @param - pin : 要清除的GPIO掩码。
145 * @return : 无
146 */
147void gpio_clearintflags(GPIO_Type* base,unsignedint pin)
148{
149 base->ISR |=(1<< pin);
150}
17.3.5按键中断驱动文件编写
本例程的目的是以中断的方式编写KEY按键驱动,当按下KEY以后触发GPIO中断,然后在中断服务函数里面控制蜂鸣器的开关。所以接下来就是要编写按键KEY对应的UART1_CTS这个IO的中断驱动,在bsp文件夹里面新建名为“exit”的文件夹,然后在bsp/exit里面新建bsp_exit.c和bsp_exit.h两个文件。在bsp_exit.h文件中输入如下代码:
示例代码17.3.5.1 bsp_exit.h文件代码
1 #ifndef _BSP_EXIT_H
2 #define _BSP_EXIT_H
3/***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5文件名 : bsp_exit.h
6作者 : 左忠凯
7版本 : V1.0
8描述 : 外部中断驱动头文件。
9其他 : 配置按键对应的GPIP为中断模式
10论坛 : www.openedv.com
11日志 : 初版V1.0 2019/1/4 左忠凯创建
12 ***************************************************************/
13 #include "imx6ul.h"
14
15/* 函数声明 */
16void exit_init(void); /* 中断初始化 */
17void gpio1_io18_irqhandler(void);/* 中断处理函数 */
18
19 #endif
示例代码17.3.5.2 bsp_exit.c文件代码
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_exit.c
作者 : 左忠凯
版本 : V1.0
描述 : 外部中断驱动。
其他 : 配置按键对应的GPIP为中断模式
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************/
1 #include "bsp_exit.h"
2 #include "bsp_gpio.h"
3 #include "bsp_int.h"
4 #include "bsp_delay.h"
5 #include "bsp_beep.h"
6
7/*
8 * @description : 初始化外部中断
9 * @param : 无
10 * @return : 无
11 */
12void exit_init(void)
13{
14 gpio_pin_config_t key_config;
15
16 /* 1、设置IO复用 */
17 IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);
18 IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
19
20 /* 2、初始化GPIO为中断模式 */
21 key_config.direction = kGPIO_DigitalInput;
22 key_config.interruptMode = kGPIO_IntFallingEdge;
23 key_config.outputLogic =1;
24 gpio_init(GPIO1,18,&key_config);
25 /* 3、使能GIC中断、注册中断服务函数、使能GPIO中断*/
26 GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
27 system_register_irqhandler(GPIO1_Combined_16_31_IRQn,
(system_irq_handler_t)gpio1_io18_irqhandler,
NULL);
28 gpio_enableint(GPIO1,18);
29}
30
31/*
32 * @description : GPIO1_IO18最终的中断处理函数
33 * @param : 无
34 * @return : 无
35 */
36void gpio1_io18_irqhandler(void)
37{
38 staticunsignedchar state =0;
39
40 /*
41 *采用延时消抖,中断服务函数中禁止使用延时函数!因为中断服务需要
42 *快进快出!!这里为了演示所以采用了延时函数进行消抖,后面我们会讲解
43 *定时器中断消抖法!!!
44 */
45
46 delay(10);
47 if(gpio_pinread(GPIO1,18)==0)/* 按键按下了 */
48 {
49 state =!state;
50 beep_switch(state);
51 }
52
53 gpio_clearintflags(GPIO1,18); /* 清除中断标志位 */
54}
bsp_exit.c文件只有两个函数exit_init和gpio1_io18_irqhandler,exit_init是中断初始化函数。第1424行都是初始化KEY所使用的UART1_CTS这个IO,设置其复用为GPIO1_IO18,然后配置GPIO1_IO18为下降沿触发中断。重点是第2628行,在26行调用函数GIC_EnableIRQ来使能GPIO_IO18所对应的中断总开关,I.MX6U中GPIO1_IO16IO31这16个IO共用ID99。27行调用函数system_register_irqhandler注册ID99所对应的中断处理函数,GPIO1_IO16IO31这16个IO共用一个中断处理函数,至于具体是哪个IO引起的中断,那就需要在中断处理函数中判断了。28行通过函数gpio_enableint使能GPIO1_IO18这个IO对应的中断。
函数gpio1_io18_irqhandler就是27行注册的中断处理函数,也就是我们学习STM32的时候某个GPIO对应的中断服务函数。在此函数里面编写中断处理代码,第50行就是蜂鸣器开关控制代码,也就是我们本试验的目的。当中断处理完成以后肯定要清除中断标志位,第53行调用函数gpio_clearintflags来清除GPIO1_IO18的中断标志位。
17.3.6编写main.c文件
在main.c中输入如下代码:
示例代码17.3.6.1 main.c文件代码
/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : mian.c
作者 : 左忠凯
版本 : V1.0
描述 : I.MX6U开发板裸机实验9 系统中断实验
其他 : 五
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
**************************************************************/
1 #include "bsp_clk.h"
2 #include "bsp_delay.h"
3 #include "bsp_led.h"
4 #include "bsp_beep.h"
5 #include "bsp_key.h"
6 #include "bsp_int.h"
7 #include "bsp_exit.h"
8
9/*
10 * @description : main函数
11 * @param : 无
12 * @return : 无
13 */
14int main(void)
15{
16 unsignedchar state = OFF;
17
18 int_init(); /* 初始化中断(一定要最先调用!) */
19 imx6u_clkinit(); /* 初始化系统时钟 */
20 clk_enable(); /* 使能所有的时钟 */
21 led_init(); /* 初始化led */
22 beep_init(); /* 初始化beep */
23 key_init(); /* 初始化key */
24 exit_init(); /* 初始化按键中断 */
25
26 while(1)
27 {
28 state =!state;
29 led_switch(LED0, state);
30 delay(500);
31 }
32
33 return0;
34}
main.c很简单,重点是第18行调用函数int_init来初始化中断系统,第24行调用函数exit_init来初始化按键KEY对应的GPIO中断。
17.4编译下载验证
17.4.1编写Makefile和链接脚本
在第十六章实验的Makefile基础上修改变量TARGET为int,在变量INCDIRS和SRCDIRS中追加“bsp/exit”和bsp/int,修改完成以后如下所示:
示例代码17.4.1.1 Makefile文件代码
1 CROSS_COMPILE ?= arm-linux-gnueabihf-
2 TARGET ?= int
3
4/* 省略掉其它代码...... */
5
6 INCDIRS:= imx6ul \
7 bsp/clk \
8 bsp/led \
9 bsp/delay \
10 bsp/beep \
11 bsp/gpio \
12 bsp/key \
13 bsp/exit \
14 bsp/int
15
16 SRCDIRS := project \
17 bsp/clk \
18 bsp/led \
19 bsp/delay \
20 bsp/beep \
21 bsp/gpio \
22 bsp/key \
23 bsp/exit \
24 bsp/int
25
26/* 省略掉其它代码...... */
27
28 clean:
29 rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS)$(SOBJS)
使用Make命令编译代码,编译成功以后使用软件imxdownload将编译完成的int.bin文件下载到SD卡中,命令如下:
chmod 777 imxdownload //给予imxdownload可执行权限,一次即可
./imxdownload int.bin /dev/sdd //烧写到SD卡中
烧写成功以后将SD卡插到开发板的SD卡槽中,然后复位开发板。本试验效果其实和试验“8_key”一样,按下KEY就会打开蜂鸣器,再次按下就会关闭蜂鸣器。LED0会不断闪烁,周期大约500ms。
原文链接:https://blog.csdn.net/weixin_55796564/article/details/114920384