第一个跑马灯程序

博主转行做嵌入式,使用S3C44B0X开发板尝试跑马灯程序。通过u-boot引导加载程序,解决ADS设置复杂性,采用GNU工具链手动编译。遇到LED控制问题,查阅资料后理解了统一编址概念和芯片的I/O控制,最终成功点亮LED。附带了源代码和Makefile。
摘要由CSDN通过智能技术生成


    最近准备转行做嵌入式,随弄来一块S3C44B0X的板子,准备好好研究一下。

    板子便宜货,没啥特别完善的资料,都是和网上差不多的ADS环境。

    因为平时还要上班,花了几个晚上的时间,总算是把u-boot和uClinux的编译和下载过程弄熟了。这不,好不容易等来一个周末,决心一定要弄出点名堂来,不能再在门外徘徊了。

    先说说工作环境。
    主机(自己家的,研究Linux内核的那个是公司的><):
        CPU: C4 2.93G
        Memory: 2G
        硬盘: 很大 ~o~
        网络: 电信ADSL 2M
        OS: WinXP SP2
    虚拟机(VirtualBox):
        Memory: 512M
        OS: Ubuntu 7.10 JeOS
        硬盘: 2G + 4G VDI
        网络: 共享主机网络
    开发板:
        MPU: S3C44B0X 66M
        ROM: 8M Flash
        RAM: 2M SDRAM
        IDE: 有
        USB: 1.1
        LCD: 单色128x64
        COM: RS232 UART x 2
        JTAG: 有
        网卡: RTL8019 10M
        其它: 1 x 4键盘,1 x 3 LED,还有其它一些东西是啥我也不懂,再说- -

    因为没多余的网卡,又不想断网,所以先用串口的蜗牛传输撑着吧。其实除了下载uClinux等待时间比较BT以外,对于自己写的程序还好,一般就几k不到,再慢的速度也无所谓倒是。

    其实附送资料上的示例程序里早有了跑马灯,按着文档上的方法跑了一遍,虽然执行正常但是毫无感觉,完全搞不明白是怎么回事,换句话说,毫无成就感。我心想这可不行,这跟没学差不多,等想想办法了。

    仔细想想最莫名其妙的部分就是ADS的一堆设置,以及代码里一些奇怪的外部符号比如|Image$$RO$$Base|之类的,不知道是怎么来的,后来google了老半天才知道是ADS的设置参数。这些东西一来就搞的人头晕眼花的,于是我决定换一个思路,扔掉ADS,手动搞定这一切。

    说起手动,不是一件容易的事情。本来我准备在板子上裸奔的,但发现自己过于菜鸟,一时半会还弄不出一个bootloader来。搞得不好一个周末下来,马没跑成,我的热情都跑光了。

    经过一阵迷茫,我突然想起了u-boot,这个之前我载完uClinux就扔掉的东西。这个开源的bootloader真是好处多多啊,既是bootloader,又能充当BIOS,甚至就像是个没有FS的DOS,可以直接跑裸机程序,但又不需要费劲去弄中断初始化之类的工作。

    于是我的方案就出来了,u-boot引导后,用loadb命令装载我的程序到RAM,然后用go命令跳转到程序入口点执行!

    做人不能光耍嘴皮子,这就立刻动手!

    为了验证我的方案可行,决定先写一个自动重启的程序,思路很简单,直接跳转到00000000h,也就是中断向量表中reset中断的位置执行。代码只有一行:
        mov    pc, #0

reset.s

