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

news2024/9/27 23:23:16

目录

    • 前言
    • 整体文件结构
    • 源码分析(保姆级讲解)
      • 中断初始化部分
        • 初始化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

结束语

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/504436.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【shell脚本】函数

函数 一、shell函数1.1函数的定义1.3 函数返回值1.4函数传参1.5递归的使用 二、实验2.1实验一2.2实验二2.3实验三 一、shell函数 使用函数可以避免代码重复使用函数可以将大的过程风为若干个小的功能模块&#xff0c;代码的可读性更强 1.1函数的定义 【1】 function 函数名 …

OJ练习第99题——推箱子

推箱子 力扣链接&#xff1a;1263. 推箱子 题目描述 「推箱子」是一款风靡全球的益智小游戏&#xff0c;玩家需要将箱子推到仓库中的目标位置。 游戏地图用大小为 m x n 的网格 grid 表示&#xff0c;其中每个元素可以是墙、地板或者是箱子。 现在你将作为玩家参与游戏&a…

深度学习—神经网络基础原理

前向传播&#xff08;Forward&#xff09; 为什么要有激活函数 这里用两层来代表多层的神经网络举例&#xff1a;第一层的输出是第二层的输入&#xff0c;其中MM的W*X矩阵乘法&#xff0c;ADD是向量加法即加上偏置&#xff0c;如果每一层都只有线性变换&#xff0c;那么最终无…

Modbus转Profibus网关连接安科瑞ARD3T电机保护器接到300PLC配置案例

案例介绍兴达易控Modbus转profibus网关&#xff08;XD-MDPB100&#xff09;把安科瑞ARD3T电机保护器在博图软件里无需编程实现由profibus转modbus协议之间的互转&#xff0c;用到的设备安科瑞ARD3T电机保护器一台&#xff1b;兴达易控Modbus转profibus网关&#xff08;XD-MDPN1…

解密Diem币:探索Facebook的数字货币计划

大家好&#xff01;我是ClonBrowser小鱼&#xff0c;今天我要和大家一起解密一种备受关注的数字货币——Diem币。 或许你已经猜到了&#xff0c;这个数字货币与社交媒体巨头Facebook有关。是的没错&#xff0c;Facebook正计划推出一种自己的加密货币&#xff0c;名为Diem币。让…

为什么做白平衡?康耐视Visionpro和Basler pylon,海康MVS如何做白平衡-三种软件相同条件下,白平衡效果一样?

为什么会有白平衡这个问题? 因为不同颜色的差异来自于不同波长光线的比例不同。 由于在不同色温下各种波长光纤比例的不同,造成白色在高色温的光线照射下显得较蓝,在低色温度的光线下显得较黄。如下图: 问题:在相同条件下,康耐视Visionpro和Basler pylon,海康MVS做白平…

机器学习决策树、回归树 sklearn-day1

#文章很多内容来自菜菜老师的课件。仅做笔记一、决策树 1、模块 2、sklearn基本建模流程 #分类树对应的代码 from sklearn import tree #导入需要的模块 clf tree.DecisionTreeClassifier() #实例化 clf clf.fit(X_train,y_train) #用训练集数据训练模型 result clf…

JavaWeb 中 Filter过滤器

Filter过滤器 每博一文案 师傅说&#xff1a;人生无坦途&#xff0c;累是必须的背负&#xff0c;看多了&#xff0c;人情人暖&#xff0c;走遍了离合聚散&#xff0c;有时会 在心里对自己说&#xff0c;我想&#xff0c;我是真的累了&#xff0c;小时候有读不完的书&#xff0…

大学生志愿者管理信息系统设计与实现(论文+源码)_kaic

摘 要 在国家的十四五期间&#xff0c;志愿服务成为推动社会文明发展的重要力量。大学生是志愿活动的中坚力量。现有的志愿管理工作不能满足志愿活动的需要&#xff0c;存在活动找不到志愿者&#xff0c;志愿者找不到活动的情况。为服务良好的志愿服务体系&#xff0c;对大学…

Hologres技术揭秘: JSON半结构化数据的极致分析性能

