ARM Integrator-CP PL110 Dirty
This page or section refers to its readers or editors using I, my, we or us. It should be edited to be in an encyclopedic tone. |
Integrator-CP QEMU PL110 16-Bit Color Example
This is quick and dirty really. I just want to get you on your feet with something that will make you enjoy doing this. Although, many people would consider going straight to working on the GUI not the correct way to do it is however very fun for many people. So here you go.
Prerequisites
Software | Why |
---|---|
QEMU | We are going to use it. We do not properly initialize the hardware. We almost do it correctly, but not quite enough for the real thing. Thats okay. |
C compiler or assembler | You need either a compiler or an assembler. I will show you code for doing it both ways. I will handwrite the assembly so it should be easier to read and work with. |
objcopy | You need something like objcopy on most Linux distributions. What we are going to have to do is copy out the .text section. Like I said this is just really quick and dirty example. We will make a flat binary. |
Memory
You will notice I am producing a flat binary output. This output contains no headers and the entry functions begins at the first byte of the image produced. The image is loaded by QEMU to the memory address 0x10000. QEMU supports loading an ELF format image and should correctly place each image section at the specified address. When you write software for real embedded devices they are generally not going to support this as you will write the image directly to their ROM. A many devices have a secondary ROM area that actually provides support when placed in a special mode to allow you to flash your BOOT image which goes in the lower part of the ROM or somewhere different. This essentially consists of two ROMs in essence where the primary boot code allows you to easily flash the device over USB when a special button is held. If not it jumps to your BOOT code. If it can handle non flat binary formats then this special initial ROM code will support this functionality and you can find it in your boards documentation.
But, for the most part you are going to be uploading a flat binary file.
C Code
typedef int(*PFN)(void);
void start(void);
void __attribute__((naked)) entry()
{
__asm__("mov sp, #0x60 << 8");
__asm__("bl start");
}
#define PL110_CR_EN 0x001
#define PL110_CR_PWR 0x800
#define PL110_IOBASE 0xc0000000
#define PL110_PALBASE (PL110_IOBASE + 0x200)
typedef unsigned int uint32;
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef struct _PL110MMIO
{
uint32 volatile tim0; //0
uint32 volatile tim1; //4
uint32 volatile tim2; //8
uint32 volatile d; //c
uint32 volatile upbase; //10
uint32 volatile f; //14
uint32 volatile g; //18
uint32 volatile control; //1c
} PL110MMIO;
void start(void)
{
PFN fn;
PL110MMIO *plio;
int x;
uint16 volatile *fb;
plio = (PL110MMIO*)PL110_IOBASE;
/* 640x480 pixels */
plio->tim0 = 0x3f1f3f9c;
plio->tim1 = 0x080b61df;
plio->upbase = 0x200000;
/* 16-bit color */
plio->control = 0x1829;
fb = (uint16*)0x200000;
for (x = 0; x < (640 * 480) - 10; ++x)
fb[x] = 0x1f << (5 + 6) | 0xf << 5;
/* uncomment this and the function pointer should crash QEMU if you set it for 8MB of ram or less */
for(;;);
fn = (PFN)0x800f20;
fn();
return;
}
A naked function implementation so we can setup the stack, which then calls the actual C function start. Then we initialize the card and draw a light blue color across the screen.
To compile it and produce the binary file to use with QEMU first copy the code above into a file named test.c. Then execute the following. Your actual binaries will likely differ. Make sure your using a cross-toolchain for ARM and not your host system unless your host system is ARM.
./gcc-arm test.c -nostdlib -o test.o ./objcopy-arm -j .text -O binary test.o test.bin qemu-system-arm -m 8 -kernel test.bin
If all goes well QEMU will display a light blue screen.
Assembly
main:
eor r0, r0, r0
mov r0, #0xc0 << 24
mov r1, #0x3f << 24
add r1, r1, #0x1f << 16
add r1, r1, #0x3f << 8
add r1, r1, #0x9c
str r1, [r0, #0]
mov r1, #0x08 << 24
add r1, r1, #0x0b << 16
add r1, r1, #0x61 << 8
add r1, r1, #0xdf
str r1, [r0, #4]
mov r1, #0x20 << 16
str r1, [r0, #0x10]
mov r1, #0x18 << 8
add r1, r1, #0x29
str r1, [r0, #0x1c]
mov r2, #0x4b << 12
mov r1, #0x20 << 16
mov r3, #0x1f
clearscreen:
strh r3, [r1], #2
subs r2, r2, #1
bne clearscreen
#b 0x800000
lockme:
b lockme
We initialize the card then we draw bright red to the screen.
To assemble it and produce the binary file to use with QEMU first copy the code above into a file named test.asm. Then execute the following. Your actual binaries will likely differ. Make sure your using a cross-toolchain for ARM and not your host system unless your host system is ARM.
./as-arm test.asm -o test.o ./objcopy-arm -j .text -O binary test.o test.bin qemu-system-arm -m 8 -kernel test.bin
If all goes well QEMU will display a bright red screen.
More Flexible Compile/Link
The 0x10000 is the area in memory this image is going to start at.
link.ld
ENTRY (entry) SECTIONS { . = 0x10000; .text : { *(.text*) *(.rodata*) *(.data*) *(.bss*) *(COMMON) } _EOI = .; }
script
You can notice now we are supporting the compile of multiple sources, and a seperate linking step which uses our linker script (above). This build scheme supports properly referencing global variables in your code, but still produces a flat binary file with no header or sections. It also directs any output written to the serial port to stdout.
#!/bin/sh arm-linux-gnueabi-gcc -Wall -Wextra -Werror -nostdlib -nostartfiles -ffreestanding -std=gnu99 -c *.c arm-linux-gnueabi-ld -T link.ld -o _armos.bin *.o arm-linux-gnueabi-objcopy -j .text -O binary _armos.bin armos.bin qemu-system-arm -m 8 -kernel armos.bin -serial stdio
See ARM_Integrator-CP_Bare_Bones for writing to the serial port.
Explanation
Essentially, what we did was set just enough to make it work with QEMU PC emulator version 0.12.5 (Debian 0.12.5+dfsg-3squeeze1), Copyright (c) 2003-2008 Fabrice Bellard. If it does not work and you have a different version of QEMU then that could be the problem, but then again make sure you used the correct toolchain.
Now, the real hardware. Well its going to want you to set more values then just that. Also, the pixels are 16-bit in the memory buffer at 0x200000 and they are in 565 format BGR. So blue bits occupy the most significant bits and there is 5 of them. B:5 G:6 R:5 - green has 6 bits. This is a common format and used with DXT1 compression format most graphics cards support.
After we initialize the card we just set the same value in a loop. Now, you will notice that in both the C and the assembly code that I have a loop and a commented out branch instruction or in the C code I have a function pointer which will crash the emulator. Its not going to hard crash it, but it will make the emulator stop and display the registers. This is highly useful as a debugging aid. Since you place it somewhere in your program and it will tell you what all the registers contain so you can debug difficult pieces of code. It can also tell you if the code has deadlocked. How else do you know if the code has completed properly or has deadlocked? You do not know unless you make it do something to tell you.
Since you have the ability to plot colored pixels on the screen you have another way to debug your operating system that you are going to build a interesting GUI on top on while completely forgetting about the real interesting stuff like processes, threads, timers, priority, disk I/O, devices, memory management, heaps...
But, having fun is important too it just requires forgetting about all that! So have fun because thats why I made this page.