【裸机开发】按键输入实验

目录

一、硬件原理分析

二、寄存器分析

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 将变为低电平。

【裸机开发】按键输入实验_第1张图片

接下来我们要去 核心板原理图上找 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。

【裸机开发】按键输入实验_第2张图片

寄存器: 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 文件(测试工程的整体结构)

测试工程的结构如下:

【裸机开发】按键输入实验_第3张图片

# ################################################################
# # 指定编译器、获取源文件、指定目标文件的生成位置
# ################################################################
# 编译器
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}/*

你可能感兴趣的:(#,裸机开发,单片机,嵌入式硬件)