Home |  Intro |  Link/SYS 0 |  SD Read |  SD Write |  ARMSchembler |  Compiler |  LCD |  PS/2 Keyboard

A Scheme Interpreter for ARM Microcontrollers:
Library Examples for Snapshot 00.0241

SourceForge.net Logo


The code on this page presents examples of Armpit Scheme libraries for: 1) reading from a FAT16-formatted SD-card, 2) writing to a FAT16-formatted SD-card, 3) on-chip ARMSchembly, 4) on-chip compilation, 5) using LCDs, and 6) PS/2 keyboard interfacing. Some libraries are limited to a few boards, as described in their descriptions, but it is hoped that the provided examples are sufficient to provide an inspirational starting point for extension to other boards and MCUs.


Linker and System 0:

Most of the libraries presented on this page are dependent on two core libraries: the linker library and the system 0 library. The linker library is MCU-independent and contains utility functions to list libraries, link assembled or compiled code and to install the result in flash or in RAM (above the heap). The system 0 library is MCU-dependent and contains peripheral addresses along with functions to configure pins.

Cut-and-paste can be used to install these libraries in a running system provided its readbuffer is large enough (see bottom of ChangeLog for readbuffer sizes). For example, the LPC2000 system 0 library can be pasted into a SFE Logomatic V2.0, without the full-line comments. Once the libraries are installed, you can verify their presence in on-chip flash by using the linker's lib function:
     aps> (import (linker))

     aps> (libs)
Also, in case of a serious mis-hap, all flash libraries can be erased using:
     aps> (erase #x20000000)


SD Card Reading (FAT16):

Armpit Scheme 00.0241 makes it possible to integrate external file systems into core functionality, via libraries. The libraries presented here demonstrate this functionality for a SD Card formatted in FAT16 format (with file names in 8.3 format) and interfaced via SPI or MCI peripherals. The SD card reading subsystem consists of two libraries: 1) a MCU-dependent (board-dependent) low-level peripheral initialization and read(/write) library, and 2) a MCU-independent (except for ARM/Thumb2 differences) file-system integration library. These libraries include both scheme code and ARMSchembled code bytevectors and require both the linker and system 0 libraries:

These libraries can be cut-and-pasted into a running system however, their size, comments excluded, can reach up to 8KB. It is therefore necessary, on several MCUs, to first re-define the readbuffer to the necessary size. This is done as follows:
     aps> (gc)       ; check the amount of free heap RAM

     aps> (let ((rbf (make-bytevector 8192 0))) ; define an 8KB bytevector
            (bytevector-u8-set! rbf 0 1) ; set number of chars in buffer to 0 (scheme int)
            (vector-set! (vector-ref (_GLV) 5) 2 (unpack (pack rbf) 1))) ; update readbuffer

     aps> (gc)       ; check that there is still a good amount of free heap RAM

Once the low-level and file-system libraries have been installed, they can be listed as shown above (using the linker library). SD FAT16 reading can then be performed in 3 steps: 1) import the (sd fat16 read) library, 2) configure the SD interface, and 3) initialize the SD interface. These steps create a port model named SDRD for reading files from a FAT16-formatted SD card. The port model can be specified as the last argument in the files, open-input-file and load functions to list all files, open an input file or load a file from a SD card, respectively. The SD interface configuration should be performed only once after the library has been imported. The SD interface initialization initiates communication with the card and should be performed each time a new card is inserted. It (SD interface initialization) may hang or throw an error if communication is difficult to establish, in which case it should be repeated until successful (it sometimes helps to remove and re-insert the card).
     aps> (import (sd fat16 read))

     aps> (sdrd-config)

     aps> (sdrd-init)

     aps> (files SDRD)

     aps> (open-input-file "hello.scm" SDRD) ; if such a file exists

Note that the file listing function (files SDRD) lists file names in upper-case and is missing the dot (file name to file extension separator). The returned file list curently includes the 8.3 format representation of files with long file names (if present on the SD card). File names used in open-input-file can mix upper- and lower-case characters and are converted internally to FAT16 8.3 format (the name must be 8 characters or less, followed, optionally, by a dot and an extension of up to 3 characters). Also, on some boards, inserting an SD card causes a power drop that freezes the system and so the card has to be already inserted at power-on to avoid this situation (or reset the system after card insertion).


SD Card Writing (FAT16):

The SD Card FAT16 writing library is designed to be loaded from an SD card and therefore requires the (sd fat16 read) library described above. It further requires that the SD low level library contain a proper _spb (SD put block) function (generally not available in the "SD low level for other MCUs" file above, at this time). The library adds file writing functionality on top of the file reading capabilities provided by the sd read library.

