I.MX6ULL裸机驱动开发四种方式总结

学习裸机驱动开发也有一阵子,终于是把几种裸机驱动方法学完了,总体来说难度并不大。因为毕竟有基础在,能大概看懂数据手册和知道如何是控制寄存器。

驱动方法目前所学共有四种:

        1.纯汇编驱动

        2.汇编驱动头文件,剩下的使用C语言来驱动

        3.模仿STM32的方式来进行驱动

        4.移植NXP的官方SDK来进行驱动

四种方法有共同点和不同点,下面我来 一 一总结:

共同点:

        1.无论怎么样,都必须使用汇编语言来启动头文件。选择了启动方式之后如果不使用汇编语言来启动头文件,任你后面的代码再怎么写你也驱动不了开发板。驱动代码对于接下来的所有方法都是相同的!

        2.编译程序的步骤都是大同小异的。第一步先把软件代码写好,第二步将.C(源文件) .S(汇编文件)文件转换为.O(已编译不可执行)文件。第三步将所有的.O文件链接为elf格式的可执行文件。最后将.elf文件转换成.bin文件。这个.bin文件会和头文件一起被写入你所选择的启动方式里面去。我建议还可以在这里再加上一步,反汇编elf文件。为什么呢?因为反汇编最重要的就是可以看到你文件的开头文件,一定要是.S文件开头,只有汇编文件开头启动一些外设,其他文件才有写入的可能!       

        下面附上:个C语言代码源文件需要经过如图四个步骤,才能变成可以被计算机执行的文件。

         3.Makefile。这个肯定是毋庸置疑很好用的make工具。使用它可以省去很多的操作步骤和少些很多的代码。而且如果需要修改一部分链接文件的代码,可以直接修改,然后编译。如果不使用,修改.C .S文件之后,又要重新走一遍编译程序的步骤,很繁琐不建议!而且最重要的一天,以后要学习的大头:系统移植和uboot都是要经常使用Makefile的!

CROSS_COMPILE ?= arm-linux-gnueabihf-
NAME          ?= ledc                          //如果以后其他模块需要使用,只需要改名字就好了

CC            := $(CROSS_COMPILE)gcc
LD            := $(CROSS_COMPILE)ld
OBJCOPY       := $(CROSS_COMPILE)objcopy
OBJDUMP       := $(CROSS_COMPILE)objdump

OBJS          :=  start.o main.o

$(NAME).bin:$(OBJS)
	$(LD) -Timx6u.lds -o $(NAME).elf $^
	$(OBJCOPY) -O binary -S $(NAME).elf $@
	$(OBJDUMP) -D -m arm $(NAME).elf > $(NAME).dis

%.o : %.c
	$(CC) -Wall -nostdlib -c -O2 -o $@ $<

%.o : %.S
	$(CC) -Wall -nostdlib -c -O2 -o $@ $<

clean:
	rm -rf *.o $(NAME).bin $(NAME).elf $(NAME).dis

download:
	./imxdownload $(NAME).bin /dev/sdb

        4.链接脚本也是可以通用的。有了链接脚本我们就可以调控很多文件到我们指定的区域!我们是通过“-Ttext”来指定链接地址是 0X87800000 的,这样的话所有的文件都会链接到以0X87800000 为起始地址的区域。但是有时候我们很多文件需要链接到指定的区域,或者叫做段里面,比如在 Linux 里面初始化函数就会放到 init 段里面。因此我们需要能够自定义一些段,这些段的起始地址我们可以自由指定,同样的我们也可以指定一个文件或者函数应该存放到哪个段里面去。要完成这个功能我们就需要使用到链接脚本。

SECTIONS{
    . = 0x87800000;                     //先将指定链接区域选中为0x87800000 .:定位计数器
    .text :                             //设置.text段
    {
        start.o          //start.o 里面包含着第一个要执行的指令,所以一定要链接到最开始的地方。
        *(.text)                        //剩余的所有.text文件  通配符*
    }
    .rodate ALIGN(4) : {*(.rodata*)}    //设置.rodata
    .data ALIGN(4) : {*(.data)}         //设置.data段,对“.data”这个段的起--
                                        //--始地址做字节对齐的, ALIGN(4)表示 4 字节对齐
    
    __bss_start=.;             
    .bss ALIGN(4) : {*(.bss) *(COMMON)}
    __bss_end=.;  
    }  
//.bss 段的起始地址和结束地址保存在“__bss_start”和“__bss_end”,.bss 段是定义了但是没有被初始化的变量


不同点:

        1.纯汇编语言驱动代码是简洁一些,但是如果用它来来做一些复杂的控制,我认为局限性比较大,例如使用汇编语言来实现呼吸灯、流水灯,这些会很麻烦。

        2.模仿STM32来驱动是很大不同的,我们需要将结构体抽象为外设,获得外设寄存器的基地址。添加寄存器结构体(在结构体中添加寄存器的时候一定要注意地址的连续性,如果不连续的话要添加占位。)由于编写寄存器结构体代码实在过于枯燥和繁琐,我只展示了一部分,剩下的按照方法来就行。

/***************************************************************
Copyright © godfatherlzq
文件名	:	 imx6ul.h
作者	   : godfatherlzq
版本	   : V1.0
描述	   : IMX6UL相关寄存器定义,参考STM32寄存器定义方法
其他	   : 无
日志	   : 初版V1.0 2022.4.22 李柱奇创建
**************************************************************/

