Quantcast
Channel: 懒得折腾
Viewing all articles
Browse latest Browse all 764

LPC810 Cortex-M0+ Programming from Scratch

$
0
0

LPC810 Cortex-M0+ Programming from Scratch – Part 1: LED Blink

http://homepages.rpi.edu/~dongr2/

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

Part 1

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 ldrbloads 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

Part 2

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 typedefs 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:

0x1C00x024=0x19C

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 0x4000C000and 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 */
  }
}


Viewing all articles
Browse latest Browse all 764

Trending Articles