LPC810 Cortex-M0+ Programming from Scratch – Part 1: LED Blink
Disclaimer: I am no ARM programming expert, and I myself am experimenting with all the things described in this series of, “tutorials”. Therefore, I might be totally wrong in reasoning, but the code should be usable.
What this (series) is about?
I am currently experimenting with the LPC810 chip on Linux using the GCC toolchain. This is more like a progress of my experiment, but I hope it will help others. Actually it is more about compiler and linker than about embedded programming, because honestly, I don’t have any problem on general purpose embedded programming since I have prior experience using AVR and STM32 with preexisting librariessuch as AVR-LibC or STM32 Standard Peripheral Library. However, this time I am going to start from scratch. Which means, starting from assembly, linker scripts, startup code, etc. Since I can’t find any “standard” NXP LPC810 C headers, I will write my own.
The LPC810
More specifically, the LPC810M021FN8, is a small ARM Cortex-M0+ chip with 4kB flash and 1kB SRAM. Not much, right? However, the best part is that it comes as a DIP-8 package, which means you can plug it right to a breadboard and start experimenting. Also, it is amazingly cheap comparing to other ARM chips: as of the date of writing, 2014-11-27, the price on Mouser is $2.41.
This chip does have a lot of limitations though. The one biggest down side for me is that it doesn’t come with Analog-Digital Converter. It does have two Analog-Digital Comparaters, though. The next obvious reason is that there are only 6 GPIOs, where one is taken by RESET(PIO0_5), and two are not enabled by default(PIO0_2 and PIO0_3, taken by SWD interface). However, this chip should be enough to experiment with ARM Startup and linker scripts and other good stuff.
Disclaimer again: I am only starting to experiment with compilers/linker scripts, so I write terrible linker scripts. I miss multiple sections because I don’t know what they do. As of the time I write this post, I only have the .text, .data, .bss section written because I don’t understand what everything else are.
Reader assumptions
- Can use GNU/Linux (I am using GNU/Linux to do everything. Probably works on Windows, but need extra knowledge).
- Knows basic assembly (Sorry, I am not good enough to teach this).
- Knows basic C (I can talk about this, though)
- At least programmed some embedded device before(like 8051 or AVR)
- Knows how to read datasheet
- Knows how to wire up a breadboard without a photo or schematic (I admit I am just lazy. The circuit is too simple anyway).
What you will need
- An LPC810 chip. Here I am using LPC810M021FN8.
- A TTL-USB cable, 3.3V.
- A piece of breadboard.
- An indicator LED rated around 10mA (Or, just a random small 3mm/5mm LED).
- A resistor around 330Ω.
- Two pushbuttons.
- LPC81xM Datasheet
- LPC81x User Manual
- Cortex-M0+ Devices Generic User Guide
- ARM Toolchain. If you don’t have experience, get a precompiled one. I have several experience that when I compile my own and encountered some unsolvable problems, simply changing the toolchain fixed the problem. Try the gcc-arm on Ubuntu/Debian or the CodeSourcery toolchain.
- lpc21isp. The downloader.
Hardware Wiring
Wire the 3.3V to Pin 6(Vdd) of the LPC810. Connect ground to Pin 7(Vss).
Connect Pin 5 to ground through a pushbutton. When Pin 5 is pulled down(i.e. the pushbutton is being pressed) during reset, chip enters ISP command handler, and can be programmed.
Connect Pin 4(PIO0_2) to LED, series with resistor to ground. Pin 2 is the TXD in ISP mode, Pin 8 is the RXD in ISP mode. Connect them accordingly to the RX/TX pin of the TTL-USB converter. Note: my converter has its TX/RX label reversed for some version, so I have to connect TXD to TXD and RXD to RXD.
Connect Pin 1 to ground through a pushbutton. If you do not connect this button, you can still reset the device by removing the power and then power on.
Software Preparation
Install toolchain and lpc21isp (Ubuntu/Debian)
apt-get install gcc-arm-none-eabi lpc21isp
Done!
Start Coding!
Just test hardware
Example Source Code if you like to just download and try: 01-Getting_Started.tar.xz
Unpack the file somewhere you can remember. Plug the TTL-USB cable in(with the breadboard, of course). Hold the reset button, then hold the ISP button. Please try to push the buttons in this sequence, otherwise if you have set the pin that connects to the ISP button to output and HIGH, think what would happend when you push that down before the controller is reset. Still holding ISP button, execute
make flash
in the directory using UNIX command line(I assume you know how to do so). You should see something like this:
lpc21isp main.hex /dev/ttyUSB0 115200 12000
lpc21isp version 1.94
File main.hex:
loaded...
converted to binary format...
image size : 276
Image size : 276
Synchronizing (ESC to abort). OK
Read bootcode version: 4
13
Read part ID: LPC810M021FN8, 4 kiB FLASH / 1 kiB SRAM (0x00008100)
Will start programming at Sector 1 if possible, and conclude with Sector 0 to ensure that checksum is written last.
Erasing sector 0 first, to invalidate checksum. OK
Sector 0: ..|.
Download Finished... taking 0 seconds
Now launching the brand new code
Then, press the reset button again. LED should blink.
Starting from Scratch
In this section, I will explain the process of starting from scratch, and explain every details of the source code.
Directory and Makefile
Make a new folder, say, 00-Getting_Started
. Create a Makefile
:
TGT = main
CSRC =
ASRC =
ASRC += crt0.S
CSRC += main.c
LDS = ./LPC810/lpc810.ld
OBJS = $(CSRC:.c=.o)
OBJS += $(ASRC:.S=.o)
CC=arm-none-eabi-gcc
OBJCOPY=arm-none-eabi-objcopy
OBJDUMP=arm-none-eabi-objdump
NM=arm-none-eabi-nm
SIZE=arm-none-eabi-size
FLASH = lpc21isp
PORT = /dev/ttyUSB0
BAUD = 115200
KFREQ = 12000
CFLAGS=-O0 -mthumb -mcpu=cortex-m0plus -nostartfiles -Wl,-T$(LDS),-nostdlib,-Map=$(TGT).map
all: $(TGT).hex
%.o: %.S
$(CC) $(CFLAGS) -c $^ -o $@
$(TGT).elf: $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@
$(TGT).hex: $(TGT).elf
$(OBJCOPY) $< -O ihex $@
$(OBJDUMP) -D $< > $(TGT).disasm
$(NM) -n $(TGT).elf > $(TGT).sym
$(SIZE) $(TGT).elf
clean:
rm -rf *.elf *.hex *.map *.o *.disasm
flash:
$(FLASH) $(TGT).hex $(PORT) $(BAUD) $(KFREQ)
Of course, this is not the best Makefile in the world, but good enough for this series.
Then, create the folders and files referenced by the Makefile
:
mkdir -pv LPC810
touch main.c crt0.S LPC810/lpc810.ld
Main code
Here comes the main code(main.c
):
1 /**
2 Author: Rongcui Dong
3
4 This is the main code for the LPC810/Cortex-M0+ tutorial series:
5 00-Getting_Started.
6 Originally posted on:
7 http://homepages.rpi.edu/~dongr2/
8
9 Inspired by http://midibel.com/blink0.html, but I rewrote the whole
10 thing
11
12 Licence: Do anything with it, I don't care. Public Domain. Just
13 remember I don't give any warrenty
14 */
15
16 typedef unsigned int volatile * vp;
17 void main() {
18 /* PIO0_2 is used by SWD, so disable it */
19 *(vp) 0x4000C1C0 = 0xFFFFFFBFUL; /* PINENABLE0 register */
20 /* Set GPIO Direction */
21 *(vp) 0xA0002000 |= 1 << 2; /* DIR0, set PIO0_2 to output */
22 for(;;) {
23 /* Toggle the LED */
24 *(vp) 0xA0002300 |= 1 << 2; /* NOT0 register */
25 volatile long wait = 240000;
26 while (wait > 0) --wait; /* WAIT */
27 }
28 }
There is nothing fancy. The special memory addresses are the device specific addresses to control hardware. In order to access those, the addresses are cast into volaile pointers. These addresses can be found under the LPC81x User Manual. The PINENABLE0
address is found in Chapter 9, LPC81x Switch Matrix, the DIR0
and NOT0
addresses are under Chapter 7, LPC81x GPIO Port. I will only point the location in this tutorial, and I will not do so for the next ones. First, the SWD related functions are disabled so that the LED pin has GPIO enabled:
*(vp) 0x4000C1C0 = 0xFFFFFFBFUL; /* PINENABLE0 register */
Then, PIO0_2
is set to output mode:
*(vp) 0xA0002000 |= 1 << 2;
In an infinite loop, the LED is toggled by writing to the NOT0
register:
*(vp) 0xA0002300 |= 1 << 2; /* NOT0 register */
To slow down the blink enough, we just waste a lot of cycles doing nothing.
volatile long wait = 240000;
while (wait > 0) --wait; /* WAIT */
That’s it for the main.c
. Simple. However, with only this source file, the linker can’t know where to put the code in the executable. Therefore, we need to tell it where to put what. In the next section, I will talk about how we instruct the linker to correctly place code.
Linker Script
Now let’s look at the LPC810/lpc810.ld
.
1 /**
2 Author: Rongcui Dong
3
4 This is the linker script for the LPC810/Cortex-M0+ tutorial series:
5 00-Getting_Started.
6 Originally posted on:
7 http://homepages.rpi.edu/~dongr2/
8
9 Licence: Do anything with it, I don't care. Public Domain. Just
10 remember I don't give any warrenty
11 */
12
13 ENTRY(Reset_Handler);
14 MEMORY {
15 FLASH(rx) : ORIGIN = 0x0, LENGTH = 4K
16 RAM(rwx) : ORIGIN = 0x10000000, LENGTH = 1K
17 }
18
19 SECTIONS {
20 . = ORIGIN(FLASH);
21 .text : {
22 KEEP(* (.isr_vectors));
23 . = ALIGN(4);
24 . = 0xC4; /* The end of vectors. Just for tutorial 0 */
25 __vec_end__ = .;
26 * (.text);
27 . = ALIGN(4);
28 __end_text__ = .;
29 } > FLASH
30 __stack_top = ORIGIN(RAM) + LENGTH(RAM);
31 }
32
33 __end = .;
34 PROVIDE(etext = __end_text__);
35 PROVIDE(end = .);
An executable is divided into multiple sections. Each section is placed into a storage device in the microcontroller. The starting address and size are documented in 81x Datasheet, in section Memory Map. In our case, an LPC810 has 4K flash and 1K RAM, starting at address 0x00000000
and0x10000000
, therefore, we have the following snippet of LD script(ignore the ENTRY()
for now):
MEMORY {
FLASH(rx) : ORIGIN = 0x0, LENGTH = 4K
RAM(rwx) : ORIGIN = 0x10000000, LENGTH = 1K
}
This means there are two storage devices, FLASH
and RAM
. FLASH
is readable and executable, while the RAM is readable, executable, and writable. FLASH
starts at 0x0
and is 4KB
long, whileRAM
starts at 0x10000000
and is 1KB
long. FLASH
device is the read only permanent memory where the code stays, and RAM
is the working memory when the code runs.
After storage devices are defined, we need to tell the linker specific information about where each sections go. Before explaining the script, we need to know some information about the Cortex-M0+ processor. In the Cortex-M0+ Devices Generic User Guide, section 2.3.4 Vector Table, we see that a vector table comes at the very first of the memory address: 0x00000000
. It stores the initial stack pointer value, and then 15 addresses of the core Cortex-M0+ exception handlers. After that, there are device specific interrupt handler addresses. Note that since this is a 32 bit device, each address takes 4 bytes. According to the LPC81x User Manual(Will be abbreviated as 81X Manual), section 3.3.1 Interrupt Sources, there are 32 interrupt sources. Therefore, the first 48 long
(4 bytes wide each) values are taken by the vector table, so the executable codes will go after them. In other words, the executable code starts at address (48+1)*4 = 0xC4
.
Now we can look into the code.
. = ORIGIN(FLASH);
This means the “current” address is set to the origin of FLASH
. This line tells the linker to place code starting at this address.
.text : {
KEEP(* (.isr_vectors));
. = ALIGN(4);
. = 0xC4; /* The end of vectors. Just for tutorial 0 */
__vec_end__ = .;
* (.text);
. = ALIGN(4);
__end_text__ = .;
} > FLASH
.text
is the target section that the code will be placed into. In order to make sure the vector table isalways placed before anything, we make it a seperate section, in this case, .isr_vectors
. TheKEEP
here is just telling the linker that it should never discard this section even if it is not referenced (affected by -Wl,--gc-sections
). When source files are compiled, different object files are generated, main.o
and crt0.o
in this case. Now we go over the syntax even more carefully.
.text : {
...
} > FLASH
This means the target section is .text
, and it will be put on the storage device FLASH
.
* (.isr_vectors);
This means for all object files (indicated by wildcard *
), copy the .isr_vectors
section into the target address. In this case, 0x0
.
. = ALIGN(4);
Since Cortex-M0+ is 32 bits, each instruction is 4 bytes wide. Therefore, we need to align the code to instructions before advancing to next section. If it is not aligned, the linker just appends 0x0
(that is an observation, not necessarily true).
. = 0xC4;
This line is just for this tutorial because otherwise you have to type a whole list of 48 vectors and create 48 function definitions before you can even start blinking an LED. We all know from above that the actual executable starts at 0xC4
, so this is totally fine(and I have tested it, so really, it is fine). Of course, you can still keep this line for the next tutorials if you like.
__vec_end__ = .;
This is just a symbol for use in source codes. __vec_end__
will store the current address, and can be referenced by C or assembly code.
* (.text)
This is the same meaning as before. Copy the .text
section from each object file, put it in the executable.
__stack_top = ORIGIN(RAM) + LENGTH(RAM);
This defines the begining of the stack, which locates at the end of RAM and grows towards the front.
Finally, we have some more symbols defined about the end of code. Now we come back to the first line:
ENTRY(Reset_Handler)
This means the ‘entry point’ of the executable is the symbol Reset_Handler
, i.e. when the device starts, the first executed instruction is at the address of symbol Reset_Handler
. Note thatReset_Handler
does not need to be a function, it can be any symbols(I think it needs to be global, though). Although I haven’t tested it, a plain address should also work in this definition.
Startup Code
Now we have the correct placement of the sections. However, we still need to write the vector table and the code to execute main()
. After all, our entry point is Reset_Handler
, not main
. Let’s look atcrt0.S
:
1 /**
2 Author: Rongcui Dong
3
4 This is the startup code for the LPC810/Cortex-M0+ tutorial series:
5 00-Getting_Started.
6 Originally posted on:
7 http://homepages.rpi.edu/~dongr2/
8
9 Licence: Do anything with it, I don't care. Public Domain. Just
10 remember I don't give any warrenty
11 */
12 .syntax unified
13 .thumb
14
15 .section .isr_vectors
16 .align
17 .long __stack_top
18 .long Reset_Handler
19
20 .text
21 .align
22
23 .thumb_func
24 .global Reset_Handler
25 Reset_Handler:
26 crt0:
27 b main
First, we have something to tell the assembler:
.syntax unified
.thumb
This tells the assembler(GNU AS in this case) to use Unified Assembler Language and Thumb instructions.
Now we start with the vector table.
.section .isr_vectors
This tells the assembler to place the later code into section .isr_vectors
.
.align
As it looks like, this tells the assembler to align code to 4 byte instructions.
.long __stack_top
.long Reset_Handler
This defines two long expressions. The first one contains the symbol __stack_top
, defined in linker script. The second one contains the symbol Reset_Handler
, which is defined later. Notice that the first .long
takes the addresses 0x00
to 0x03
, and the second .long
takes 0x04
to 0x07
. Recall the vector table in Cortex-M0+ Devices Generic User Guide (will be abbreviated as CM0 Guidefrom now on), the first long is indeed the initial stack pointer, while the second one is the address of the program counter after a reset. Since we already set the beginning of .text
to be 0xC4
, we don’t need to define all the other vectors. Convenient, right?
.text
This means put the later code into the .text
section. This line is the same as .section .text
.
.thumb_func
This line is needed for GNU AS to generate correct mix of Thumb/ARM instructions. I don’t know a lot about this, but I know that if this line is omitted, the generated executable is incorrect.
.global Reset_Handler
This line makes symbol Reset_Handler
to be global, so that code from other source files can access this symbol.
Reset_Handler:
crt0:
b main
Why do I have two symbols but nothing between? Because there will be some initialize code there for the next tutorials. These three lines are the minimal code to execute the main()
. For newbies(like me), notice that the symbols don’t take up memory, so the only line of code here is b main
. As documented in both CM0 Guide and 81x Manual, B{cc} <label>
is the instruction for “Branch to <label> {conditionally}”. In this case, “branch to label main
unconditionally”.
Now the startup code is finished. Do the same thing as compiling the example:
make
make flash
Don’t forget to press the ISP button. Also don’t forget to reset after programming. After reset, you should see the LED blink!
Some Notes
If you look at the disassembled file main.disasm
, in the first ‘subsection’ under section .text
, you will see:
Disassembly of section .text:
00000000 <main-0xc4>:
0: 10000400 andne r0, r0, r0, lsl #8
4: 00000111 andeq r0, r0, r1, lsl r1
According to dwelch’s reply in this post, the address of all exception handlers should be odd. Remember the first item is the initial stack pointer, so that is correct.
LPC810 Cortex-M0+ Programming from Scratch – Part 2: .data and .bss
In this tutorial, we are going to finish the vector table and the initialization of .data
and .bss
section. The tutorial will continue based on Tutorial 1. If you want the entire working code, 02-BSS_Data.tar.xz.
Basics about .rodata, .data and .bss
Disclaimer again: I am not expert on this topic, so I can be wrong.
.rodata
contains the constant variables in C. Such as:
const unsigned int variable = 1;
.data
section contains initialized variables in C. Such as:
unsigned int variable = 1;
.bss
section contains uninitialized variables. Such as:
unsigned int variable;
Typically, the data in .bss
section is initialized as zero.
Linker Script
I will attach the entire linker script at the end of this section. Now I will just go over the details.
.rodata Section
First, since .rodata
is just constants, it doesn’t need to be copied to RAM. After all, our small device only contains 1KB of RAM. Therefore, we change the .text
section definition like this:
.text : {
KEEP(* (.isr_vectors));
. = ALIGN(4);
__vec_end__ = .;
/*. = 0xC4;*/ /* End of vectors */
* (.text);
* (.text*);
. = ALIGN(4);
* (.rodata);
* (.rodata*);
. = ALIGN(4);
__end_text__ = .;
} > FLASH
The above code will tell the linker to put .rodata
directly after .text
of each object file, and into the.text
section of the generated executable. Note the (.text*)
is here because sometimes there are some .text.something
generated by the compiler.
.data Section
This section is somewhat different from the previous ones. Apparently, at execution, all data is in RAM, but since they contain initialized values, they have to be stored into flash. Therefore, the .data
section has two addresses: load address and run-time address. The labels in .data
section should be in run-time address in order for the program to access and change the value. Now we will look into the details of the definition of this section.
.text : {
...
} > FLASH
__flash_sdata__ = .;
.data : AT(__flash_sdata__) {
...
} > RAM
Here, the __flash_sdata__
symbol is the address right after the .text
section, and is where.data
section is stored. In other words, __flash_sdata__
is the beginning of the load address of the .data
section. Since the labels are using run-time address, there is the > RAM
.
.data : AT(__flash_sdata__){
__data_start__ = .;
* (.data);
* (.data*);
. = ALIGN(4);
__data_end__ = .;
} > RAM
__data_size__ = __data_end__ - __data_start__;
The __data_start__
and __data_end__
are the start and end of run-time address. The others are have familiar syntax, so I will not explain here. The labels will be used by startup code later.
.bss Section
The .bss
section has no initial value, so it only has a run-time address.
.bss : {
__bss_start__ = .;
* (.bss);
* (.bss*);
. = ALIGN(4);
__bss_end__ = .;
} > RAM
__bss_size__ = __bss_end__ - __bss_start__;
The syntax is the same, so I won’t explain.
The entire Linker Script
Note: Since the above code snippet are copied and pasted into the post, while the following one is included directly, I may forget to update the above snippets. Please use this one as the correct reference.
/**
Author: Rongcui Dong
This is the linker script for the LPC810/Cortex-M0+ tutorial series:
02-BSS_Data
Originally posted on:
http://homepages.rpi.edu/~dongr2/
Licence: Do anything with it, I don't care. Public Domain. Just
remember I don't give any warrenty
*/
ENTRY(Reset_Handler);
MEMORY {
FLASH(rx) : ORIGIN = 0x0, LENGTH = 0x1000 /* 4K */
RAM(rwx) : ORIGIN = 0x10000000, LENGTH = 0x400 /* 1K */
}
SECTIONS {
. = ORIGIN(FLASH);
.text : {
KEEP(* (.isr_vectors));
. = ALIGN(4);
__vec_end__ = .;
/*. = 0xC4;*/ /* End of vectors */
* (.text);
* (.text*);
. = ALIGN(4);
* (.rodata);
* (.rodata*);
. = ALIGN(4);
__end_text__ = .;
} > FLASH
__flash_sdata__ = .;
.data : AT(__flash_sdata__){
__data_start__ = .;
* (.data);
* (.data*);
. = ALIGN(4);
__data_end__ = .;
} > RAM
__data_size__ = __data_end__ - __data_start__;
.bss : {
__bss_start__ = .;
* (.bss);
* (.bss*);
. = ALIGN(4);
__bss_end__ = .;
} > RAM
__bss_size__ = __bss_end__ - __bss_start__;
__stack_top = ORIGIN(RAM) + LENGTH(RAM);
}
_end = .;
PROVIDE(end = .);
Vector Table and Startup Code
The startup file
This section only gives the startup file crt0.S
. Details will be explained later
/**
Author: Rongcui Dong
This is the startup code for the LPC810/Cortex-M0+ tutorial series:
02-BSS_Data
Originally posted on:
http://homepages.rpi.edu/~dongr2/
Licence: Do anything with it, I don't care. Public Domain. Just
remember I don't give any warrenty
*/
.syntax unified
.thumb
.section .isr_vectors
.align 2
.long __stack_top
.long Reset_Handler
.long NMI_Handler
.long HardFault_Handler
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long SVCall_Handler
.long 0
.long 0
.long PendSV_Handler
.long SysTick_Handler
.long 0
/* IRQ 0 */
.long SPI0_IRQ
.long SPI1_IRQ
.long 0
.long UART0_IRQ
.long UART1_IRQ
.long UART2_IRQ
.long 0
.long 0
/* IRQ 8 */
.long I2C0_IRQ
.long SCT_IRQ
.long MRT_IRQ
.long CMP_IRQ
.long WDT_IRQ
.long BOD_IRQ
.long 0
.long WKT_IRQ
/* IRQ 16 */
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
/* IRQ 24 */
.long PININT0_IRQ
.long PININT1_IRQ
.long PININT2_IRQ
.long PININT3_IRQ
.long PININT4_IRQ
.long PININT5_IRQ
.long PININT6_IRQ
.long PININT7_IRQ
.text
.align
.thumb_func
.global Reset_Handler
Reset_Handler:
cpsid i
ldr r0, =__flash_sdata__
ldr r1, =__data_start__
ldr r2, =__data_size__
@@ If data_size == 0
cmp r2, #0
beq init_bss
copy:
ldrb r4, [r0] @ Load *r0 to r4
strb r4, [r1] @ Load r4 to *r1
adds r0, #1 @ Increment flash pointer
adds r1, #1 @ Increment RAM pointer
subs r2, #1 @ Loop until all data are copied
bne copy
init_bss:
ldr r0, =__bss_start__
ldr r1, =__bss_end__
ldr r2, =__bss_size__
cmp r2, #0
beq crt0
movs r4, #0
zero:
strb r4, [r0] @ Zero out
adds r0, #1 @ Increment pointer
subs r2, #1
bne zero
crt0:
cpsie i
b main
.align
Vector Table
This part is just work: Copy everything from the CM0+ Guide(Cortex-M0+ Devices Generic User Guide) and 81x Manual(LPC81x User Manual). It is just that long list of .long
expressions (really, longexpressions). Note that currently none of the added labels are defined yet, so we are going to define them. Since we all like C instead of assembly(at least, for me), we are going to define them in a C file. Here comes vector.c
:
extern void Reset_Handler(void);
extern void NMI_Handler(void);
extern void HardFault_Handler(void);
extern void SVCall_Handler(void);
extern void PendSV_Handler(void);
extern void SysTick_Handler(void);
extern void SPI0_IRQ(void);
extern void SPI1_IRQ(void);
extern void UART0_IRQ(void);
extern void UART1_IRQ(void);
extern void UART2_IRQ(void);
extern void I2C0_IRQ(void);
extern void SCT_IRQ(void);
extern void MRT_IRQ(void);
extern void CMP_IRQ(void);
extern void WDT_IRQ(void);
extern void BOD_IRQ(void);
extern void WKT_IRQ(void);
extern void PININT0_IRQ(void);
extern void PININT1_IRQ(void);
extern void PININT2_IRQ(void);
extern void PININT3_IRQ(void);
extern void PININT4_IRQ(void);
extern void PININT5_IRQ(void);
extern void PININT6_IRQ(void);
extern void PININT7_IRQ(void);
__attribute__((naked))
void _unhandled_exception(void) {
for(;;);
}
void NMI_Handler(void) __attribute__((weak, alias("_unhandled_exception")));
void HardFault_Handler(void) __attribute__((weak, alias("_unhandled_exception")));
void SVCall_Handler(void) __attribute__((weak, alias("_unhandled_exception")));
void PendSV_Handler(void) __attribute__((weak, alias("_unhandled_exception")));
void SysTick_Handler(void) __attribute__((weak, alias("_unhandled_exception")));
void SPI0_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void SPI1_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void UART0_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void UART1_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void UART2_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void I2C0_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void SCT_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void MRT_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void CMP_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void WDT_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void BOD_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void WKT_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void PININT0_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void PININT1_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void PININT2_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void PININT3_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void PININT4_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void PININT5_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void PININT6_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
void PININT7_IRQ(void) __attribute__((weak, alias("_unhandled_exception")));
Before compiling, don’t forget to add this file to CSRC
in the Makefile
.
The function prototypes all have the extern
keyword, since the handler can be declared outside thevector.c
. However, to make development easier, we don’t want to declare all those handlers before starting to blink an LED, so we first make a naked function _unhandled_exception()
which just does an infinite loop, so that when the processor receives an unhandled exception, it will ‘stop’, although it is running.
__attribute__((naked))
This basically means the C compiler is not saving context of this function, so current registers are not pushed in stack.
void NMI_Handler(void) __attribute__((weak, alias("_unhandled_exception")));
...
Here, the attribute weak
means that we can override this declaration somewhere else. alias
means that it is just another name of _unhandled_exception()
. Now, whenever there is an exception occuring but we have not defined a handler, the _unhandled_exception()
will be called.
Initialization
Now we are finally explaining how .data
and .bss
are initialized. Before looking at the code, please have the CM0+ Guide or 81x Manual open to the section where instructions are described.
.thumb_func
.global Reset_Handler
Reset_Handler:
cpsid i
ldr r0, =__flash_sdata__
ldr r1, =__data_start__
ldr r2, =__data_size__
As explained in previous part, .thumb_func
is required for the assembler to correctly generate code..global Reset_Handler
means that the symbol Reset_Handler
is accessible externally. I will go over syntax of each line containing new instructions or directives here.
label:
This is a label containing the address of the next instruction.
cpsid i
This instruction disables all maskable interrupts
ldr Rt, label
This instruction loads the value from address label into register Rt. Here, for example, we have label__flash_sdata__
. Here I don’t understand the detailed reasoning of the syntax, but to load the address of a label you will need =label_name
. In this case,
ldr r0, =__flash_sdata__
The next unfamiliar instruction is
cmp Rn, #imm
This compares an immediate number with value stored in Rn. An immediate number is a constant that is a part of the instruction. Here, the imm
must be in range 0-255. The reasoning why imm
cannot be 32 bit is quite simple: one instruction is 16 or 32 bit wide, so the imm
cannot take 32 bits.
Here, we have
cmp r2, #0
If the value in R2 equals to 0, the condition flag Z in register Application Program Status Retister(APSR) is set(read CM0+ Guide for detailed description).
Next instruction is
b{cond} label
The {cond}
here is suffix to the condition to test. If the condition is true, the program will branch to the label, i.e. the next instruction executed is the one right after the label. In our case, we are testing for equal, i.e. flag Z set. Therefore, the instruction used is:
beq init_bss
Therefore, if __data_size__
is zero, the label copy
will be skipped. Otherwise, it is executed.
The next instruction:
ldr<h|b> Rt, [Rn{, #imm}]
Here, the <h|b>
means it can be ldrh
or ldrb
. ldrh
loads a halfword(2 bytes), while ldrb
loads one byte. If written in syntax like C it is something like
Rt = *(Rn+imm)
That means, load a value to register Rt from the address stored in Rn plus #imm
(i.e the offset).
Here, we are loading the value from address stored in R0
into register R4
ldrb r4, [r0]
The store instruction is
str<h|b> Rt, [Rn{, #imm}]
It is basically the same as the load instruction. If written in C-like syntax it is
*(Rn+imm) = Rt
That is, store the value of R4
into the address in Rn with offset #imm
.
add{s} {Rd, } Rn, <Rn|#imm>
Here there are too many combinations, so please read the CM0+ Guide for detail. Just note that onlyadds
can use immediate numbers, add
can only take registers (at least for my device). Since we are copying data, we need to advance the ‘pointers’ after each copy, we have code like this:
adds r0, #1
In order to copy the correct number of data, we decrement R2
, which stores the remaining bytes to copy, and loop over copy
. So we have:
subs r2, #1
bne copy
The above means, after the subs
instruction, if R2
is not zero, the flag Z is cleared, and the conditionNE will be true, and bne copy
will branch to copy label.
The next section is the same, and the only unfamiliar instruction is
movs Rd, #imm
Which means load an immediate number into register Rd
. Here, the register R4
is used to store value0
for clearing the bss addresses.
After the copy
and zero
section, it is finally time to branch to main!
cpsie i
The above instruction enables interrupts.
b main
Here, since no {cond}
is specified, the program will unconditionally branch to main
label. In this case, it is just the main()
.
Finally, we need to align the code to 4 bytes becaue we use the Thumb instructions, which mixes some 32 bit instructions and some 16 bit instructions. I tried without the .align
directive, and while I read the disassembly code and it seems that some part is incorrect. I am not completely sure about this, because the behavior didn’t look wrong. However, I will still include this just in case. The worst thing of this directive is to waste 2 bytes of flash, anyway.
The Main Code
Now let’s setup the main code to test that everything works right.
/**
Author: Rongcui Dong
This is the main code for the LPC810/Cortex-M0+ tutorial series:
00-Getting_Started.
Originally posted on:
http://homepages.rpi.edu/~dongr2/
Licence: Do anything with it, I don't care. Public Domain. Just
remember I don't give any warrenty
*/
unsigned long wait;
unsigned long until = 0xDEADBEEF;
const unsigned long begin = 0xDEAFBEEF;
typedef unsigned int volatile * vp;
void main() {
/* PIO0_2 is used by SWD, so disable it */
*(vp) 0x4000C1C0 = 0xFFFFFFBFUL; /* PINENABLE0 register */
/* Set GPIO Direction */
*(vp) 0xA0002000 |= 1 << 2; /* DIR0, set PIO0_2 to output */
for(;;) {
/* Toggle the LED */
*(vp) 0xA0002300 |= 1 << 2; /* NOT0 register */
wait = begin;
while (wait > until) --wait; /* WAIT */
}
}
This file does the same thing as the one in the previous part. However, it tests all sections and startup code:
unsigned long wait;
This line tests the .bss
section.
unsigned long until = 0xDEADBEEF;
This line tests the .data
section.
const unsigned long begin = 0xDEAFBEEF;
This line tests the .rodata
section.
If the LED blinks, everything works.
Up to the date of writing (2014-11-27), this is the farthest I can go. In other words, that is all I know. I am now working on a library that defines useful macros and structs, and that will come next.
LPC810 Cortex-M0+ Programming from Scratch – Part 3: Migrate to CMake and Start Writing Library
In this tutorial, we are going to do the following things
- Migrate to CMake
- Start writing library for GPIO
If you just want the source, download here: 03-GPIO.tar.xz
Migrate to CMake
This is not a tutorial on CMake, so I will not explain the syntax. The comments should be enough. First, change the directory structure like this(generated by tree -n
):
.
├── CMakeLists.txt
├── lpc810
│ ├── crt0.S
│ ├── device.h
│ ├── lpc810.ld
│ └── vectors.c
├── main.c
└── Toolchain-arm-none-eabi.cmake
1 directory, 7 files
I would recommend you to keep the Makefile
for now to compare. If cmake
doesn’t generate the exact same output(can be inspected from the output of size
), something is wrong. Basically, just rename LPC810
to lpc810
, move crt0.S
and vectors.c
into lpc810
. Now let’s look at the CMake files.
CMakeLists.txt:
# Project CMake file for use in tutorial series
# Author: Rongcui Dong
# Original site: http://homepages.rpi.edu/~dongr2/
# License: Public Domain
# NO WARRENTY, of course
# Do whatever you like with this. Good if you acknowledge my name,
# but you are not required to do so
# CMake starts supporting cross compiling since 2.6
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
# Must be specified BEFORE the "PROJECT" line
SET(CMAKE_TOOLCHAIN_FILE "Toolchain-arm-none-eabi.cmake")
# Our project. Both C and ASM exist
PROJECT("03-GPIO" C ASM)
# The output files variables
SET(TGT "main")
SET(ELF "${TGT}.elf")
SET(HEX "${TGT}.hex")
# Information about the target device
SET(CPU "lpc810")
SET(FLASH "lpc21isp")
SET(PORT "/dev/ttyUSB0")
SET(BAUD 115200)
SET(KFREQ 12000)
# C Sources
SET (CSRC
${CMAKE_CURRENT_SOURCE_DIR}/main.c
${CMAKE_CURRENT_SOURCE_DIR}/${CPU}/vectors.c
)
# Assembly Sources
SET (ASRC
${CMAKE_CURRENT_SOURCE_DIR}/${CPU}/crt0.S
)
# Linker Script
SET(LDS
${CMAKE_CURRENT_SOURCE_DIR}/${CPU}/${CPU}.ld
)
# Flags
SET(CMAKE_C_FLAGS
"-O0 -mthumb -mcpu=cortex-m0plus -nostartfiles -ggdb"
)
SET(CMAKE_ASM_FLAGS
"${CMAKE_ASM_FLAGS} ${CMAKE_C_FLAGS}"
)
SET(CMAKE_EXE_LINKER_FLAGS "-T\"${LDS}\" -nostdlib")
# Main ELF file
ADD_EXECUTABLE(${ELF}
${CSRC}
${ASRC}
)
# To recompile when linker script is changed.
# Inspired by: http://www.cmake.org/pipermail/cmake/2010-May/037206.html
SET_SOURCE_FILES_PROPERTIES (
main.c PROPERTIES OBJECT_DEPENDS ${LDS}
)
# Make the HEX file, disassembly, symbol table
ADD_CUSTOM_COMMAND(OUTPUT ${HEX} ${TGT}.disasm ${TGT}.sym
DEPENDS ${ELF}
COMMAND ${OBJCOPY} ${ELF} -O ihex ${HEX}
COMMAND ${OBJDUMP} -SD ${ELF} > ${TGT}.disasm
COMMAND ${NM} -n ${ELF} > ${TGT}.sym
COMMAND ${SIZE} ${ELF}
VERBATIM)
# Make the hex target automatically invoked
ADD_CUSTOM_TARGET(hex ALL
DEPENDS ${HEX}
VERBATIM)
# The target to program the device
ADD_CUSTOM_TARGET(flash
DEPENDS ${HEX}
COMMAND ${FLASH} ${HEX} ${PORT} ${BAUD} ${KFREQ}
VERBATIM)
Toolchain-arm-none-eabi.cmake:
# Toolchain for arm-none-eabi
# Author: Rongcui Dong
# Original site: http://homepages.rpi.edu/~dongr2/
# License: Public Domain
# NO WARRENTY, of course
# Do whatever you like with this. Good if you acknowledge my name,
# but you are not required to do so
#
# Inspired by:
# http://www.vtk.org/Wiki/CMake_Cross_Compiling#The_toolchain_file
# http://stackoverflow.com/questions/15132185/mixing-c-sources-and-assembly-source-and-build-with-cmake
#
# Rewritten by me
INCLUDE(CMakeForceCompiler)
SET(CMAKE_SYSTEM_NAME Generic)
# Set the correct toolchain. arm-elf might work, I haven't tested
CMAKE_FORCE_C_COMPILER(arm-none-eabi-gcc GNU)
CMAKE_FORCE_CXX_COMPILER(arm-none-eabi-g++ GNU)
SET(OBJCOPY arm-none-eabi-objcopy)
SET(OBJDUMP arm-none-eabi-objdump)
SET(NM arm-none-eabi-nm)
SET(SIZE arm-none-eabi-size)
# Add your toolchain install path if you like.
#SET(CMAKE_FIND_ROOT_PATH /opt/arm-none-eabi-toolchain)
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
# Enable preprocessor on assembly files
SET(ASM_OPTIONS "-x assembler-with-cpp")
# Assembler Flags
SET(CMAKE_ASM_FLAGS "${CMAKE_C_CFLAGS} ${ASM_OPTIONS}")
To use CMake on this project, first make sure the toolchain install root is added into the toolchain file (Toolchain-arm-none-eabi.cmake
). Not the <somewhere>/bin
directory, but the <somewhere>
path. Then, make a folder for building the source. I made a build/
directory under the source directory in this example. Then, under the build/
directory, run the following command:
cmake ..
If everything goes right, when you run make
, the code should compile without error. Compare the output of arm-none-eabi-size
with the one generated by Makefile
. In this tutorial, I have:
text data bss dec hex filename
392 4 4 400 190 main.elf
This is almost definitely different from yours, because this is the size of the executable after I finishedthis tutorial.
GPIO Library
For now and some future tutorials, we are going to work on the new file device.h
. It is located underlpc810/device.h
. Why we are working on this? Obviously no one would like to look up the datasheet and calculate the memory address each time working with peripherals. Therefore, we are making a header to ease the job.
Note: I assume the reader at least knows basics of C, otherwise this part is almost impossible to understand. However, if you just want the library, you can safely copy the code only. Just remember this library is a work in progress. I will post the entire device.h
at the end.
Some typedefs
These typedef
s are recommended by the CMSIS standard. I don’t know that standard a lot and I don’t intend to implement the entire CMSIS, I just come over it and think that it is a good idea to include them:
#define __I volatile const
#define __O volatile
#define __IO volatile
Now, read only registers are marked __I
, write only registers are marked __O
, read and writeregisters are marked __IO
.
Switch Matrix
Since the first register we used in the blink example is in the switch matrix, we are going to implement it first. Please prepare 81x Manual (LPC81x User Manual) and open to the chapter Switch Matrix. At the section Register description, there is a table about all registers available in the switch matrix. The important part here are the Offset values and the base address. First, make a struct like this:
typedef struct {
} SWM_TypeDef;
This defines a type of struct
, SWM_Typedef
. At this point it is still unclear how it will be used, but it will become clear as we progress.
Reading the table, we see there are PINASSIGN0
through PINASSIGN8
, each takes a word, or 32 bits. Therefore, we add the definitions like this:
struct {
__IO uint32_t PINASSIGN0;
__IO uint32_t PINASSIGN1;
__IO uint32_t PINASSIGN2;
__IO uint32_t PINASSIGN3;
__IO uint32_t PINASSIGN4;
__IO uint32_t PINASSIGN5;
__IO uint32_t PINASSIGN6;
__IO uint32_t PINASSIGN7;
__IO uint32_t PINASSIGN8;
} SWM_TypeDef;
In C, the start address of one element follows right after the end address of the previous element. Therefore, PINASSIGN0
has offset 0x000
and PINASSIGN1
has offset 0x004
, etc. Clearly, this pattern matches the table up to PINASSIGN8. The next element, PINENABLE0
, has offset 0x1C0
, so we need some spacing after PINASSIGN8
. Doing some simple math:
We add an array of bytes for the spacing (I will not include the struct {}
. If unclear, please read the complete file, or send me an email).
uint8_t RESERVED9[0x19C];
Remember, the actual address does not correspond to any physical memory, so this won’t take 412 bytes of memory. After this line, we define the PINENABLE0
:
__IO uint32_t PINENABLE0;
Now, this register has offset 0x1C0
. We have defined the struct for switch matrix now, so we have all the offsets. To add the base address, we need this #define
statement outside:
#define SWM ((SWM_TypeDef *) ((uint32_t) 0x4000C000))
This line may be confusing for those not very familiar with C. It means take the number 0x4000C000
and cast it into a SWM_Typedef
pointer. Therefore, if we are accessing SWM->PINENABLE0
, we are actually accessing memory address 0x4000C1C0
.
Now it’s time to integrate this into our main code. In the main.c
, change this line:
*(vp) 0x4000C1C0 = 0xFFFFFFBFUL;
to
SWM->PINENABLE0 = 0xFFFFFFBFUL;
Both line does the exact same thing, but the second line is much clearer. Note that the generated assembly code is a bit different. The former uses a direct accesss without offset, the latter uses an offset:
/* PIO0_2 is used by SWD, so disable it */
SWM->PINENABLE0 = 0xFFFFFFBFUL;
c8: 4a16 ldr r2, [pc, #88] ; (124 <main+0x60>)
ca: 23e0 movs r3, #224 ; 0xe0 <- 2 more bytes
cc: 005b lsls r3, r3, #1 <- 2 more bytes
ce: 2141 movs r1, #65 ; 0x41
d0: 4249 negs r1, r1
d2: 50d1 str r1, [r2, r3]
Notice the str
instruction, in the [r2, r3]
, r2
contains 0x4000C000
, and r3
contains 0x1C0
. Therefore, this method takes 4 bytes more space than direct address access. However, once optimize is turned on, there should be no difference.
IOCON
We don’t need to use this yet, but we are still including it. Note: since I haven’t used it, I cannot guarantee that this part is correct, so I will not explain it here. Please read the complete source. The only thing I’d like to mention is that unlike struct
, where the starting address of each member follows the ending address of the previous member, all members in a union
starts at the same address. Therefore, in the following code snippet:
union {
uint32_t word;
struct {
uint8_t B0;
uint8_t B1;
uint8_t B2;
uint8_t B3;
} bytes;
uint8_t array[4];
} a_union;
The struct bytes
contains the same address as word
and array
, while a_union.array[0]
is the same address as a_union.bytes.B0
, etc. The entire a_union
takes 4 bytes.
GPIO Registers
This is quite a long definition because the GPIO register includes a lot of information. Please open the81x Manual to the GPIO Port chapter, Register description section. Now, let’s look at the definition:
typedef struct {
struct { /* Offset: 0x0000 */
__IO uint8_t B0;
__IO uint8_t B1;
__IO uint8_t B2;
__IO uint8_t B3;
__IO uint8_t B4;
__IO uint8_t B5;
__IO uint8_t B6;
__IO uint8_t B7;
__IO uint8_t B8;
__IO uint8_t B9;
__IO uint8_t B10;
__IO uint8_t B11;
__IO uint8_t B12;
__IO uint8_t B13;
__IO uint8_t B14;
__IO uint8_t B15;
__IO uint8_t B16;
__IO uint8_t B17;
} PBYTE;
uint8_t RESERVED0[0xFED];
struct { /* Offset: 0x1000 */
__IO uint32_t W0;
__IO uint32_t W1;
__IO uint32_t W2;
__IO uint32_t W3;
__IO uint32_t W4;
__IO uint32_t W5;
__IO uint32_t W6;
__IO uint32_t W7;
__IO uint32_t W8;
__IO uint32_t W9;
__IO uint32_t W10;
__IO uint32_t W11;
__IO uint32_t W12;
__IO uint32_t W13;
__IO uint32_t W14;
__IO uint32_t W15;
__IO uint32_t W16;
__IO uint32_t W17;
} PWORD;
uint8_t RESERVED1[0xFB8];
__IO uint32_t DIR; /* Offset: 0x2000 */
uint8_t RESERVED2[0x7C];
__IO uint32_t MASK; /* Offset: 0x2080 */
uint8_t RESERVED3[0x7C];
__IO uint32_t PIN; /* Offset: 0x2100 */
uint8_t RESERVED4[0x7C];
__IO uint32_t MPIN;
uint8_t RESERVED5[0x7C];
__IO uint32_t SET;
uint8_t RESERVED6[0x7C];
__O uint32_t CLR;
uint8_t RESERVED7[0x7C];
__O uint32_t NOT;
} GPIO_TypeDef;
This is basically the same principle as the switch matrix struct. Just notice that I pack the PBYTE
andPWORD
into separate structs to give easier access.
To use this in main code, change
*(vp) 0xA0002000 |= 1 << 2; /* DIR0, set PIO0_2 to output */
to
GPIO0->DIR |= 1 << 2;
And change
*(vp) 0xA0002300 |= 1 << 2; /* NOT0 register */
to
GPIO0->DIR |= 1 << 2;
How clear it becomes! Now, with lots of lots of patience we can finally make the entire library(I haven’t done so yet)!
The Complete Files
device.h up to now:
/**
This is the device header file for LPC810, for the
LPC810 Cortex-M0+ Programming from Scratch
tutorial series.
Author: Rongcui Dong
Originally posted: http://homepages.rpi.edu/~dongr2/
License: Public Domain. No warrenty of any kind. Appreciated but not
required to keep my name
*/
#ifndef DEVICE_H
#define DEVICE_H
#include <inttypes.h>
#define __I volatile const
#define __O volatile
#define __IO volatile
/**
@brief The structure of IOCON registers.
*/
typedef union {
__IO uint32_t W;
__IO uint16_t PIO;
} IOCON_TypeDef;
/**
@brief The structures that groups all IOCON registers
*/
typedef struct {
IOCON_TypeDef PIO0_17;
IOCON_TypeDef PIO0_13;
IOCON_TypeDef PIO0_12;
IOCON_TypeDef PIO0_5;
IOCON_TypeDef PIO0_4;
IOCON_TypeDef PIO0_3;
IOCON_TypeDef PIO0_2;
IOCON_TypeDef PIO0_11;
IOCON_TypeDef PIO0_10;
IOCON_TypeDef PIO0_16;
IOCON_TypeDef PIO0_15;
IOCON_TypeDef PIO0_1;
IOCON_TypeDef RESERVED;
IOCON_TypeDef PIO0_9;
IOCON_TypeDef PIO0_8;
IOCON_TypeDef PIO0_7;
IOCON_TypeDef PIO0_6;
IOCON_TypeDef PIO0_0;
IOCON_TypeDef PIO0_14;
} LPC_IOCON_TypeDef;
/**
@brief Structure of GPIO port register
*/
typedef struct {
struct { /* Offset: 0x0000 */
__IO uint8_t B0;
__IO uint8_t B1;
__IO uint8_t B2;
__IO uint8_t B3;
__IO uint8_t B4;
__IO uint8_t B5;
__IO uint8_t B6;
__IO uint8_t B7;
__IO uint8_t B8;
__IO uint8_t B9;
__IO uint8_t B10;
__IO uint8_t B11;
__IO uint8_t B12;
__IO uint8_t B13;
__IO uint8_t B14;
__IO uint8_t B15;
__IO uint8_t B16;
__IO uint8_t B17;
} PBYTE;
uint8_t RESERVED0[0xFED];
struct { /* Offset: 0x1000 */
__IO uint32_t W0;
__IO uint32_t W1;
__IO uint32_t W2;
__IO uint32_t W3;
__IO uint32_t W4;
__IO uint32_t W5;
__IO uint32_t W6;
__IO uint32_t W7;
__IO uint32_t W8;
__IO uint32_t W9;
__IO uint32_t W10;
__IO uint32_t W11;
__IO uint32_t W12;
__IO uint32_t W13;
__IO uint32_t W14;
__IO uint32_t W15;
__IO uint32_t W16;
__IO uint32_t W17;
} PWORD;
uint8_t RESERVED1[0xFB8];
__IO uint32_t DIR; /* Offset: 0x2000 */
uint8_t RESERVED2[0x7C];
__IO uint32_t MASK; /* Offset: 0x2080 */
uint8_t RESERVED3[0x7C];
__IO uint32_t PIN; /* Offset: 0x2100 */
uint8_t RESERVED4[0x7C];
__IO uint32_t MPIN;
uint8_t RESERVED5[0x7C];
__IO uint32_t SET;
uint8_t RESERVED6[0x7C];
__O uint32_t CLR;
uint8_t RESERVED7[0x7C];
__O uint32_t NOT;
} GPIO_TypeDef;
/**
@brief Struct of Switch Matrix
*/
typedef struct {
__IO uint32_t PINASSIGN0;
__IO uint32_t PINASSIGN1;
__IO uint32_t PINASSIGN2;
__IO uint32_t PINASSIGN3;
__IO uint32_t PINASSIGN4;
__IO uint32_t PINASSIGN5;
__IO uint32_t PINASSIGN6;
__IO uint32_t PINASSIGN7;
__IO uint32_t PINASSIGN8;
uint8_t RESERVED9[0x19C];
__IO uint32_t PINENABLE0;
} SWM_TypeDef;
/* Device Registers */
#define IOCON ((LPC_IOCON_TypeDef *) ((uint32_t) 0x40044000))
#define GPIO0 ((GPIO_TypeDef *) ((uint32_t) 0xA0000000))
#define SWM ((SWM_TypeDef *) ((uint32_t) 0x4000C000))
#endif // DEVICE_H
main.c:
/**
Author: Rongcui Dong
This is the main code for the
LPC810 Cortex-M0+ Programming from Scratch
tutorial series.
Originally posted on:
http://homepages.rpi.edu/~dongr2/
Licence: Do anything with it, I don't care. Public Domain. Just
remember I don't give any warrenty
*/
#include "lpc810/device.h"
unsigned long wait;
unsigned long until = 0xDEADBEEF;
const unsigned long begin = 0xDEAFBEEF;
const uint32_t test = (uint32_t) &(SWM->PINENABLE0);
typedef unsigned int volatile * vp;
void main() {
/* PIO0_2 is used by SWD, so disable it */
SWM->PINENABLE0 = 0xFFFFFFBFUL;
/* Set GPIO Direction */
GPIO0->DIR |= 1 << 2;
for(;;) {
/* Toggle the LED */
GPIO0->NOT |= 1 << 2;
wait = begin;
while (wait > until) --wait; /* WAIT */
}
}