Getting Started with
AT91SAM7SE Microcontrollers
This application note is aimed at helping the reader become familiar with the Atmel ARM® Thumb®-based AT91SAM7SE microcontroller.
It describes in detail a simple project that uses several important features present on AT91SAM7SE chips. This includes how to setup the microcontroller prior to executing the application, as well as how to add the functionalities themselves. After going through this guide, the reader should be able to successfully start a new project from scratch.
This document also explains how to setup and use a GNU ARM toolchain in order to compile and run a software project.
Note that the Getting Started example has been ported and is included in IAR® EWARM 4.41A; the reader should disregard the section “Building the Project” on page 17 when using this version.
To be able to use this document efficiently, the reader should be experienced in using the ARM core. For more information about the ARM core architecture, please refer to the appropriate documents available from http://www.arm.com.
The software provided with this application notes requires several components:
•The AT91SAM7SE Evaluation Kit
•A computer running Microsoft® Windows® 2000/XP
•An ARM cross-compiler toolchain (such as YAGARTO)
•AT91-ISP V1.8 or later
AT91 ARM
Thumb
Microcontrollers
Application Note
6295A–ATARM–27-Mar-07
This section describes how to program a basic application that helps you to become familiar with AT91SAM7SE microcontrollers. It is divided into two main sections: the first one covers the specification of the example (what it does, what peripherals are used); the other details the programming aspect.
The demonstration program makes two LEDs on the board blink at a fixed rate. This rate is generated by using a timer for the first LED; the second one uses a Wait function based on a 1 ms tick. The blinking can be stopped using two buttons (one for each LED).
While this software may look simple, it uses several peripherals which make up the basis of an operating system. As such, it makes a good starting point for someone wanting to become familiar with the AT91SAM microcontroller series.
In order to perform the operations described in the previous section, the software example uses the following set of peripherals:
•Parallel Input/Output (PIO) controller
•Timer Counter (TC)
•Periodic Interval Timer (PIT)
•Advanced Interrupt Controller (AIC)
•Debug Unit (DBGU)
LEDs and buttons on the board are connected to standard input/output pins of the chip; those are managed by a PIO controller. In addition, it is possible to have the controller generate an interrupt when the status of one of its pins changes; buttons are configured to have this behavior.
The TC and PIT are used to generate two time bases, in order to obtain the LED blinking rates. They are both used in interrupt mode: the TC triggers an interrupt at a fixed rate, each time toggling the LED state (on/off). The PIT triggers an interrupt every millisecond, incrementing a variable by one tick; the Wait function monitors this variable to provide a precise delay for toggling the second LED state.
Using the AIC is required to manage interrupts. It allows the configuration of a separate vector for each source; three different functions are used to handle PIO, TC and PIT interrupts.
Finally, an additional peripheral is used to output debug traces on a serial line: the DBGU. Having the firmware send debug traces at key points of the code can greatly help the debugging process.
The AT91SAM7SE512 found on AT91SAM7SE-EK evaluation boards features two internal memories: a 512 KB Flash and 32 KB SRAM. In addition, it provides an External Bus Interface (EBI), enabling the connection of external memories; a 32MB SDRAM chip is present on the EK.
2 Application Note
6295A–ATARM–27-Mar-07
The Getting Started example software can be compiled and loaded on the internal Flash and the external SDRAM.
The AT91SAM7SE Evaluation Kit features two pushbuttons, connected to pins PB22 and PB25.
When pressed, they force a logical low level on the corresponding PIO line.
The Getting Started example uses both buttons (PB22 and PB27).
There are two general-purpose green LEDs on the AT91SAM7SE-EK, as well as a softwarecontrollable yellow power LED; they are wired to pins PA1, PA2 and PA0, respectively. Setting a logical low level on these PIO lines turns the corresponding LED on.
The example application uses the two green LEDs (PA1 and PA2).
On the AT91SAM7SE, the Debug Unit uses pins PA9 and PA10 for the DRXD and DTXD signals, respectively.
As stated previously, the example defined above requires the use of several peripherals. It must also provide the necessary code for starting up the microcontroller. Both aspects are described in detail in this section, with commented source code when appropriate.
Most of the code of an embedded application is written in C. This makes the program easier to understand, more portable and modular. However, using the C language requires the initialization of several components. These initialization procedures must be performed using assembly language, and are grouped into a file referred to as C-startup. The C-startup code must:
•Provide exception vectors
•Initialize critical peripherals
•Initialize stacks
•Initialize memory segments
These steps are described in the following paragraphs. More information about startup code can be found in the AT91 Assembler Code Startup Sequence for C Code Applications Software application note (literature no. 2644), available on http://www.atmel.com.
When an exception occurs (e.g., data abort, undefined instruction, IRQ, etc.), the core instantly jumps to one of the 8 instructions located between addresses 0x00 and 0x1C.
If the program does not need to handle an exception, then the corresponding instruction can simply be set to an infinite loop, i.e. a branch to the same address. For vectors which are to be handled, a branch instruction to a function must be provided. Since address 0x00 is used after a Reset, the associated branch must always jump to the beginning of the code.
In this example, the only relevant vector is the one for IRQs (excluding the Reset vector). It must simply branch to the IRQ handler, which is described in Section 3.2.1.2 on page 4.
The code for all eight vectors looks like this:
3 Application Note
6295A–ATARM–27-Mar-07
reset_vector:
ldr pc, =reset_handler undef_vector:
b undef_vector /* Undefined Instruction */ swi_vector:
b swi_vector /* Software Interrupt */ pabt_vector:
ldr pc, =pabt_handler /* Prefetch Abort */ dabt_vector:
ldr pc, =dabt_handler /* Data Abort */ rsvd_vector:
b rsvd_vector /* reserved */ irq_vector:
b irq_handler /* IRQ : read the AIC */ fiq_vector:
b fiq_vector /* FIQ */
The main purpose of the IRQ handler is to fetch the correct jump address for the pending interrupt. This information is held in the Interrupt Vector Register (IVR) of the AIC (see Section 3.2.3 on page 10 for more information about the AIC). Once the address is loaded, the handler just branches to it. This is done as follows:
ldr r14, =AT91C_BASE_AIC ldr r0, [r14, #AIC_IVR]
bx r0
Registers r0 to 12 are not banked, which means they are shared between (almost) all modes. Since r0-r3 and r12 are defined as scratch registers by the ARM C calling convention, they must be saved prior to the jump. In addition, r14 contains the interrupt handler return address plus 4, so it must also be decremented and then saved. The following code saves registers on the stack and jumps to the interrupt vector:
sub r14, r14, #4
stmfd sp!, {r0-r3, r12, r14} ldr r14, =AT91C_BASE_AIC ldr r0, [r14, #AIC_IVR]
bx r0
The final step is to acknowledge the pending interrupt in the AIC (by writing anything in the End
Of Interrupt Command Register), restore registers and then jump back to the main program:
ldr r14, =AT91C_BASE_AIC str r14, [r14, #AIC_EOICR] ldmfd sp!, {r0-r3, r12, pc}^
Note that such a handler does not allow for nested interrupts (since IRQs are masked when the core enters the IRQ mode).
3.2.1.3Low-Level Initialization
The first step of the initialization process is to configure critical peripherals:
• Embedded Flash Controller (EFC)
4 Application Note
6295A–ATARM–27-Mar-07
•Main oscillator and its PLL
•Advanced Interrupt Controller
•Watchdog
These operations are often grouped into one C function. Since it is likely the function tries to access the stack, the stack pointer (r13) must be set to the top memory address before the call:
ldr sp, =STACK_ADDR
ldr r0, =AT91C_LowLevelInit mov lr, pc
bx r0
After carrying out all of these actions, the program can jump to the main application.
The following sections explain why these peripherals are considered critical, and detail the required operations to configure them properly.
3.2.1.4Low-Level Initialization: Embedded Flash Controller
Whenever the microcontroller core runs too fast for the internal Flash, it uses one or more wait states, i.e. cycles during which it does nothing but wait for the memory. The number of wait states can be configured in the EFC.
After reset, the chip uses its internal slow clock (cadenced at 32 kHz), so there is no need for any wait state. However, before switching to the main oscillator (in order to run at full-speed), the correct number of wait states must be set. If not, the core may no longer be able to read the code from the Flash.
Configuring the number of wait states is done in the Flash Mode Register (FMR) of the EFC. For example, a 48 MHz operation requires the use of one wait state:
AT91C_BASE_MC->MC_FMR = AT91C_FMR_FWS_1FWS;
For more information about the required number of wait states depending on the operating frequency of a microcontroller, please refer to the AC Electrical Characteristics section of the corresponding datasheet.
3.2.1.5Low-Level Initialization: Main Oscillator and PLL
After reset, the chip is running using a slow clock, which is cadenced at 32 kHz. The main oscillator and its Phase Lock Loop (PLL) must be configured in order to run at full speed. Both can be configured in the Power Management Controller (PMC).
The first step is to enable the main oscillator and wait for it to stabilize. Writing the oscillator startup time and the MOSCEN bit in the Main Oscillator Register (MOR) of the PMC starts the oscillator; stabilization occurs when bit MOSCS of the PMC Status Register becomes set. The following piece of code performs these two operations:
AT91C_BASE_PMC->PMC_MOR = (AT91C_CKGR_OSCOUNT & (0x8 << 8)) | AT91C_CKGR_MOSCEN;
while (!(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MOSCS));
Calculation of the correct oscillator startup time value is done by looking at the DC characteristics given in the datasheet of the product. Note that the internal slow clock of the AT91SAM7SE is generated using a RC oscillator; this must be taken into account as this impacts the slow clock accuracy. Here is an example:
RC oscillator frequency range in kHz: 22 ≤fRC ≤42
5 Application Note
6295A–ATARM–27-Mar-07
Oscillator frequency range in MHz: 3 ≤ fOsc≤20
Oscillator frequency on EK: fOsc = 18.432MHz
Oscillator startup time:1ms ≤ tStartup≤1.4ms
Value for a 2ms startup: OSCOUNT 42000 × 0.0014 8
= ----------------------------------------- =
8
Once the oscillator is started and stabilized, the PLL can be configured. The PLL is made up of two chained blocks: the first one divides the input clock, while the second one multiplies it. The MUL and DIV factors are set in the PLL Register (PLLR) of the PMC. These two values must be chosen according to the main oscillator (input) frequency and the desired main clock (output) frequency. In addition, the multiplication block has a minimum input frequency, and the master clock has a maximum allowed frequency; these two constraints have to be taken into account. The PLL calculator (available on http://www.atmel.com) can be used to compute the best MUL and DIV values. Example given for the AT91SAM7SE-EK:
fInput |
= 18.432 |
|
|
DIV = 14 |
|
||
MUL = ( 73 – 1) |
= 72 |
||
fOutput |
= |
18.432 |
× 73 = 96.109MHz |
14 |
|||
|
|
----------------- |
|
Like the main oscillator, a PLL startup time must also be provided. Again, it can be calculated by looking at the DC characteristics given in the datasheet of the corresponding microcontroller. After PLLR is modified with the PLL configuration values, the software must wait for the PLL to become locked; this is done by monitoring the Status Register of the PMC.
AT91C_BASE_PMC->PMC_PLLR = AT91C_CKGR_OUT_0
| (AT91C_CKGR_PLLCOUNT & (40 << 8)) | (AT91C_CKGR_MUL & (72 << 16))
| (AT91C_CKGR_DIV & 14); while(!(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_LOCK));
Finally, the prescaling value of the main clock must be set, and the PLL output selected. Note that the prescaling value must be set first, to avoid having the chip run at a frequency higher than the maximum operating frequency defined in the AC characteristics. As such, this step is done using two register writes, with two loops to wait for the main clock to be ready:
AT91C_BASE_PMC->PMC_MCKR = AT91C_PMC_PRES_CLK_2; while (!(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY));
AT91C_BASE_PMC->PMC_MCKR |= AT91C_PMC_CSS_PLL_CLK; while (!(AT91C_BASE_PMC->PMC_SR & AT91C_PMC_MCKRDY));
At this point, the chip is configured to run on the main clock with the PLL, at the desired frequency.
3.2.1.6Low-Level Initialization: Advanced Interrupt Controller
How to set up the AIC properly is described in Section 3.2.3 on page 10.
3.2.1.7Low-Level Initialization: Watchdog
The Watchdog peripheral is enabled by default after a processor reset. If the application does not use it, which is the case in this example, then it shall be disabled in the Watchdog Mode Register (WDMR):
6 Application Note
6295A–ATARM–27-Mar-07
AT91C_BASE_WDTC->WDTC_WDMR = AT91C_WDTC_WDDIS;
Each ARM mode has its own stack pointer (register sp); thus, each mode which is used in the application must have its stack initialized.
Since stacks are descending, i.e. the stack pointer decreases in value when data is stored, the first stack pointer is located at the top of the internal SRAM. A particular length is reserved for each mode, depending on its uses. Supervisor and user modes usually have big stacks, IRQ and FIQ modes have a medium-sized stack, and other modes most often have only a few bytes. In this example, only the Supervisor (SVC) and IRQ modes are used.
Stack initialization is done by entering each mode one after another, setting r13 to the correct value. The top memory address is stored in a register, and decremented each time the stack pointer is set. Note that interrupts are masked (i.e., I and F bits set) during this whole process, except for the last mode (only F bit set). This results in the following code:
/*- Load top memory address in r0 ldr r0, =IRAMEND
/*- Enter Interrupt mode, setup stack */
msr CPSR_c, #ARM_MODE_IRQ | I_BIT | F_BIT mov r13, r0
sub r0, r0, #IRQ_STACK_SIZE
/*- Enter Supervisor mode, setup stack, IRQs unmasked */ msr CPSR_c, #ARM_MODE_SVC | F_BIT
mov r13, r0
A binary file is usually divided into two segments: the first one holds the executable code of the application, as well as read-only data (declared as const in C). The second segment contains read/write data, i.e., data that can be modified. These two sections are called text and data, respectively.
Variables in the data segment are said to be either uninitialized or initialized. In the first case, the programmer has not set a particular value when declaring the variable; conversely, variables fall in the second case when they have been declared with a value. Unitialized variables are held in a special subsection called BSS (for Block Started by Symbol).
Whenever the application is loaded in the internal Flash memory of the chip, the Data segment must be initialized at startup. This is necessary because read/write variables are located in SRAM or SDRAM, not in Flash. Depending on the toolchain used, there might be library function for doing this; for example, IAR Embedded Workbench® provides __segment_init().
Initialized data is contained in the binary file and loaded with the rest of the application in the memory. Usually, it is located right after the text segment. This makes it easy to retrieve the starting and ending address of the data to copy. To load these addresses faster, they are explicitly stored in the code using a compiler-specific instruction. Here is an example for the GNU toolchain:
_lp_data:
.word _etext
.word _sdata
.word _edata
7 Application Note
6295A–ATARM–27-Mar-07
The actual copy operation consists of loading these values and several registers, and looping through the data:
_init_data: |
|
ldr |
r2, =_lp_data |
ldmia |
r2, {r1, r3, r4} |
cmp |
r1, r3 |
beq |
_branch_main |
1: |
|
cmp |
r3, r4 |
ldrcc |
r2, [r1], #4 |
strcc |
r2, [r3], #4 |
bcc |
1b |
In addition, it is both safer and more useful for debug purposes to initialize the BSS segment by filling it with zeroes. Theoretically, this operation is unneeded; however, it can have several benefits. For example, it makes it easier when debugging to see which memory regions have been modified. This can be a valuable tool for spotting stack overflow and similar problems.
Initialization of the BSS and Data segments are similar, except register r2 is initialized at zero after the ldmia instruction and never modified (c.f. the above code).
Several microcontrollers of the AT91SAM family feature an External Bus Interface, enabling the connection of external memories. Those can be used to store an application and run it from there, instead of using internal flash or internal SRAM.
The ARM core always fetches exception vectors at address 0. However, this poses a problem for other memories: the exceptions vectors of the application, located at the beginning of the memory space, are never read by the core.
In addition, having exception vectors in SRAM benefits performance, even when running from the internal Flash. Indeed, since the SRAM is accessed at processor speed, this reduces interrupt latency. As such, a memory remap operation is performed in the software example regardless of which memory model is used.
Placing exception vectors in SRAM can be done simply by putting them at the beginning of the Data segment. Since it is itself located at the beginning of the SRAM, this means that exception vectors is automatically copied during the segment initialization.
Figure 3-1 and Figure 3-2 show the memory mapping after loading an application in SDRAM, and after the Data segment has been initialized and the remap command executed.
8 Application Note
6295A–ATARM–27-Mar-07