As we have our tools ready, it is the time to create a sample project. So, let's connect our SAM4L board J-LInk (rightmost connector that close to power switch) using micro-USB cable to desktop, turn on the power, run GDB Server, and start Eclipse IDE. It is magic time!
In Eclipse menu, chose File->New->C Project. In popup dialog window, select 'ARM Cross Target Application'->Empty Project type. At the right side of this window select 'ARM Linux GCC (Sourcery G++ Lite) toolchain. Do not forget to name your new project (SAM4LHello) and click 'Finish'.
We must explain details of our embedded platform to linker, so it will be able to manage our sections correctly. From Eclipse menu select 'File'->'New'->'Other'. From pop-up dialog select General->'Untitled Text File'. Type one whitespace into this file, and chose File->Save to popup save dialog. Click on 'SAM4LHello' project name so Eclipse can manage this file within the project, and give new file its name (memory.map).
This file will contain three major sections. In first section, we will define output architecture, file format, and entry point. This chip uses little endian byte notation.
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_init_reset)
Second section is defining target system memory map: we have 256K of flash ROM (starting at 0x00000000) and 32K of internal static RAM (starting at 0x20000000). Flash memory could be used to read data and execute the code (rx), internal RAM could be used to read/write data and execute code (xrw).
MEMORY
{
rom(rx) : ORIGIN = 0x00000000, LENGTH = 256K
ram(xrw) : ORIGIN = 0x20000000, LENGTH = 32K
}
Finally, third section is allocating compiled code and data sections along the memory. All sections must be four bytes aligned. Vectors table should be placed at address 0x00000000 and could be relocated later if necessary. Stack size is set to 512 bytes.
C_STACK_SIZE = 512;
SECTIONS
{
/* .text section contains code and read-only data (const) to be placed in ROM */
.text :
{
. = ALIGN(4);
KEEP(*(.vectors .vectors.*)) /* Vector table */
. = ALIGN(4);
*(.text .text*) /* Program code */
. = ALIGN(4);
*(.rodata .rodata*) /* constants */
} > rom
_etext = .;
/* initialy, we place .data section at ROM followed by .text section (mind _etext label) */
/* and copy their initial values at init code before main() would be called */
.data : AT (_etext)
{
. = ALIGN(4);
_ramdatastart = .;
. = ALIGN(4);
*(.ramfunc .ramfunc.*);
. = ALIGN(4);
*(.data .data.*) /* Data memory */
_ramdataend = .;
} > ram
/* uninitialized in-memory variables that will be zeroed up at init code */
.bss (NOLOAD):
{
. = ALIGN(4);
__bss_start__ = . ;
*(.bss*) /* Zero-filled run time allocate data memory */
__bss_end__ = .;
} > ram
.stack (NOLOAD):
{
. = ALIGN(8);
_sstack = . ;
. = . + C_STACK_SIZE;
. = ALIGN (8);
_estack = .;
} > ram
_end = .;
end = .;
}
Please note the following
GDB server will distinguish only two ELF sections: text and data. Other sections are assumed by linker, but will not be actually transferred into the device
We do not want any data to be transferred into RAM as they will be lost during system restart. Instead, we are placing initialized data (.data and .ramfunc sections) into flash memory, and write startup code to copy these sections into SRAM on startup.
When file is ready, tell linker to use it: Go to project properties (Project->Properties from Eclipse menu), select 'C/C++ Build'->Settings. On the 'Tool Settings' page select 'ARM Sourcery GCC C Linker'->General, and specify this file as 'Script file(-T)'.
.globl _init_reset
.section .vectors
// use Unified Assembler Language
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0473c/BABJIHGJ.html
// so we can use Thumb-2 instructions too
.syntax unified
// processor vectors table should be first code to generate and placed at 0x00000000
.align
.word _estack // initial SP value to be loaded
.word _init_reset // initial PC value to be loaded
.word _NMI_Handler // NonMaskable Interrupt
.word _HardFault_Handler // Hard Fault
.word _MemManage_Handler // Memory management (MSU) fault
.word _BusFault_Handler // Bus (memory) fault
.word _UsageFault_Handler // Usage fault
.word 0, 0, 0, 0 // Reserved
.word _SVC_Handler // supervisor call
.word _DebugMon_Handler // Debug monitor
.word 0 // Reserved
.word _PendSV_Handler // Interrupt-driven request for system-level service
.word _SysTick_Handler // SysTick exception from system timer or software
// followed by 80 NVIC interrupt lines (00 - 79)
NVIC_Table: .skip 80 * 4
The bootloader code will receive the control right after system reset. It should zero-up bss section (global variables that was not initialized or initialized to zero), copy data section (initialized global variables) from flash ROM to RAM, and call main() function of our hello LED application. Hence it is a short assembly code, it is sharing the source file with vectors table. Please note that startup code uses labels defined in memory.map linker script.
.section .text
.align // align to 32-bit (dword) boundary
_init_reset:
// clean .bss section
ldr r0, =__bss_start__
ldr r1, =__bss_end__
mov r2, #0
1:
stmia r0!, {r2}
cmp r0, r1
blt 1b
// copy .data section initial values from ROM to RAM
ldr r0, =_etext
ldr r1, =_ramdatastart
ldr r3, =_ramdataend
2:
ldmia r0!, {r2}
stmia r1!, {r2}
cmp r1, r3
blt 2b
// go to C main() function
bl main
// loop forever
3:
b 3b
_NMI_Handler:
b _NMI_Handler
_HardFault_Handler:
b _HardFault_Handler
_MemManage_Handler:
b _MemManage_Handler
_BusFault_Handler:
b _BusFault_Handler
_UsageFault_Handler:
b _UsageFault_Handler
_SVC_Handler:
b _SVC_Handler
_DebugMon_Handler:
b _DebugMon_Handler
_PendSV_Handler:
b _PendSV_Handler
_SysTick_Handler:
b _SysTick_Handler
.end
On this board, we can easily control LED0 LED, which is installed right below the LCD and connected to pin PC14. Also, we can drive pin PC10, which turns on LCD backlight.
In the following code, I am borrowing function names and certain definitions from Atmel ASF framework. However, as we are not linking against this framework, we must implement all those functions ourselves.
int main()
{
// enable GPIO module clocking
sysclk_priv_enable_module(PM_CLK_GRP_PBC, SYSCLK_GPIO);
// enable-disable LCD backlight (PC14)
ioport_set_pin_dir(PIN_PC14, IOPORT_DIR_OUTPUT);
ioport_set_pin_level(PIN_PC14, 1);
ioport_set_pin_level(PIN_PC14, 0);
// turn on-off onboard LED (LED0)
ioport_set_pin_dir(PIN_PC10, IOPORT_DIR_OUTPUT);
ioport_set_pin_level(PIN_PC10, 1);
ioport_set_pin_level(PIN_PC10, 0);
return 0;
}
GPIO pins are managed as 32 bit ports.
Each port has a set of configuration registers starting from 0x400E1000.
Each port takes 0x200 in address space for its set of registers.
See GPIO.S attached for the full implementation code.
[TBD]