3.2 C语言实现GPIO输出实验(LED)——使用BSP工程管理代码(二)

一、简述

在写一些小的工程时,我们会源码文件放到工程的根目录下,如果工程文件比较少的话这样做无可厚非,但是如果工程源文件达到几十、甚至数百个的时候,这样一股脑全部放到根目录下就会使工程显得混乱不堪。所以我们必须对工程文件做管理,将不同功能的源码文件放到不同的目录中。另外我们也需要将源码文件中,所有完成同一个功能的代码提取出来放到一个单独的文件中,也就是对程序分功能管理。使其美观、功能模块清晰、易于阅读。
本文所做的事主要有三件:
1、移植官方的SDK定义文件;
2、使用结构体来组织同一类型外设的地址;
3、使用工程管理目录结构。

目录结构

二、移植移植官方的SDK

I.MX6ULL 的 SDK 包在 NXP 官网下载。SDK中将 fsl_common.h、fsl_iomuxc.h 和 MCIMX6Y2.h 这三个文件拷贝到工程中直接编译的话肯定会出错的,需要对其做删减。并新建一个名为 cc.h 的头文件,cc.h 里面存放一些 SDK 库文件需要使用到的数据类型,因为有些第三方库会用到这些变量类型。

/*filename : cc.h*/

#ifndef __CC_H
#define __CC_H

#define __I     volatile
#define __O     volatile
#define __IO    volatile

#define ON      1
#define OFF     0

typedef signed char     int8_t;
typedef signed short    int16_t;
typedef signed int      int32_t;
typedef unsigned char   uint8_t;
typedef unsigned short  uint16_t;
typedef unsigned int    uint32_t;
typedef unsigned long long   uint64_t;

typedef signed char         s8;
typedef signed short        s16;
typedef signed int          s32;
typedef signed long long    s64;
typedef unsigned char       u8;
typedef unsigned short      u16;
typedef unsigned int        u32;
typedef unsigned long long  u64;
#endif

在bsp_led.c中的初始化函数有调用SDK文件所定义的函数:

/* 初始化LED */
void led_init(void)
{
    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
    IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0);

    /* GPIO初始化 */
    GPIO1->GDIR = 0x8;           /* 设置为输出 */
    GPIO1->DR = 0X0;             /* 打开LED灯 */
}

三、使用结构体来组织同一类型外设的地址

C语言实现GPIO输出实验(LED)——让C跑起来(一)时候,每个寄存器的地址我们都需要写宏定义,使用起来非常的不方便。在移值SDK后,我们可以使用“GPIO1->GDIR = 0x8;”这种方式来给GPIO1 的寄存器 GDIR 赋值,因为在 I.MX6U中同属于一个外设的所有寄存器地址基本是相邻的(有些会有保留寄存器)。因此我们可以借助 C 语言里面的结构体成员地址递增的特点来将某个外设的所有寄存器写入到一个结构体里面,然后定义一个结构体指针指向这个外设的寄存器基地址,这样我们就可以通过这个结构体指针来访问这个外设的所有寄存器。

1、下面就以GPIO1进行说明

GPIO1 memory map

/* ----------------------------------------------------------------------------
   -- GPIO Peripheral Access Layer
   ---------------------------------------------------------------------------- */

/*!
 * @addtogroup GPIO_Peripheral_Access_Layer GPIO Peripheral Access Layer
 * @{
 */

/** GPIO - Register Layout Typedef */
typedef struct {
  __IO uint32_t DR;                                /**< GPIO data register, offset: 0x0 */
  __IO uint32_t GDIR;                              /**< GPIO direction register, offset: 0x4 */
  __I  uint32_t PSR;                               /**< GPIO pad status register, offset: 0x8 */
  __IO uint32_t ICR1;                              /**< GPIO interrupt configuration register1, offset: 0xC */
  __IO uint32_t ICR2;                              /**< GPIO interrupt configuration register2, offset: 0x10 */
  __IO uint32_t IMR;                               /**< GPIO interrupt mask register, offset: 0x14 */
  __IO uint32_t ISR;                               /**< GPIO interrupt status register, offset: 0x18 */
  __IO uint32_t EDGE_SEL;                          /**< GPIO edge select register, offset: 0x1C */
} GPIO_Type;

/* GPIO - Peripheral instance base addresses */
/** Peripheral GPIO1 base address */
#define GPIO1_BASE                               (0x209C000u)
/** Peripheral GPIO1 base pointer */
#define GPIO1                                    ((GPIO_Type *)GPIO1_BASE)

经过上面的定义后,我们可以直接使用GPIO1->GDIR = 0x8; GPIO1->DR = 0X0; 对GPIO1的相关寄存器进行读写。

2、有时需要保留地址

在这里插入图片描述
从上图可以看出IIC所使用的是16位寄存器,相当于2字节,但相邻两个地址间相隔了4个地址(21A_0004——21A_0000),也就是指向下一个寄存器需要移动四个字节(字址)。但一个寄存器我们需要使用16位的无符号整型数类型来定义,所以我们在定义下一个寄存器前,需要保留16位保留成员来达到32位(4字节)间隔。