作者&#xff1a;王华峰&#xff08;花名继儒&#xff09;&#xff0c;Hologres研发 近年来&#xff0c;随着移动端应用的普及&#xff0c;应用埋点、用户标签计算等场景开始诞生&#xff0c;为了更好的支撑这类场景&#xff0c;越来越多的大数据系统开始使用半结构化JSON格式…

保姆级教程:OpenAI获取Account详细教程2023/05/09最新

前言 最近OpenAI封的比较严重&#xff0c;建议大家不要批量&#xff0c;容易被封&#xff0c;也不要用公用的IP 一、上网 首先你要学会上网&#xff08;懂&#xff1f;&#xff09;、这一步是必须的。 现在MG等地方的IP已经很难了&#xff0c;可以选择一些小众国家的IP 二、开…

【论文阅读】Online multi-sensor calibration based on moving object tracking

目录 Online multi-sensor calibration based on moving object trackingAbstract1. Introduction2. Proposed Method2.1 Object Detection2.2 Tracking of moving objects2.3 Track-to-track association2.4 Decalibration(解关联) detection2.5 Graph-based extrinsic calibr…

花菁染料CY5.5标记活性脂 Cy5.5-NHS

Cy5.5 NHS ester用于染色蛋白质、标记DNA、标记细胞表面抗原、标记抗体和其他生物分子。Cy5.5 NHS ester还可以用于分子影像学&#xff0c;可以追踪细胞内的变化。它还可以用于荧光免疫检测&#xff0c;以检测细胞表面抗原和抗体。 产品名称&#xff1a;五甲川花菁染料CY5.5标记…

OpenGL 4.0的Tessellation Shader(细分曲面着色器)

细分曲面着色器&#xff08;Tessellation Shader&#xff09;处于顶点着色器阶段的下一个阶段&#xff0c;我们可以看以下链接的OpenGL渲染流水线的图&#xff1a;Rendering Pipeline Overview。它是由ATI在2001年率先设计出来的。 目录 细分曲面着色器细分曲面Patch细分曲面控…

手把手实现项目中自定义动态数据源?

手把手实现项目中自定义动态数据源&#xff1f; 第一步&#xff1a;创建项目&#xff0c;添加POM依赖第二步&#xff1a;添加application.yml配置文件第三步&#xff1a;自定义注解指定使用哪个数据源第四步&#xff1a;创建DynamicDataSourceContextHolder工具类&#xff0c;存…

LeetCode:347. 前 K 个高频元素

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340; 算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 题解目录 一、&#x1f331;[347. 前 K 个高频元素](https://leetcode.cn/problems/bina…

安全防线再升级 | 中睿天下全流量安全分析系统重磅回归

随着信息化的加速&#xff0c;企业网络日趋完善。企业数字化的加速&#xff0c;让越来越多的关键业务运行在计算机网络基础之上&#xff0c;越来越多的重要信息通过网络传送&#xff0c;企业网络面临日益严重的安全威胁&#xff0c;这些安全威胁以窃取信息和收集情报为主&#…

基于docker安装MySQL

为了更好的管理&#xff0c;打算把MySQL、redis等服务放在虚拟机中统一部署&#xff0c;这样不会因为这些服务的问题影响到系统本身。前段时间正好在看docker相关的内容&#xff0c;打算在虚拟机中通过docker来使用MySQL等服务。 这次先记录安装MySQL的过程。 docker安装 首先…

超越 Nginx!号称下一代 Web 服务器,用起来够优雅!

Nginx是一款非常流行的Web服务器&#xff0c;在Github上已有16KStar&#xff0c;我们经常用它来做静态资源托管或反向代理。最近发现了一款全新的Web服务器Caddy&#xff0c;Star数超越Nginx&#xff0c;标星38KStar。试用了一下Caddy&#xff0c;发现它使用起来比Nginx优雅多了…

掌握这五个核心步骤,让你的方案完美无缺

写策划方案怎么来写&#xff0c;可能会是刚入行策划人的难点&#xff0c;策划方案其实就是一份营销计划&#xff0c;一份完整的策划能让策划人看清自己、认清竞争对手&#xff0c;形成对市场的整体认知。 一般大的营销策划方案里面会用到STP、SWOT和4P等模型&#xff0c;模型本…