目录
- 1. GPIO模式控制流程
- 1.1 LED、蜂鸣器、按键
- 1.2 GPIO控制流程
- 2. 标准库的简要分析及实现:
- 2.1 问题引入:
- 2.2 代码实现:
- 3. 时钟配置总结:
- 3.1 时钟树概要:
- 3.2 IMX6ULL时钟概要及时钟树:
- 3.3 IMX6ULL时钟配置代码:
1. GPIO模式控制流程
1.1 LED、蜂鸣器、按键
这些功能的实现都是利用了引脚的GPIO模式,通过GPIO模式进行引脚配置输出或者输入,进而实现LED、蜂鸣器、按键,的功能
1.2 GPIO控制流程
核心配置思路如下:引脚X->寄存器A->寄存器A1->寄存器B
(伪寄存器)
引脚X
:顾名思义,看得见,摸得着的物理引脚,一个引脚可以配置成多个模式,X可取范围是1-32,因为这个芯片是32位的,一个寄存器最多能控制32个引脚的输入和输出;寄存器A
:例如对于引脚X,那么我如何把这个引脚配置成我想要的模式呢?例如配置成GPIO模式,答案就是通过寄存器A,也就是IOMUXC_SW_MUX_CTL_PAD_GPIO1_X
寄存器,每个引脚都有对应的寄存器A
,通过配置寄存器A来确定GPIO的工作模式寄存器A1
:A和A1是一一映射的,一个引脚确定了模式,下一步就是配置电气属性,因此寄存器A1就是配置电气属性的,例如确定了GPIO的工作模式,下一步就是配置这个引脚的电气属性:就是通过寄存器A1来配置的,在数据手册中也叫:IOMUXC_SW_PAD_CTL_PAD_GPIO1_X
寄存器B
:引脚确定了模式和电器属性,那么下一步就是确定这个模式下如何进行工作,例如上面确定了GPIO(通用输入输出模式)那么下一步就是配置寄存器B
(GPIO寄存器)进行输入或者输出、上下拉、保持、速度等功能的配置- 思路如下图:
2. 标准库的简要分析及实现:
2.1 问题引入:
仔细观察一下,对于以下数据手册中引脚的寄存器地址分布,是不是每个引脚之间的寄存器地址都差4。所以知道了GPIO1_DR的地址后,剩下的寄存器地址我们都可以推出来,因此可以把这一类的寄存器放在一个结构体中,因为结构体存储数据是可以按照等字节连续存储,如何把结构体中的第一个值的地址宏定义一下,那么结构体中的其他寄存器的地址自动获得
2.2 代码实现:
&emsp注意,代码把地址强制转换成了结构体指针,因为物理硬件地址不会发生变化,在一些底层的硬件编程中,需要直接访问特定的硬件寄存器。通过将寄存器的地址定义为一个结构体指针,可以方便地进行访问和操作。
//定义结构体中的首地址,那么结构体中的其他寄存器的地址自动获得
#define GPIO1_BASE (0x0209C000)
#define GPIO2_BASE (0x020A0000)
#define GPIO3_BASE (0x020A4000)
#define GPIO4_BASE (0x020A8000)
#define GPIO5_BASE (0x020AC000)typedef struct
{volatile unsigned int DR; volatile unsigned int GDIR; volatile unsigned int PSR; volatile unsigned int ICR1; volatile unsigned int ICR2; volatile unsigned int IMR; volatile unsigned int ISR; volatile unsigned int EDGE_SEL;
}GPIO_Type;#define GPIO1 ((GPIO_Type *)GPIO1_BASE)//注意,这里把地址强制转换成了结构体指针,//因为物理硬件地址不会发生变化
//在一些底层的硬件编程中,需要直接访问特定的硬件寄存器。
//通过将寄存器的地址定义为一个结构体指针,可以方便地进行访问和操作。
#define GPIO2 ((GPIO_Type *)GPIO2_BASE)
#define GPIO3 ((GPIO_Type *)GPIO3_BASE)
#define GPIO4 ((GPIO_Type *)GPIO4_BASE)
#define GPIO5 ((GPIO_Type *)GPIO5_BASE)
以上就是一个简单的标准库的实现,其实可以把整个模块都按照这样的形式写出来,例如,IO口寄存器,GPIO,USART,CLOCK,等等
3. 时钟配置总结:
3.1 时钟树概要:
对于每款MCU都有专有的时钟路线,但是这些芯片有共同点,那就是时钟树,时钟树的根就是外部的晶振,通过对外部晶振的倍频和分频可以得到几大主干道,也就是时钟树的枝干,而通过这些枝干在进行分频和倍频可以得到各个外设需要的时钟。
3.2 IMX6ULL时钟概要及时钟树:
IMX6ULL大体上有7个PLL和8个PFD时钟总线,其余的大都是从这些时钟总线上分频得到的;其中PLL1作为ARM内核的时钟总线,是要首先进行初始化的,初始化过程中注意点ARM时钟切换频率时要先进行时钟的转接,换好后再把系统时钟接回来;配置ARM时钟时主要参考图如下:
配置其他时钟主要参考是时钟树,IMX6ULL时钟树部分截图:注意时钟树中黄色的是寄存器对选择器的配置,红色的是寄存器对分频器的的控制,分频过程中注意不要直接清零,清零代表不分频,可能时钟频率会超出下线的承受范围导致板子卡死,注意有的分频过后要进行握手检测,也就是握手换频率不用关时钟,关时钟换频率不用握手;
3.3 IMX6ULL时钟配置代码:
#include "bsp_clk.h"
/*使能外设时钟*/
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;
}/*初始化时钟*/
void imx6u_clkinit(void)
{unsigned int reg=0;/*初始化6u的主频为528MHz*/if((((CCM->CCSR)>>2) & 0x1)==0) /*当前时钟使用的是pll1_main_clk,也就是pll1,要进行时钟切换*/{CCM->CCSR &= ~(1<<8);/*设置step_clk=osc_clk=24MHz*/CCM->CCSR |= (1<<2);/*pll1_main_clk=step_clk=24MHz*/}/*系统时钟切换已经设置好了,下一步就是设置PLL1=1056MHz,根据公式设置DIV_SELECT这八位为88,且使能*/CCM_ANALOG->PLL_ARM = ((1<<13)|((88<<0) & 0x7f));CCM->CACRR = 1;/*设置二分频*//*现在设置好了时钟,下一步就是把时钟切换回来*/CCM->CCSR &= ~(1<<2);/*把这一位PLL1_SW_CLK_SEL清零就行,切换回来了,设置pll1_main_clk=1056MHz*//*二位选择器,通过PLL1_SW_CLK_SEL=0/1来进行选择时钟频率*//*设置PL2的4路PFD*/reg=CCM_ANALOG->PFD_528;reg &=~(0x3f3f3f3f);reg |=(32<<24); /*初始化PLL2_PFD3=297MHz,利用公式的得到:*/reg |=(24<<16); /*初始化PLL2_PFD2=396MHz,利用公式的得到:*/reg |=(16<<8); /*初始化PLL2_PFD1=594MHz,利用公式的得到:*/reg |=(27<<0); /*初始化PLL2_PFD0=352MHz,利用公式的得到:*/CCM_ANALOG->PFD_528=reg; /*设置完毕*//*设置PL3的4路PFD*/reg=0;reg=CCM_ANALOG->PFD_480;reg &=~(0x3f3f3f3f);reg |=(19<<24); /*初始化PLL3_PFD3=454.7MHz,利用公式的得到:*/reg |=(17<<16); /*初始化PLL3_PFD2=508.2MHz,利用公式的得到:*/reg |=(16<<8); /*初始化PLL3_PFD1=540MHz,利用公式的得到:*/reg |=(12<<0); /*初始化PLL3_PFD0=720MHz,利用公式的得到:*/CCM_ANALOG->PFD_480=reg; /*设置完毕*//*设置AHB_CLK_ROOT=132MHZ*/CCM->CBCMR &= ~(3<<18);CCM->CBCMR |= (1<<18);//四路选择器选择时钟PLL2_PFD2,即设置:PRE_PERIPH_CLK_SEL=400M(396Mhz)/*设置第二个选择器,通过CBCDR寄存器进行设置*/CCM->CBCDR &= ~(1<<25);while (CCM->CDHIPR & (1<<5)); /*等待握手信号*/reg=0;reg= CCM->CBCDR;reg &= ~(7<<10); //先清零,这里不能直接先 CCM->CBCDR &= ~(7<<10)清零,因为清零直接变成1分频,超过了132Mhz,直接死机reg |= (2<<10); //在赋值3分频CCM->CBCDR=reg;while((CCM->CDHIPR >> 1) & (0x1));/*等待握手信号*//*设置IPG_CLK_ROOT=66MHz*//*通过设置CBCDR寄存器的IPG_PODF位实现*/reg=0;reg = CCM->CBCDR;reg &= ~(3<<8);reg |= (1<<8);CCM->CBCDR=reg; /*设置完毕*//*设置PERCLK_CLK_ROOT时钟为66Mhz*//*设置CSCMR1寄存器的PERCLK_CLK_SEL位为0(第六位),两路选择器选择进入66MHz*/CCM->CSCMR1 &= ~(1<<6); /*时钟源已经选择了IPG,可以对照着时钟树进行了解*//*分频器不分频*/CCM->CSCMR1 &= ~(0x3f << 0);/*设置完毕,1分频,66MHZ*/}