• 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控制的基本流程。

  • 相关阅读:
    python_网络编程struct模块解决黏包问题
    python_网络编程socket(UDP)
    python_网络编程socket(TCP)
    python_面向对象
    python_生成器
    python_迭代器
    linux实操_shell自定义函数
    linux实操_shell系统函数
    linux实操_shell读取控制台输入
    scrapy-redis 0.6.8 配置信息
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/12445576.html
Copyright © 2020-2023  润新知