C语言裸机GPIO控制—基于I.MX6UL嵌入式SoC

1、前言

在前面的文章《汇编裸机GPIO控制—基于I.MX6UL嵌入式SoC》中,链接如下:

https://www.cnblogs.com/Cqlismy/p/12445395.html

描述了I.MX6UL这款SoC中IOMUX控制器复用GPIO的基本机制以及GPIO的控制原理,最后使用了汇编代码实现了一个GPIO的简单控制,但是在实际嵌入式的开发过程中,直接使用汇编代码对目标板上的外设进行控制是比较少的,例如用来启动Linux内核的Uboot,最开始运行的汇编代码一般也只是用来完成一些前期工作而已,比如ARM内核时钟开启、初始化DDR以及完成C语言运行环境等工作,当这些前期环境都准备好后,将会跳转到C语言的函数去执行。

2、C语言GPIO控制代码

接下来,学习如何使用C语言进行简单的裸机GPIO控制,在前面也提到了,要想运行C语言的代码,肯定是先要初始化好C语言的运行环境,比如说初始化DDR、初始化堆栈指针SP等,对于I.MX6UL这款SoC而言,DDR的初始化是通过Boot ROM中的代码读取可编程镜像中的DCD数据结构来初始化,有的SoC就要需要自己使用汇编代码进行初始化了。

先来看看,初始化C语言运行环境的汇编代码,新创建start.S汇编文件,代码如下:

.global _start

/**
 *  _start函数,程序先在这开始执行,用来设置C语言运行环境
 */
_start:
    /* SoC进入SVC运行模式 */
    mrs r0, cpsr
    bic r0, r0, #0x1f   /* 将cpsr寄存器的M[4:0]清0 */
    orr r0, r0, #0x13   /* SoC使用SVC运行模式 */
    msr cpsr,   r0

    /* 设置C语言运行环境 */
    ldr sp, =0x80200000 /* 设置栈指针 */
    b app      /* 跳转到C语言的app()函数执行 */

汇编代码首先定义了一个全局标号_start,然后定义了_start函数,该函数调后,先将SoC的运行模式初始化为SVC运行模式,对于运行模式的配置是通过CPSR寄存器的M[4:0]来完成的,接下来就是设置SP堆栈指针的值为0x80200000,在我当前的目标板CoM-P6UL中,DDR是256MB的,内存地址的范围为0x80000000~0x90000000,对于ARM v7架构的处理器堆栈是向下生长的,因此在这里定义的栈空间为2MB,最后,则是使用b指令进行跳转,也就是跳转到C语言的app()函数处执行。

对于DDR的初始化,就不需要使用汇编代码区实现了,I.MX6UL芯片内部的Boot ROM启动代码将会读取可编程镜像文件中DCD数据结构来完成DDR的初始化。

接下来,新创建app.h文件,将需要用到的寄存器地址进行定义,如下:

#ifndef __APP_H
#define __APP_H

/**
 *  SoC中CCM_CCGR寄存器地址
 */
#define CCM_CCGR0   *((volatile unsigned int *)0x020c4068)
#define CCM_CCGR1   *((volatile unsigned int *)0x020c406c)
#define CCM_CCGR2   *((volatile unsigned int *)0x020c4070)
#define CCM_CCGR3   *((volatile unsigned int *)0x020c4074)
#define CCM_CCGR4   *((volatile unsigned int *)0x020c4078)
#define CCM_CCGR5   *((volatile unsigned int *)0x020c407c)
#define CCM_CCGR6   *((volatile unsigned int *)0x020c4080)

/**
 *  SoC中IOMUXC中CSI_DATA00引脚复用配置寄存器地址
 */
#define SW_MUX_CTL_PAD_CSI_DATA00   *((volatile unsigned int *)0x020e01e4)
#define SW_PAD_CTL_PAD_CSI_DATA00   *((volatile unsigned int *)0x020e0470)

/**
 *  SoC中GPIO4寄存器相关地址
 */
#define GPIO4_DR        *((volatile unsigned int *)0x020a8000)
#define GPIO4_GDIR      *((volatile unsigned int *)0x020a8004)
#define GPIO4_PSR       *((volatile unsigned int *)0x020a8008)
#define GPIO4_ICR1      *((volatile unsigned int *)0x020a800c)
#define GPIO4_ICR2      *((volatile unsigned int *)0x020a8010)
#define GPIO4_IMR       *((volatile unsigned int *)0x020a8014)
#define GPIO4_ISR       *((volatile unsigned int *)0x020a8018)
#define GPIO4_EDGE_SEL  *((volatile unsigned int *)0x020a801c)

#endif

相关的寄存器地址可以在I.MX6UL芯片的参考手册中找到。

接下来,新创建app.c文件,实现完成GPIO控制的myapp()函数,实现的流程和使用汇编代码进行GPIO控制的流程一样,先使能相关的外设时钟,然后将IO口复用为GPIO复用模式,设置GPIO的方向为输出,通过写内部GPIO_DR寄存器来实现GPIO引脚的电平控制,app.c文件代码如下:

#include "app.h"

/**
 * system_clk_enable() - 使能SoC上所有外设时钟 
 */
