目录
一、硬件原理分析
二、寄存器分析
1、时钟源初始化
2、设置 IO 复用
3、初始化 IO 复用引脚(设置电气属性)
4、初始化GPIO
三、汇编代码(start.s)
四、公共头文件(imx6u.h)
四、C 代码编写
1、clk 模块(bsp_clk.c)
2、led 模块(bsp_led.c)
2、delay 模块(bsp_delay.c)
3、key 模块(bsp_key.c)
4、main.c
五、链接脚本(imx6u.lds)
六、Makefile 文件(测试工程的整体结构)
一、硬件原理分析
我们在《imx6ull 底板原理图》中找到按键 KEY 模块。R12 是一个上拉电阻,当按键 KEY0 没有按下时,KEY0 处于高电平;当按键KEY0 按下时,KEY0 接地,此时KEY0 将变为低电平。
接下来我们要去 核心板原理图上找 KEY0 连接的哪个引脚。我们发现是和 UART1_CTS 这个引脚相连。
二、寄存器分析
1、时钟源初始化
这里就不再赘述,可以参考:时钟源初始化
2、设置 IO 复用
按键属于 IO 的范畴,既然和 IO 有关,那我们就要去找第32章的 IOMUX(IO复用选择器)。根据之前的习惯,我们要找和 UART1_CTS 相关的复用寄存器。
IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B 就是我们要找的寄存器了。UART1_CTS 似乎可以复用为很多功能,我们选择的是复用为 GPIO1_IO18 功能。其他位不变,低四位设为 0101。
寄存器: IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B
基地址: 0x20E008C
初始化值: 0x5 # 低四位为0101,其他位不变
注意:
虽然找的是 UART,但不代表是复用为 UART功能,UART 是用于数据传输的;GPIO 只是简单控制某个设备的开关,或者读取某个设备的状态。
3、初始化 IO 复用引脚(设置电气属性)
接下来我们要初始化复用引脚,此时要找前缀为 IOMUXC_SW_PAD_xxx 而且和 UART 相关的寄存器。然后设置每一位。多数都和驱动LED时的设置一样。(初始化 IO 复用)
- 0:0
- 2-1:00
- 5-3:000 (这里要禁用输出,相当于用于输入)
- 7-6:10
- 10-8:000
- 11:0
- 12:1
- 13:1(选择 pull 功能)
- 15-14:11(按键 KEY0 默认是上拉状态)
- 16:0
- 31-17:剩余为 0
寄存器: IOMUXC_SW_PAD_CTL_PAD_UART1_CTS_B
基地址: 0x20E0318
初始化值: 0xF080
4、初始化GPIO
从“设置IO复用”部分我们可以知道,UART_CTS 可被复用为 GPIO1_IO18,因此我们需要去初始化 GPIO1_IO18 引脚,设置的方式和驱动LED使用的方式类似。
寄存器: GPIO1_GDIR
基地址: 0x209C004
初始值: GPIO1_GDIR &= ~(1 << 18) # 因为是GPIO1的第18个引脚设为输入,即第 18 bit应为0
寄存器: GPIO1_DR
基地址: 0x209C000
初始值:
- 低电平: GPIO1_DR &= ~(1 << 18)
- 高电平: GPIO1_DR |= (1 << 18)
三、汇编代码(start.s)
汇编代码用于搭建C语言环境。关于汇编代码,可以参考之前驱动LED的汇编代码:汇编代码解析
四、公共头文件(imx6u.h)
公共头文件保存了要用到的寄存器的基地址。
#ifndef _IMX6U_H
#define _IMX6U_H
typedef unsigned int uint32_t;
/*
* 时钟相关寄存器地址
*/
#define CCM_CCGR0 *((volatile uint32_t*)0x20C4068)
#define CCM_CCGR1 *((volatile uint32_t*)0x20C406C)
#define CCM_CCGR2 *((volatile uint32_t*)0x20C4070)
#define CCM_CCGR3 *((volatile uint32_t*)0x20C4074)
#define CCM_CCGR4 *((volatile uint32_t*)0x20C4078)
#define CCM_CCGR5 *((volatile uint32_t*)0x20C407C)
#define CCM_CCGR6 *((volatile uint32_t*)0x20C4080)
/*
* IOMUX 相关寄存器地址
*/
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 *((volatile uint32_t*)0x020E0068) // IO复用为 GPIO1_IO03
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 *((volatile uint32_t*)0x020E02F4) // 设置复用引脚的电气属性
#define IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B *((volatile uint32_t*)0x20E008C) // IO 复用为 GPIO1_IO18
#define IOMUXC_SW_PAD_CTL_PAD_UART1_CTS_B *((volatile uint32_t*)0x20E0318) // 设置复用引脚的电气属性
/*
* 设置GPIO输出相关寄存器地址
*/
#define GPIO1_DR *((volatile uint32_t*)0x0209C000) // GPIO输出
#define GPIO1_GDIR *((volatile uint32_t*)0x0209C004) // 设置输入还是输出
#endif
四、C 代码编写
为了验证开关,我们加入之前的 led 代码,之前的 led 代码已经被封装好了,直接调用函数即可。我们的目的是通过开关控制灯的亮灭。
1、clk 模块(bsp_clk.c)
void clk_init()
{
CCM_CCGR0 = 0xffffffff;
CCM_CCGR1 = 0xffffffff;
CCM_CCGR2 = 0xffffffff;
CCM_CCGR3 = 0xffffffff;
CCM_CCGR4 = 0xffffffff;
CCM_CCGR5 = 0xffffffff;
CCM_CCGR6 = 0xffffffff;
}
2、led 模块(bsp_led.c)
void led_init()
{
/* 1、设置IO复用为GPIO1_IO03 */
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x5;
/* 2、初始化复用引脚,设置电气属性 */
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0x10B0;
/* 3、初始化GPIO1 */
GPIO1_GDIR |= (1 << 3);
}
void led_on()
{
GPIO1_DR &= ~(1 << 3);
}
void led_off()
{
GPIO1_DR |= (1 << 3);
}
void switch_led(uint8_t status)
{
if (status == ON)
{
led_on();
}
else
{
led_off();
}
}
2、delay 模块(bsp_delay.c)
void delay_short(volatile unsigned int n)
{
while(n--){}
}
void delay(volatile unsigned int n)
{
while(n--)
{
delay_short(0x7ff);
}
}
3、key 模块(bsp_key.c)
void key_init()
{
/* 1、设置IO复用为GPIO1_IO03 */
IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B = 0x5;
/* 2、初始化复用引脚,设置电气属性 */
IOMUXC_SW_PAD_CTL_PAD_UART1_CTS_B = 0xF080;
/* 3、初始化GPIO1 */
GPIO1_GDIR &= ~(1 << 18);
}
unsigned char read_key()
{
unsigned char ret = 1;
if (((GPIO1_DR >> 18) & 0x01) == 0)
{
delay(10);
if (((GPIO1_DR >> 18) & 0x01) == 0)
{
ret = 0;
}
}
return ret;
}
4、main.c
int main(void)
{
clk_init(); // 初始化时钟
led_init(); // 初始化LED
key_init(); // 初始化按键
led_on(); // LED 初始为亮
unsigned char status = OFF;
while (1)
{
if (read_key() == 0)
{
switch_led(status);
status = ON;
}
}
return 0;
}
五、链接脚本(imx6u.lds)
SECTIONS
{
. = 0x87800000;
.text :
{
obj/start.o
*(.text)
}
.rodata ALIGN(4) : { *(.rodata) }
.data ALIGN(4) : { *(.data) }
. = ALIGN(4);
__bss_start = . ;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = . ;
}
六、Makefile 文件(测试工程的整体结构)
测试工程的结构如下:
# ################################################################
# # 指定编译器、获取源文件、指定目标文件的生成位置
# ################################################################
# 编译器
TOOLCHAIN_PATH := /home/pigeon/workspace/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
CC := $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-gcc
LD := $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-ld
OBJCOPY := $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-objcopy
OBJDUMP := $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-objdump
# 目标文件名
TARGET_NAME := key
OBJ_DIR := obj
# 引入头文件
INCLUDES := -Icommon -Ibsp
# 获取到一级子目录下的源文件
DIR1 := project
SOURCES += $(wildcard ${DIR1}/*.c ${DIR1}/*.s)
# 获取到二级子目录下的源文件
DIR2 := bsp
SUBDIRS = $(shell ls $(DIR2) -l | grep ^d | awk '{print $$9}')
SOURCES += $(foreach subdir,${SUBDIRS}, $(wildcard $(DIR2)/${subdir}/*.c))
# 创建 $(OBJ_DIR) 目录
$(shell \
if test -e $(OBJ_DIR); then \
rm -rf $(OBJ_DIR)/*; \
else \
mkdir -p $(OBJ_DIR); \
fi)
# 生成 .o 文件
define compile
OBJ = $(if $(findstring .c,$1), $(patsubst %.c,%.o,$1), $(patsubst %.s,%.o,$1))
NODIR_OBJ = $$(notdir $$(OBJ))
$$(shell $$(CC) -o $$(OBJ_DIR)/$$(NODIR_OBJ) -c $1 $$(INCLUDES))
OBJ_FILE += $$(OBJ_DIR)/$$(NODIR_OBJ)
endef
OBJ_FILE :=
$(foreach src_file, ${SOURCES}, $(eval $(call compile, $(src_file)))) # 遍历每一个源文件,同时生成对应的 .o 文件
# ################################################################
# # 编译生成目标文件、库文件/执行文件
# ################################################################
default:
$(LD) -Timx6u.lds -o $(OBJ_DIR)/$(TARGET_NAME).elf $(OBJ_FILE)
$(OBJCOPY) -O binary -S -g $(OBJ_DIR)/$(TARGET_NAME).elf $(OBJ_DIR)/$(TARGET_NAME).bin
$(OBJDUMP) -D $(OBJ_DIR)/$(TARGET_NAME).elf > $(OBJ_DIR)/$(TARGET_NAME).dis
# ################################################################
# # 伪目标/自定义函数
# ################################################################
.PHONY:clean
clean:
rm -rf ${OBJ_DIR}/*