/*!
 * @addtogroup I2C_Peripheral_Access_Layer I2C Peripheral Access Layer
 * @{
 */

/** I2C - Register Layout Typedef */
typedef struct {
  __IO uint16_t IADR;                              /**< I2C Address Register, offset: 0x0 */
       uint8_t RESERVED_0[2];
  __IO uint16_t IFDR;                              /**< I2C Frequency Divider Register, offset: 0x4 */
       uint8_t RESERVED_1[2];
  __IO uint16_t I2CR;                              /**< I2C Control Register, offset: 0x8 */
       uint8_t RESERVED_2[2];
  __IO uint16_t I2SR;                              /**< I2C Status Register, offset: 0xC */
       uint8_t RESERVED_3[2];
  __IO uint16_t I2DR;                              /**< I2C Data I/O Register, offset: 0x10 */
} I2C_Type;

在上面代码中使用 uint8_t RESERVED_0[2];(两个无符号8位整型数据)来保留地址。

四、使用工程管理代码

1、工程目录结构

在这里插入图片描述
bsp :用来存放驱动文件, 比如函数 clk_enable、 led_init 和 delay,这三个函数可以分为三类:时钟驱动、 LED 驱动和延时驱动。因此我们可以在 bsp 文件夹下创建三个子文件夹: clk、 delay 和 led,分别用来存放时钟驱动文件、延时驱动文件和 LED驱动文件,这样main.c 函数就会清爽很多,程序功能模块清晰。;

imx6ul :用来存放跟芯片有关的文件,比如 NXP 官方的 SDK库文件;

obj :用来存放编译生成的.o 文件;

project :存放 start.S 和 main.c 文件,也就是应用文件;

2、Makefile文件

CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET        ?= led
 
CC          := $(CROSS_COMPILE)gcc
LD          := $(CROSS_COMPILE)ld
OBJCOPY     := $(CROSS_COMPILE)objcopy
OBJDUMP     := $(CROSS_COMPILE)objdump
 
INCUDIRS    :=   imx6u \
            bsp/clk \
            bsp/led \
            bsp/delay 
       
SRCDIRS     :=   project \
            bsp/clk \
            bsp/led \
            bsp/delay 
 
INCLUDE     := $(patsubst %, -I %, $(INCUDIRS))
 
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) -Timx6u.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).bin $(TARGET).dis $(OBJS)
 
print:
  @echo INCLUDE = $(INCLUDE)
  @echo SFILES = $(SFILES)
  @echo CFILES = $(CFILES)
  @echo SFILENDIR = $(SFILENDIR)
  @echo CFILENDIR = $(CFILENDIR)
  @echo SOBJS = $(SOBJS)
  @echo COBJS = $(COBJS)
  @echo OBJS = $(OBJS)

Makefile代码说明:
第 1~7 行定义了一些变量,除了第 2 行以外其它的都是跟编译器有关的,如果使用其它编译器的话只需要修改第 1 行即可。第 2 行的变量 TARGET 目标名字;
第 9 行的变量 INCDIRS 包含整个工程的.h 头文件目录,文件中的所有头文件目录都要添加到变量INCDIRS中。
INCDIRS := imx6ul bsp/clk bsp/led bsp/delay
第 14 行是变量 SRCDIRS,和变量 INCDIRS 一样,只是 SRCDIRS 包含的是整个工程的所有.c 和.S 文件目录。
SRCDIRS := project bsp/clk bsp/led bsp/delay
第 19 行的变量 INCLUDE 是用到了函数 patsubst,通过函数 patsubst 给变量INCDIRS 添加一个“-I”,加“-I”的目的是因为 Makefile 语法要求指明头文件目录的时候需要加上“-I”。即:
INCLUDE := -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay
第 21和22 行变量 CFILES 和变量 SFILES 一样,只是 CFILES 保存工程中所有的.c 文件(包含绝对路径),最终 CFILES 如下:
CFILES = project/main.c bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c
第 24 和 25 行的变量 SFILENDIR 和 CFILENDIR 包含所有的.S 汇编文件和.c 文件,相比变量 SFILES 和 CFILES, SFILENDIR 和 CFILNDIR 只是文件名,不包含文件的绝对路径。使用函数 notdir 将SFILES 和 CFILES 中的路径去掉即可, SFILENDIR 和 CFILENDIR 如下:
SFILENDIR = start.S
CFILENDIR = main.c bsp_clk.c bsp_led.c bsp_delay.c

第 27 和 28 行的变量 SOBJS 和 COBJS 是.S 和.c 文件编译以后对应的.o 文件目录,默认所有的文件编译出来的.o 文件和源文件在同一个目录中,这里我们将所有的.o 文件都放到 obj 文件夹下, SOBJS 和 COBJS 内容如下:
SOBJS = obj/start.o
COBJS = obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o

第 30行变量 OBJS 是变量 SOBJS 和 COBJS 的集合,如下:
OBJS = obj/start.o obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
第 32行的 VPATH 是指定搜索目录的,这里指定的搜素目录就是变量 SRCDIRS 所保存的目录,这样当编译的时候所需的.S 和.c 文件就会在 SRCDIRS 中指定的目录中查找。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值