The SD Fat16 writing library is installed as follows: 1) copy the library file onto an SD card on your development machine, 2) insert the card in your armpit scheme system and reset it, 3) import, configure and intitialize the (sd fat16 read) component as shown above, and 4) load the library from the SD card. Step 4 is performed as follows:
     aps> (load "sdrwlib.scm" SDRD)   ; for ARM

     aps> (load "sdrwlibT.scm" SDRD)  ; for Cortex-M3 (Thumb2)

The load operation does not use the readbuffer and is therefore not subject to its limitations. It may however use substantial RAM (for parsing and packing) and hence it may be important to reset the MCU in step 2 to insure that such RAM is available.

Installation of the library can be verified using the libs function of the linker library as described earlier. If the wrong library has been installed (eg. ARM vs Thumb2) it can be removed with the command (erase -1) that removes the most recently added library. If everything went well, the library can be imported, configured, initialized and tested. The read-write port model defined by this library is named SDFT (for SD FAT16) and replaces the SDRD port model of the read-only library. And (oh!) the library name is (sd fat16).

     aps> (import (sd fat16))
     aps> (sd-config)

     aps> (sd-init)

     aps> (files SDFT)

A three-part test is presented here: 1) writing a file, named numbers.txt, containing integers from -16384 to 16384, 2) reading numbers.txt one item at a time and writing their square roots to a file named sqrtnum.txt, and, 3) verifying that squaring the values in sqrtnum.txt produces values within error tolerance of those found in numbers.txt:

     aps> (let ((tgt (open-output-file "numbers.txt" SDFT)))
            (if (zero? tgt)
                (let loop ((n -16384))
            	  (if (> n 16384)
          	    (close-output-port tgt)
          	      (write n tgt)
          	      (if (zero? (remainder n 512)) (write n))
          	      (loop (+ n 1)))))))

     aps> (files SDFT)     ; check for presence of NUMBERS TXT

     aps> (let ((src (open-input-file "numbers.txt" SDFT))
               (tgt (open-output-file "sqrtnum.txt" SDFT)))
           (if (or (zero? src) (zero? tgt))
               (let loop ((n 0)
         		 (v (read src)))
         	(if (eof-object? v)
         	      (close-input-port src)
         	      (close-output-port tgt))
         	      (write (sqrt v) tgt)
         	      (if (zero? (remainder n 512)) (write n))
         	      (loop (+ n 1) (read src)))))))

     aps> (files SDFT)     ; check for presence of SQRTNUM TXT

     aps> (let ((port1 (open-input-file "numbers.txt" SDFT))
                (port2 (open-input-file "sqrtnum.txt" SDFT)))
            (if (or (zero? port1) (zero? port2)) 
          	(let loop ((v1 (read port1)) (v2 (read port2)) (n 0))
          	  (if (and (eof-object? v1) (eof-object? v2))
          	      (write #t)
          	      (if (or (eof-object? v1) (eof-object? v2))
          		  (write #f)
          		  (if (> (abs (- v1 (* v2 v2))) (abs (* 1.05e-5 v1)))
          		      (write (list "difference above threshold at: " n v1 (abs (- v1 (* v2 v2)))))
          			(if (zero? (remainder n 512)) (write n))
          			(loop (read port1) (read port2) (+ n 1)))))))
          	(close-input-port port1)
          	(close-input-port port2))))



The ARMSchembler presented here updates that of prior snapshots for the renaming of functions such as logand and ash to bitwise-and and bitwise-arithmetic-shift (among others). The ARMSchembler is also repackaged as a system of libraries such that it can run from flash (this makes it possible to run it on an LPC2148, with 32KB of RAM, for example). The system of libraries has been partitioned such that it can be loaded, from SD card, into an SFE Logomatic V2.0 (ARM version) or an IDM L35 (Thumb2 version). The limitation at play is the amount of available heap RAM as loading from SD card does not use the readbuffer. Rather, in the case of loading from SD card, it is the RAM necessary to allocate a string for the (library) expression read from the file, parsing this expression, packing the result and writing it to flash that limits the maximum size of a single library (hence the splitting of the ARMSchembler into several, small, sub-libraries).

The ARMSchembler is Scheme code with one version for ARM MCUs and another for Cortex-M3 (Thumb-2) MCUs. It requires no other library, but, as it is designed to be loaded from an SD card, installation of the (sd fat16 read) or (sd fat16) library is recommended as a prerequisite. Additionally, if the assembled code calls core functions, the linker library is needed to link them in.

