看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动GPIO中断《包括时钟讲解》) 2023.5.9
目录
- 前言
- 整体文件结构
- 源码分析(保姆级讲解)
- 中断初始化部分
- 初始化GIC控制器
- 初始化中断向量表
- 设置中断向量表偏移
- 系统时钟初始化部分
- 使能所有的时钟部分
- led初始化部分
- beep初始化部分
- key初始化部分
- 按键中断初始化部分
- 按键中断服务函数部分
- while循环部分
- 最终编译验证
- 结束语
前言
首先我们在使用开发板开发的过程中难免会碰到使用中断的场景(即判断是哪种中断,并且进入对应的中断服务函数),所以我们接下来开始学习这种机制。
整体文件结构
源码分析(保姆级讲解)
中断初始化部分
初始化GIC控制器
首先我们使用中断,需要先对中断进行初始化。
GIC_Init();
该函数讲解开始:
//这个函数主要对GIC
进行初始化,那什么是GIC
呢?我们在stm32
的开发过程中配置中断会使用到NVIC
这个中断管理机构,全称叫做 Nested Vectored Interrupt Controller
,那么对应的在I.MX6U
中的中断管理机构叫做GIC
,全称是 general interrupt controller
。当 GIC
接收到外部中断信号以后就会报给 ARM 内核
,但是ARM 内核
只提供了四个信号给 GIC
来汇报中断情况:VFIQ
、VIRQ
、FIQ
和 IRQ
,如下图所示:
在这四个中断中,我们只会使用到一个中断即上图中的IRQ
中断(外部中断)。
接下来我们就看看是如何对GIC
这个中断管理机构进行配置的?
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
GIC_Type *gic = (GIC_Type *)(__get_CBAR() & 0xFFFF0000UL);
//结构体 GIC_Type
就是 GIC
控制器,列举出了 GIC
控制器的所有寄存器,可以通过结构体 GIC_Type
来访问 GIC
的所有寄存器。
其中GIC_Type
结构体如下所示:
typedef struct
{uint32_t RESERVED0[1024];__IOM uint32_t D_CTLR; /*!< Offset: 0x1000 (R/W) Distributor Control Register */__IM uint32_t D_TYPER; /*!< Offset: 0x1004 (R/ ) Interrupt Controller Type Register */__IM uint32_t D_IIDR; /*!< Offset: 0x1008 (R/ ) Distributor Implementer Identification Register */uint32_t RESERVED1[29];__IOM uint32_t D_IGROUPR[16]; /*!< Offset: 0x1080 - 0x0BC (R/W) Interrupt Group Registers */uint32_t RESERVED2[16];__IOM uint32_t D_ISENABLER[16]; /*!< Offset: 0x1100 - 0x13C (R/W) Interrupt Set-Enable Registers */uint32_t RESERVED3[16];__IOM uint32_t D_ICENABLER[16]; /*!< Offset: 0x1180 - 0x1BC (R/W) Interrupt Clear-Enable Registers */uint32_t RESERVED4[16];__IOM uint32_t D_ISPENDR[16]; /*!< Offset: 0x1200 - 0x23C (R/W) Interrupt Set-Pending Registers */uint32_t RESERVED5[16];__IOM uint32_t D_ICPENDR[16]; /*!< Offset: 0x1280 - 0x2BC (R/W) Interrupt Clear-Pending Registers */uint32_t RESERVED6[16];__IOM uint32_t D_ISACTIVER[16]; /*!< Offset: 0x1300 - 0x33C (R/W) Interrupt Set-Active Registers */uint32_t RESERVED7[16];__IOM uint32_t D_ICACTIVER[16]; /*!< Offset: 0x1380 - 0x3BC (R/W) Interrupt Clear-Active Registers */uint32_t RESERVED8[16];__IOM uint8_t D_IPRIORITYR[512]; /*!< Offset: 0x1400 - 0x5FC (R/W) Interrupt Priority Registers */uint32_t RESERVED9[128];__IOM uint8_t D_ITARGETSR[512]; /*!< Offset: 0x1800 - 0x9FC (R/W) Interrupt Targets Registers */uint32_t RESERVED10[128];__IOM uint32_t D_ICFGR[32]; /*!< Offset: 0x1C00 - 0xC7C (R/W) Interrupt configuration registers */uint32_t RESERVED11[32];__IM uint32_t D_PPISR; /*!< Offset: 0x1D00 (R/ ) Private Peripheral Interrupt Status Register */__IM uint32_t D_SPISR[15]; /*!< Offset: 0x1D04 - 0xD3C (R/ ) Shared Peripheral Interrupt Status Registers */uint32_t RESERVED12[112];__OM uint32_t D_SGIR; /*!< Offset: 0x1F00 ( /W) Software Generated Interrupt Register */uint32_t RESERVED13[3];__IOM uint8_t D_CPENDSGIR[16]; /*!< Offset: 0x1F10 - 0xF1C (R/W) SGI Clear-Pending Registers */__IOM uint8_t D_SPENDSGIR[16]; /*!< Offset: 0x1F20 - 0xF2C (R/W) SGI Set-Pending Registers */uint32_t RESERVED14[40];__IM uint32_t D_PIDR4; /*!< Offset: 0x1FD0 (R/ ) Peripheral ID4 Register */__IM uint32_t D_PIDR5; /*!< Offset: 0x1FD4 (R/ ) Peripheral ID5 Register */__IM uint32_t D_PIDR6; /*!< Offset: 0x1FD8 (R/ ) Peripheral ID6 Register */__IM uint32_t D_PIDR7; /*!< Offset: 0x1FDC (R/ ) Peripheral ID7 Register */__IM uint32_t D_PIDR0; /*!< Offset: 0x1FE0 (R/ ) Peripheral ID0 Register */__IM uint32_t D_PIDR1; /*!< Offset: 0x1FE4 (R/ ) Peripheral ID1 Register */__IM uint32_t D_PIDR2; /*!< Offset: 0x1FE8 (R/ ) Peripheral ID2 Register */__IM uint32_t D_PIDR3; /*!< Offset: 0x1FEC (R/ ) Peripheral ID3 Register */__IM uint32_t D_CIDR0; /*!< Offset: 0x1FF0 (R/ ) Component ID0 Register */__IM uint32_t D_CIDR1; /*!< Offset: 0x1FF4 (R/ ) Component ID1 Register */__IM uint32_t D_CIDR2; /*!< Offset: 0x1FF8 (R/ ) Component ID2 Register */__IM uint32_t D_CIDR3; /*!< Offset: 0x1FFC (R/ ) Component ID3 Register */__IOM uint32_t C_CTLR; /*!< Offset: 0x2000 (R/W) CPU Interface Control Register */__IOM uint32_t C_PMR; /*!< Offset: 0x2004 (R/W) Interrupt Priority Mask Register */__IOM uint32_t C_BPR; /*!< Offset: 0x2008 (R/W) Binary Point Register */__IM uint32_t C_IAR; /*!< Offset: 0x200C (R/ ) Interrupt Acknowledge Register */__OM uint32_t C_EOIR; /*!< Offset: 0x2010 ( /W) End Of Interrupt Register */__IM uint32_t C_RPR; /*!< Offset: 0x2014 (R/ ) Running Priority Register */__IM uint32_t C_HPPIR; /*!< Offset: 0x2018 (R/ ) Highest Priority Pending Interrupt Register */__IOM uint32_t C_ABPR; /*!< Offset: 0x201C (R/W) Aliased Binary Point Register */__IM uint32_t C_AIAR; /*!< Offset: 0x2020 (R/ ) Aliased Interrupt Acknowledge Register */__OM uint32_t C_AEOIR; /*!< Offset: 0x2024 ( /W) Aliased End Of Interrupt Register */__IM uint32_t C_AHPPIR; /*!< Offset: 0x2028 (R/ ) Aliased Highest Priority Pending Interrupt Register */uint32_t RESERVED15[41];__IOM uint32_t C_APR0; /*!< Offset: 0x20D0 (R/W) Active Priority Register */uint32_t RESERVED16[3];__IOM uint32_t C_NSAPR0; /*!< Offset: 0x20E0 (R/W) Non-secure Active Priority Register */uint32_t RESERVED17[6];__IM uint32_t C_IIDR; /*!< Offset: 0x20FC (R/ ) CPU Interface Identification Register */uint32_t RESERVED18[960];__OM uint32_t C_DIR; /*!< Offset: 0x3000 ( /W) Deactivate Interrupt Register */
} GIC_Type;
从上述代码中可知,该GIC
寄存器中分为两部分,分别是分发器端
和CPU接口端
。比如我们想要访问C_CTLR
该寄存器,相对于GIC基地址
的偏移为0x2000
,同样的,获取到GIC基地址
之后只需要加上0x2000
即可访问GIC
的CPU接口段寄存器
。那么问题来了,GIC控制器
的寄存器基地址在哪里,这里我们就需要用到 Cortex-A
的 CP15
协处理器了。那什么是CP15
协处理器?
CP15 协处理器
一般用于存储系统管理
,但是在中断中也会使用到,CP15 协处理器
一共有16
个 32 位寄存器
。CP15 协处理器
的访问通过如下另个指令完成
CP15协处理器
有16个32位寄存器
,c0~c15
,由于我们在本例中使用到GPIO中断
,所以我们暂时先看c0、c1、c12 和 c15
这四个寄
存器。具体怎么使用会结合着代码进行讲解。
进入__get_CBAR()
这个函数中:
这个函数的作用为获取GIC
的基地址,我们使用到c15
这个寄存器,具体含义如下:
由上图可知,当op1 = 4 ,crm = c0,op2 = 0
时,代表此c15
寄存器为CBAR
寄存器。
/* C语言实现MRC指令 */
#define __MRC(coproc, opcode_1, CRn, CRm, opcode_2) \({ \uint32_t __dst; \__ASM volatile ("MRC " __STRINGIFY(p##coproc) ", " __STRINGIFY(opcode_1) ", " \"%0, " __STRINGIFY(c##CRn) ", " __STRINGIFY(c##CRm) ", " \__STRINGIFY(opcode_2) \: "=r" (__dst) ); \__dst; \})
CBAR寄存器
如下图所示:
从上图可知,通过MRC
设置c15
为CBAR
寄存器后,需要获取该寄存器的高16位
,所以在代码中需要和0xFFFF0000UL
进行“与”
操作。
获取到GIC基地址
后,使用gic指针
指向该基地址。并且该指针的类型为GIC_Type
。
irqRegs = (gic->D_TYPER & 0x1FUL) + 1;
其中(gic->D_TYPER & 0x1FUL)
用来获取其中断数量,即获取该寄存器的低5位。具体寄存器如下:
加1
是因为比如我们实际获取的中断数是128
个,但是由于是从0
开始计数,所以实际输出值为127
,所以我们需要加1
。
for (i = 0; i < irqRegs; i++)gic->D_ICENABLER[i] = 0xFFFFFFFFUL;
//我们家来需要看看实际情况的内部GIC控制器是什么样子的?
上图中的左侧部分是中断源,中间部分是GIC控制器
,最右侧就是中断控制器向处理器内核发送中断信息。其中GIC
将众多的中断源分为三类
,并且已经在上图中用红框标出。
所以上述代码就是在对所有的PPI
,SGI
和SPI
这三种中断源进行不使能操作。并且该寄存器如下图所示:
gic->C_PMR = (0xFFUL << (8 - __GIC_PRIO_BITS)) & 0xFFUL;
//其中
所以上述代码相当于
gic->C_PMR = (0xFFUL << 3) & 0xFFUL;
相当于
gic->C_PMR = 0xF8;
其中C_PWR
寄存器具体配置如下图所示:
所以由上图可知,将优先级等级设置为32
。
gic->C_BPR = 7 - __GIC_PRIO_BITS;
上述代码相当于
gic->C_BPR = 2;
并且该寄存器如下图所示:
即设置没有子优先级,所有优先级级别都允许抢占。
gic->D_CTLR = 1UL;
其中该寄存器具体配置如下所示:
启用group0
分发器。
gic->C_CTLR = 1UL;
其中该寄存器具体配置如下所示:
启用group0
接口端。
至此GIC_Init函数讲解完毕
初始化中断向量表
system_irqtable_init();
该函数讲解开始:
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);}
}
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
#define NUMBER_OF_INT_VECTORS 160 /**< Number of interrupts in the Vector table */
//这里为什么是160
呢?
由上图GIC
控制器可知,需要去处理很多中断源,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID
,这些 ID
就是中断 ID
。每一个cpu
最多支持1020
个中断ID
。中断 ID
号为 ID0~ID1019
。这 1020
个 ID
包含了 PPI、SPI 和 SGI
,那么这三类中断是如何分配这 1020
个中断 ID
的呢?这 1020
个 ID
分配如下:
ID0~ID15
:这 16 个 ID
分配给 SGI
。
ID16~ID31
:这 16 个 ID
分配给 PPI
。
ID32~ID1019
:这 988 个 ID
分配给 SPI
,像 GPIO
中断、串口中断等这些外部中断。
至于具体到某个 ID
对应哪个中断那就由半导体厂商根据实际情况去定义了。比如 I.MX6U
的总共使用了 128
个中断 ID
,加上前面属于 PPI
和 SGI
的 32 个 ID
,I.MX6U
的中断源共有 128+32=160个
,所以设置为160
个。
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;
}
所以就需要针对每一个中断源进行配置。
该函数的参数主要有三个,分别是IRQn_Type irq
,system_irq_handler_t handler
和void *userParam
。
IRQn_Type irq
:其中IRQn_Type
是关于160
个中断源的结构体。irq
是要注册的中断号。
system_irq_handler_t handler
:其中system_irq_handler_t
是一个中断服务函数形式。
void *userParam
:中断服务处理函数参数
irqTable[irq].irqHandler = handler;
传入该函数的hander等于default_irqhandler
void default_irqhandler(unsigned int giccIar, void *userParam)
{while(1) {}
}
由于只是初始化部分,所以该中断服务函数里面什么也不做。
irqTable[irq].userParam = userParam;
传入该函数的参数userParam
等于NULL
。
至此system_irqtable_init();函数讲解完毕
设置中断向量表偏移
__set_VBAR((uint32_t)0x87800000);
该函数讲解开始:
设置中断向量表的偏移,时期偏移到起始地址,即0x87800000
,即程序起始运行地址。
系统时钟初始化部分
void imx6u_clkinit(void)
{unsigned int reg = 0;/* 1、设置ARM内核时钟为792MHz *//* 1.1、判断当前ARM内核是使用的那个时钟源启动的,正常情况下ARM内核是由pll1_sw_clk驱动的,而* pll1_sw_clk有两个来源:pll1_main_clk和tep_clk。* 如果我们要让ARM内核跑到792M的话那必须选择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为792MHz* 因为pll1_sw_clk进ARM内核的时候会被二分频!* 配置CCM_ANLOG->PLL_ARM寄存器* bit13: 1 使能时钟输出* bit[6:0]: 66, 由公式:Fout = Fin * div_select / 2.0,792=24*div_select/2.0,* 得出:div_select= 66 */CCM_ANALOG->PLL_ARM = (1 << 13) | ((66 << 0) & 0X7F); /* 配置pll1_main_clk=792MHz */CCM->CCSR &= ~(1 << 2); /* 将pll_sw_clk时钟重新切换回pll1_main_clk */CCM->CACRR = 0; /* ARM内核时钟为pll1_sw_clk/1=792/1=792Mhz *//* 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分频 */
}
好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!
unsigned int reg = 0;
//声明一个无整型变量reg
,并且赋值为0
。
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 */}CCM_ANALOG->PLL_ARM = (1 << 13) | ((66 << 0) & 0X7F); /* 配置pll1_main_clk=792MHz */
CCM->CCSR &= ~(1 << 2); /* 将pll_sw_clk时钟重新切换回pll1_main_clk */
CCM->CACRR = 0; /* ARM内核时钟为pll1_sw_clk/1=792/1=792Mhz */
//这一部分主要是设置ARM
内核时钟的,那么ARM
内核时钟是由什么来决定的呢?
由上图可知,其内核时钟源来自于PLL1
,此时PLL1为996MHz
,然后经过分频操作后,最终就会配置到ARM
上。
在本例中设置ARM内核时钟为792MHz
,所以我们肯定要更改PLL1
时钟频率,那么在修改之前,我们需要先将内核时钟源改为其他的时钟源,那么我们看一下可修改的时钟源有哪些?
①、pll1_sw_clk
也就是 PLL1
的最终输出频率。
②、此处是一个选择器,选择 pll1_sw_clk
的时钟源,由寄存器 CCM_CCSR
的PLL1_SW_CLK_SEL
位决定 pll1_sw_clk
是选择 pll1_main_clk
还是 step_clk
。正常情况下应该选择 pll1_main_clk
,但是如果要对 pll1_main_clk(PLL1)
的频率进行调整的话,比如我们要设置
PLL1=1056MHz
,此时就要先将 pll1_sw_clk
切换到 step_clk
上。等 pll1_main_clk
调整完成以后再切换回来。
③、此处也是一个选择器,选择 step_clk
的时钟源,由寄存器 CCM_CCSR
的 STEP_SEL
位来决定 step_clk
是选择 osc_clk
还是 secondary_clk
。一般选择 osc_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 */}
寄存器CCM_CCSR
的配置如下所示:
由上图可知,PLL1_SW_CLK_SEL
是第2
位。所以要进行右移2
位,然后变成最低位,如果是0
,则代表是pll1_main_clk
,如果是1
,则代表是step_clk
。所以如果判断是pll1_main_clk
,我们则需要更改成step_clk
。将step_clk
的时钟源为24M
的晶振。
CCM_ANALOG->PLL_ARM = (1 << 13) | ((66 << 0) & 0X7F); /* 配置pll1_main_clk=792MHz */
该寄存器的配置如下:
将第13
位设置成1
代表使能PLL1
输出。
并且本例中设置ARM
内核时钟为792MHz
,即PLL1
设置为792MHz
,PLL1 CLK = Fin *div_seclec/2.0
,Fin=24MHz
,所以可以计算div_seclec为66
。
CCM->CCSR &= ~(1 << 2); /* 将pll_sw_clk时钟重新切换回pll1_main_clk */
即将第2
位设置成1
即可。
CCM->CACRR = 0; /* ARM内核时钟为pll1_sw_clk/1=792/1=792Mhz */
由于我们已经将PLL1
设置成792MHz
,那么我们只需要将分频系数设置为1
即可。该寄存器配置如下图所示:
/* 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 */
由上图可知,我们已经成功配置了PLL1
时钟频率,那么接下来我们开始配置这PLL2
和PLL3
,其中由上图时钟树可知,PLL2
有4
路PFD
时钟,PLL3
有4路PFD
时钟。
我们先设置PLL2
的4路PFD
时钟。
reg = CCM_ANALOG->PFD_528;
reg &= ~(0X3F3F3F3F); /* 清除原来的设置 */
由上图可知,存器 CCM_ANALOG_PFD_528n
其实分为四组
,分别对应PFD0~PFD3
,每组 8 个 bit
。
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 */
我们就以 PFD0
为例,看一下如何设置 PLL2_PFD0
的频率。PFD0
对应的寄存器位如下:
PFD0_FRAC: PLL2_PFD0
的分频数,PLL2_PFD0
的计算公式为 528*18/PFD0_FRAC
,此为 可 设 置 的 范 围 为 12~35
。 如 果 PLL2_PFD0
的 频 率 要 设 置 为 352MHz
的 话PFD0_FRAC=528*18/352=27。
PFD0_STABLE:
此位为只读位,可以通过读取此位判断 PLL2_PFD0
是否稳定。
PFD0_CLKGATE: PLL2_PFD0
输出使能位,为 1
的时候关闭 PLL2_PFD0
的输出,为 0
的时候使能输出。
/* 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 */
我们先设置PLL3
的4
路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 */
寄存器 CCM_ANALOG_PFD_480n
和 CCM_ANALOG_PFD_528n
的结构是一模一样的,只是一个是 PLL2
的,一个是 PLL3
的。寄存器位的含义也是一样的,只是 频 率 计 算 公 式 不 同 ,
比 如 PLL3_PFDX=480*18/PFDX_FRAC(X=0~3) 。
如果PLL3_PFD0=720MHz的话,PFD0_FRAC=12;
如果 PLL3_PFD1=540MHz 的话,PFD1_FRAC=16;
如果PLL3_PFD2=508.2MHz 的话,PFD2_FRAC=17;
如果 PLL3_PFD3=454.7MHz的话,PFD3_FRAC=19。
/* 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));/* 等待握手完成 */
/* 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分频 */
其时钟结构图如下图所示:
从图中可以看出
使能所有的时钟部分
void clk_enable(void)
{CCM->CCGR0 = 0XFFFFFFFF;CCM->CCGR1 = 0XFFFFFFFF;CCM->CCGR2 = 0XFFFFFFFF;CCM->CCGR3 = 0XFFFFFFFF;CCM->CCGR4 = 0XFFFFFFFF;CCM->CCGR5 = 0XFFFFFFFF;CCM->CCGR6 = 0XFFFFFFFF;
}
这里举CCGR0
为例。
led初始化部分
led_init(); /* 初始化led */
这个部分在之前文章中已经讲过了
看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动LED灯《使用汇编语言编写》) 2023.4.17
看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动LED灯《使用C语言编写》) 2023.4.18
beep初始化部分
void beep_init(void)
{/* 1、初始化IO复用,复用为GPIO5_IO01 */IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0); /* 2、、配置GPIO1_IO03的IO属性 *bit 16:0 HYS关闭*bit [15:14]: 00 默认下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驱动能力*bit [0]: 0 低转换率*/IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0X10B0);/* 3、初始化GPIO,GPIO5_IO01设置为输出 */GPIO5->GDIR |= (1 << 1); /* 4、设置GPIO5_IO01输出高电平,关闭蜂鸣器 */GPIO5->DR |= (1 << 1);
}
key初始化部分
void key_init(void)
{ gpio_pin_config_t key_config;/* 1、初始化IO复用, 复用为GPIO1_IO18 */IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);/* 2、、配置UART1_CTS_B的IO属性 *bit 16:0 HYS关闭*bit [15:14]: 11 默认22K上拉*bit [13]: 1 pull功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 000 关闭输出*bit [0]: 0 低转换率*/IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);/* 3、初始化GPIO *///GPIO1->GDIR &= ~(1 << 18); /* GPIO1_IO18设置为输入 */ key_config.direction = kGPIO_DigitalInput;gpio_init(GPIO1,18, &key_config);}
按键中断初始化部分
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的中断功能 */
}
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 复用为GPIO1_IO18 */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);
由上图可知,key
所连接的接口为UART1_CTS
,所以需要将UART1_CTS
设置复用功能为GPIO
功能。并且将其GPIO1_IO18
为下降沿触发中断。
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */
调用函数 GIC_EnableIRQ
来使能 GPIO_IO18
所对应的中断总开关,I.MX6U
中 GPIO1_IO16~IO31
这 16 个 IO
共用 ID99
。
system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 注册中断服务函数 */
调用函数 system_register_irqhandler
注册 ID99
所对应的中断处理函数,其中GPIO1_Combined_16_31_IRQn
为注册的中断号,gpio1_io18_irqhandler
为注册的中断服务函数,当产生这个中断后,会自动进入gpio1_io18_irqhandler
这个中断服务函数中。
gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */
通过函数 gpio_enableint
使能 GPIO1_IO18
这个 IO
对应的中断。
按键中断服务函数部分
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); /* 清除中断标志位 */
}
调用函数 gpio_clearintflags
来清除 GPIO1_IO18
的中断标志位
while循环部分
while(1) { state = !state;led_switch(LED0, state);delay(500);}
每隔500ms
,led
灯亮灭。
最终编译验证
按下 KEY
就会打开蜂鸣器,再次按下就会关闭蜂鸣器。LED0
会不断闪烁,周期大约 500ms
。
结束语
如果觉得这篇文章还不错的话,记得点赞 ,支持下!!!
相关文章:
看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动GPIO中断《包括时钟讲解》) 2023.5.9
目录 前言整体文件结构源码分析(保姆级讲解)中断初始化部分初始化GIC控制器初始化中断向量表设置中断向量表偏移 系统时钟初始化部分使能所有的时钟部分led初始化部分beep初始化部分key初始化部分按键中断初始化部分按键中断服务函数部分 while循环部分 …...
MySql -- 事务
目录 1.概念 2.事务的运用场景 3.事务的四大特点 4.执行事务带来的问题 4.1 脏读 4.2 不可重复度 4.3 幻读 5. MySQL中事务的隔离级别 1.概念 事务就是把若干个独立操作打包成一个整体而诞生的一种功能. 2.事务的运用场景 比如:A——>B 转账500 A的余额-500…...
关于大模型对未来影响的一点看法
人们总是高估了未来一到两年的变化,低估了未来十年的变革。 ---比尔盖茨 近来OpenAI的GPT技术可以说在全球都带来了巨大的影响,也让大家看到了什么叫大力出奇迹。chatGPT和GPT4的能力给了大家很大的震撼,其流畅自如、逻辑清晰、出众的能力&am…...
Android - 约束布局 ConstraintLayout
一、概念 解决布局嵌套过多的问题,采用方向约束的方式对控件进行定位。 二、位置约束 2.1 位置 至少要保证水平和垂直方向都至少有一个约束才能确定控件的位置。 layout_constraintLeft_toLeftOf我的左边,与XXX左边对齐。layout_constraintLeft_toRight…...
Addictive Multiplicative in NN
特征交叉是特征工程中的重要环节,在以表格型(或结构化)数据为输入的建模中起到了很关键的作用。 特征交互的作用,一是尽可能挖掘对目标有效的模式、特征,二是具有较好的可解释性,三是能够将对数据的洞见引…...
LeetCode 1206. 实现跳表
不使用任何库函数,设计一个跳表。 跳表是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。 例如,一个跳表包…...
离散数学_九章:关系(2)
9.2 n元关系及其应用 1、n元关系,关系的域,关系的阶2、数据库和关系 1. 数据库 2. 主键 3. 复合主键 3、n元关系的运算 1. 选择运算 (Select) 2. 投影运算 (Project) 3. 连接运算 (Join) n元关系:两个以上集合的元素间的关系 1、n元关系…...
[ubuntu][原创]通过apt方式去安装libnccl库
ubuntu18.04版本安装流程: wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-ubuntu1804.pin sudo mv cuda-ubuntu1804.pin /etc/apt/preferences.d/cuda-repository-pin-600 sudo apt-key adv --fetch-keys https://develo…...
YonLinker连接集成平台构建新一代产业互联根基
近日,由用友公司主办的“2023用友BIP技术大会“在用友产业园(北京)盛大召开,用友介绍了更懂企业业务的用友BIP-iuap平台,并发布了全面数智化能力体系,助力企业升级数智化底座,加强加速数智化推进…...
泛型的详解
泛型的理解和好处 首先我们先来看看泛型的好处 1)编译时,检查添加元素的类型,提高了安全性 2)减少了类型转换的次数,提高效率[说明] 不使用泛型 Dog -> Object -> Dog//放入到ArrayList 会先转成Object,在取出时&#x…...
用科技创造未来!流辰信息技术助您实现高效办公
随着社会的迅猛发展,科技的力量无处不见。它正在悄悄地改变整个社会,让人类变得进步和文明,让生活变得便捷和高效。在办公自动化强劲发展的今天,流辰信息技术让通信业、电网、汽车、物流等领域的企业实现了高效办公,数…...
基于R语言APSIM模型
随着数字农业和智慧农业的发展,基于过程的农业生产系统模型在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农田固碳和温室气体排放等领域扮演着越来越重要的作用。 APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物…...
块状链表实现BigString大字符串操作(golang)
前言 块状链表是介于链表和数组之间的数据结构,能够在 O ( n ) O(\sqrt{n}) O(n )时间内完成插入、删除、访问操作。 数据结构如图所示。假设最大容量为 n n n, 则它有一个长度为 s n s\sqrt{n} sn 的链表。链表中每个结点是一个长度为 2 n 2 \times \sqrt{…...
项目问题记录(持续更新)
1.在 yarn install的时候报 error achrinza/node-ipc9.2.2: The engine "node" is incompatible with this module. Expected version "8 || 10 || 12 || 14 || 16 || 17". Got "20.1.0" error Found incompatible module.需要执行 yarn config…...
Linux的进程
目录 一、进程占用的内存资源 二、进程的系统环境 三、进程一直在切换 四、父进程和子进程 五、进程状态 六、查看进程 1.ps -ef 列出所有进程 2.ps -lax 列出所有进程 3.ps aux列出所有进程 4.树形列出所有进程 七、作业(用来查看管理进程) …...
与其焦虑被 AI 取代或猜测前端是否已死, 不如看看 vertical-align 扎实你的基础!!!
与其焦虑被 AI 取代或猜测前端是否已死, 不如看看 vertical-align 扎实你的基础!!! vertical-align 设置 display 值为 inline, inline-block 和 table-cell 的元素竖直对齐方式. 从 line-height: normal 究竟是多高说起 我们先来看一段代码, 分析一下为什么第二行的行高, 也就…...
路由、交换机、集线器、DNS服务器、广域网/局域网、端口、MTU
前言:网络名词术语解析(自行阅读扫盲),推荐大家去读户根勤的《网络是怎样连接的》 路由(route): 数据包从源地址到目的地址所经过的路径,由一系列路由节点组成。某个路由节点为数据包选择投递方向的选路过程。 路由器工作原理 路…...
在全志V851S开发板上进行屏幕触摸适配
1.修改屏幕驱动 从ft6236 (删掉,不要保留),改为下面的 路径:/home/wells/tina-v853-open/tina-v853-open/device/config/chips/v851s/configs/lizard/board.dts(注意路径,要设置为自己的实际路…...
字符串拷贝时的内存重叠问题
字符串拷贝时的内存重叠问题 1.什么是内存重叠 拷贝的目的地址在源地址的范围内,有重叠。 如在写程序的过程中,我们用到的strcpy这个拷贝函数,在这个函数中我们定义一个目的地址,一个源地址,在拷贝的过程中如果内存重…...
告别PPT手残党!这6款AI神器,让你秒变PPT王者!
如果你是一个PPT手残党,每每制作PPT总是让你焦头烂额,那么你一定需要这篇幽默拉风的推广文案! 我向你保证,这篇文案将帮助你发现6款AI自动生成PPT的神器,让你告别PPT手残党的身份,成为一名PPT王者。 无论…...
JVM配置与优化
参考: JVM内存分区及作用(JDK8) https://blog.csdn.net/BigBug_500/article/details/104734957 java 进程占用系统内存过高分析 https://blog.csdn.net/fxh13579/article/details/104754340 Java之jvm和线程的内存 https://blog.csdn.ne…...
电力系统储能调峰、调频模型研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
C++基础之类、对象一(类的定义,作用域、this指针)
目录 面向对象的编程 类的引入 简介 类的定义 简介 访问限定符 命名规则 封装 简介 类的作用域 类的大小及存储模型 this指针 简介 面向对象的编程 C与C语言不同,C是面向对象的编程,那么什么是面向对象的编程呢? C语言编程,规定…...
javaScript---设计模式-封装与对象
目录 1、封装对象时的设计模式 2、基本结构与应用示例 2.1 工厂模式 2.2 建造者模式 2.3 单例模式 封装的目的:①定义变量不会污染外部;②能作为一个模块调用;③遵循开闭原则。 好的封装(不可见、留接口):①…...
【消息中间件】kafka高性能设计之内存池
文章目录 前言实现创建内存池分配内存释放内存 总结 前言 Kafka的内存池是一个用于管理内存分配的缓存区域。它通过在内存上保留一块固定大小的内存池,用于分配消息缓存、批处理缓存等对象,以减少频繁调用内存分配函数的开销。 Kafka内存池的实现利用了…...
创建型模式——单例(singleton)
1. 模式说明 单例模式保证类只有一个实例;创建一个对象,当你创建第二个对象的时候,此时你获取到的是已经创建过的对象,而不是一个新的对象; 1.1 使用场景 共享资源的访问权限;任务的管理类;数…...
算法:迷宫问题
描述 定义一个二维数组 N*M ,如 5 5 数组下所示: int maze[5][5] { 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, }; 它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或…...
聊聊并发编程的12种业务场景
前言 并发编程是一项非常重要的技术,无论在面试,还是工作中出现的频率非常高。 并发编程说白了就是多线程编程,但多线程一定比单线程效率更高? 答:不一定,要看具体业务场景。 毕竟如果使用了多线程&…...
MySQL执行顺序
MySQL执行顺序 MySQL语句的执行顺序也是在面试过程中经常问到的问题,并且熟悉执行顺序也有助于SQL语句的编写。 SELECT FROM JOIN ON WHERE GROUP BY HAVING ORDER BY LIMIT执行顺序如下: FROM ON JOIN WHERE GROUP BY # (开始使用别名) SUM # SUM等…...
引领真无线耳机未来趋势,NANK南卡OE骨传导真无线耳机惊艳亮相
传统的蓝牙耳机存在很多问题,例如续航时间短、长期佩戴耳朵会不舒服,甚至影响听力等等。为了解决这些问题,在骨传导领域深耕十多年的南卡品牌推出了这款真无线骨传导耳机——NANK南卡 OE。 NANK南卡OE即将正式上线,这一消息一经宣…...
宿迁网站制作公司/抖音广告投放平台官网
3.Pandas 文章目录3.Pandas3.3 Pandas进阶3.3.1 数据重塑和轴向旋转(1)层次化索引Series的层次化索引DataFrame的层次化索引层次化——电影数据示列(2)数据旋转3.3.2 数据分组、分组运算3.3.3 离散化处理3.3.4 合并数据集…...
南京营销型网站建设/线下推广宣传方式有哪些
内存数据库:大数据时代数据管理新宠在2012中国系统架构师大会上,笔者曾做过一份有关大数据的调查,其中一项“在众多的技术趋势中,您所关注的数据管理的新技术是什么?”的调查结果中,“内存数…...
招工做哪个网站/百度大数据预测平台
深入C系列: 1、《C STL中文版》 2、《More Effective C(中文版)》 3、《深度探索C对象模型》 4、《泛型编程与STL》 5、《Effective STL》 6、《C Primer中文版》 7、《C程序设计原理与实践》 8、《C编程思想》 9、《C编程规范&…...
品牌网站设计案例/怎样把自己的产品放到网上销售
扩展就是向一个已有的类、结构体或枚举类型添加新功能(functionality)。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即逆向建模)。扩展和 Objective-C 中的分类(categories)类似。(…...
炫酷业务网站/网络营销属于什么专业类型
原文链接:http://tecdat.cn/?p19839tecdat.cn机器学习算法可用于找到最佳值来交易您的指标。相对强弱指标(RSI)是最常见的技术指标之一。它用于识别超卖和超买情况。传统上,交易者希望RSI值超过70代表超买市场状况,而低于30则代表超卖市场…...
男女做羞羞的视频网站/广州网站排名专业乐云seo
本文结合实例,讲解使用PHPMySqljQuery实现的“顶”和“踩”投票功能,通过记录用户IP,判断用户的投票行为是否有效,该实例也可以扩展到投票系统中。如果您具备PHP、MySql和jQuery相关基础知识,那么请继续往下阅读。本文…...