.text

    mov    pc# 0


    注意,因为使用GNU工具链有着各种各样的好处,并且对我来说也是轻车熟路,因此这里的汇编语法是GNU AS的语法,不熟悉的人自己google去吧,其实和ADS的ARMASM没什么本质的不同。

    因为这程序太简单了,连Makefile也没必要写,在Linux下执行:
        arm-elf-as -o reset.o reset.s
        arm-elf-objcopy -O binary -R .comment -R .note -S reset.o reset.bin

    于是乎就得到了裸机程序reset.bin,大小只有4字节,汗,因为就只有一个指令。

    OK,给板子加电,打开超级终端,进入u-boot,输入loadb 0xC0000000后把reset.bin传过去,瞬间就传完了(因为只有4字节。。汗),然后go 0xC0000000。

    成功!板子立刻重启了。这证明了我的方案是可行的,剩下的活就是怎么点灯了。

    因为我是极度菜鸟,完全不知道怎么下手,看示例代码又完全不知所云。无奈使出google大法,找了一大堆资料。最后我明白了问题的关键就在于I/O(ARM的可编程I/O,PGIO)上,但是由于该芯片的I/O端口是复用的,所以需要编程操作I/O控制寄存器。

    顺便提一个概念。与x86机器有很大的不同,ARM是统一编址的(这个概念我以前也只有在课本上见过XD),而x86是独立编址,也就是说存储器和设备寄存器的编址是分开的,因此我们通常需要in out指令来操作IBM PC硬件寄存器,而所谓的硬件端口其实就是不同于存储器的另一套编址方式。对ARM来说,设备寄存器和存储器的编址是统一的,也就是说,直接通过mov等传送指令就可以访问硬件寄存器。独立编址和统一编址各有利弊,这里不做讨论,有兴趣的人请回去补习计算机体系结构。

    S3C44B0X这款芯片的硬件寄存器被映射到了物理地址01c00000h~01ffffffh之间4M的地址空间里,对它们访问则可以控制硬件。

    我看到网上某篇文章讲到,LED0和LED1通过23(nGCS4)和24(nGCS5)号引脚连接到S3C44B0X芯片,而根据S3C44B0X芯片手册,nGCS4和nGCS5引脚受PCONB控制寄存器控制,当该寄存器的第9位PB9或第10位PB10为1时,通过访问PDATB的第9位或第10 位则可以控制LED0和LED1,低电平LED亮,高电平LED灭。

    看起来并不难,动手写了个C文件按照该文所说实现了一个程序led.c,并且在跑马灯结束后自动reset,Linux下:
       arm-elf-gcc -nostdinc -c -o led.o led.c
       arm-elf-objcopy -O binary -R .comment -R .note -S led.o led.bin

    用loadb(不写参数默认载到0x0c008000)载到板上跑了下一看,无效。。。。而且死机(没有reset)。

    我郁闷,经过无数的检查之后恍然大悟,直接objcopy led.o,由于led.c里并没有函数入口点,默认入口点会被当作是00000000h,而我的程序是跑在0c008000h处。这么一来,长跳转和函数调用的目标地址都会比正确值小0x0c008000,那当然会有问题了!于是需要这么干:
       arm-elf-gcc -c -o led.o led.c
       arm-elf-ld -nostdinc -Ttext 0x0c008000 -o led.elf led.o
       arm-elf-objcopy -O binary -R .comment -R .note -S led.elf led.bin

    载到板上,还是无效!不过不会死机了。

    继续郁闷,突然想起来早上泡的衣服还没晾,急急忙忙去晾衣服。在晾衣服的过程中,我突然想到了是不是由于那文章所用的板和我的不一样,因此发光二极管接的位置也不同?于是洗完衣服后我急忙翻出PCB原理图看了起来。幸好大学时候没白费光阴,多少还是能看懂一些的。根据原理图,我发现我的板子LED0~2分别接在117(GPC1)、116(GPC2)、115(GPC3)三个引脚上,并且三个LED都多接了一个反相器(74HC04),如下图。看来果然问题就出在这里。


    我重新翻出I/O控制寄存器表,发现这三个引脚是由PCONC控制的,哈,这下好办了。根据资料,我把先前的程序又改了一下,编译通过,载到板子,成功! LED灯终于亮了!不过亮灭是反的。。。怎么回事呢?问题就出在那三个反向器,我把它们给忘了,先前参考的文章里是没有它们的。于是把高低电平反过来,编译,下载,运行,成功!这次LED终于如预期般的亮灭了。

    喜悦之余也感叹前面的路途遥远,点个灯都这么难,自己还是太菜了。

    最后奉上源代码和Makefile:

led.c

#define  PCONC   ((volatile int *)0x1d20010)

#define  PDATC   ((volatile int *)0x1d20014)

#define  PUPC    ((volatile int *)0x1d20018)

 

void  init( void );

void  led( int int );

void  delay( int );

 

void entry( void )   /* 对于裸机C程序,入口函数必须放在文件的最前面 */

...{

    init();

    while (1...{

        led(1, 1);      /* 点亮LED1 */

        delay(1000);    /* 延时 */

        led(2, 1);      /* 点亮LED2 */

        delay(1000);

        led(3, 1);      /* 点亮LED3 */

        delay(1000);

        led(10);      /* 熄灭LED1 */

        delay(1000);

        led(2, 0);      /* 熄灭LED2 */

        delay(1000);

        led(3, 0);      /* 熄灭LED3 */

        delay(1000);

    }

}

 

void init(void)

...{   /* 下面两句将PCONC寄存器的PC1、PC2、PC3都置为01,意为将其作为LED输出 */

    *PCONC &= ~0xa8;

    *PCONC |= 0x54;

}

 

void led(int num, int light)

...{

    if (light)

        *PDATC |= 1 << num;      /* 点亮第num盏灯 */

    else

        *PDATC &= ~(1 << num);   /* 熄灭第num盏灯 */

}

 

void delay(int times)

...{

    volatile int i;   /* volatile声明防止下面的循环被编译器和谐 */

    for (i = 0; i < (times << 10); ++i);   /* 非精确延时,临时方法 */

}


Makefile

CC  =  arm -elf- gcc

AS arm-elf-as

LD  =  arm -elf- ld

OBJCOPY  =  arm -elf- objcopy

ADDRESS  =   0x0c008000

 

.PHONY : all clean

 

all : led.bin

 

clean :

    rm  - f led.bin

    rm  - led.o

    rm  - f led.elf

    rm  - led.S

 

led.bin : led.elf

    $(OBJCOPY - O binary  - R .comment  - R .note  - S $ <  $@

    chmod a - x $@

 

led.elf : led.o

    $(LD - Ttext $(ADDRESS -nostdinc - o $@ $ <

    chmod a - x $@

 

led.o : led.S

    $(AS - o $@ $ <

 

led.S : led.c

    $(CC -S - o $@ $ <


    在u-boot下使用loadb命令,通过超级终端把led.bin下载到开发板的RAM里,然后通过go 0x0c008000命令跳转到程序入口点即可。板子和我的不同的可以根据资料自行修改。
 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值