After copying the ARMSchembler source file to a SD card on your development system, place the card in an ArmPit Scheme system, reset it (if desired or needed) and initialize the SD sub-system as described earlier. Then, install the ARMSchembler (note: use SDRD instead of SDFT if the SD write library is not available):
     aps> (load "asarmlib.scm" SDFT)   ; for ARM, with (sd fat16)

     aps> (load "asT2_lib.scm" SDFT)  ; for Cortex-M3 (Thumb2), with (sd fat16)

The code below presents an example use of the ARMSchembler library (tested on a SFE Logomatic V2.0 -- LPC2148 MCU, and on an EVB-LM3S6965).

;; import the needed libraries
(import (as) (as const) (linker))

;; initialize the assembler

;; optional: set *inline-cons* option (default is #f)
(set! *inline-cons* #t)

;; assemble and install the atak function (assembled tak)
(define atak
    (let ((cvec
	    'var 3                ; sv1 <- x, sv2 <- y, sv3 <- z
	    '(takin               ; [internal entry]
	      (cmp sv2 sv1)       ; done?
	      (if                 ; 
	       (pl set! sv1 sv3)  ;    if so,  sv1 <- z, result
	       (pl set! pc  cnt)) ;    if so,  return
	      (save sv1 sv2 sv3 cnt) ; dts <- (x y z cnt ...)
	      (sub sv1 sv1 4)     ; sv1 <- (- x 1)
	      (call takin)        ; sv1 <- xnew = (tak sv1 sv2 sv3)
	      (snoc! sv3 sv4 dts) ; sv3 <- x,    sv4 <- (y z cnt ...)
	      (snoc! sv4 sv5 sv4) ; sv4 <- y,    sv5 <- (z cnt ...)
	      (car sv2 sv5)       ; sv2 <- z
	      (save sv1)          ; dts <- (xnew x y z cnt ...)
	      (sub sv1 sv4 4)     ; sv1 <- (- y 1)
	      (call takin)        ; sv1 <- ynew = (tak sv1 sv2 sv3)
	      (cdr sv4 dts)       ; sv4 <- (x y z cnt ...)
	      (snoc! sv2 sv4 sv4) ; sv2 <- x,    sv4 <- (y z cnt ...)
	      (snoc! sv3 sv4 sv4) ; sv3 <- y,    sv4 <- (z cnt ...)
	      (car sv4 sv4)       ; sv4 <- z
	      (save sv1)          ; dts <- (ynew xnew x y z cnt ...)
	      (sub sv1 sv4 4)     ; sv1 <- (- z 1)
	      (call takin)        ; sv1 <- znew = (tak sv1 sv2 sv3)
	      (set! sv3 sv1)      ; sv3 <- znew
	      (restore sv2 sv1)   ; sv1 <- xnew, sv2 <- ynew, dts <- (x y z cnt ...)
	      (cdddr dts dts)     ; dts <- (cnt ...)
	      (restore cnt)       ; cnt <- cnt,   dts <- (...)
	      (b takin)))))       ; jump to compute (tak sv1 sv2 sv3)
      (vector cvec '() '() *long-jumps*)))))

;; test the atak function
(atak 18 12 6)

; longer test for atak -- assembled -- 5 secs on Logomatic 2 (60MHz), 7 secs on LM3S6965 (50MHz)
; atak assembled with:
;  (set! *inline-cons* #t)
  (write (gc))
  (let loop ((n 100))
    (if (zero? n)
	  (atak 18 12 6)
	  (loop (- n 1))))))



The Compiler for ArmPit Scheme (caps) library is an update of the compiler of prior snapshots. The update has been performed along the same lines as those described for the ARMSchembler above. The compiler requires the ARMSchembler and linker libraries. The system of libraries that defines the compiler is packaged as a single file:

It is designed to be copied to an SD card and then installed on an ArmPit Scheme system using (note: use SDRD instead of SDFT if the SD write library is not available):
     aps> (load "caps.scm" SDFT)   ; all architectures

Once the compiler has been installed, it can be tested using (for example):

;; import the needed libraries
(import (caps top) (caps) (as) (as const) (linker))

;; initialize the compiler (also initializes the assembler)

