基于正点原子 ALPHA开发板,长文预警,建议收藏后检查
文章目录
- 主频与时钟
-
- I.MX6U系统时钟分析
-
- 7路PLL
- 时钟树
- 外设如何选择时钟?
- 需要初始化PLL和PFD
- I.MX6U系统配置
-
- 配置系统主频
- 各个PLL时钟的配置
- 其它外设时钟源配置
- C代码
- 中断
-
- Cortex-A7中断系统
-
- Cortex-A中断向量表
- 中断向量偏移
- GIC中断控制器
- IMX6U中断号
-
- 编写中断服务函数
- 编制按键中断例程。
- 修改start.S
- CP15协处理器
- 退出中断
- 中断汇编代码
-
- 复位中断函数
- IRQ中断函数
- C语言中断编写
-
- 具有中断号的中断处理函数
- irqTable结构体
- 中断函数服务表赋值函数
- 初始化函数中断,在main开始调用函数
- 中断初始化和中断过程
-
- 初始化过程中断
- 中断过程
- 定时器和延迟
-
- EPIT定时器
-
- 相关寄存器
- C程序编写
-
- 初始化
- 中断服务函数
- 外部调用
- GPT定时器
-
- 相关寄存器
- C程序编写
-
- GPT初始化
- 延时api
- 串口
-
- 6ULL串口寄存器
- C程序编写
-
- 使能/失能代码
- 初始化代码
- 发送代码
- 接收代码
- 移植C标准库函数printf
- DDR3
-
- 内存简介
- DDR3时间参数
- I.MX6U MMDC控制器
- DDR3L初始化和测试
-
- ddr_stress_tester配置文件
- 开始测试
- RGBLCD
-
- 显示原理
-
- 像素点
- 分辨率
- 像素格式
- LCD屏幕接口
- LCD时间参数和LCD时序
-
- 行时序
- 帧时序
- 像素时钟
- 显存
- 6ULL的RGB接口
-
- LCDIF控制器接口原理
- LCD设置像素时钟
- C程序编写
-
- LCD编写驱动程序
-
- LCD初始化
-
- IO复用初始化
- 时钟初始化
- LCD控制器的初始化
- 底层API
- LCD操作API函数编写
- RTC
-
- RTC原理详解
- 时间乱码的问题
-
- 问题
- 解决问题的方法
- C程序编写
-
- 相关API以及结构体
- RTC初始化
- I2C
-
- I2C简要介绍协议
- 开发板上的I2C
-
- AP3216C简介
- 6ULL I2C接口详解
- C程序编写
-
- I2C模块
-
- I2C初始化
- I2C产生主机时序
- I2C读写
- I2C读写封装
- AP3216C模块
-
- 读写
- 初始化
- 读取数据
- SPI
-
- SPI协议简单介绍
- 开发板上的SPI接口
- 6ULL SPI接口详解
- C程序编写
-
- SPI模块
-
- SPI初始化
- 读写数据
- 错误修改
- 多点电容触摸屏
-
- 多点电容触摸屏简介
- FT54x6/FT52x6电容接触芯片
- C程序编写
-
- 芯片的读写
- 初始化芯片
- 读取坐标
- 中服务
- PWM背光实验
-
- 6ULL的PWM
- C程序编写
-
- 初始化
- 写在最后
主频与时钟
恩智浦在内部ROM中配置为默认的396MHz。但是官方文档显示最高可达800M左右,有些浪费CPU资源。
##硬件原理图分析
1、32.768khz的晶振,共给RTC使用。
2、在6U的T16和T17这两个IO上接了一个24MHz的晶振。
I.MX6U系统时钟分析
7路PLL
为了方便生成时钟,6从24MHz晶振生出来7路PLL。这7路PLL中有的又生出来PFD。
PLL1:ARM PLL供给ARM内核。
PLL2:sysytem PLL,528MHz,528_PLL,此路PLL分出了4路PFD,分别为PLL2_PFD0~PFD3
PLL3: USB1 PLL,480MHz 480_PLL,此路PLL分出了4路PFD,分别为PLL3_PFD0~PFD3。
PLL4: Audio PLL,主供音频使用。
PLL5: Video PLL,主供视频外设,比如RGB LCD接口,和图像处理有关的外设。
PLL6:ENET PLL,主供网络外设。
PLL7: USB2_PLL ,480MHz,无PFD。
时钟树
太难了,原图分为了两截,我重新用ps拼接了一下
外设如何选择时钟
比如ESAI时钟源选择:
PLL4、PLL3_PFD2、PLL5、PLL3。
需要初始化的PLL和PFD
PLL1,
PLL2,以及PLL2_PFD0~PFD3.
PLL3以及PLL3_PFD0~PFD3.
一般按照时钟树里面的值进行设置。
I.MX6U系统配置
系统主频的配置
- 设置ARM内核主频为528MHz,设置CACRR寄存器的ARM_PODF位为2分频,然后设置PLL1=1056MHz即可。CACRR的bit3~0为ARM_PODF位,可设置0~7,分别对应1~8分频。应该设置CACRR寄存器的ARM_PODF=1。
- 设置PLL1=1056MHz。PLL1=pll1_sw_clk。pll1_sw_clk有两路可以选择,分别为pll1_main_clk,和step_clk,通过CCSR寄存器的pll1_sw_clk_sel位(bit2)来选择。为0的时候选择pll1_main_clk,为1的时候选step_clk。
- 设置系统时钟的时候需要给6ULL一个临时的时钟,也就是step_clk。在修改PLL1的时候需要将pll1_sw_clk切换到step_clk上。
- 设置step_clk。Step_clk也有两路来源,由CCSR的step_sel位(bit8)来设置,为0的时候设置step_clk为osc=24MHz。
- 时钟切换成功以后就可以修改PLL1的值。
- 通过CCM_ANALOG_PLL_ARM寄存器的DIV_SELECT位(bit6~0)来设置PLL1的频率,公式为:Output = fref*DIV_SEL/2因此 1056=24*DIV_SEL/2 -> DIEV_SEL=88。 设置CCM_ANALOG_PLL_ARM寄存器的DIV_SELECT位=88即可。PLL1=1056MHz
- 设置CCM_ANALOG_PLL_ARM寄存器的ENABLE位(bit13)为1,也就是使能输出。
- 在切换回PLL1之前,设置置CACRR寄存器的ARM_PODF=1!!切记。
各个PLL时钟的配置
- PLL2和PLL3。PLL2固定为528MHz,PLL3固定为480MHz。
- 初始化PLL2_PFD0~PFD3。寄存器CCM_ANALOG_PFD_528用于设置4路PFD的时钟。比如PFD0= 528*18/PFD0_FRAC。设置PFD0_FRAC位即可。比如PLL2_PFD0=352M=528*18/PFD0_FRAC,因此FPD0_FRAC=27。
- 初始化PLL3_PFD0~PFD3
其他外设时钟源配置
AHB_CLK_ROOT、PERCLK_CLK_ROOT以及IPG_CLK_ROOT。
- 因为PERCLK_CLK_ROOT和IPG_CLK_ROOT要用到AHB_CLK_ROOT,所以我们要初始化AHB_CLK_ROOT。
- AHB_CLK_ROOT的初始化。
- AHB_CLK_ROOT=132MHz。 设置 CBCMR寄存器的PRE_PERIPH_CLK_SEL位,设置CBCDR寄存器的PERIPH_CLK_SEL位0。设置 CBCDR寄存器的AHB_PODF位为2,也就是3分频,因此396/3=132MHz。
- IPG_CLK_ROOT初始化
- 设置 CBCDR寄存器IPG_PODF=1,也就是2分频。
- PERCLK_CLK_ROOT初始化
- 设置 CSCMR1寄存器的PERCLK_CLK_SEL位为0,表示PERCLK的时钟源为IPG。
C代码
/** * @description : 初始化系统时钟,设置系统时钟为528Mhz,并且设置PLL2和PLL3各个 PFD时钟,所有的时钟频率均按照I.MX6U官方手册推荐的值. * @param : 无 * @return : 无 */
void imx6u_clkinit(void)
{
unsigned int reg = 0;
/* 1、设置ARM内核时钟为528MHz */
/* 1.1、判断当前ARM内核是使用的那个时钟源启动的,正常情况下ARM内核是由pll1_sw_clk驱动的,而 * pll1_sw_clk有两个来源:pll1_main_clk和tep_clk(参考手册648页)。 * 如果我们要让ARM内核跑到528M的话那必须选择pll1_main_clk作为pll1的时钟源。 * 如果我们要修改pll1_main_clk时钟的话就必须先将pll1_sw_clk从pll1_main_clk切换到step_clk, * 当修改完pll1_main_clk以后在将pll1_sw_clk切换回pll1_main_clk。而step_clk的时钟源可以选择 * 板子上的24MHz晶振。 */
if((((CCM->CCSR) >> 2) & 0x1 ) == 0) /* 当前pll1_sw_clk使用的pll1_main_clk*/
{
CCM->CCSR &= ~(1 << 8); /* 配置step_clk时钟源为24MH OSC */
CCM->CCSR |= (1 << 2); /* 配置pll1_sw_clk时钟源为step_clk */
}
/* 1.2、设置pll1_main_clk为1056MHz,也就是528*2=1056MHZ, * 因为pll1_sw_clk进ARM内核的时候会被二分频! * 配置CCM_ANLOG->PLL_ARM寄存器 * bit13: 1 使能时钟输出 * bit[6:0]: 88, 由公式:Fout = Fin * div_select / 2.0,1056=24*div_select/2.0, * 得出:div_select= 88 */
CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0X7F); /* 配置pll1_main_clk=1056MHz */
CCM->CCSR &= ~(1 << 2); /* 将pll_sw_clk时钟重新切换回pll1_main_clk */
CCM->CACRR = 1; /* ARM内核时钟为pll1_sw_clk/2=1056/2=528Mhz */
/* 2、设置PLL2(SYS PLL)各个PFD */
reg = CCM_ANALOG->PFD_528;
reg &= ~(0X3F3F3F3F); /* 清除原来的设置 */
reg |= 32<<24; /* PLL2_PFD3=528*18/32=297Mhz */
reg |= 24<<16; /* PLL2_PFD2=528*18/24=396Mhz(DDR使用的时钟,最大400Mhz) */
reg |= 16<<8; /* PLL2_PFD1=528*18/16=594Mhz */
reg |= 27<<0; /* PLL2_PFD0=528*18/27=352Mhz */
CCM_ANALOG->PFD_528=reg; /* 设置PLL2_PFD0~3 */
/* 3、设置PLL3(USB1)各个PFD */
reg = 0; /* 清零 */
reg = CCM_ANALOG->PFD_480;
reg &= ~(0X3F3F3F3F); /* 清除原来的设置 */
reg |= 19<<24; /* PLL3_PFD3=480*18/19=454.74Mhz */
reg |= 17<<16; /* PLL3_PFD2=480*18/17=508.24Mhz */
reg |= 16<<8; /* PLL3_PFD1=480*18/16=540Mhz */
reg |= 12<<0; /* PLL3_PFD0=480*18/12=720Mhz */
CCM_ANALOG->PFD_480=reg; /* 设置PLL3_PFD0~3 */
/* 4、设置AHB时钟 最小6Mhz, 最大132Mhz (boot rom自动设置好了可以不用设置)*/
CCM->CBCMR &= ~(3 << 18); /* 清除设置*/
CCM->CBCMR |= (1 << 18); /* pre_periph_clk=PLL2_PFD2=396MHz */
CCM->CBCDR &= ~(1 << 25); /* periph_clk=pre_periph_clk=396MHz */
while(CCM->CDHIPR & (1 << 5));/* 等待握手完成 */
/* 修改AHB_PODF位的时候需要先禁止AHB_CLK_ROOT的输出,但是 * 我没有找到关闭AHB_CLK_ROOT输出的的寄存器,所以就没法设置。 * 下面设置AHB_PODF的代码仅供学习参考不能直接拿来使用!! * 内部boot rom将AHB_PODF设置为了3分频,即使我们不设置AHB_PODF, * AHB_ROOT_CLK也依旧等于396/3=132Mhz。 * 淦,就这?就这?就这? */
#if 0
/* 要先关闭AHB_ROOT_CLK输出,否则时钟设置会出错 */
CCM->CBCDR &= ~(7 << 10); /* CBCDR的AHB_PODF清零 */
CCM->CBCDR |= 2 << 10; /* AHB_PODF 3分频,AHB_CLK_ROOT=132MHz */
while(CCM->CDHIPR & (1 << 1));/* 等待握手完成 */
#endif
/* 5、设置IPG_CLK_ROOT最小3Mhz,最大66Mhz (boot rom自动设置好了可以不用设置)*/
CCM->CBCDR &= ~(3 << 8); /* CBCDR的IPG_PODF清零 */
CCM->CBCDR |= 1 << 8; /* IPG_PODF 2分频,IPG_CLK_ROOT=66MHz */
/* 6、设置PERCLK_CLK_ROOT时钟 */
CCM->CSCMR1 &= ~(1 << 6); /* PERCLK_CLK_ROOT时钟源为IPG */
CCM->CSCMR1 &= ~(7 << 0); /* PERCLK_PODF位清零,即1分频 */
}
中断
Cortex-A7中断系统
Cortex-A中断向量表
Cortex-A中断向量表有8个中断,其中重点关注IRQ。
外部中断均进入IRQ中断,根据中断号进行区分。
Cortex-A的中断向量表需要用户自己定义。
中断向量偏移
裸机例程都是从0X87800000开始的,因此要设置中断向量偏移。
GIC中断控制器
同NVIC一样,GIC用于管理Cortex-A的中断。GIC提供了开关中断,设置中断优先级。
- GIC 将众多的中断源分为分为三类:
- SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
- PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
- SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信
IMX6U中断号
为了区分不同的中断,引入了中断号。
ID0~ID15是给SGI。
ID16~ID31是给PPI。
ID32~ID1019给SPI,也就是按键中断、串口中断。。。。
6ULL支持128个中断。
中断服务函数的编写
IRQ中断服务函数的编写。
在IRQ中断服务函数里面去查找并运行的具体的外设中断服务函数。
编写按键中断例程。
KEY0使用UART1_CTS这个IO。编写UART1_CTS的中断代码。
修改start.S
添加中断向量表,编写复位中断服务函数和IRQ中断服务函数。
/**
* 描述: _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 最开始加入以下代码
*/
_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(快速中断)未定义中断 */
- 编写复位中断服务函数,内容如下:
- 关闭I,D Cache和MMU。(内部ROM已关)
- 设置处理器9中工作模式下对应的SP指针(每种工作模式都有自己独有的SP指针)。要使用中断那么必须设置IRQ模式下的SP指针。索性直接设置所有模式下的SP指针。
- 清除bss段。
- 跳到main函数
MMU负责地址映射,将CPU中虚拟地址VA映射到物理地址PA
i-cache(instruction cache):指令高速缓冲存储器
dcache(data cache):数据高速缓冲存储器
CP15协处理器
CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15 协处理器一共有
16 个 32 位寄存器。CP15 协处理器的访问通过如下另个指令完成
MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。
MCR :将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中:
@MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
@各个参数的意义
@cond:指令执行的条件码,如果忽略的话就表示无条件执行。
@opc1:协处理器要执行的操作码。
@Rt:ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器中。
@CRn:CP15 协处理器的目标寄存器。
@CRm:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将
@CRm 设置为 C0,否则结果不可预测。
@opc2:可选的协处理器特定操作码,当不需要的时候要设置为 0。
SCTLR寄存器。也就是系统控制寄存器,此寄存器bit0用于打开和关闭MMU,bit1控制对齐,bit2控制D Cache的打开和关闭。Bit11用于控制分支预测。Bit12用于控制I Cache。
下图
所以SCTLR的读取方式如下
mrc p15, 0, r0, c1, c0, 0 /* 读取SCTLR寄存器到R0中*/
mcr p15, 0, r0, c1, c0, 0 /* 将r0中的值写入到SCTLR寄存器中 */
将新的中断向量表首地址写入到CP15协处理器的VBAR寄存器。
同理,可得VBAR寄存器的访问方法如下:
@mcr{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
mrc p15,0,r0,c12,c0,0
mcr p15,0,r0,c12,c0,0
mrc p15, 4, r1, c15, c0, 0
读取CBAR寄存器。CBAR寄存器保存了GIC控制器的寄存器组首地址。GIC寄存器组偏移0x10000x1fff为GIC的分发器。0x20000x3fff为CPU接口端。
代码中,R1寄存器保存着GIC控制器的CPU接口端基地址。读取CPU接口段的GICC_IAR寄存器的值保存到R0寄存器里面。
GICC_IAR的bit9~0存放着中断ID号,然后跳转到对应的中断处理函数。
system_irqhandler就是具体的中断处理函数,此函数有一个参数,为GICC_IAR寄存器的值。
system_irqhandler处理完具体的中断以后,需要将对应的中断ID值写入到GICC_EOIR寄存器里面。
退出中断
subs pc, lr, #4 /* 将lr-4赋给pc */
中断处理完成以后就要重新返回到曾经被中断打断的地方运行,这里为什么要将lr-4 然后赋给 pc 呢?而不是直接将 lr 赋值给 pc?ARM 的指令是三级流水线:取指、译指、执行,pc 指向的是正在取值的地址,这就是很多书上说的 pc=当前执行指令地址+8。
比如下面代码示例:
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)开始运行,那么就有一个指令没有执行,那就是地址 0X2004 处的指令“MOV R2, R3”,显然这是一个很严重的错误!所以就需要将 lr-4 赋值给 pc,也就是 pc=0X2004,从指令“MOV R2,R3”开始执行。
中断汇编代码
复位中断函数
复位中断函数会在上电时执行
/* 复位中断 */
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
/* 汇编版本设置中断向量表偏移 该部分在c语言的中断编写种也有写到,因此注释*/
ldr r0, =0X87800000
dsb
isb
mcr p15, 0, r0, c12, c0, 0
dsb @数据同步指令,有该指定确保之前的数据全部写入或者读取成功
isb @指令同步指令,有该指定确保之前的指令全部写入或者读取成功
#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函数 */
IRQ中断函数
IRQ_Handler:
push {lr} /* 保存lr地址 ,lr为当前pc的值*/
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 */
C语言中断编写
带有中断号的中断处理函数
/** * @brief : 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--; /* 中断执行完成,中断嵌套寄存器减一 */
}
irqTable结构体
/* 中断服务函数形式 */
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;
/* 中断嵌套计数器 */
static unsigned int irqNesting;
/* 中断服务函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
中断函数服务表赋值函数
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);
}
}
/** * @brief : 给指定的中断号注册中断服务函数 * @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;
}
/** * @brief : 默认中断服务函数 * @param - giccIar : 中断号 * @param - usrParam : 中断服务处理函数参数 * @return : 无 */
void default_irqhandler(unsigned int giccIar, void *userParam)
{
while(1)
{
}
}
/** * IRQn_Type 为core_ca7中的关于终端号的定义 * 这两个函数实现了IRQ中断服务函数的赋值 * 此处所有的中断服务函数均使用了默认的中断服务函数以作示例,在具体项目中,需要对其具体定义 */
中断初始化函数,在main函数最开始进行调用中
/** * @brief : 中断初始化函数 * @param : 无 * @return : 无 */
void int_init(void)
{
GIC_Init(); /* 初始化GIC 此函数声明在core_ca7.h */
system_irqtable_init(); /* 初始化中断表 */
__set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移,偏移到起始地址 */
}
中断初始化以及中断流程
中断初始化流程
上电执行汇编语言中的 _start函数, 在函数中定义了a7内核支持的八种中断服务函数(其实是7个,有一个未使用)。
执行复位函数,因为初始化了中断服务函数,因此在之后执行复位中断函数,先关闭全局中断以及保存在通用寄存器的中断服务函数指针、I cache、D cache以及MMU也需要关闭。之后定义9种运行状态下的sp指针(栈指针),跳转到c语言中的main函数
main函数执行int_init函数,初始化所有的中断服务函数。
中断过程
当中断发生时,进入到IRQ_Handler函数,保存现场
IRQ_Handler判断中断ID号,进入中断ID对应的中断服务函数
执行用户指定的中断服务内容
恢复现场
定时器与延时
EPIT定时器
实现周期性的中断以及定时功能。
- EPIT是32位的一个向下计数器。
- EPIT的时钟源可以选择,例程选择ipg_clk=66MHz。
- 可以对时钟源进行分频,12位的分频器,0~4095分别代表1~4096分频。
- 开启定时器以后,计数寄存器会每个时钟减1,如果和比较寄存器里面的值相等的话就会触发中断。
- EPIT有两种工作模式:Set-add-forget,一个是free-runing
- 5、6ULL有两个EPIT定时器。
EPIT_CR寄存器用于配置EPIT。
相关寄存器
EPIT_CR bit0为1,设置EPIT使能,bit1为1,设置计数器的初始值为记载寄存器的值。Bit2为1使能比较中断,bit3为1设置定时器工作在set-and-forget模式下。Bit15~bit4设置分频值。Bit25:24设置时钟源的选择,我们设置为1,那么EPIT的时钟源就为ipg_clock=66MHz
EPIT_SR寄存器,只有bit0有效,表示中断状态,写1清零。当OCIF位为1的时候表示中断发生,为0的时候表示中断未发生。我们处理完定时器中断以后一定要清除中断标志位。
EPIT_LR寄存器设置计数器的加载值。计数器每次计时到0以后就会读取LR寄存器的值重新开始计时。
CMPR比较计数器,当计数器的值和CMPR相等以后就会产生比较中断。
使用EPIT实现500ms周期的定时器。500ms进入一次中断。
C程序编写
初始化
/** * @brief : 初始化EPIT定时器. * EPIT定时器是32位向下计数器,时钟源使用ipg=66Mhz * @param - frac : 分频值,范围为0~4095,分别对应1~4096分频。 * @param - value : 倒计数值。 * @return : 无 */
void epit1_init(unsigned int frac, unsigned int value)
{
if(frac > 0XFFF)
frac = 0XFFF;
EPIT1->CR = 0; /* 先清零CR寄存器 */
/* * CR寄存器: * bit25:24 01 时钟源选择Peripheral clock=66MHz * bit15:4 frac 分频值 * bit3: 1 当计数器到0的话从LR重新加载数值 * bit2: 1 比较中断使能 * bit1: 1 初始计数值来源于LR寄存器值 * bit0: 0 先关闭EPIT1 */
EPIT1->CR = (1<<24 | frac << 4 | 1<<3 | 1<<2 | 1<<1);
EPIT1->LR = value; /* 倒计数值 */
EPIT1->CMPR = 0; /* 比较寄存器,当计数器值和此寄存器值相等的话就会产生中断 */
/* 使能GIC中对应的中断 */
GIC_EnableIRQ(EPIT1_IRQn);
/* 注册中断服务函数 */
system_register_irqhandler(EPIT1_IRQn, (system_irq_handler_t)epit1_irqhandler, NULL);
EPIT1->CR |= 1<<0; /* 使能EPIT1 */
}
中断服务函数
/** * @brief : EPIT中断处理函数 */
void epit1_irqhandler(void)
{
if(EPIT1->SR & (1<<0)) /* 判断比较事件发生 */
{
/* User code begin*/
/* User code end */
}
EPIT1->SR |= 1<<0; /* 清除中断标志位 */
}
外部调用
epit1_init(0, 66000000/2); /* 初始化EPIT1定时器,1分频计数值为:66000000/2,也就是定时周期为500ms。*/
GPT定时器
正点原子使用该定时器实现高精度精准阻塞延时
- GPT定时器是32位向上计数器。
- GPT定时器有捕获的功能。
- GPT定时器支持比较输出或中断功能。
- GPT定时器有一个12位的分频器。
- GPT时钟源可以选择,这里我们使用ipg_clk=66M作为GPT的时钟源。
- GPT定时器有两种工作模式:restart和free-run。
Restart模式下:定时器计数值和比较寄存器OCR的值相等的话定时器就会重新从0开始计时。注意!只有比较通道1才有此功能。
Free-run模式:所有三个输出比较通道都适用。从0开始一直加到0xffffffff,然后重新从0开始,周而复始。
相关寄存器
GPT_CR寄存器,bit0为GPT使能位,为0的时候关闭GPT,为1的时候使能GPT。Bit1确定GPT定时器计数器的初始值,为0的时候表示GPT定时器计数值默认为上次关闭的时候遗留的值,为1的话计数值为0。Bit8~6为时钟源的选择,设置为1,表示GPT时钟源为ipg_clk=66MHz。bit9设置GPT定时器工作模式,为0的时候工作在restart模式,为1的时候工作在free-run模式。Bit15软件复位。
GPT_PR寄存器的bit110为分频值,可设置0-4095,表示14096分频。
GPT_SR寄存器,bit5表示溢出发生,bit4和bit3分别为输入通道2和1的捕获中断标志位。Bit20,也就是OF3OF1为比较中断。
GPT_IR寄存器,也就是中断使能寄存器
C程序编写
GPT初始化
/** * @brief : 延时有关硬件初始化,主要是GPT定时器 GPT定时器时钟源选择ipg_clk=66Mhz * @param : 无 * @return : 无 */ void delay_init(void) { GPT1->CR = 0; /* 清零,bit0也为0,即停止GPT */ GPT1->CR = 1 << 15; /* bit15置1进入软复位 */ while((GPT1->CR >> 15) & 0x01); /*等待复位完成 */ /** * GPT的CR寄存器,GPT通用设置 * bit22:20 000 输出比较1的输出功能关闭,也就是对应的引脚没反应 * bit9: 0 Restart模式,当CNT等于OCR1的时候就产生中断 * bit8:6 001 GPT时钟源选择ipg_clk=66Mhz * bit */ GPT1->CR = (1<<6); 标签:
嵌入式电容触摸液晶显示器3rt电容切换专用接触器