zoukankan      html  css  js  c++  java
  • 嵌入式开发学习(8)<一步一步点亮LED灯>

    场景:拿到了一块开发板(S5PV210),板上面有四颗LED灯,怎样写程序用软件(汇编语言)去控制LED灯,让它亮起来?

    准备:开发板(S5PV210)、DNW烧写工具、安装好DNW的usb驱动、在linux中(我用的是centos6.5 64位)中安装好交叉编译工具链arm-none-linux-gnueabi-*(这里的“*”代表gcc、g++等),开发板原理图(厂家自带)。

    1、交叉编译工具的的安装

      下载工具包arm-2009q3.tar.bz2包,

      执行 tar -jxvf  arm-2009q3.tar.bz2 解压后进去bin文件夹,可以看到很多以arm-none-linux-gnueabi-开头的可执行文件,这是工具已经安装完了。

      检验能不能使用,执行arm-none-linux-gnueabi-gcc -v ,发现报错了(因为我的是64机,32位机不会报错)。

      执行 yum -y install glibc.i686 和 yum -y install ncurses 后,再执行 arm-none-linux-gnueabi-gcc -v ,发现成功呢。

      添加环境变量:vim /etc/profile 在最后一行中添加PATH=/usr/local/arm/arm-2009q3/bin:/usr/local/mysql/bin:/usr/local/mysql/lib:$PATH(只需添加“=”后到第一个“:”之间的内容),执行 source /etc/profile 让修改生效。(当误改/etc/profile 后,使用export PATH=/usr/bin:/usr/sbin:/bin:/sbin:/usr/X11R6/bin 临时生效。)

      执行 echo $PATH ,发现环境变量中已经有了。

      为工具链arm-none-linux-gnueabi-* 做符号链接:进入/usr/local/arm/arm-2009q3/bin下,执行ln arm-none-linux-gnueabi-gcc -s arm-linux-gcc...后续的也照这个格式建立符号链接。之后随便找一个目录,执行 arm-linux-gcc -v ,能成功打印出版本信息gcc version 4.4.1字样,至此,交叉编译工具链安装完毕。

    进入正文:

      1、LED的物理特性

        LED有两个极,正极和负极,当两极的电压差为5v时,LED灯亮,一般是正极5v,负极0v。电压太高会将LED击穿。

      2、打开开发板原理图 D:开发版光盘资料X210V3S_AhardwareI210BV3I210BV3.pdf ,如下图:

      

      图中一个三角块代表一颗LED,一共有五颗。三角的平面端是LED的正极,尖角端是LED的负极。最下面一颗,正极接5v电压,负极接地,所以开发板一加电,这颗LED就会亮起来。上面四颗是我们要关注的,正极接直流IO电流(我们不可改变),负极接SoC的引脚GPJ0_3、GPJ0_4、GPJ0_5、GPD0_1,这是我们要关注的。

    由上分析得出,LED的正极已经定了(5v)。我们只能通过SoC中编程来控制负极输出顶电平(0v)LED即可点亮。

                    

                                                      (图1)

      3、GPIO的概念引入。

      GPIO general purpose input output 通用功能输入输出。朱友鹏老师的理解:芯片上的部分引脚。我的理解:人通过程序操作CPU的接口。

      功能特点:可以通过编程控制工作的模式和电压高低等。上面的四颗LED就是接在GPIO上面。

      4、打开数据手册E:BaiduNetdiskDownload开发版光盘资料X210V3S_ADataSheetS5PV210_UM_REV1.1.pdf ,找到GPIO,如下图

      

                                          (图2)

    上图打开的是GPIO寄存器的菜单,程序控制硬件的关键是:寄存器。我们要操作的硬件是LED,就是操作LED对应的GPIO里相应的寄存器。由图1可知四颗LED对应的寄存器是GPJ0寄存器组和GPD0寄存器组。

    GPJ0寄存器组中有以下寄存器:

      GPJ0CON    control寄存器。用来配置各引脚的工作模式。是一个控制开关。32位,有8个引脚,每个引脚展4位。由图2看出共有7中模式,其中0000代表input,                    0001代表output

      GPJ0DAT  data寄存器。也有32位,但是8~31位未定义使用,只有0~7位有定义使用。具体定义如下:

      When the port is configured as input port, the corresponding bit is the pin state. When the port is configured as output port, the pin state is the same as the corresponding bit.When the port is configured as functional pin, the undefined value will be read.

      英文意思:当control寄存器的某个引脚配置为输入模式时,相应的位是引脚的状态,此时引脚为输入接口,数据通过引脚读入到data寄存器相应的位。比如说当你配置GPJ0的二号引脚GPJ0_2为高电平时,对应的data寄存器的二号位是1,如果配置GPJ0的二号引脚GPJ0_2为低电平时,对应的data寄存器的二号位是0。当control寄存器的某个引脚配置为输出模式时,此时引脚为输出接口,数据通过引脚输出data寄存器中相应的位。当data寄存器中的位为1,相应的引脚输出高电平,当为0是输出低电平。GPIO中的GPJ0组中的DAT寄存器的八个位就是负责接收这八个引脚的值或者往这八个引脚输出值,是该接收或者该输出,就得看CON寄存器的八个引脚配置脸色行事呢,算是理清呢。

      GPJ0PUD (pull up down) 上拉下拉寄存器,控制引脚上的上下拉电阻。

      GPJ0DRV (driver)控制引脚的驱动能力。

      GPJ0CONPDN (power down)模式下的控制寄存器。

      GPJ0PUDPDN  (power down)模式下的上下拉配置寄存器。
      注:在驱动LED点亮时,应该将GPIO配置为output模式。

    进一步分析点亮LED的方法:

      1、设置 GPIO中GPJ0组中的control寄存器模式为output模式(由图一可知,其实只设置GPJ0_3、GPJ0_4、GPJ0_5、PWMTOUT1(对应核心板上GPD0_1)

      2、设置GPIO中GPJ0组中的data控制的相应的位为0。

      由图2看出GPJ0的control寄存器对应的内存地址是0xE0200240,同理可查知dat的内存地址为0xE0200244。

    开始编码:

      为了方便演示,代码只做到控制前三颗LED灯。

      Makefile内容:

    led.bin: led.o
            arm-linux-ld -Ttext 0x0 -o led.elf $^
            arm-linux-objcopy -O binary led.elf led.bin
            arm-linux-objdump -D led.elf > led_elf.dis
            gcc mkv210_image.c -o mkx210
            ./mkx210 led.bin 210.bin
            
    %.o : %.S
            arm-linux-gcc -o $@ $< -c
    
    %.o : %.c
            arm-linux-gcc -o $@ $< -c 
    
    clean:
            rm *.o *.elf *.bin *.dis mkx210 -f

      mkv210_image.c内容(这个文件中的内容可以先不用明白):

    /*
     * mkv210_image.c的主要作用就是由usb启动时使用的led.bin制作得到由sd卡启动的镜像210.bin
     *
     * 本文件来自于友善之臂的裸机教程,据友善之臂的文档中讲述,本文件是一个热心网友提供,在此表示感谢。
     */
    /* 在BL0阶段,Irom内固化的代码读取nandflash或SD卡前16K的内容,
     * 并比对前16字节中的校验和是否正确,正确则继续,错误则停止。
     */
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define BUFSIZE                 (16*1024)
    #define IMG_SIZE                (16*1024)
    #define SPL_HEADER_SIZE         16
    //#define SPL_HEADER              "S5PC110 HEADER  "
    #define SPL_HEADER              "****************"
    
    int main (int argc, char *argv[])
    {
        FILE        *fp;
        char        *Buf, *a;
        int        BufLen;
        int        nbytes, fileLen;
        unsigned int    checksum, count;
        int        i;
        
        // 1. 3个参数
        if (argc != 3)
        {
            printf("Usage: %s <source file> <destination file>
    ", argv[0]);
            return -1;
        }
    
        // 2. 分配16K的buffer
        BufLen = BUFSIZE;
        Buf = (char *)malloc(BufLen);
        if (!Buf)
        {
            printf("Alloc buffer failed!
    ");
            return -1;
        }
    
        memset(Buf, 0x00, BufLen);
    
        // 3. 读源bin到buffer
        // 3.1 打开源bin
        fp = fopen(argv[1], "rb");
        if( fp == NULL)
        {
            printf("source file open error
    ");
            free(Buf);
            return -1;
        }
        // 3.2 获取源bin长度
        fseek(fp, 0L, SEEK_END);                                // 定位到文件尾
        fileLen = ftell(fp);                                    // 得到文件长度
        fseek(fp, 0L, SEEK_SET);                                // 再次定位到文件头
        // 3.3 源bin长度不得超过16K-16byte
        count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE))
            ? fileLen : (IMG_SIZE - SPL_HEADER_SIZE);
        // 3.4 buffer[0~15]存放"S5PC110 HEADER  "
        memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE);
        // 3.5 读源bin到buffer[16]
        nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp);
        if ( nbytes != count )
        {
            printf("source file read error
    ");
            free(Buf);
            fclose(fp);
            return -1;
        }
        fclose(fp);
    
        // 4. 计算校验和
         // 4.1 从第16byte开始统计buffer中共有几个1
        // 4.1 从第16byte开始计算,把buffer中所有的字节数据加和起来得到的结果
        a = Buf + SPL_HEADER_SIZE;
        for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++)
            checksum += (0x000000FF) & *a++;
        // 4.2 将校验和保存在buffer[8~15]
        a = Buf + 8;                            // Buf是210.bin的起始地址,+8表示向后位移2个字,也就是说写入到第3个字
        *( (unsigned int *)a ) = checksum;
    
        // 5. 拷贝buffer中的内容到目的bin
        // 5.1 打开目的bin
        fp = fopen(argv[2], "wb");
        if (fp == NULL)
        {
            printf("destination file open error
    ");
            free(Buf);
            return -1;
        }
        // 5.2 将16k的buffer拷贝到目的bin中
        a = Buf;
        nbytes    = fwrite( a, 1, BufLen, fp);
        if ( nbytes != BufLen )
        {
            printf("destination file write error
    ");
            free(Buf);
            fclose(fp);
            return -1;
        }
    
        free(Buf);
        fclose(fp);
    
        return 0;
    }

      write2sd内容:

    #!/bin/sh
    sudo dd iflag=dsync oflag=dsync if=210.bin of=/dev/sdb seek=1

      led.S内容(S是大写,我是我们要编写点亮LED的真正代码):

    /*
     *文件名:led.s
     *作者:airduce
     *描述:S5PV210开发板裸机学习第一个程序。
     */
     _start:
        //第一步:把0X11111111写入0xE0200240(GPJ0CON)位置
        ldr r0, =0x11111111  //这里如果是#代表指令,如果是=带表是伪指令
        ldr r1, =0xE0200240
        str r0, [r1]         //寄存器间接寻址。功能室吧r0中的数写入r1中的数为地址的内存中去
        //第二步:0x0写入0xE0200244(GPJ0DATA)位置
        ldr r0, =0x0
        ldr r1, =0xE0200244
        str r0, [r1]         //把0写入到GPJ0DATA寄存器中,引脚即输出低电平,LED点亮。
    flag:
        b flag                 //这两行写了一个死循环(不然程序只执行一遍)

      编写上面led.S的文件,其他的三个先照搬就行,执行make 命令后,把得到的led.bin用dnw工具烧到开发板的0xd0020010地址中,可以看到前三颗LED灯亮起来呢。

    简单的用汇编实现流水灯:

      

    #define GPJ0CON  0xE0200240  //LED1、LED2、LED3所对应的GPJ0组的control寄存器地址
    #define GPJ0DAT  0xE0200244  //LED1、LED2、LED3所对应的GPJ0组的data寄存器地址
    #define GPD0CON  0xE02000A0  //LED4所对应的GPD0组的control寄存器地址
    #define GPD0DAT  0xE02000A4  //LED4所对应的GPD0组的data寄存器地址
    .global _start
     _start:
        ldr r0, =0x11111111    //设置GPJ0的CON寄存器的模式为output(这里只是统统搞为1,其实还可以更精确)
        ldr r1, =GPJ0CON
        str r0, [r1]
    
        ldr r0, =0x11111111    //设置GPD0的CON寄存器的模式为output(这里只是统统搞为1,其实还可以更精确)
            ldr r1, =GPD0CON
            str r0, [r1]
    
    flash:         //循环开始
        ldr r0, =0<<3 | 1<<4 | 1<<5    //设置第一颗LED亮,第二、三颗灭可以写成~(1<<3)   
    ldr r1, =GPJ0DAT str r0, [r1] ldr r0, =1<<1 //设置第四颗灭 ldr r1, =GPD0DAT str r0, [r1] bl delay ldr r0, =1<<3 | 0<<4 | 1<<5 //设置第二颗LED亮,第一、三颗灭 ldr r1, =GPJ0DAT str r0, [r1] ldr r0, =1<<1 //设置第四颗灭 ldr r1, =GPD0DAT str r0, [r1] bl delay ldr r0, =1<<3 | 1<<4 | 0<<5 //设置第三颗LED亮,第一、二颗灭 ldr r1, =GPJ0DAT str r0, [r1] ldr r0, =1<<1 //设置第四颗灭 ldr r1, =GPD0DAT str r0, [r1] bl delay ldr r0, =1<<3 | 1<<4 | 1<<5 //设置第一、二、三颗灭 ldr r1, =GPJ0DAT str r0, [r1] ldr r0, =0<<1 //设置第四颗亮 ldr r1, =GPD0DAT str r0, [r1] bl delay b flash //调到循环开始处 delay: //延时函数 ldr r2, =9000000 ldr r3, =0x0 delay_loop: sub r2, r2, #1 //r2 = r2 -1 cmp r2, r3 // cmp会影响Z标志位,如果r2等于r3则Z=1,下一句中eq就会成立 bne delay_loop //r2 r3 如果不相等,就跳转到delay_loop mov pc, lr

     总结一下编程操控一个硬件的步骤:

    1、分析硬件工作原理

    2、分析原理图

    3、分析数据手册

    4、找到相对应的SFR

    5、编码

    6、烧录运行

  • 相关阅读:
    踏个脚印,今天在博客园安了个家!
    微软WebDeployment Project插件发布网站时老是报出"aspnet_merge.exe”已退出,代码为1的错误
    从xap文件中读取文件
    ASP.NET MVC学习笔记二(URL映射规则)
    Siverlight Contrib的alpha版与正式版有很大区别
    带你尝鲜LiteOS 组件EasyFlash
    带你了解几种二进制代码相似度比较技术
    让数据大白于天下:GCC插件实现代码分析和安全审计
    鸿蒙轻内核源码分析:MMU协处理器
    高性能云网关,打通云内外业务互通的任督二脉
  • 原文地址:https://www.cnblogs.com/airduce/p/7531188.html
Copyright © 2011-2022 走看看