;; test
(xcompile '(+ 2 3 4))

; -> ((save cnt) (lldr sv1 const-0) (call _lkp) (save sv1) (set! sv2 $null)
;     (set! sv1 17) (cons sv2 sv1 sv2) (set! sv1 13) (cons sv2 sv1 sv2) (set! sv1 9)
;     (cons sv2 sv1 sv2) (restore sv1) (restore cnt) (b _apl) const-0 (128 . 431))

;; test
(cxcompile (+ 2 3 4))

; -> ((save cnt) (lldr sv1 const-0) (call _lkp) (save sv1) (set! sv2 $null)
;     (set! sv1 17) (cons sv2 sv1 sv2) (set! sv1 13) (cons sv2 sv1 sv2) (set! sv1 9)
;     (cons sv2 sv1 sv2) (restore sv1) (restore cnt) (b _apl) const-0 (128 . 431))

;; test
(ascompile (+ 2 3 4))

; result (ARM):
; -> #vu8(223 0 0 0
;          26 0 0 235 0 16 138 229 92 64 159 229 12 192 160 225 15 16 160 225
;          23 0 0 234 20 0 0 235 0 64 138 229 15 80 160 227 17 64 160 227
;          20 0 0 235 48 0 162 232 8 80 66 226 2 0 130 227 13 64 160 227
;          15 0 0 235 48 0 162 232 8 80 66 226 2 0 130 227 9 64 160 227
;          10 0 0 235 48 0 162 232 8 80 66 226 2 0 130 227 16 4 154 232
;          2 4 154 232 6 0 0 234 175 1 128 0 4 240 31 229 0 0 0 0 4 240 31 229
;          0 0 0 0 4 240 31 229 0 0 0 0 4 240 31 229 0 0 0 0 0 0 0 0 0)

;; test
(escompile (+ 2 3 4))

; result (ARM):
; -> #(#vu8(223 0 0 0 26 0 0 235 0 16 138 229 92 64 159 229 12 192 160 225
;            15 16 160 225 23 0 0 234 20 0 0 235 0 64 138 229 15 80 160 227
;            17 64 160 227 20 0 0 235 48 0 162 232 8 80 66 226 2 0 130 227
;            13 64 160 227 15 0 0 235 48 0 162 232 8 80 66 226 2 0 130 227
;            9 64 160 227 10 0 0 235 48 0 162 232 8 80 66 226 2 0 130 227
;            16 4 154 232 2 4 154 232 6 0 0 234 175 1 128 0 4 240 31 229
;            0 0 0 0 4 240 31 229 0 0 0 0 4 240 31 229 0 0 0 0 4 240 31 229
;            0 0 0 0 0 0 0 0 0)
;       ((112 . 32769))
;       ((32769 . "+"))
;       ((144 . "_apl") (136 . "_cns") (128 . "_lkp") (120 . "_sav")))

;; test
    (+ 2 3 4))))) ; -> 9

The above tests, along with several additional tests (inlcuding many related to compliance with r5rs) are presented here:



As with the ARMSchembler and Compiler (above) code examples for the nokia-like LCD and for the LCDs on the IDM-L35 and LPC-2478-STK boards have been updated for this snapshot and packaged in the form of libraries. The code for the nokia-like LCD has further been modified to use 12-bit color mode (vs 8-bit color in prior examples). It has been tested only on a SFE Logomatic V2.0 however. The ARMSchembly source code for the IDM L35 and LPC-2478-STK are the same as that of prior snapshots. These libraries require the system 0 and linker libraries.

In similarity to the ARMSchembler and Compiler, these libraries are designed to be installed from an SD card:
     aps> (load "noki12lb.scm" SDFT)  ; for nokia-like LCD (tested on SFE Logomatic V2.0 only)

     aps> (load "idmlcdlb.scm" SDFT)  ; for IDM L35

     aps> (load "lcd_2478.scm" SDFT)  ; for LPC-2478-STK

After installation, the libraries can be imported, initialized and tested. The library names for the 3 LCDs differ but the remainder of their operation is similar, except for the different number of color bits. As with other libraries, configuration should be performed only once, but initialization may be performed multiple times. The following example illustrates (adapted from prior snapshots, for library operation):
     aps> (import (nokia lcd))   ; nokia-like
     aps> (import (idm lcd))     ; IDM L35
     aps> (import (lcd output))  ; LPC 2478 STK

     aps> ;; ------ common code ---------------

     aps> (lcd-config)     ; configure LCD pins and lcop

     aps> (lcd-init)       ; initialize the LCD

     aps> (write 100 lcop)

     aps> (display "hello" lcop)

     aps> (define ocop (current-output-port)) ; save the current output-port

     aps> (define (current-output-port) lcop) ; set default output to LCD

     aps> 100

     aps> "hello"
