嵌入式开发学习(8)<一步一步点亮LED灯>

发布者:TranquilSilence最新更新时间:2024-11-19 来源: cnblogs关键字:嵌入式开发  点亮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

#include

#include


#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 n', argv[0]);

        return -1;

    }


    // 2. 分配16K的buffer

    BufLen = BUFSIZE;

    Buf = (char *)malloc(BufLen);

    if (!Buf)

    {

        printf('Alloc buffer failed!n');

        return -1;

    }


    memset(Buf, 0x00, BufLen);


    // 3. 读源bin到buffer

    // 3.1 打开源bin

    fp = fopen(argv[1], 'rb');

    if( fp == NULL)

    {

        printf('source file open errorn');

        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 errorn');

        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 errorn');

        free(Buf);

        return -1;

    }

    // 5.2 将16k的buffer拷贝到目的bin中

    a = Buf;

    nbytes    = fwrite( a, 1, BufLen, fp);

    if ( nbytes != BufLen )

    {

        printf('destination file write errorn');

        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]

[1] [2]
关键字:嵌入式开发  点亮LED灯 引用地址:嵌入式开发学习(8)<一步一步点亮LED灯>

上一篇:嵌入式开发学习(6)
下一篇:最后一页

小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved