第一个跑马灯程序

来源:互联网 发布:rrelieff算法 编辑:程序博客网 时间:2024/05/21 09:37

    最近准备转行做嵌入式,随弄来一块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(intint);
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命令跳转到程序入口点即可。板子和我的不同的可以根据资料自行修改。

原创粉丝点击