On the LPC-2478-STK there appears to be some interference between the DMA channel used by the SD card and the LCD display such that reading or writing from/to an SD card temporarily makes the display flicker (but does not appear to affect SD function). On the nokia-like lcd, if the screen is too dark, you can try (VOLCTR command):
     aps> (for-each wlcd '(#x81 #x12a #x103)) ; nokia-like LCD VOLCTR

     aps> (for-each wlcd '(#x81 #x12c #x103)) ; nokia-like LCD VOLCTR

The drawing example of prior snapshots is updated below (notes: 1- choose one of the three sets of color, corresponding to your lcd, 2- the lcd library needs to be imported, configured and initialized before running this example).

; TESTING LCD draw functions

; open an output file for the tests
(define p (open-output-file "init-lcd-test"))

; define colors -- 12-bit (nokia-like)
    (define color1 #x0f0)
    (define color2 #xf00)
    (define color3 #x00f)
    (define color4 #x00f)
    (define color5 #x0f0))

; define colors -- 16-bit (IDM L35)
    (define color1 #x07e0)
    (define color2 #xf800)
    (define color3 #x001f)
    (define color4 #x001f)
    (define color5 #x07e0))

; define colors -- 24-bit (LPC-2478-STK)
    (define color1 #x00ff00)
    (define color2 #xff0000)
    (define color3 #x0000ff)
    (define color4 #x0000ff)
    (define color5 #x00ff00))

; cosine
    (let loop ((x 0))
      (if (> x 129) #t
	  (pixel x (inexact->exact (round (+ 64 (* -50 (cos (/ x 10)))))) color1)
	  (loop (+ x 1)))))

; grid
    (let vlin ((x 0))
      (if (> x 128) #t
	  (fill x 2 x 130 color2)
	  (vlin (+ x 8)))))
    (let hlin ((y 2))
      (if (> y 130) #t
	  (fill 0 y 127 y color2)
	  (hlin (+ y 8)))))

; checkerboard
    (let rlop ((y 3))
      (if (> y 123) #t
	(let clop ((x 1))
	  (if (> x 121) (rlop (+ y 32))
	      (fill x y (+ x 15) (+ y 15) color3)
	      (clop (+ x 32)))))))
    (let rlop ((y 19))
      (if (> y 123) #t
	(let clop ((x 16))
	  (if (> x 121) (rlop (+ y 32))
	      (fill x y (+ x 15) (+ y 15) color3)
	      (clop (+ x 32)))))))

; moving rectangle
    (let mlop ()
      (if (char-ready?) (read-char)
	  (fill 0 60 40 70 #xe0)
	  (let rlop ((x 0))
	    (let wlop ((n 100)) (if (zero? n) #t (wlop (- n 1))))
	    (if (> x 88) #t
		(fill (+ x 41) 60 (+ x 41) 70 color4)
		(fill x 60 x 70 #x00)
		(rlop (+ x 1)))))
	  (let rlop ((x 88))
	    (let wlop ((n 100)) (if (zero? n) #t (wlop (- n 1))))
	    (if (< x 0) #t
		(fill (+ x 41) 60 (+ x 41) 70 #x00)
		(fill x 60 x 70 color5)
		(rlop (- x 1)))))

; close the file
(close-output-port p)


PS/2 Keyboard:

One of the PS/2 Keyboard examples from a prior snapshot has been updated and packaged in the form of a library: the one using a timer/counter (LPC-2478-STK). The library requires the linker and system 0 libraries, and is also meant to be used with the LCD library presented above. It is designed to be installed from an SD card. Its ARMSchembly source code is the same as in prior snapshots. The ability to change the echo port has been added (between LCD and uart).

Once installed, the PS/2 library (with LCD library) can be used as follows:
     aps> (import (lcd output))    ; import the LCD library

     aps> (lcd-config)

     aps> (lcd-init)

     aps> (import (ps2 keyboard))  ; import the PS/2 keyboard library

     aps> (ps2-config)

     aps> (ps2-init)

     aps> (ps2-set-echo! lcop)     ; set PS/2 echo port to LCD

     aps> (define ocop (current-output-port))

     aps> (define (current-output-port) lcop) ; at this point the system is standalone PS/2<->LCD

     aps> (ps2-set-echo! UAR0)     ; set PS/2 echo port back to uart

     aps> (define (current-output-port) ocop)

Last updated February 11, 2011