;;; -*- Mode:Asm Mode:outline-minor-mode outline-regexp:";;;+" comment-start: "; " -*- ;;; ser3.asm ;;; Frank Sergeant frank@pygmy.utoh.org ;;; Test program for the Olimex LPC P-2378 board. ;;; Set up serial port using the fractional divider register. ;;; Echo incoming characters. ;;; Note, it appears that with the PLL turned off and with CCLK of 12 MHz ;;; and PCLK of 3 MHz, the maximum speed is 38,400 bps. To get up to ;;; 115,200 bps, set the PCLKDIVISOR to 1 so that PCLK is 12 MHz. .include "equates-lpc23xx.s" ;;; Code .code 32 .section .text .global vectors .org 0 .global _start ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Define either the symbol 'fractional' or the symbol 'no_fractional' ;;; but not both. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .equ fractional, 1 ; yes, use fractional divider register ;.equ no_fractional, 1 ; no, do not use fractional divider register ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Equates for clock calculations and serial ports ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .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) ;; We will set ALL the PCLKs to the same value for now ;; i.e. the value to divide CCLK by to get the default PCLKs ;.equ PCLKDIVISOR, 4 ; must be 1, 2, or 4 ;.equ PCLKDIVISOR, 2 ; must be 1, 2, or 4 .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) ; default PCLK for all peripherals ;; serial port speed calculations .equ BAUDRATE, 4800 ;.equ BAUDRATE, 38400 ;.equ BAUDRATE, 57600 ;.equ BAUDRATE, 115200 .ifdef fractional ;; Using 8 and 5 gives a ratio of 8 / (8+5) = 8/13 ;; thus PCLK will be scaled by a factor of 8/13ths. .equ MULVAL, 8 .equ DIVADDVAL, 5 ;; A PCLK of 12 MHz (or 6 or 3 MHz) is not very good for dividing down ;; to produce the standard serial port baud rates. We like to convert ;; PCLK into a different frequency that *does* divide down to the ;; standard baud rates. 1.843200 MHz, or a multiple of it, is perfect. ;; By prescaling PCLK with the fractional divider register, we can ;; close enough. 5/13ths of 3 MHz is 1.846154. ;; If we knew PCLK would be 3 MHz, we could hard code PRESCALEDPCLK as 1846154 ;.equ PRESCALEDPCLK, 1846154 ;; but to be more flexible, we will calculate it instead. ;; We would like to write it as the following but the math seems to exceed the ;; assembler's capability ;.equ PRESCALEDPCLKTST, (PCLK * 5) / 13 ; close to the optimal 1,843,200 Hz or a multiple ;; So, we write it as follows .equ PRESCALEDPCLK, (4 * 1846154) / PCLKDIVISOR .equ DIVISOR, PRESCALEDPCLK / (16 * BAUDRATE) .endif .ifdef no_fractional .equ DIVISOR, PCLK / (16 * BAUDRATE) .endif ;;; Vectors ; each interrupt vector runs an endless loop except for reset vectors: b _start b . b . b . b . b . b . b . .section .text _start: ldr sp, = mstk ; initialize "hardware" stack ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Clock Setup ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 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] bl feed_pll ldr r1, = PLLCON ldr r0, [r1] bic r0, r0, #1 ; clear bit 0 (PLLE) to disable PLL str r0, [r1] bl feed_pll ; 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 divisor then later we ;; can change specific ones if necessary. ldr r0, = PCLKVALUE ; divide by PCLKDIVISOR ldr r1, = PCLKSEL0 str r0, [r1] ldr r1, = PCLKSEL1 str r0, [r1] ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Serial Port Setup ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; select_uart0: ;; set pins P0.2 and P0.3 to be UART0 tx and rx ;; bits 7:6 contro RXD0 and bits 5:4 control TXD0 ldr r6, = PINSEL0 mov r0, #0x50 ; i.e. b'01010000' str r0, [r6] configure_uart0: ldr r6, = U0BASE mov r0, #0x83 ; access divisor latches, 8 bits, 1 stop, no parity strb r0, [r6, # ULCR] mov r0, # (DIVISOR / 256) ; most significant byte of divisor strb r0, [r6, # UDLM] mov r0, # (DIVISOR % 256) ; least significant byte of divisor strb r0, [r6, #UDLL] mov r0, #03 strb r0, [r6, # ULCR] ; turn off access to divisor latches but ; leave uart0 set to 8 bits, 1 stop, no parity .ifdef fractional ;; set up fractional divider register ldr r0, = ((MULVAL * 16) + DIVADDVAL) str r0, [r6, # UFDR] .endif mov r0, #1 strb r0, [r6, # UFCR] ; enable FIFOs (required) ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Send Greeting ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov r1, #'h bl tx mov r1, #'i bl tx mov r1, #0x0d ; carriage return bl tx mov r1, #0x0a ; line feed bl tx ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Main Loop -- echo incoming characters back to sender ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; echo: bl rx bl tx b echo ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Subroutines ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; tx: ;; Subroutine to send the character in r1 to the serial port (uart0) ldr r6, = U0BASE tdre: ldrb r2, [r6, # ULSR] tst r2, #0x20 ; Wait for transmit data register empty flag beq tdre ; to go true. strb r1, [r6, # UDATA] ; Write to serial port mov pc, lr rx: ;; Subroutine to read a character from the serial port (uart0). ;; This blocks until a character is ready. ldr r6, = U0BASE rdr: ldrb r1, [r6, # ULSR] tst r1, #0x01 ; Wait for receive data ready flag beq rdr ; to go true. ldrb r1, [r6, # UDATA] ; Read character from serial port into r0 mov pc, lr ; no, don't echo it, just return feed_pll: ;; PLL feed sequence ldr r1, = PLLFEED mov r0, #0xaa strb r0, [r1] mov r0, #0x55 strb r0, [r1] mov pc, lr ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; RAM ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Stacks .section .ram mstklimit: .skip 0x200 ; 512-byte hardware stack mstk: .end