void system_clk_enable(void)
{
    CCM_CCGR0 = 0xffffffff;
    CCM_CCGR1 = 0xffffffff;
    CCM_CCGR2 = 0xffffffff;
    CCM_CCGR3 = 0xffffffff;
    CCM_CCGR4 = 0xffffffff;
    CCM_CCGR5 = 0xffffffff;
    CCM_CCGR6 = 0xffffffff;
}

/**
 * gpio_init() - 初始化控制的GPIO
 * 
 * @param: 无
 * @return: 无
 */
void gpio_init(void)
{
    /* 设置CSI_DATA00引脚IO复用为GPIO4_IO21 */
    SW_MUX_CTL_PAD_CSI_DATA00 = 0x5;

    /**
     * 配置GPIO4_IO21引脚电气属性 
     * bit[16]: 0 关闭HYS
     * bit[15:14]: 00 默认下拉
     * bit[13]: 0 keeper
     * bit[12]: 1 pull/keeper使能
     * bit[11]: 0 禁止开路输出
     * bit[10:8]: 000 reserved
     * bit[7:6]: 10 速度为100MHz
     * bit[5:3]: 110 驱动能力为R0/6
     * bit[2:1]: 00 reserved
     * bit[0]: 0 低摆率
     */
    SW_PAD_CTL_PAD_CSI_DATA00 = 0x10b0;

    /* 设置GPIO4_IO21的方向为输出 */
    GPIO4_GDIR = 0x00200000;

    /* 设置GPIO4_IO21引脚输出高电平 */
    GPIO4_DR = 0x00200000;
}

/**
 * gpio_output_low() - GPIO4_IO21输出低电平
 * 
 * @param: 无
 * @return: 无
 */
void gpio_output_low(void)
{
    GPIO4_DR &= ~(1 << 21);
}

/**
 * gpio_output_hight() - GPIO4_IO21输出高电平
 * 
 * @param: 无
 * @return: 无
 */
void gpio_output_hight(void)
{
    GPIO4_DR |= (1 << 21);
}

/**
 * delay_short() - 短时间延时函数
 * 
 * @n: 要循环的次数
 * 
 * @return: 无
 */
void delay_short(volatile unsigned int n)
{
    while(n--);
}

/**
 * delay() - 延时函数,SoC在396MHz大概延时1ms
 * 
 * @n: 要延时的ms数
 * 
 * @return: 无
 */
void delay(volatile unsigned int n)
{
    while(n--) {
        delay_short(0x07ff);
    }
}

/**
 * app() - 主函数
 */
void app(void)
{
    system_clk_enable();    /* 外设时钟使能 */
    gpio_init();            /* GPIO初始化 */

    while (1) {
        gpio_output_hight();
        delay(1000);
        gpio_output_low();
        delay(1000);
    }
}

函数实现都比较简单,GPIO控制的思路和汇编实现的是一样的。

接下来,需要为我们的工程新创建一个Makefile文件,要不然怎么编译链接得到我们需要的.bin文件呢?链接的地址还是0x87800000,Makefile的代码如下:

objs := start.o app.o

gpioctrl.bin:$(objs)
    arm-linux-gnueabihf-ld -Ttext 0x87800000 -o gpioctrl.elf $^
    arm-linux-gnueabihf-objcopy -O binary -S gpioctrl.elf $@
    arm-linux-gnueabihf-objdump -D -m arm gpioctrl.elf > gpioctrl.dis

%.o:%.S
    arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

%.o:%.c
    arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<

clean:
    rm -rf *.o *.bin *.elf *.dis

在Makefile文件中使用到了一些变量,首先,Makefile文件开头定义了一个变量objs,该objs变量包含了编译生成目标文件gpioctrl.bin所需要的文件:start.o和app.o文件,需要注意的是,start.o要放到最前面,因为在后面链接生成gpioctrl.bin文件的时候,start.o要在前面,程序也是先从start.S里面的代码先运行的,在使用arm-linux-gnueabihf-ld命令链接的时候,使用了Makefile的自动变量"$^",表示所有依赖文件的集合,也就是objs这个变量,如果当前的工程目录下,没有start.o和app.o文件的话,就会寻找相应的规则去编译生成start.o和app.o文件,比如app.o是app.c文件编译生成的,就会执行"%.o:%.c"这个规则编译,将app.c文件编译成app.o文件,自动变量"$@"表示目标集合,例如app.o,自动变量"$<"表示依赖目标集合的第一个文件,例如app.c源文件,最后就是整个工程生成文件的清理规则clean。

在这里,使用的链接地址仍然是DDR的地址0x87800000,编译链接生成gpioctrl.bin文件如下:

最后,使用mkimage工具在gpioctrl.bin文件的基础上添加IVT、Boot Data和DCD数据生成gpioctrl.imx文件,使用MfgTools工具将可编程镜像gpioctrl.imx文件烧写到目标板的启动设备就可以进行上电测试了。

3、小结

本文主要简单介绍了基于I.MX6UL芯片平台上使用C语言进行裸机GPIO控制的基本流程。

原文地址:https://www.cnblogs.com/Cqlismy/p/12445576.html