1
完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
一、回顾STM32中断系统
1、STM32中断向量表 ARM芯片从0X00000000开始运行,执行指令。在程序开始的地方存放着中断向量表。中断向量表主要功能是描述中断对应的中断服务函数。 对于STM32来说代码最开始的地址存放堆栈栈顶指针。 2、中断向量偏移 一般ARM从0X000000000地址开始运行,对于STM32我们设置连接首地址为0X8000000。 如果代码一定要从0X8000000开始运行,那么需要告诉一下soc内核。也就是设置中断向量偏移。设置SCB的VTOR寄存器为新的中断向量表起始地址即可。 3、NVIC中断控制器。 NVIC就是中断管理机构。使能和关闭指定的中断、设置中断优先级。 4、中断服务函数的编写 中断服务函数就是中断要做的事情。 二、Cortex-A7中断系统 1、Cortex-A中断向量表 Cortex-A中断向量表有8个中断,其中重点关注IRQ。Cortex-A的中断向量表需要用户自己去定义。 /* * 描述: _start函数,首先是中断向量表的创建 * 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器) * ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常) */ _start: ldr pc, =Reset_Handler /* 复位中断 */ ldr pc, =Undefined_Handler /* 未定义中断 */ ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */ ldr pc, =PrefAbort_Handler /* 预取终止中断 */ ldr pc, =DataAbort_Handler /* 数据终止中断 */ ldr pc, =NotUsed_Handler /* 未使用中断 */ ldr pc, =IRQ_Handler /* IRQ中断 */ ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */ 2、中断向量偏移 我们的裸机历程都是从0X87800000开始的,因此要设置中断向量偏移。 3、GIC中断控制器。 同NVIC一样,GIC用于管理Cortex-A的中断。GIC提供了开关中断,设置中断优先级。 4、IMX6U中断号 为了区分不同的中断,引入了中断号。ID0ID15是给SGI,ID16ID31是给PPI。剩下的ID32~1019给SPI,也就是按键中断、串口中断。。。。 6ULL支持128个中断。 5、中断服务函数的编写 一个是IRQ中断服务函数的编写,另一个就是在IRQ中断服务函数里面去查找并运行的具体的外设中断服务函数, 三、中断实验编写 1、编写按键中断例程。 KEY0使用UART1_CTS这个IO。编写UART1_CTS的中断代码。 2、修改start.S 添加中断向量表,编写复位中断服务函数和IRQ中断服务函数。 编写复位中断服务函数,内容如下: ①、关闭I,D Cache和MMU。 ②、设置处理器9中工作模式下对应的SP指针。要使用中断那么必须设置IRQ模式下的SP指针。索性直接设置所有模式下的SP指针。 ③、清除bss段。 ④、跳到C函数,也就是main函数 3、CP15协处理器 《Cortex-A7 Technical ReferenceManua.pdf》 《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》 MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。 MRC 就是读 CP15 寄存器, MCR 就是写 CP15 寄存器, MCR 指令格式如下: MCR{cond} p15, , , , , MRC p15, 0, r0, c0,c0,0 现在要关闭I,D ache和MMU,打开Cortex-A7参考手册到105页,找到SCTLR寄存器。也就是系统控制寄存器,此寄存器bit0用于打开和关闭MMU,bit1控制对齐,bit2控制D Cache的打开和关闭。Bit11用于控制分支预测。Bit12用于控制I Cache。中断向量偏移设置将新的中断向量表首地址写入到CP15协处理器的VBAR寄存器。 MCR{cond} p15, IRQ中断服务函数 mrc p15, 4, r1, c15, c0, 0 读取CP15的CBAR寄存器。CBAR寄存器保存了GIC控制器的寄存器组首地址。GIC寄存器组偏移0x10000x1fff为GIC的分发器。0x20000x3fff为CPU接口端。意味我们可以访问GIC控制器了! 代码中,R1寄存器吧保存着GIC控制器的CPU接口端基地址。读取CPU接口段的GICC_IAR寄存器的值保存到R0寄存器里面。可以从GICC_IAR的bit9~0读取中断ID,我们读取中断ID的目的就是为了得到对应的中断处理函数。 system_irqhandler就是具体的中断处理函数,此函数有一个参数,为GICC_IAR寄存器的值。 system_irqhandler处理完具体的中断以后,需要将对应的中断ID值写入到GICC_EOIR寄存器里面。 四. 代码 .global _start /* 全局标号 */ /* * 描述: _start函数,首先是中断向量表的创建 * 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器) * ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常) */ _start: ldr pc, =Reset_Handler /* 复位中断 */ ldr pc, =Undefined_Handler /* 未定义中断 */ ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */ ldr pc, =PrefAbort_Handler /* 预取终止中断 */ ldr pc, =DataAbort_Handler /* 数据终止中断 */ ldr pc, =NotUsed_Handler /* 未使用中断 */ ldr pc, =IRQ_Handler /* IRQ中断 */ ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */ /* 复位中断 */ Reset_Handler: cpsid i /* 关闭全局中断 */ /* 关闭I,DCache和MMU * 采取读-改-写的方式。 */ mrc p15, 0, r0, c1, c0, 0 /* 读取CP15的C1寄存器到R0中 */ bic r0, r0, #(0x1 << 12) /* 清除C1寄存器的bit12位(I位),关闭I Cache */ bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit2(C位),关闭D Cache */ bic r0, r0, #0x2 /* 清除C1寄存器的bit1(A位),关闭对齐 */ bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit11(Z位),关闭分支预测 */ bic r0, r0, #0x1 /* 清除C1寄存器的bit0(M位),关闭MMU */ mcr p15, 0, r0, c1, c0, 0 /* 将r0寄存器中的值写入到CP15的C1寄存器中 */ #if 0 /* 汇编版本设置中断向量表偏移 */ ldr r0, =0X87800000 d*** i*** mcr p15, 0, r0, c12, c0, 0 d*** i*** #endif /* 设置各个模式下的栈指针, * 注意:IMX6UL的堆栈是向下增长的! * 堆栈指针地址一定要是4字节地址对齐的!!! * DDR范围:0X80000000~0X9FFFFFFF */ /* 进入IRQ模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0x80600000 /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */ /* 进入SYS模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0x80400000 /* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */ /* 进入SVC模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0X80200000 /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */ cpsie i /* 打开全局中断 */ #if 0 /* 使能IRQ中断 */ mrs r0, cpsr /* 读取cpsr寄存器值到r0中 */ bic r0, r0, #0x80 /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */ msr cpsr, r0 /* 将r0重新写入到cpsr中 */ #endif b main /* 跳转到main函数 */ /* 未定义中断 */ Undefined_Handler: ldr r0, =Undefined_Handler bx r0 /* SVC中断 */ 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 /* IRQ中断!重点!!!!! */ IRQ_Handler: push {lr} /* 保存lr地址 */ push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */ mrs r0, spsr /* 读取spsr寄存器 */ push {r0} /* 保存spsr寄存器 */ mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中 * 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49 * Cortex-A7 Technical ReferenceManua.pdf P68 P138 */ add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */ ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器, * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据 * 这个中断号来绝对调用哪个中断服务函数 */ push {r0, r1} /* 保存r0,r1 */ cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */ push {lr} /* 保存SVC模式的lr寄存器 */ ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/ blx r2 /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */ pop {lr} /* 执行完C语言中断服务函数,lr出栈 */ cps #0x12 /* 进入IRQ模式 */ pop {r0, r1} str r0, [r1, #0X10] /* 中断执行完成,写EOIR */ pop {r0} msr spsr_cxsf, r0 /* 恢复spsr */ pop {r0-r3, r12} /* r0-r3,r12出栈 */ pop {lr} /* lr出栈 */ subs pc, lr, #4 /* 将lr-4赋给pc */ /* FIQ中断 */ FIQ_Handler: ldr r0, =FIQ_Handler bx r0 //Makefile CROSS_COMPILE ?= arm-linux-gnueabihf- TARGET ?= int CC := $(CROSS_COMPILE)gcc LD := $(CROSS_COMPILE)ld OBJCOPY := $(CROSS_COMPILE)objcopy OBJDUMP := $(CROSS_COMPILE)objdump INCDIRS := imx6ul bsp/clk bsp/led bsp/delay bsp/beep bsp/gpio bsp/key bsp/exit bsp/int SRCDIRS := project bsp/clk bsp/led bsp/delay bsp/beep bsp/gpio bsp/key bsp/exit bsp/int INCLUDE := $(patsubst %, -I %, $(INCDIRS)) SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S)) CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c)) SFILENDIR := $(notdir $(SFILES)) CFILENDIR := $(notdir $(CFILES)) SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o)) COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o)) OBJS := $(SOBJS) $(COBJS) VPATH := $(SRCDIRS) .PHONY: clean $(TARGET).bin : $(OBJS) $(LD) -Timx6ul.lds -o $(TARGET).elf $^ $(OBJCOPY) -O binary -S $(TARGET).elf $@ $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis $(SOBJS) : obj/%.o : %.S $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $< $(COBJS) : obj/%.o : %.c $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $< clean: rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS) //imx6ul.ld SECTIONS{ . = 0X87800000; .text : { obj/start.o *(.text) } .rodata ALIGN(4) : {*(.rodata*)} .data ALIGN(4) : { *(.data) } __bss_start = .; .bss ALIGN(4) : { *(.bss) *(COMMON) } __bss_end = .; } //bsp_int.c #include "bsp_int.h" /* 中断嵌套计数器 */ static unsigned int irqNesting; /* 中断服务函数表 */ static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS]; /* * @description : 中断初始化函数 * @param : 无 * @return : 无 */ void int_init(void) { GIC_Init(); /* 初始化GIC */ system_irqtable_init(); /* 初始化中断表 */ __set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移,偏移到起始地址 */ } /* * @description : 初始化中断服务函数表 * @param : 无 * @return : 无 */ 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); } } /* * @description : 给指定的中断号注册中断服务函数 * @param - irq : 要注册的中断号 * @param - handler : 要注册的中断处理函数 * @param - usrParam : 中断服务处理函数参数 * @return : 无 */ void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) { irqTable[irq].irqHandler = handler; irqTable[irq].userParam = userParam; } /* * @description : C语言中断服务函数,irq汇编中断服务函数会 调用此函数,此函数通过在中断服务列表中查 找指定中断号所对应的中断处理函数并执行。 * @param - giccIar : 中断号 * @return : 无 */ void system_irqhandler(unsigned int giccIar) { uint32_t intNum = giccIar & 0x3FFUL; /* 检查中断号是否符合要求 */ if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS)) { return; } irqNesting++; /* 中断嵌套计数器加一 */ /* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/ irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam); irqNesting--; /* 中断执行完成,中断嵌套寄存器减一 */ } /* * @description : 默认中断服务函数 * @param - giccIar : 中断号 * @param - usrParam : 中断服务处理函数参数 * @return : 无 */ void default_irqhandler(unsigned int giccIar, void *userParam) { while(1) { } } //bsp_gpio #include "bsp_gpio.h" /* * @description : GPIO初始化。 * @param - base : 要初始化的GPIO组。 * @param - pin : 要初始化GPIO在组内的编号。 * @param - config : GPIO配置结构体。 * @return : 无 */ void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config) { base->IMR &= ~(1U << pin); if(config->direction == kGPIO_DigitalInput) /* GPIO作为输入 */ { base->GDIR &= ~( 1 << pin); } else /* 输出 */ { base->GDIR |= 1 << pin; gpio_pinwrite(base,pin, config->outputLogic); /* 设置默认输出电平 */ } gpio_intconfig(base, pin, config->interruptMode); /* 中断功能配置 */ } /* * @description : 读取指定GPIO的电平值 。 * @param - base : 要读取的GPIO组。 * @param - pin : 要读取的GPIO脚号。 * @return : 无 */ int gpio_pinread(GPIO_Type *base, int pin) { return (((base->DR) >> pin) & 0x1); } /* * @description : 指定GPIO输出高或者低电平 。 * @param - base : 要输出的的GPIO组。 * @param - pin : 要输出的GPIO脚号。 * @param - value : 要输出的电平,1 输出高电平, 0 输出低低电平 * @return : 无 */ void gpio_pinwrite(GPIO_Type *base, int pin, int value) { if (value == 0U) { base->DR &= ~(1U << pin); /* 输出低电平 */ } else { base->DR |= (1U << pin); /* 输出高电平 */ } } /* * @description : 设置GPIO的中断配置功能 * @param - base : 要配置的IO所在的GPIO组。 * @param - pin : 要配置的GPIO脚号。 * @param - pinInterruptMode: 中断模式,参考枚举类型gpio_interrupt_mode_t * @return : 无 */ 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) /* 低16位 */ { icr = &(base->ICR1); } else /* 高16位 */ { 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; } } /* * @description : 使能GPIO的中断功能 * @param - base : 要使能的IO所在的GPIO组。 * @param - pin : 要使能的GPIO在组内的编号。 * @return : 无 */ void gpio_enableint(GPIO_Type* base, unsigned int pin) { base->IMR |= (1 << pin); } /* * @description : 禁止GPIO的中断功能 * @param - base : 要禁止的IO所在的GPIO组。 * @param - pin : 要禁止的GPIO在组内的编号。 * @return : 无 */ void gpio_disableint(GPIO_Type* base, unsigned int pin) { base->IMR &= ~(1 << pin); } /* * @description : 清除中断标志位(写1清除) * @param - base : 要清除的IO所在的GPIO组。 * @param - pin : 要清除的GPIO掩码。 * @return : 无 */ void gpio_clearintflags(GPIO_Type* base, unsigned int pin) { base->ISR |= (1 << pin); } //bsp_exit #include "bsp_exit.h" #include "bsp_gpio.h" #include "bsp_int.h" #include "bsp_delay.h" #include "bsp_beep.h" /* * @description : 初始化外部中断 * @param : 无 * @return : 无 */ void exit_init(void) { gpio_pin_config_t key_config; /* 1、设置IO复用 */ IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 复用为GPIO1_IO18 */ IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080); /* 2、初始化GPIO为中断模式 */ key_config.direction = kGPIO_DigitalInput; key_config.interruptMode = kGPIO_IntFallingEdge; key_config.outputLogic = 1; gpio_init(GPIO1, 18, &key_config); GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */ system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 注册中断服务函数 */ gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */ } /* * @description : GPIO1_IO18最终的中断处理函数 * @param : 无 * @return : 无 */ void gpio1_io18_irqhandler(void) { static unsigned char state = 0; /* *采用延时消抖,中断服务函数中禁止使用延时函数!因为中断服务需要 *快进快出!!这里为了演示所以采用了延时函数进行消抖,后面我们会讲解 *定时器中断消抖法!!! */ delay(10); if(gpio_pinread(GPIO1, 18) == 0) /* 按键按下了 */ { state = !state; beep_switch(state); } gpio_clearintflags(GPIO1, 18); /* 清除中断标志位 */ } //main.c #include "bsp_clk.h" #include "bsp_delay.h" #include "bsp_led.h" #include "bsp_beep.h" #include "bsp_key.h" #include "bsp_int.h" #include "bsp_exit.h" /* * @description : main函数 * @param : 无 * @return : 无 */ int main(void) { unsigned char state = OFF; int_init(); /* 初始化中断(一定要最先调用!) */ imx6u_clkinit(); /* 初始化系统时钟 */ clk_enable(); /* 使能所有的时钟 */ led_init(); /* 初始化led */ beep_init(); /* 初始化beep */ key_init(); /* 初始化key */ exit_init(); /* 初始化按键中断 */ while(1) { state = !state; led_switch(LED0, state); delay(500); } return 0; } 问题 chmod 777 imxdownload ./imxdownload ledc.bin /dev/sdb 烧录后能LED正常闪,但是按下按键后卡死,查看int.dis文件发现87800000处存放的不是中断向量表。错误的把清bss段代码放在了中断向量表前面。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1767 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1619 浏览 1 评论
1069 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
724 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1673 浏览 2 评论
1935浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
727浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
567浏览 3评论
592浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
551浏览 3评论
小黑屋| 手机版| Archiver| 德赢Vwin官网 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-21 22:23 , Processed in 0.747663 second(s), Total 76, Slave 60 queries .
Powered by 德赢Vwin官网 网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
德赢Vwin官网 观察
版权所有 © 湖南华秋数字科技有限公司
德赢Vwin官网 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号