;;; -*- Mode:Asm Mode:outline-minor-mode outline-regexp:";;;+" comment-start: "; " -*- ;;; ser1.asm ;;; Frank Sergeant frank@pygmy.utoh.org ;;; Test program for the Olimex LPC P-2378 board. ;;; Transmit a character from the serial port ;; Transmit a "Q" out the serial port every time the ;; STAT LED (connected to P1.19) turns on or off. ;; This is much like led4.asm in that we set up the PLL ;; (by making sure it is off), select the clock source, ;; set various clock dividers, etc., and blink the ;; LED. ;; However, the main purpose of this program is to set ;; up UART0 (the first serial port). ;; Do not set up stacks (therefore no subroutines or interrupts). ;; Set the GPIOM bit, so we can use the Fast GPIO. .include "equates-lpc23xx.s" .include "olimex-lpc2378-equates.s" ;;; Timing related equates ;; frequency is given in number of clocks per second ;; Unlike the LPC2106 and others, the LPC2378 has multiple ;; PCLKs. For now, we will set all the PCLKs to the same ;; value. .equ PLLCLKIN, 12000000 ; Main crystal clock frequency .equ PLL_MULTIPLIER, 1 ; Multiplier must be 1 since we turn off PLL .equ PLLCLK, (PLLCLKIN * PLL_MULTIPLIER) .equ CPUDIVISOR, 1 .equ CCLK, (PLLCLK / CPUDIVISOR) ;; Set ALL the PCLKs to the same value for now .equ PCLKDIVISOR, 1 ; must be 1, 2, or 4 .if PCLKDIVISOR == 1 .equ PCLKVALUE, 0x55555555 ; divide by 1 .endif .if PCLKDIVISOR == 2 .equ PCLKVALUE, 0xAAAAAAAA ; divide by 2 .endif .if PCLKDIVISOR == 4 .equ PCLKVALUE, 0x00000000 ; divide by 4 .endif .equ PCLK, (CCLK / PCLKDIVISOR) .equ PCLK_TIMER0_DIVISOR, PCLKDIVISOR .equ TIMER0_PRESCALE_DIVISOR, 1 .equ TIMER0FREQ, ((CCLK / PCLK_TIMER0_DIVISOR) / TIMER0_PRESCALE_DIVISOR) .equ SECONDS, 1 ; let's have the LED on for 1 second ; then off for 1 second .equ LEDDELAY, (SECONDS * TIMER0FREQ) ;; serial port speed calculations .equ BAUDRATE, 4800 ;;; Code .code 32 .section .text .global vectors .org 0 .global _start ;;; Vectors ; each interrupt vector runs an endless loop except for reset vectors: b _start b . b . b . b . b . b . b . .section .text _start: ;;;; Clock ;; disconnect and disable the PLL ; clear the 2 ls bits of PLLCON to prepare to disable and ; disconnect the PLL. bit1=PLLC, bit0=PLLE. However, ; the manual, p. 44, shows disconnecting with one feed sequence ; then disabling with another feed sequence, so that's what ; we will do here. ldr r1, = PLLCON ldr r0, [r1] bic r0, r0, #2 ; clear bit 1 (PLLC) to disconnect PLL str r0, [r1] ;; PLL feed sequence ldr r1, = PLLFEED mov r0, #0xaa strb r0, [r1] mov r0, #0x55 strb r0, [r1] ldr r1, = PLLCON ldr r0, [r1] bic r0, r0, #1 ; clear bit 0 (PLLE) to disable PLL str r0, [r1] ;; PLL feed sequence ldr r1, = PLLFEED mov r0, #0xaa strb r0, [r1] mov r0, #0x55 strb r0, [r1] ; At this point, the PLL should be off and disconnected and ; the selected clock source should be providing the clock to the ; CPU, bypassing the PLL. This clock source should be the IRC ; (4 MHz), providing the CPU clock divider is set to 1. ; Set the CPU clock divider so it divides by CPUDIVISOR. ; (Set the value of the CCLKCFG reg to 1 less than the ; desired divider.) ldr r1, = CCLKCFG mov r0, # (CPUDIVISOR - 1) strb r0, [r1] ; Turn on the main clock, wait for it to stabilize, then ; switch from the IRC clock to the main clock. ldr r1, = SCS ldr r0, [r1] bic r0, r0, #0x10 ; clear bit 4 to select 1-20MHz OSRANGE str r0, [r1] orr r0, r0, #0x20 ; set bit 5 OSCEN to start main oscillator str r0, [r1] oscstab: ; wait for main oscillator to stabilize by reading bit 6 (OSCSTAT) ; until it goes to 1. ldr r0, [r1] tst r0, #0x40 ; Wait for OSCSTAT beq oscstab ; to go true. chgclk: ; for LPC2378, now that the main oscillator is stable, ; switch from the IRC clock to the main clock ldr r1, = CLKSRCSEL mov r0, #1 strb r0, [r1] pclksetup: ;; Rather than one PCLK there are PCLKs for lots of peripherals. ;; Let's set *all* of them to the same value. ldr r0, = PCLKVALUE ; divide by PCLKDIVISOR ldr r1, = PCLKSEL0 str r0, [r1] ldr r1, = PCLKSEL1 str r0, [r1] ;;;; Ports and Pins ;; Set the GPIOM bit so we can use the Fast GPIO. ;; (The bootloader might have set it already, but we do it here ;; to make sure.) ;; select fast GPIO mode for ports 0 and 1 ldr r6, = SCS ldr r0, [r6] orr r0, r0, #1 ; set bit zero (GPIOM) to force fast GPIO mode str r0, [r6] ;; Clear all the mask bits so that no pins are masked ldr r6, = FIO0MASK mov r0, #0 str r0, [r6] ; FIO0MASK str r0, [r6, #0x20] ; FIO1MASK str r0, [r6, #0x40] ; FIO2MASK str r0, [r6, #0x60] ; FIO3MASK str r0, [r6, #0x80] ; FIO4MASK ;; set P1.19 as a GPIO pin ;; P1.19 is controlled by bits 7:6 of PINSEL3 ldr r6, = PINSEL3 ldr r0, [r6] bic r0, r0, # 0xc0 ; clear bits 7:6 to force GPIO mode str r0, [r6] ;; set LED output pin (i.e. P1.19) as an output ldr r6, = FIO1DIR ; for PORT1 mov r0, # STAT_LED_MASK ; all inputs except for pin 19 str r0, [r6] ;; set P0.2 and P0.3 as UART0 rather than GPIO pin ;; PINSEL0 bits 5:4 and 7:6 ;; ...01010000 = 0x50 ldr r6, = PINSEL0 mov r0, #0x50 str r0, [r6] ;;;; Timer Setup ;; Use timer0 to delay between LED blinks ;; With a prescale value of 0, the counter increments on ;; every rising edge of PCLK. ldr r6, = T0CTCR ; Put timer0 into timer (not counter) mode, mov r0, #0 ; almost certainly not needed as strb r0, [r6] ; bootloader probably did not disturb it. ldr r6, = T0TCR ; start timer0 mov r0, #1 strb r0, [r6] ldr r6, = T0PR ; set timer0 prescaler mov r0, #0 ; start with no prescaling str r0, [r6] ldr r6, = T0MCR ; Reset the counter when it mov r0, #3 ; matches the match register value str r0, [r6] ; and also generate an interrupt. ; That is, set the interrupt bit ; for it in the timer interrupt register ; but this does to cause an actual interrupt ; because we have not turned it on in ; the VIC. ldr r6, = T0MR0 ; set the counter match value ldr r0, = LEDDELAY str r0, [r6] ;;;; Serial Port Setup ;; PORT0 pins 2 and 3 are the UART0 transmit and receive pins. ;; See the Ports and Pins section above. ldr r6, = U0BASE setdiv: ;; Set the divisor registers that determine the baudrate .equ DIVISOR, ((PCLK / 16) / BAUDRATE) ; Write to U0LCR to set DLAB bit and then set baud rate, etc. mov r1, #0x83 ; access divisor latches, 8 bits, 1 stop, no parity strb r1, [r6, #ULCR] mov r1, # (DIVISOR / 256) ; most significant byte of divisor strb r1, [r6, #UDLM] mov r1, # (DIVISOR % 256) ; least significant byte of divisor strb r1, [r6, #UDLL] mov r1, #03 ; turn off access to divisor latches but strb r1, [r6, #ULCR] ; leave uart0 set to 8 bits, 1 stop, no parity enfifo: mov r0, #1 ; enable FIFOs (required) strb r0, [r6, #UFCR] ; must set bit0 for uart to work at all, apparently mov r0, #0x80 ; it should not be necessary to write a 1 to the TXEN strb r0, [r6, #UTER] ; flag in the Transmit Enable Register, but just in case ... ;;;; Blink the LED forever ldr r5, = FIO1PIN ;; r6 still contains U0BASE ldr r4, = T0IR ; bit 0 goes high when timer count matches ; the value in match register 0 loop1: eor r0, r0, # STAT_LED_MASK ; flip the bit controlling the LED str r0, [r5] ; set or clear P1.19, turning LED on or off 2: ldr r1, [r4] ; read timer interrupt register ands r1, r1, #1 ; set the zero flag if bit 0 is not high yet beq 2b ; loop until bit goes high str r1, [r4] ; write back the 1 we just read from the interrupt ; register to clear the bit. tdre: ldrb r2, [r6, #ULSR] tst r2, #0x20 ; Wait for transmit data register empty flag beq tdre ; to be true. mov r1, #'Q' ; send a Q every time we blink the LED strb r1, [r6, #UDATA] ; Write to serial port b loop1 ; continue forever