响应式网站建设的应用场景,宝塔linux面板官网,用一段话来解释网站建设,招聘58同城找工作系列文章目录 文章目录 系列文章目录前言STM32 中断系统IMX6U中断控制8个中断GIC中断控制器GIC介绍中断IDGIC逻辑分块GIC协处理器 中断使能中断优先级 重点代码分析官方SDK函数start.S文件自行编写中断驱动文件 前言
最近在学习中发现#xff0c;学Linux嵌入式不仅是对Linux的…系列文章目录 文章目录 系列文章目录前言STM32 中断系统IMX6U中断控制8个中断GIC中断控制器GIC介绍中断IDGIC逻辑分块GIC协处理器 中断使能中断优先级 重点代码分析官方SDK函数start.S文件自行编写中断驱动文件 前言
最近在学习中发现学Linux嵌入式不仅是对Linux的学习熟悉而且还是对Cortex-A内核的学习掌握 没怎么看懂内容太多了我觉得暂时先搞清楚一些原理概念以及简单的外部函数接口就行内部可能在后续的学习中进行钻研 STM32 中断系统
STM32 的中断系统主要有以下几个关键点 ①、 中断向量表。 ②、 NVIC(内嵌向量中断控制器)。 ③、 中断使能。 ④、 中断服务函数。
中断向量表是一个表这个表里面存放的是中断向量。中断向量表是一系列中断服务程序入口地址组成的表。 由半导体厂商定好的当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处。 中断向量表在整个程序的最前面如 STM32F103
__Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; Reset HandlerDCD NMI_Handler ; NMI HandlerDCD HardFault_Handler ; Hard Fault HandlerDCD MemManage_Handler ; MPU Fault HandlerDCD BusFault_Handler ; Bus Fault HandlerDCD UsageFault_Handler ; Usage Fault HandlerDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD SVC_Handler ; SVCall HandlerDCD DebugMon_Handler ; Debug Monitor HandlerDCD 0 ; ReservedDCD PendSV_Handler ; PendSV HandlerDCD SysTick_Handler ; SysTick Handler; External InterruptsDCD WWDG_IRQHandler ; Window WatchdogDCD PVD_IRQHandler ; PVD through EXTI Line detectDCD TAMPER_IRQHandler ; TamperDCD RTC_IRQHandler ; RTCDCD FLASH_IRQHandler ; FlashDCD RCC_IRQHandler ; RCCDCD EXTI0_IRQHandler ; EXTI Line 0DCD EXTI1_IRQHandler ; EXTI Line 1DCD EXTI2_IRQHandler ; EXTI Line 2DCD EXTI3_IRQHandler ; EXTI Line 3DCD EXTI4_IRQHandler ; EXTI Line 4DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7DCD ADC1_2_IRQHandler ; ADC1_2DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TXDCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0DCD CAN1_RX1_IRQHandler ; CAN1 RX1DCD CAN1_SCE_IRQHandler ; CAN1 SCEDCD EXTI9_5_IRQHandler ; EXTI Line 9..5DCD TIM1_BRK_IRQHandler ; TIM1 BreakDCD TIM1_UP_IRQHandler ; TIM1 UpdateDCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and CommutationDCD TIM1_CC_IRQHandler ; TIM1 Capture CompareDCD TIM2_IRQHandler ; TIM2DCD TIM3_IRQHandler ; TIM3DCD 0 ; ReservedDCD I2C1_EV_IRQHandler ; I2C1 EventDCD I2C1_ER_IRQHandler ; I2C1 ErrorDCD 0 ; ReservedDCD 0 ; ReservedDCD SPI1_IRQHandler ; SPI1DCD 0 ; ReservedDCD USART1_IRQHandler ; USART1DCD USART2_IRQHandler ; USART2DCD 0 ; ReservedDCD EXTI15_10_IRQHandler ; EXTI Line 15..10DCD RTC_Alarm_IRQHandler ; RTC Alarm through EXTI LineDCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
__Vectors_End中断向量表都是链接到代码的最前面比如一般 ARM 处理器都是从地址 0X00000000 开始执行指令的那么中断向量表就是从 0X00000000 开始存放的。
“__initial_sp”就是第一条中断向量存放的是栈顶指针 复位中断复位函数 Reset_Handler 的入口地址
ARM 处理器都是从地址 0X00000000 开始运行但是我们是下载到 0X8000000 开始的存储区域中Cortex-M 架构引入了中断向量表偏移中断向量表偏移配置在函数 SystemInit 中完成通过向 SCB_VTOR 寄存器写入新的中断向量表首地址即可。
void SystemInit (void)
{RCC-CR | (uint32_t)0x00000001;/* 省略其它代码 */#ifdef VECT_TAB_SRAMSCB-VTOR SRAM_BASE | VECT_TAB_OFFSET; //将中断向量表设置到 RAM 中#elseSCB-VTOR FLASH_BASE | VECT_TAB_OFFSET; //将中断向量表设置到 ROM 中#endif
}#define FLASH_BASE ((uint32_t)0x08000000)对于Cortex-M来说: 中断使能
NVIC_InitStructure.NVIC_IRQChannel EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0x02; //抢占优先级 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority 0x02; //子优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; //使能外部中断通道
NVIC_Init(NVIC_InitStructure);中断服务函数 要处理的工作就可以放到中断服务函数中去完成
/* 外部中断 2 服务程序 */
void EXTI2_IRQHandler(void)
{
/* 中断处理代码 */
}IMX6U中断控制
Cortex-M 内核有个中断系统的管理机构—NVICNested Vectored Interrupt Controller Cortex-A7 内核有个中断系统管理机构——GICgeneral interrupt controller
8个中断
Cortex-A内核有8个异常中断 其中还包括一个未使用中断实际只有七个中断。
对于 Cortex-M 内核来说中断向量表列举出了一款芯片所有的中断向量包括芯片外设的所有中断。
Cortex-A 内核 CPU 的所有外部中断都属于这个 IRQ 中断当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断进而根据具体的中断做出相应的处理。 ①、复位中断(Rest) CPU 复位以后就会进入复位中断我们可以在复位中断服务函数里面做一些初始化工作比如初始化 SP 指针、 DDR 等等。 ②、未定义指令中断(Undefined Instruction)如果指令不能识别的话就会产生此中断。 ③、软中断(Software Interrupt,SWI)由 SWI 指令引起的中断 Linux 的系统调用会用 SWI指令来引起软中断通过软中断来陷入到内核空间。 ④、指令预取中止中断(Prefetch Abort)预取指令的出错的时候会产生此中断。 ⑤、数据访问中止中断(Data Abort)访问数据出错的时候会产生此中断。 ⑥、 IRQ 中断(IRQ Interrupt)外部中断前面已经说了芯片内部的外设中断都会引起此中断的发生。 ⑦、 FIQ 中断(FIQ Interrupt)快速中断如果需要快速处理中断的话就可以使用此中断。
常用复位中断和IRQ中断实际需要编写的只有复位中断服务函数 Reset_Handler 和 IRQ 中断服务函数 IRQ_Handler其它的中断本暂时没用到所以都是死循环。
GIC中断控制器
GIC介绍
GIC目前有V1-V4V1太老淘汰了GIC V2 是给 ARMv7-A 架构使用的比如 Cortex-A7、 Cortex-A9、 Cortex-A15 等V3 和 V4 是给 ARMv8-A/R 架构使用的也就是 64 位芯片使用的。ARM 会根据 GIC 版本的不同研发出不同的 IP 核那些半导体厂商直接购买对应的 IP 核即可ARM 针对 GIC V2 就开发出了 GIC400 这个中断控制器 IP 核。当 GIC 接收到外部中断信号以后就会报给 ARM 内核但是ARM 内核只提供了四个信号给 GIC 来汇报中断情况 VFIQ、 VIRQ、 FIQ 和 IRQ。
VFIQ:虚拟快速 FIQ。VIRQ:虚拟外部 IRQ。FIQ:快速中断 IRQ。IRQ:外部中断 IRQ。 VFIQ 和 VIRQ 是针对虚拟化的不管只看IRQ ①、 SPI(Shared Peripheral Interrupt),共享中断所有 Core 共享的中断那些外部中断都属于 SPI 中断。比如按键中断、串口中断等等这些中断所有的 Core 都可以处理不限定特定 Core。 ②、 PPI(Private Peripheral Interrupt)私有中断 GIC 是支持多核的每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理。 ③、 SGI(Software-generated Interrupt)软件中断由软件触发引起的中断通过向寄存器GICD_SGIR 写入数据来触发系统会使用 SGI 中断来完成多核之间的通信。
中断ID
每一个 CPU 最多支持 1020 个中断 ID中断 ID 号为 ID0~ID1019。这 1020 个 ID 包含了 PPI、 SPI 和 SGI。
ID0~ID15这 16 个 ID 分配给 SGI。 ID16~ID31这 16 个 ID 分配给 PPI。 ID32~ID1019这 988 个 ID 分配给 SPI像 GPIO 中断、串口中断等这些外部中断
I.MX6U 的总共使用了 128 个SPI中断以及 PPI 和 SGI 的 32 个 ID一共160个
中断ID全部都在参考手册的第三章 移植的官方SDKSoftware Development Kit中就有所有中断ID的枚举
GIC逻辑分块
GIC 架构分为了两个逻辑块 Distributor 和 CPU Interface也就是分发器端和 CPU 接口端
Distributor(分发器端)中断事件应该发送到哪个 CPU 接口将优先级最高的中断事件发送到 CPU 接口端 全局中断使能控制。控制每一个中断的使能或者关闭。设置每个中断的优先级。设置每个中断的目标处理器列表。设置每个外部中断的触发模式电平触发或边沿触发。设置每个中断属于组 0 还是组 1。
CPU Interface(CPU 接口端)每个 CPU Core 都可以在 GIC 中找到一个与之对应的 CPU Interface 使能或者关闭发送到 CPU Core 的中断请求信号。应答中断。通知中断处理完成。设置优先级掩码通过掩码来设置哪些中断不需要上报给 CPU Core。定义抢占策略。当多个中断到来的时候选择优先级最高的中断通知给 CPU Core。
在我们移植的 core_ca7.h 文件里面有定义结构体 GIC_Type分发器端和 CPU 接口端
GIC协处理器
通过 c0 寄存器可以获取到处理器内核信息通过 c1 寄存器可以使能或禁止 MMU、 I/D Cache 等通过 c12 寄存器可以设置中断向量偏移通过 c15 寄存器可以获取 GIC 基地址。暂时没太理解后续补充
中断使能
使用 I.MX6U 上的外设中断就必须先打开 IRQ 中断 寄存器 CPSR 的 I1 禁止 IRQ当 I0 使能 IRQ F1 禁止 FIQ F0 使能 FIQ。 还可以使用更简单的指令 GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用来完成外部中断的使能和禁止Cortex-A7 内核中断 ID 只使用了 512 个。一个 bit 控制一个中断 ID 的使能那么就需要 512/3216 个 GICD_ISENABLER 寄存器和16个GICD_ICENABLER来完成中断的使能与禁止。 GICD_ISENABLER0 的 bit[15:0]对应ID15 ~ 0 的 SGI 中断 GICD_ISENABLER0 的 bit[31:16]对应 ID31 ~ 16 的 PPI 中断。剩下的GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。
中断优先级
Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级两者同样是可以配置的。 GIC 控制器最多可以支持 256 个优先级数字越小优先级越高 Cortex-A7 选择了 32 个优先级。
在使用中断的时候需要初始化 GICC_PMR 寄存器设置优先级数 Cortex-A7内核支持 32 个优先级。
抢占优先级和子优先级位数设置由寄存器 GICC_BPR决定 寄存器 GICC_BPR 只有低 3 位有效其值不同抢占优先级和子优先级占用的位数也不同 有32个抢占优先级Cortex-A7 使用了 512 个中断 ID 如要设置ID40 中断的优先级为 5
GICD_IPRIORITYR[40] 5 3重点代码分析
官方SDK函数
先了解官方SDK包的API函数
GIC_Init //初始化 GIC。
GIC_EnableIRQ //使能指定的外设中断。
GIC_DisableIRQ //关闭指定的外设中断。
GIC_AcknowledgeIRQ //返回中断号。
GIC_DeactivateIRQ //无效化指定中断。
GIC_GetRunningPriority //获取当前正在运行的中断优先级。
GIC_SetPriorityGrouping //设置抢占优先级位数。
GIC_GetPriorityGrouping //获取抢占优先级位数。
GIC_SetPriority //设置指定中断的优先级。
GIC_GetPriority //获取指定中断的优先级。这些函数都在文件末尾
start.S文件
.global _start /* 全局标号 *//** 描述 _start函数首先是中断向量表的创建* 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P423 ARM Processor Modes and RegistersARM处理器模型和寄存器* 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 关闭 I/D Cache、 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, 0X87800000dsbisbmcr p15, 0, r0, c12, c0, 0dsbisb
#endif/* 设置各个模式下的栈指针* 注意IMX6UL的堆栈是向下增长的* 堆栈指针地址一定要是4字节地址对齐的* DDR范围:0X80000000~0X9FFFFFFF*//* 进入IRQ模式 */mrs r0, cpsrbic 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, cpsrbic 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, cpsrbic 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中 */
#endifb main /* 跳转到main函数 *//* 未定义中断 */
Undefined_Handler:ldr r0, Undefined_Handlerbx r0/* SVC中断 */
SVC_Handler:ldr r0, SVC_Handlerbx r0/* 预取终止中断 */
PrefAbort_Handler:ldr r0, PrefAbort_Handler bx r0/* 数据终止中断 */
DataAbort_Handler:ldr r0, DataAbort_Handlerbx r0/* 未使用的中断 */
NotUsed_Handler:ldr r0, NotUsed_Handlerbx r0/* IRQ中断重点 */
IRQ_Handler:push {lr} /* 保存lr地址 */push {r0-r3, r12} /* 保存r0-r3r12寄存器 */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 重点在复位中断和IRQ中断目前学的比较浅还不太理解但是关于下图 之前学习过ARM的三级流水过程类似下图 中断发生保存的是0x2008所以要跳回到0x2004处执行这条指令不然漏了这条指令。
自行编写中断驱动文件
irqNesting中断嵌套计数器 irqTable中断服务函数数组大小为 I.MX6U 的中断源个数160 个 int_init中断初始化函数初始化了 GIC然后初始化了中断服务函数表最终设置了中断向量表偏移 system_irqtable_init中断服务函数表初始化函数初始化 irqTable给其赋初值 start.S 中调用的 system_irqhandler 函数根据中断号在中断处理函数表 irqTable 中取出对应的中断处理函数并执行 default_irqhandler 默认中断处理函数
#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) {}
}还是没怎么看懂内容太多了我觉得暂时先搞清楚一些原理概念以及简单的外部函数接口就行内部可能在后续的学习中进行钻研
gpio内部有中断的设置
中断函数写在这里 主函数进行初始化