当前位置: 首页 > news >正文

看完这篇文章你就彻底懂啦{保姆级讲解}-----(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 来汇报中断情况:VFIQVIRQFIQIRQ,如下图所示:

在这里插入图片描述
在这四个中断中,我们只会使用到一个中断即上图中的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即可访问GICCPU接口段寄存器。那么问题来了,GIC控制器的寄存器基地址在哪里,这里我们就需要用到 Cortex-ACP15 协处理器了。那什么是CP15协处理器?

CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15 协处理器一共有1632 位寄存器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设置c15CBAR寄存器后,需要获取该寄存器的高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将众多的中断源分为三类,并且已经在上图中用红框标出。

在这里插入图片描述
所以上述代码就是在对所有的PPISGISPI这三种中断源进行不使能操作。并且该寄存器如下图所示:

在这里插入图片描述

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。这 1020ID 包含了 PPI、SPI 和 SGI,那么这三类中断是如何分配这 1020中断 ID 的呢?这 1020ID 分配如下:

ID0~ID15:这 16 个 ID 分配给 SGI
ID16~ID31:这 16 个 ID 分配给 PPI
ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断。

至于具体到某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义了。比如 I.MX6U 的总共使用了 128 个中断 ID,加上前面属于 PPISGI32 个 IDI.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 irqsystem_irq_handler_t handlervoid *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_CCSRPLL1_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_CCSRSTEP_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设置为792MHzPLL1 CLK = Fin *div_seclec/2.0Fin=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时钟频率,那么接下来我们开始配置这PLL2PLL3,其中由上图时钟树可知,PLL24PFD时钟,PLL34路PFD时钟。

在这里插入图片描述
我们先设置PLL24路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 					*/	

我们先设置PLL34PFD时钟。

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_480nCCM_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.MX6UGPIO1_IO16~IO3116 个 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);}

每隔500msled灯亮灭。

最终编译验证

按下 KEY 就会打开蜂鸣器,再次按下就会关闭蜂鸣器。LED0 会不断闪烁,周期大约 500ms

结束语

如果觉得这篇文章还不错的话,记得点赞 ,支持下!!!

相关文章:

看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动GPIO中断《包括时钟讲解》) 2023.5.9

目录 前言整体文件结构源码分析&#xff08;保姆级讲解&#xff09;中断初始化部分初始化GIC控制器初始化中断向量表设置中断向量表偏移 系统时钟初始化部分使能所有的时钟部分led初始化部分beep初始化部分key初始化部分按键中断初始化部分按键中断服务函数部分 while循环部分 …...

MySql -- 事务

目录 1.概念 2.事务的运用场景 3.事务的四大特点 4.执行事务带来的问题 4.1 脏读 4.2 不可重复度 4.3 幻读 5. MySQL中事务的隔离级别 1.概念 事务就是把若干个独立操作打包成一个整体而诞生的一种功能. 2.事务的运用场景 比如&#xff1a;A——>B 转账500 A的余额-500…...

关于大模型对未来影响的一点看法

人们总是高估了未来一到两年的变化&#xff0c;低估了未来十年的变革。 ---比尔盖茨 近来OpenAI的GPT技术可以说在全球都带来了巨大的影响&#xff0c;也让大家看到了什么叫大力出奇迹。chatGPT和GPT4的能力给了大家很大的震撼&#xff0c;其流畅自如、逻辑清晰、出众的能力&am…...

Android - 约束布局 ConstraintLayout

一、概念 解决布局嵌套过多的问题&#xff0c;采用方向约束的方式对控件进行定位。 二、位置约束 2.1 位置 至少要保证水平和垂直方向都至少有一个约束才能确定控件的位置。 layout_constraintLeft_toLeftOf我的左边&#xff0c;与XXX左边对齐。layout_constraintLeft_toRight…...

Addictive Multiplicative in NN

特征交叉是特征工程中的重要环节&#xff0c;在以表格型&#xff08;或结构化&#xff09;数据为输入的建模中起到了很关键的作用。 特征交互的作用&#xff0c;一是尽可能挖掘对目标有效的模式、特征&#xff0c;二是具有较好的可解释性&#xff0c;三是能够将对数据的洞见引…...

LeetCode 1206. 实现跳表

不使用任何库函数&#xff0c;设计一个跳表。 跳表是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树&#xff0c;其功能与性能相当&#xff0c;并且跳表的代码长度相较下更短&#xff0c;其设计思想与链表相似。 例如&#xff0c;一个跳表包…...