/* 
 * 外设寄存器组的基地址 
 */
#define CCM_BASE					(0X020C4000)
#define CCM_ANALOG_BASE				(0X020C8000)
#define IOMUX_SW_MUX_BASE			(0X020E0044)
#define IOMUX_SW_PAD_BASE			(0X020E0204)
#define GPIO1_BASE                  (0x0209C000)
#define GPIO2_BASE                  (0x020A0000)
#define GPIO3_BASE                  (0x020A4000)
#define GPIO4_BASE                  (0x020A8000)
#define GPIO5_BASE                  (0x020AC000)

/* 
 * CCM寄存器结构体定义,分为CCM和CCM_ANALOG 
 */
typedef struct 
{
	volatile unsigned int CCR;
	volatile unsigned int CCDR;
	volatile unsigned int CSR;
	volatile unsigned int CCSR;
	volatile unsigned int CACRR;
	volatile unsigned int CBCDR;
	volatile unsigned int CBCMR;
	volatile unsigned int CSCMR1;
	volatile unsigned int CSCMR2;
	volatile unsigned int CSCDR1;
	volatile unsigned int CS1CDR;
	volatile unsigned int CS2CDR;
	volatile unsigned int CDCDR;
	volatile unsigned int CHSCCDR;
	volatile unsigned int CSCDR2;
	volatile unsigned int CSCDR3;	
	volatile unsigned int RESERVED_1[2];
	volatile unsigned int CDHIPR;  
	volatile unsigned int RESERVED_2[2];
	volatile unsigned int CLPCR;
	volatile unsigned int CISR;
	volatile unsigned int CIMR;
	volatile unsigned int CCOSR;
	volatile unsigned int CGPR;
	volatile unsigned int CCGR0;
	volatile unsigned int CCGR1;
	volatile unsigned int CCGR2;
	volatile unsigned int CCGR3;
	volatile unsigned int CCGR4;
	volatile unsigned int CCGR5;
	volatile unsigned int CCGR6;
	volatile unsigned int RESERVED_3[1];
	volatile unsigned int CMEOR;	
 } CCM_Type;

只要将这个配置的头文件配置好之后,剩下的我们就可以模仿STM32调用底层寄存器的方法来编写驱动代码啦。熟悉的感觉又回来了哈哈哈!

#include "imx6u.h"

/*
 * @description	: 使能I.MX6U所有外设时钟
 * @param 		: 无
 * @return 		: 无
 */
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;
}

/*
 * @description	: 初始化LED对应的GPIO
 * @param 		: 无
 * @return 		: 无
 */
void led_init(void)
{
	/* 1、初始化IO复用 */
	IOMUX_SW_MUX->GPIO1_IO03 = 0X5;		/* 复用为GPIO1_IO03 */


	/* 2、配置GPIO1_IO03的IO属性	
	 *bit 16:0 HYS关闭
	 *bit [15:14]: 00 默认下拉
     *bit [13]: 0 kepper功能
     *bit [12]: 1 pull/keeper使能
     *bit [11]: 0 关闭开路输出
     *bit [7:6]: 10 速度100Mhz
     *bit [5:3]: 110 R0/6驱动能力
     *bit [0]: 0 低转换率
     */
    IOMUX_SW_PAD->GPIO1_IO03 = 0X10B0;


	/* 3、初始化GPIO */
	GPIO1->GDIR = 0X0000008;	/* GPIO1_IO03设置为输出 */

	/* 4、设置GPIO1_IO03输出低电平,打开LED0 */	
	GPIO1->DR &= ~(1 << 3);	
	
}

/*
 * @description	: 打开LED灯
 * @param 		: 无
 * @return 		: 无
 */
void led_on(void)
{
	/* 将GPIO1_DR的bit3清零 	*/
	GPIO1->DR &= ~(1<<3); 
}

/*
 * @description	: 关闭LED灯
 * @param 		: 无
 * @return 		: 无
 */
void led_off(void)
{
	/* 将GPIO1_DR的bit3置1 */
	GPIO1->DR |= (1<<3); 
}

        3.通过移植NXP的官方SDK包来写驱动代码。上面自己来编写寄存器结构体确实过于枯燥,在STM32中TI公司编写了这部分代码,那在我们的I.MX6U系列芯片里面有吗?当然是有的,就是官方的SDK包。只需要在官网下载完毕后通过FZ软件传给Ubuntu,装进去就可以使用了!但是因为我们在SDK包里会用到很多数据类型,我们就需要自己来设置一个头文件来定义一些常用的数据类型。

#ifndef __CC_H
#define __CC_H


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

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

这部分和STM32中我们熟知的u8也差不多。有了配置好的SDK包编写代码会比以前的更简单

    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0);
	IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03,0X10b0);

通过两个函数就可以解决所有配置的问题,很方便!

全文总结:

        四种驱动方式,各有各的好处,我觉得应该都会一点。了解他们的优缺点,已便将来逻辑开发时能够快速的选择最好的驱动方法!总结一遍下来,感觉我对基础驱动的理解又深刻了一分,加油!

你可能感兴趣的:(裸机开发,Linux,arm,c语言,visual,studio,code)