Sometimes it makes sense to write everything in assembly, even these days. For example if using a tiny microcontroller. Or just if one just don’t need all the productivity of the C/C++ tools. And it is a good educational experience: getting hands-on on the lower levels.

In this tutorial I’ll show how you can run an assembly only project for the NXP LPC845. We will create a C project, but then get rid of everything and create a starting point with an assembly file. For this I’m using the NXP MCUXpresso IDE with the LPC845-BRK board.

Creating project
First, create a normal C project for the target board:

Next, we can delete all the files an folders, but we keep the ‘source’ (empty) folder:

In that source folder we add a new assembly file with the .s file extension:

In this assembly file we will implement our code.
Vector Table
First, I add the vector table:
/* ----------- Vector table ------------------------------*/ .section .isr_vector, "a" .align 4 .global vector_table .global M0_NMI_Handler, M0_HardFault_Handler .global __valid_user_code_checksum /* LPC ROM Library function */ .type vector_table, %object vector_table: .word top_of_stack @ Entry 0: Initial stack pointer .word ResetISR @ Entry 0: Reset .word M0_NMI_Handler @ Entry 1: NMI .word M0_HardFault_Handler @ Entry 2: HardFault .word 0 @ Entry 3: ... .word 0 @ Entry 4: ... .word 0 @ Entry 5: ... .word __valid_user_code_checksum @ Entry 6: ... .word 0 .word 0 .word 0 .word isr_default @ SVC .word 0 .word 0 .word isr_default @ PendSV .word isr_default @ SysTick @ Chip level .word isr_default @ SPI0_IRQHandler, // 16: SPI0 interrupt .word isr_default @ SPI1_IRQHandler, // 17: SPI1 interrupt .word isr_default @ DAC0_IRQHandler, // 18: DAC0 interrupt .word isr_default @ USART0_IRQHandler, // 19: USART0 interrupt .word isr_default @ USART1_IRQHandler, // 20: USART1 interrupt .word isr_default @ USART2_IRQHandler, // 21: USART2 interrupt .word isr_default @ Reserved22_IRQHandler, // 22: Reserved interrupt .word isr_default @ I2C1_IRQHandler, // 23: I2C1 interrupt .word isr_default @ I2C0_IRQHandler, // 24: I2C0 interrupt .word isr_default @ SCT0_IRQHandler, // 25: State configurable timer interrupt .word isr_default @ MRT0_IRQHandler, // 26: Multi-rate timer interrupt .word isr_default @ CMP_CAPT_IRQHandler, // 27: Analog comparator interrupt or Capacitive Touch interrupt .word isr_default @ WDT_IRQHandler, // 28: Windowed watchdog timer interrupt .word isr_default @ BOD_IRQHandler, // 29: BOD interrupts .word isr_default @ FLASH_IRQHandler, // 30: flash interrupt .word isr_default @ WKT_IRQHandler, // 31: Self-wake-up timer interrupt .word isr_default @ ADC0_SEQA_IRQHandler, // 32: ADC0 sequence A completion. .word isr_default @ ADC0_SEQB_IRQHandler, // 33: ADC0 sequence B completion. .word isr_default @ ADC0_THCMP_IRQHandler, // 34: ADC0 threshold compare and error. .word isr_default @ ADC0_OVR_IRQHandler, // 35: ADC0 overrun .word isr_default @ DMA0_IRQHandler, // 36: DMA0 interrupt .word isr_default @ I2C2_IRQHandler, // 37: I2C2 interrupt .word isr_default @ I2C3_IRQHandler, // 38: I2C3 interrupt .word isr_default @ CTIMER0_IRQHandler, // 39: Timer interrupt .word isr_default @ PIN_INT0_IRQHandler, // 40: Pin interrupt 0 or pattern match engine slice 0 interrupt .word isr_default @ PIN_INT1_IRQHandler, // 41: Pin interrupt 1 or pattern match engine slice 1 interrupt .word isr_default @ PIN_INT2_IRQHandler, // 42: Pin interrupt 2 or pattern match engine slice 2 interrupt .word isr_default @ PIN_INT3_IRQHandler, // 43: Pin interrupt 3 or pattern match engine slice 3 interrupt .word isr_default @ PIN_INT4_IRQHandler, // 44: Pin interrupt 4 or pattern match engine slice 4 interrupt .word isr_default @ PIN_INT5_DAC1_IRQHandler, // 45: Pin interrupt 5 or pattern match engine slice 5 interrupt or DAC1 interrupt .word isr_default @ PIN_INT6_USART3_IRQHandler, // 46: Pin interrupt 6 or pattern match engine slice 6 interrupt or UART3 interrupt .word isr_default @ PIN_INT7_USART4_IRQHandler, // 47: Pin interrupt 7 or pattern match engine slice 7 interrupt or UART4 interrupt @ Set the initial stack pointer to 0x10003be8
The above table is for the LPC845 (ARM-Cortex M0+), so if you are using a different device this will need tweaking. The table defines the initial PC (Reset) and MSP (Main Stack Pointer) which is set to the end of the RAM:
/* ----------- Stack (MSP) ------------------------------*/ .section .stack .align 4 top_of_stack: .word 0x10000000+0x3fe0 @ Set the initial stack pointer to the end of the RAM
Most vectors are pointing to a isr_default one, meaing they stay in there to indicate that the interrupt is not handled (yet):
isr_default: b isr_default
Below I have defined a few interrupt placeholders:
/* ----------- Interrupt Handlers ------------------------------*/ isr_default: b isr_default M0_NMI_Handler: b M0_NMI_Handler M0_HardFault_Handler: b M0_HardFault_Handler isr_handler: @ Handle the interrupt here bx lr @ Return from the interrupt
Reset
Finally, the Reset vector or startup handling. In this simple application I call a main routine:
/* ----------- Reset ------------------------------*/ .text .global ResetISR, .section .after_vectors ResetISR: nop @ just do something.... b main
Main
Finally, the main loop of the application:
/* ----------- Main ------------------------------*/ .text .global main main: nop b main
The last thing is a LPC845 specific CRP value:
/* ------------------------------------ * Variable to store CRP value in. Will be placed automatically by the linker when "Enable Code Read Protect" selected. */ .global CRP_WORD .section .crp .align 4 .equ CRP_WORD, 0xffffffff
That’s it!
arm-none-eabi-size "LPC845_Assembly.axf"; # arm-none-eabi-objcopy -v -O binary "LPC845_Assembly.axf" "LPC845_Assembly.bin" ; # checksum -p LPC845 -d "LPC845_Assembly.bin"; text data bss dec hex filename 784 0 80 864 360 LPC845_Assembly.axf
Debugging
Debugging works the same as for any C/C++ application:

Summary
Of course, an assembly-only project is not for everyone and every project. But it is a good start for a tiny project, where you do not need any high level drivers or the productivity of C or C++. Or when you need to care about every byte of RAM and FLASH, then this is a good start.
You can find the project on GitHub.
Happy assembling 🙂
Links
- GNU Assembly examples: https://cs.lmu.edu/~ray/notes/gasexamples/
- LPC845-BRK Board: Tutorial: Blinky with the NXP LPC845-BRK Board
- Project on GitHub: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/LPC845-BRK/LPC845_Assembly