离散数学_九章:关系(2)

9.2 n元关系及其应用 1、n元关系&#xff0c;关系的域&#xff0c;关系的阶2、数据库和关系 1. 数据库 2. 主键 3. 复合主键 3、n元关系的运算 1. 选择运算 (Select) 2. 投影运算 (Project) 3. 连接运算 (Join) n元关系&#xff1a;两个以上集合的元素间的关系 1、n元关系…...

[ubuntu][原创]通过apt方式去安装libnccl库

ubuntu18.04版本安装流程&#xff1a; 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连接集成平台构建新一代产业互联根基

近日&#xff0c;由用友公司主办的“2023用友BIP技术大会“在用友产业园&#xff08;北京&#xff09;盛大召开&#xff0c;用友介绍了更懂企业业务的用友BIP-iuap平台&#xff0c;并发布了全面数智化能力体系&#xff0c;助力企业升级数智化底座&#xff0c;加强加速数智化推进…...

泛型的详解

泛型的理解和好处 首先我们先来看看泛型的好处 1)编译时&#xff0c;检查添加元素的类型&#xff0c;提高了安全性 2)减少了类型转换的次数&#xff0c;提高效率[说明] 不使用泛型 Dog -> Object -> Dog//放入到ArrayList 会先转成Object&#xff0c;在取出时&#x…...

用科技创造未来!流辰信息技术助您实现高效办公

随着社会的迅猛发展&#xff0c;科技的力量无处不见。它正在悄悄地改变整个社会&#xff0c;让人类变得进步和文明&#xff0c;让生活变得便捷和高效。在办公自动化强劲发展的今天&#xff0c;流辰信息技术让通信业、电网、汽车、物流等领域的企业实现了高效办公&#xff0c;数…...

基于R语言APSIM模型

随着数字农业和智慧农业的发展&#xff0c;基于过程的农业生产系统模型在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农田固碳和温室气体排放等领域扮演着越来越重要的作用。 APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物…...

块状链表实现BigString大字符串操作(golang)

前言 块状链表是介于链表和数组之间的数据结构&#xff0c;能够在 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.树形列出所有进程 七、作业&#xff08;用来查看管理进程&#xff09; …...

与其焦虑被 AI 取代或猜测前端是否已死, 不如看看 vertical-align 扎实你的基础!!!

与其焦虑被 AI 取代或猜测前端是否已死, 不如看看 vertical-align 扎实你的基础!!! vertical-align 设置 display 值为 inline, inline-block 和 table-cell 的元素竖直对齐方式. 从 line-height: normal 究竟是多高说起 我们先来看一段代码, 分析一下为什么第二行的行高, 也就…...

路由、交换机、集线器、DNS服务器、广域网/局域网、端口、MTU

前言&#xff1a;网络名词术语解析(自行阅读扫盲)&#xff0c;推荐大家去读户根勤的《网络是怎样连接的》 路由(route)&#xff1a; 数据包从源地址到目的地址所经过的路径&#xff0c;由一系列路由节点组成。某个路由节点为数据包选择投递方向的选路过程。 路由器工作原理 路…...

在全志V851S开发板上进行屏幕触摸适配

1.修改屏幕驱动 从ft6236 &#xff08;删掉&#xff0c;不要保留&#xff09;&#xff0c;改为下面的 路径&#xff1a;/home/wells/tina-v853-open/tina-v853-open/device/config/chips/v851s/configs/lizard/board.dts&#xff08;注意路径&#xff0c;要设置为自己的实际路…...

字符串拷贝时的内存重叠问题

字符串拷贝时的内存重叠问题 1.什么是内存重叠 拷贝的目的地址在源地址的范围内&#xff0c;有重叠。 如在写程序的过程中&#xff0c;我们用到的strcpy这个拷贝函数&#xff0c;在这个函数中我们定义一个目的地址&#xff0c;一个源地址&#xff0c;在拷贝的过程中如果内存重…...

告别PPT手残党!这6款AI神器,让你秒变PPT王者!

如果你是一个PPT手残党&#xff0c;每每制作PPT总是让你焦头烂额&#xff0c;那么你一定需要这篇幽默拉风的推广文案&#xff01; 我向你保证&#xff0c;这篇文案将帮助你发现6款AI自动生成PPT的神器&#xff0c;让你告别PPT手残党的身份&#xff0c;成为一名PPT王者。 无论…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...