1.按键输入实验
按键输入简介
按键就两个状态:按下或弹起,将按键连接到一个 IO 上,通过读取这个 IO 的值就知道按键是按下的还是弹起的。至于按键按下的时候是高电平还是低电平要根据实际电路来判断。前 面几章我们都是讲解 I.MX6U 的 GPIO 作为输出使用,当 GPIO 连接按键的时候就要做为输入 使用。我们的主要工作就是配置按键 所连接的 IO 为输入功能,然后读取这个 IO 的值来判断按键是否按下。 I.MX6U-ALPHA 开发板上有一个按键 KEY0 ,我们将会编写代码通过这个 KEY0 按键 来控制开发板上的蜂鸣器,按一下 KEY0 蜂鸣器打开,再按一下蜂鸣器就关闭。硬件原理分析可以看出,按键 KEY0 是连接到 I.MX6U 的 UART1_CTS 这个 IO 上的, KEY0接了一个 10K 的上拉电阻,因此 KEY0 没有按下的时候 UART1_CTS 应该是高电平,当 KEY0 按下以后 UART1_CTS 就是低电平。实验程序编写上一章试验例程的基础上完成,重新创建 VSCode 工程,工作区名字为“ key ”,在工程目录的 bsp 文件夹中创建名为“ key ”和“ gpio ”两个文件夹。按键相关的驱动文件都放到“ key ”文件夹中,本章试验我们对 GPIO 的操作编写一个函数集合,也就是编写一个 GPIO驱动文件, GPIO 的驱动文件放到“gpio”文件夹里面。1.设置UART1_CTS复用为GPIO1_IO182.设置UART1_CTS的电气属性3.配置 GPIO1_IO18为输入模式4.读取按键值,也就是GPIO1_IO18的高低电平新建 bsp_gpio.c 和 bsp_gpio.h 这两个文件,将这两个文件都保存到刚刚创建的 bsp/gpio 文件夹里面,然后在 bsp_gpio.h 文件夹里面输入如下内容:#ifndef _BSP_GPIO_H #define _BSP_GPIO_H #define _BSP_KEY_H #include "imx6ul.h"/* 枚举类型和结构体定义 */ typedef enum _gpio_pin_direction {kGPIO_DigitalInput = 0U, /* 输入 */kGPIO_DigitalOutput = 1U, /* 输出 */ } gpio_pin_direction_t;typedef struct _gpio_pin_config {gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */uint8_t outputLogic; /* 如果是输出的话,默认输出电平 */ } gpio_pin_config_t;/* 函数声明 */ void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config); int gpio_pinread(GPIO_Type *base, int pin); void gpio_pinwrite(GPIO_Type *base, int pin, int value);#endif
bsp_gpio.h 中定义了一个枚举类型 gpio_pin_direction_t 和结构体 gpio_pin_config_t ,枚举类 型 gpio_pin_direction_t 表示 GPIO 方向,输入或输出。结构体 gpio_pin_config_t 是 GPIO 的配置 结构体,里面有 GPIO 的方向和默认输出电平两个成员变量。在 bsp_gpio.c 中输入如下所示内容:#include "bsp_gpio.h"/** @description : GPIO初始化。* @param - base : 要初始化的GPIO组。* @param - pin : 要初始化GPIO在组内的编号。* @param - config : GPIO配置结构体。* @return : 无*/ void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config) {if(config->direction == kGPIO_DigitalInput) /* 输入 */{base->GDIR &= ~( 1 << pin);}else /* 输出 */{base->GDIR |= 1 << pin;gpio_pinwrite(base,pin, config->outputLogic);/* 设置默认输出电平 */} }/** @description : 读取指定GPIO的电平值 。* @param - base : 要读取的GPIO组。* @param - pin : 要读取的GPIO脚号。* @return : 无*/int gpio_pinread(GPIO_Type *base, int pin){return (((base->DR) >> pin) & 0x1);}/** @description : 指定GPIO输出高或者低电平 。* @param - base : 要输出的的GPIO组。* @param - pin : 要输出的GPIO脚号。* @param - value : 要输出的电平,1 输出高电平, 0 输出低低电平* @return : 无*/ void gpio_pinwrite(GPIO_Type *base, int pin, int value) {if (value == 0U){base->DR &= ~(1U << pin); /* 输出低电平 */}else{base->DR |= (1U << pin); /* 输出高电平 */} }
文件 bsp_gpio.c 中有三个函数: gpio_init 、 gpio_pinread 和 gpio_pinwrite ,函数 gpio_init 用于初始化指定的 GPIO 引脚,最终配置的是 GDIR 寄存器,此函数有三个参数,这三个参数的含义如下:base: 要初始化的 GPIO 所属于的 GPIO 组,比如 GPIO1_IO18 就属于 GPIO1 组。pin : 要初始化 GPIO 在组内的标号,比如 GPIO1_IO18 在组内的编号就是 18 。config: 要初始化的 GPIO 配置结构体,用来指定 GPIO 配置为输出还是输入。函数 gpio_pinread 是读取指定的 GPIO 值,也就是读取 DR 寄存器的指定位,此函数有两个参数和一个返回值,参数含义如下:base: 要读取的 GPIO 所属于的 GPIO 组,比如 GPIO1_IO18 就属于 GPIO1 组。pin : 要读取的 GPIO 在组内的标号,比如 GPIO1_IO18 在组内的编号就是 18 。返回值: 读取到的 GPIO 值,为 0 或者 1 。函数 gpio_pinwrite 是控制指定的 GPIO 引脚输入高电平 (1) 或者低电平 (0) ,就是设置 DR 寄存器的指定位,此函数有三个参数,参数含义如下:base: 要设置的 GPIO 所属于的 GPIO 组,比如 GPIO1_IO18 就属于 GPIO1 组。pin : 要设置的 GPIO 在组内的标号,比如 GPIO1_IO18 在组内的编号就是 18 。value: 要设置的值, 1( 高电平 ) 或者 0( 低电平 ) 。我们以后就可以使用函数 gpio_init 设置指定 GPIO 为输入还是输出,使用函数 gpio_pinread和 gpio_pinwrite 来读写指定的 GPIO ,文件 bsp_gpio.c 文件就讲解到这里。接下来编写按键驱动文件,新建 bsp_key.c 和 bsp_key.h 这两个文件,将这两个文件都保存到刚刚创建的 bsp/key 文件夹里面,然后在 bsp_key.h 文件夹里面输入如下内容:#ifndef _BSP_KEY_H #define _BSP_KEY_H #include "imx6ul.h"/* 定义按键值 */ enum keyvalue{KEY_NONE = 0,KEY0_VALUE,KEY1_VALUE,KEY2_VALUE, };/* 函数声明 */ void key_init(void); int key_getvalue(void);#endif
bsp_key.h 文件中定义了一个枚举类型: keyvalue ,此枚举类型表示按键值,因为 I.MX6UALPHA 开发板上只有一个按键,因此枚举类型里面只到 KEY0_VALUE 。在 bsp_key.c 中输入 如下所示内容:#include "bsp_key.h" #include "bsp_gpio.h" #include "bsp_delay.h"/** @description : 初始化按键* @param : 无* @return : 无*/ 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);}/** @description : 获取按键值 * @param : 无* @return : 0 没有按键按下,其他值:对应的按键值*/ int key_getvalue(void) {int ret = 0;static unsigned char release = 1; /* 按键松开 */ if((release==1)&&(gpio_pinread(GPIO1, 18) == 0)) /* KEY0 */{ delay(10); /* 延时消抖 */release = 0; /* 标记按键按下 */if(gpio_pinread(GPIO1, 18) == 0)ret = KEY0_VALUE;}else if(gpio_pinread(GPIO1, 18) == 1){ret = 0;release = 1; /* 标记按键释放 */}return ret; }
bsp_key.c 中一共有两个函数: key_init 和 key_getvalue , key_init 是按键初始化函数,用来初始化按键所使用的 UART1_CTS 这个 IO 。函数 key_init 先设置 UART1_CTS 复用为GPIO1_IO18 ,然后配置 UART1_CTS 这个 IO 为速度为 100MHz ,默认 22K 上拉。最后调用函 数 gpio_init 来设置 GPIO1_IO18 为输入功能。 函数 key_getvalue 用于获取按键值,此函数没有参数,只有一个返回值,返回值表示按键值,返回值为 0 的话就表示没有按键按下,如果返回其他值的话就表示对应的按键按下了。获取按键值其实就是不断的读取 GPIO1_IO18 的值,如果按键按下的话相应的 IO 被拉低,那么GPIO1_IO18 值就为 0 ,如果按键未按下的话 GPIO1_IO18 的值就为 1。此函数中静态局部变量 release 表示按键是否释放。57 行是按键消抖延时函数,延时时间大约为 10ms,用于消除按键抖动。理想型的按键电压变化过程如图按键没有按下的时候按键值为 1 ,当按键在 t1 时刻按键被按下以后按键值就变为 0,这是最理想的状态。但是实际的按键是机械结构,加上刚按下去的一瞬间人手可能 也有抖动,实际的按键电压变化过程如图t1 时刻按键被按下,但是由于抖动的原因,直到 t2 时刻才稳定下来, t1 到 t2 这段时间就是抖动。一般这段时间就是十几 ms 左右,从图 15.3.2 中可以看出在抖动期间会 有多次触发,如果不消除这段抖动的话软件就会误判,本来按键就按下了一次,结果软件读取 IO 值发现电平多次跳变以为按下了多次。所以我们需要跳过这段抖动时间再去读取按键的 IO 值,也就是至少要在 t2 时刻以后再去读 IO 值。在 main.c 中输入如下代码:#include "bsp_clk.h"#include "bsp_delay.h"#include "bsp_led.h"#include "bsp_beep.h"#include "bsp_key.h"/** @description : main函数* @param : 无* @return : 无*/int main(void){int i = 0;int keyvalue = 0;unsigned char led_state = OFF;unsigned char beep_state = OFF;clk_enable(); /* 使能所有的时钟 */led_init(); /* 初始化led */beep_init(); /* 初始化beep */key_init(); /* 初始化key */while(1) { keyvalue = key_getvalue();if(keyvalue){switch ((keyvalue)){case KEY0_VALUE:beep_state = !beep_state;beep_switch(beep_state);break;}}i++;if(i==50){i = 0;led_state = !led_state;led_switch(LED0, led_state);}delay(10);}return 0;}
getvalue 来读取按键值,如果 KEY0 按下的话就打开 / 关闭蜂鸣器。 LED0 作为系统提示指示灯闪烁,闪烁周期大约为 500ms 。编译下载验证编写 Makefile 和链接脚本Makefile 使用通用 Makefile ,修改变量 TARGET 为 key ,在变量 INCDIRS和 SRCDIRS 中追加“ bsp/gpio ”和“ bsp/key ”,修改完成以后如下所示:CROSS_COMPILE ?= arm-linux-gnueabihf-TARGET ?= keyCC := $(CROSS_COMPILE)gccLD := $(CROSS_COMPILE)ldOBJCOPY := $(CROSS_COMPILE)objcopyOBJDUMP := $(CROSS_COMPILE)objdumpINCDIRS := imx6ul \bsp/clk \bsp/led \bsp/delay \bsp/beep \bsp/gpio \bsp/keySRCDIRS := project \bsp/clk \bsp/led \bsp/delay \bsp/beep \bsp/gpio \bsp/keyINCLUDE := $(patsubst %, -I %, $(INCDIRS))SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))SFILENDIR := $(notdir $(SFILES))CFILENDIR := $(notdir $(CFILES))SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))OBJS := $(SOBJS) $(COBJS)VPATH := $(SRCDIRS).PHONY: clean$(TARGET).bin : $(OBJS)$(LD) -Timx6ul.lds -o $(TARGET).elf $^$(OBJCOPY) -O binary -S $(TARGET).elf $@$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis$(SOBJS) : obj/%.o : %.S$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<$(COBJS) : obj/%.o : %.c$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<clean:rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
编译下载使用 Make 命令编译代码,编译成功以后使用软件 imxdownload 将编译完成的 key.bin 文件下载到 SD 卡中,命令如下chmod 777 imxdownload //给予 imxdownload 可执行权限,一次即可 ./imxdownload key.bin /dev/sdd //烧写到 SD 卡中,不能烧写到/dev/sda 或 sda1 设备里面!
烧写成功以后将 SD 卡插到开发板的 SD 卡槽中,然后复位开发板。如果代码运行正常的话 LED0 会以大约 500ms 周期闪烁, 按下开发板上的 KEY0 按键,蜂鸣器打开,再按下 KEY0 按键,蜂鸣器关闭。实验成功
2.主频和时钟配置实验
2.1I.MX6U 时钟系统详解
I.MX6U 的系统主频为 528MHz ,有些型号可以跑到 696MHz ,但是默认情况下内部 boot
rom 会将 I.MX6U 的主频设置为 396MHz,我们在使用 I.MX6U的时候肯定是要发挥它的最大性能,那么主频肯定要设置到 528MHz(其它型号可以设置更高,比如 696MHz),其它的外设时钟也要设置到 NXP 推荐的值。
系统时钟来源
可以看出 I.MX6U-ALPHA 开发板的系统时钟来源于两部分: 32.768KHz 和
24MHz 的晶振,其中 32.768KHz 晶振是 I.MX6U 的 RTC 时钟源, 24MHz 晶振是 I.MX6U 内核
和其它外设的时钟源,也是我们重点要分析的。
7 路 PLL 时钟源
I.MX6U 的外设有很多,不同的外设时钟源不同, NXP 将这些外设的时钟源进行了分组, 一共有 7 组,这 7 组时钟源都是从 24MHz 晶振 PLL 而来的,因此也叫做 7 组 PLL
时钟树简介
一共有三部分: CLOCK_SWITCHER 、 CLOCK ROOT GENERATOR 和 SYSTEM CLOCKS。其中左边的 CLOCK_SWITCHER 就是我们上一小节讲解的那 7 路 PLL 和 8 路 PFD ,右边的 SYSTEM CLOCKS 就是芯片外设,中间的 CLOCK ROOT GENERATOR 是最 复杂的!这一部分就像“月老”一样,给左边的CLOCK_SWITCHER 和右边的 SYSTEM CLOCKS
进行牵线搭桥。外设时钟源是有多路可以选择的, CLOCK ROOT GENERATOR 就负责从 7 路
PLL 和 8 路 PFD 中选择合适的时钟源给外设使用。具体操作肯定是设置相应的寄存器,我们以
ESAI 这个外设为例,ESAI 的时钟图如图
①、此部分是时钟源选择器, ESAI 有 4 个可选的时钟源: PLL4 、 PLL5 、 PLL3_PFD2 和
pll3_sw_clk 。 具 体 选 择 哪 一 路 作 为 ESAI 的时钟源是由寄存器 CCM->CSCMR2 的
ESAI_CLK_SEL 位来决定的,用户可以自由配置,配置如图
②、此部分是 ESAI 时钟的前级分频,分频值由寄存器 CCM_CS1CDR 的 ESAI_CLK_PRED
来确定的,可设置 1~8 分频,假如现在 PLL4=650MHz ,我们选择 PLL4 作为 ESAI 时钟,前级
分频选择 2 分频,那么此时的时钟就是 650/2=325MHz 。
③、此部分又是一个分频器,对②中输出的时钟进一步分频,分频值由寄存器
CCM_CS1CDR 的 ESAI_CLK_PODF 来决定,可设置 1~8 分频。假如我们设置为 8 分频的话,
经过此分频器以后的时钟就是 325/8=40.625MHz 。因此最终进入到 ESAI 外设的时钟就是
40.625MHz 。
2.2内核时钟设置
由时钟树可以看到 ARM 内核时钟如图①、内核时钟源来自于 PLL1,假如此时 PLL1 为 996MHz。
②、通过寄存器 CCM_CACRR 的 ARM_PODF 位对 PLL1 进行分频,可选择 1/2/4/8 分频,假如我们选择 2 分频,那么经过分频以后的时钟频率是 996/2=498MHz 。③、大家不要被此处的 2 分频给骗了,此处没有进行 2 分频 ( 我就被这个 2 分频骗了好久,主频一直配置不正确! ) 。④、经过第②步 2 分频以后的 498MHz 就是 ARM 的内核时钟,也就是 I.MX6U 的主频。经过上面几步的分析可知,假如我们要设置内核主频为 528MHz ,那么 PLL1 可以设置为1056MHz ,寄存器 CCM_CACRR 的 ARM_PODF 位设置为 2 分频即可。同理,如果要将主频设 置为 696MHz ,那么 PLL1 就可以设置为 696MHz , CCM_CACRR 的 ARM_PODF 设置为 1 分 频即可。现在问题很清晰了,寄存器 CCM_CACRR 的 ARM_PODF 位很好设置, PLL1 的频率 可以通过寄存器 CCM_ANALOG_PLL_ARMn 来设置。
实验程序编写
本试验在上一章试验“ 7_key ”的基础上完成,因为本试验是配置 I.MX6U 的系统时钟,因
此我们直接在文件“ bsp_clk.c ”上做修改,修改 bsp_clk.c 的内容如下:
#include "bsp_clk.h"//添加了函数 imx6u_clkinit(),完成 I.MX6U 的系统时钟初始化
17 /*
18 * @description : 使能 I.MX6U 所有外设时钟
19 * @param : 无
20 * @return : 无
21 */
22 void clk_enable(void)
23 {
24 CCM->CCGR0 = 0XFFFFFFFF;
25 CCM->CCGR1 = 0XFFFFFFFF;
26 CCM->CCGR2 = 0XFFFFFFFF;
27 CCM->CCGR3 = 0XFFFFFFFF;
28 CCM->CCGR4 = 0XFFFFFFFF;
29 CCM->CCGR5 = 0XFFFFFFFF;
30 CCM->CCGR6 = 0XFFFFFFFF;
31 }
32
33 /*
34 * @description : 初始化系统时钟 528Mhz,并且设置 PLL2 和 PLL3 各个
35 PFD 时钟,所有的时钟频率均按照 I.MX6U 官方手册推荐的值.
36 * @param : 无
37 * @return : 无
38 */
39 void imx6u_clkinit(void)
40 {
41 unsigned int reg = 0;
42 /* 1、设置 ARM 内核时钟为 528MHz */
43 /* 1.1、判断当使用哪个时钟源启动的,正常情况下是由 pll1_sw_clk 驱动的,而
44 * pll1_sw_clk 有两个来源:pll1_main_clk 和 step_clk,如果要
45 * 让 I.MX6ULL 跑到 528M,那必须选择 pll1_main_clk 作为 pll1 的时钟
46 * 源。如果我们要修改 pll1_main_clk 时钟的话就必须先将 pll1_sw_clk 从
47 * pll1_main_clk 切换到 step_clk,当修改完以后再将 pll1_sw_clk 切换
48 * 回 pll1_main_cl,step_clk 等于 24MHz。
49 */
50
51 if((((CCM->CCSR) >> 2) & 0x1 ) == 0) /* pll1_main_clk */
52 {
53 CCM->CCSR &= ~(1 << 8); /* 配置 step_clk 时钟源为 24MHz OSC */
54 CCM->CCSR |= (1 << 2); /* 配置 pll1_sw_clk 时钟源为 step_clk */
55 }
56
57 /* 1.2、设置 pll1_main_clk 为 1056MHz,也就是 528*2=1056MHZ,
58 * 因为 pll1_sw_clk 进 ARM 内核的时候会被二分频!
59 * 配置 CCM_ANLOG->PLL_ARM 寄存器
60 * bit13: 1 使能时钟输出
61 * bit[6:0]: 88, 由公式:Fout = Fin * div_select / 2.0,
62 * 1056=24*div_select/2.0, 得出:div_select=88。
63 */
64 CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0X7F);
65 CCM->CCSR &= ~(1 << 2);/* 将 pll_sw_clk 时钟切换回 pll1_main_clk */
66 CCM->CACRR = 1; /* ARM 内核时钟为 pll1_sw_clk/2=1056/2=528Mhz */
67
68 /* 2、设置 PLL2(SYS PLL)各个 PFD */
69 reg = CCM_ANALOG->PFD_528;
70 reg &= ~(0X3F3F3F3F); /* 清除原来的设置 */
71 reg |= 32<<24; /* PLL2_PFD3=528*18/32=297Mhz */
72 reg |= 24<<16; /* PLL2_PFD2=528*18/24=396Mhz */
73 reg |= 16<<8; /* PLL2_PFD1=528*18/16=594Mhz */
74 reg |= 27<<0; /* PLL2_PFD0=528*18/27=352Mhz */
75 CCM_ANALOG->PFD_528=reg; /* 设置 PLL2_PFD0~3 */
76
77 /* 3、设置 PLL3(USB1)各个 PFD */
78 reg = 0; /* 清零 */
79 reg = CCM_ANALOG->PFD_480;
80 reg &= ~(0X3F3F3F3F); /* 清除原来的设置 */
81 reg |= 19<<24; /* PLL3_PFD3=480*18/19=454.74Mhz */
82 reg |= 17<<16; /* PLL3_PFD2=480*18/17=508.24Mhz */
83 reg |= 16<<8; /* PLL3_PFD1=480*18/16=540Mhz */
84 reg |= 12<<0; /* PLL3_PFD0=480*18/12=720Mhz */
85 CCM_ANALOG->PFD_480=reg; /* 设置 PLL3_PFD0~3 */
86
87 /* 4、设置 AHB 时钟 最小 6Mhz, 最大 132Mhz */
88 CCM->CBCMR &= ~(3 << 18); /* 清除设置*/
89 CCM->CBCMR |= (1 << 18); /* pre_periph_clk=PLL2_PFD2=396MHz */
90 CCM->CBCDR &= ~(1 << 25); /* periph_clk=pre_periph_clk=396MHz */
91 while(CCM->CDHIPR & (1 << 5));/* 等待握手完成 */
92
93 /* 修改 AHB_PODF 位的时候需要先禁止 AHB_CLK_ROOT 的输出,但是
94 * 我没有找到关闭 AHB_CLK_ROOT 输出的的寄存器,所以就没法设置。
95 * 下面设置 AHB_PODF 的代码仅供学习参考不能直接拿来使用!!
96 * 内部 boot rom 将 AHB_PODF 设置为了 3 分频,即使我们不设置 AHB_PODF,
97 * AHB_ROOT_CLK 也依旧等于 396/3=132Mhz。
98 */
99 #if 0
100 /* 要先关闭 AHB_ROOT_CLK 输出,否则时钟设置会出错 */
101 CCM->CBCDR &= ~(7 << 10);/* CBCDR 的 AHB_PODF 清零 */
102 CCM->CBCDR |= 2 << 10; /* AHB_PODF 3 分频,AHB_CLK_ROOT=132MHz */
103 while(CCM->CDHIPR & (1 << 1));/* 等待握手完成 */
104 #endif
105
106 /* 5、设置 IPG_CLK_ROOT 最小 3Mhz,最大 66Mhz */
107 CCM->CBCDR &= ~(3 << 8); /* CBCDR 的 IPG_PODF 清零 */
108 CCM->CBCDR |= 1 << 8; /* IPG_PODF 2 分频,IPG_CLK_ROOT=66MHz */
109
110 /* 6、设置 PERCLK_CLK_ROOT 时钟 */
111 CCM->CSCMR1 &= ~(1 << 6); /* PERCLK_CLK_ROOT 时钟源为 IPG */
112 CCM->CSCMR1 &= ~(7 << 0); /* PERCLK_PODF 位清零,即 1 分频 */
113 }
文件 bsp_clk.c 中一共有两个函数: clk_enable 和 imx6u_clkinit ,其中函数 clk_enable 前面
已经讲过了,就是使能 I.MX6U 的所有外设时钟。函数 imx6u_clkinit 才是本章的重点,
imx6u_clkinit 先设置系统主频为 528MHz ,然后根据我们上一小节分析的 I.MX6U 时钟系统来
设置 8 路 PFD ,最后设置 AHB 、 IPG 和 PERCLK 的时钟频率。
在 bsp_clk.h 文件中添加函数 imx6u_clkinit 的声明,最后修改 main.c 文件,在 main 函数里
面调用 imx6u_clkinit 来初始化时钟,如下所示:
1 int main(void)
2 {
3 int i = 0;
4 int keyvalue = 0;
5 unsigned char led_state = OFF;
6 unsigned char beep_state = OFF;
7
8 imx6u_clkinit(); /* 初始化系统时钟 */
9 clk_enable(); /* 使能所有的时钟 */
10 led_init(); /* 初始化 led */
11 beep_init(); /* 初始化 beep */
12 key_init(); /* 初始化 key */
13
14 /* 省略掉其它代码 */
15 }
编译下载验证
因为本章是在试验“ 7_key ”上修改的,而且本章试验没有添加任何新的文件,因此只需
要修改 Makefile 的变量 TARGET 为“ clk ”即可
使用 Make 命令编译代码,编译成功以后使用软件 imxdownload 将编译完成的 clk.bin 文件
下载到 SD 卡中,命令如下:
chmod 777 imxdownload //给予 imxdownload 可执行权限,一次即可
./imxdownload clk.bin /dev/sdd //烧写到 SD 卡中,不能烧写到/dev/sda 或 sda1 设备里面!
烧写成功以后将 SD 卡插到开发板的 SD 卡槽中,然后复位开发板。本试验效果其实和试
验“ 7_key ”一样,但是 LED 灯的闪烁频率相比试验“ 7_key ”要快一点。因为试验“ 7_key ”的
主频是 396MHz ,而本试验的主频被配置成了 528MHz ,因此代码执行速度会变快,所以延时函
数的运行就会加快。