Home |  Overview |  General |  Listing |  Core |  Base |  Ports |  Read-Write |  Library |  R6RS Library |  System 0


A Scheme Interpreter for ARM Microcontrollers: Language (050)

SourceForge.net Logo
 

Overview:


The Armpit Scheme language implementation is based on the description of Scheme in the Revised^5 Report on the Algorithmic Language Scheme (r5rs), with few extensions and omissions. One notable omission is that arbitrary precision numbers are not implemented. The system includes some functions from r6rs that are useful for microcontrollers and can be optionally reduced to r3rs (to fit on the smaller microcontrollers: NXP 1343, 2103 and 2131).

This web page describes Armpit Scheme language extensions to r5rs. For this purpose, language elements are separated into 7 categories: Core, Base, Ports, Read-Write, Library, R6RS Library and System 0. The Base, Read-Write and Library categories represent most of r5rs and their conformance to the standard is presented on an other page, here. For these three categories, this web page presents only the extensions that Armpit Scheme provides (for example read/write from/to MCU registers). For the R6RS Library category, the focus of the description is also on extensions to the standard (for example, applying exact bitwise arithmetic operations to bytevectors of size 4). The library functionality of R6RS (import, export, ...) is part of Core language elements. The page organization follows the 7 categories listed above but starts, below, with some general items followed by an approach for listing the language elements included on a given MCU. These elements differ between regular MCUs and the 3 small MCUs (NXP 1343, 2104, 2131) and some elements may be included conditionally (those related to input/output on specific ports).

 

General Items:


When interacting with Armpit Scheme it may at times be necessay to interrupt the system, for example to exit from an infinite loop. The 2-key combination ctrl-c can be used (in most cases) for this purpose and returns the user to the rep.

On startup, Armpit Scheme looks (by default) for a file named "boot" on its default storage space (on-chip flash, on-board flash or an SD-card) and, if found, executes the scheme code in that file before entering the rep (if the "boot" code does end). If the boot file is absent, the system indicates so with a (FILE throw "boot") message (which is purely indicative and of no consequence to system operation). The default behavior of loading the boot file is controlled by a general purpose input pin of the MCU (attached to a button on several boards) such that the user can prevent the system from loading the boot file if its code has been found to contain errors (or if it is an infinite loop and needs modifications). The specific pin or button to actuate for this purpose is found in the source code (under mcu-specific directories, in files named *_init_io*.s, at label FlashInitCheck:).

 

Listing Language Elements:


For a regular MCU, the symbols (functions, syntax elements and ground items) known to Armpit Scheme can be listed using:

  (let* ((envs (vector-ref (_GLV) 13))
         (ne (vector-length envs)))
    (let elop ((e 1))
       (newline)
       (newline)
       (if (= e ne)
          #t
          (let* ((syms (vector-ref envs e))
                 (ns (vector-length syms)))
             (let slop ((s 0))       
               (if (= s ns)
                  (elop (+ e 1))
                  (begin
                    (write (vector-ref syms s))
                    (write-char #\ )
                    (slop (+ s 2)))))))))
On a BeagleBoard-XM, for example, this produces (with category headers added for clarity):

CORE:
-----
_winders _catch _prg _GLV _lkp _mkc _apl _dfv _alo _cns _sav _isx _ism throw gc 
_gc _err version address-of packed-data-set! unpack pack library export import 

BASE:
-----
quote lambda if set! begin unquote unquote-splicing quasiquote let-syntax letrec-syntax
syntax-rules ... _ expand match substitute define define-syntax eqv? eq? equal? number?
= < > <= >= + * - / quotient remainder modulo number->string string->number complex? 
real? rational? integer? exact? inexact? numerator denominator floor ceiling truncate 
round exp log sin cos tan asin acos atan sqrt expt make-rectangular make-polar real-part 
imag-part magnitude angle exact->inexact inexact->exact pair? cons car cdr set-car! 
set-cdr! symbol? symbol->string string->symbol char? char=? char? char<=? 
char>=? char->integer integer->char string? make-string string-length string-ref 
string-set! vector? make-vector vector-length vector-ref vector-set! procedure? apply 
call/cc call-with-current-continuation values call-with-values dynamic-wind eval 
scheme-report-environment null-environment interaction-environment input-port? 
output-port? current-input-port current-output-port open-input-file open-output-file 
close-input-port close-output-port read-char peek-char eof-object? char-ready? 
write-char unlock files sd-init 

PORTS:
------
FILE MEM UAR0 UAR1 USB SDFT I2C0 I2C1 

READ-WRITE:
-----------
read write display load parse prompt 

LIBRARY:
--------
cond case and or let let* letrec do delay _mkp else => var0 var1 var2 var3 var4
var5 var6 not boolean? zero? positive? negative? odd? even? max min abs gcd lcm
rationalize caar cadr cdar cddr caaar caadr cadar caddr cdaar cdadr cddar cdddr caaaar
caaadr caadar caaddr cadaar cadadr caddar cadddr cdaaar cdaadr cdadar cdaddr cddaar 
cddadr cdddar cddddr null? list? list length append reverse list-tail list-ref 
memv memq member assq assv assoc char-ci=? char-ci? char-ci<=? char-ci>=? 
char-alphabetic? char-numeric? char-whitespace? char-upper-case? char-lower-case? 
char-upcase char-downcase string string=? string-ci=? string? string<=? 
string>=? string-ci? string-ci<=? string-ci>=? substring string-append 
string->list list->string string-copy string-fill! vector vector->list list->vector 
vector-fill! map for-each force call-with-input-file call-with-output-file 
newline defined? link unpack-above-heap libs unpack-to-lib 

R6RS LIBRARY:
-------------
bytevector? make-bytevector bytevector-length bytevector-copy! bytevector-u8-ref 
bytevector-u8-set! bytevector-u16-native-ref bytevector-u16-native-set! 
bytevector-s32-native-ref bytevector-s32-native-set! bitwise-ior bitwise-xor 
bitwise-and bitwise-not bitwise-arithmetic-shift bitwise-if bitwise-bit-set? 
bitwise-copy-bit bitwise-bit-field bitwise-copy-bit-field register-copy-bit 
register-copy-bit-field fx=? fx>? fx=? fx<=? fxmax fxmin fx+ fx- fx* fx/

SYSTEM 0 (mcu specific):
------------------------
core_cm wkup_cm per_cm sysc VIC gio1 gio2 gio3 gio4 gio5 gio6 tmr1 tmr2 tmr3 tmr4 
tmr5 tmr6 tmr7 tmr8 tmr9 tmr10 tmr11 uar0 uar3 i2c0 i2c1 spi1 spi2 spi3 spi4 mci 
config-power config-pad pin-set-dir pin-set pin-clear pin-set? restart stop rd16 
wr16 i2c-reset i2c-read i2c-write 

R5RS functions are found under the BASE, READ-WRITE and LIBRARY headers, along with some additions. The conformance of the language under these headers to the r5rs standard is presented here The R6RS extensions are under the R6RS LIBRARY header while CORE, PORTS and SYSTEM 0 list additional system functions. The listing is similar on other MCUs (except small MCUs: NXP 1343, 2103 and 2131), apart from SYSTEM 0 that is mcu-specific, and SD card, i2c, USB, on-board file system and fixnum (r6rs) functionality which may be excluded (the on-board file system is excluded on chips or boards without flash, i.e. for Live-SD versions).

For small MCUs (NXP 1343, 2103 and 2131) the implementation symbols can be listed using (let, let* and newline are not defined on small MCUs):

  ((lambda ()
    (define (newline) (write-char (integer->char 13)))
    (define (elop envs ne e)
       (newline)
       (newline)
       (if (= e ne)
          #t
          (begin
            (slop (vector-ref envs e) (vector-length (vector-ref envs e)) 0)
            (elop envs ne (+ e 1)))))
    (define (slop syms ns s)
       (if (= s ns)
          #t
          (begin
            (write (vector-ref syms s))
            (write-char #\ )
            (slop syms ns (+ s 2)))))
    (elop (vector-ref (_GLV) 13) (vector-length (vector-ref (_GLV) 13)) 1)))

On the Tiny2131, for example, this produces (with category headers added):

CORE:
-----
_winders _catch _prg _GLV _lkp _mkc _apl _dfv _alo _cns _sav _isx _ism throw gc
_gc _err version address-of packed-data-set! unpack pack library export import fsc 

BASE:
-----
quote lambda if set! begin unquote unquote-splicing quasiquote define define-syntax 
eqv? eq? equal? number? = < > <= >= + * - / quotient remainder modulo number->string 
string->number pair? cons car cdr set-car! set-cdr! symbol? symbol->string
string->symbol char? char=? char? char<=? char>=? char->integer integer->char 
string? make-string string-length string-ref string-set! vector? make-vector 
vector-length vector-ref vector-set! procedure? apply call/cc 
call-with-current-continuation eval scheme-report-environment null-environment 
interaction-environment input-port? output-port? current-input-port 
current-output-port open-input-file open-output-file close-input-port 
close-output-port read-char peek-char eof-object? char-ready? write-char unlock 
files erase fpgw 

PORTS:
------
FILE MEM UAR0 

READ-WRITE:
-----------
read write display load parse prompt 

R6RS LIBRARY:
-------------
bytevector? make-bytevector bytevector-length bytevector-copy! bytevector-u8-ref 
bytevector-u8-set! bytevector-u16-native-ref bytevector-u16-native-set! 
bytevector-s32-native-ref bytevector-s32-native-set! bitwise-ior bitwise-xor 
bitwise-and bitwise-not bitwise-arithmetic-shift bitwise-if bitwise-bit-set? 
bitwise-copy-bit bitwise-bit-field bitwise-copy-bit-field register-copy-bit 
register-copy-bit-field 

SYSTEM 0 (mcu specific):
------------------------
sysc VIC psl0 rtc0 gio0 gio1 tmr0 tmr1 uar0 uar1 pwm0 i2c0 i2c1 spi0 spi1 adc0 adc1 
config-power config-pin pin-set-dir pin-set pin-clear pin-set? restart stop 
spi-put spi-get 

In comparison to regular MCUs, the small MCUs do not include the r5rs LIBRARY functions. The pack function in their CORE is not functional (it returns #f). Their BASE does not include macro functionality and is limited to integers (no float, complex or rationals and associated functions, like sqrt, sin, exp, ... in CORE, BASE, READ-WRITE, ...). They also have fewer pre-defined PORTS and do not include R6RS LIBRARY fixnum functions. These simplifications (and others) make the small MCU system fit in 24KB of flash (vs 50KB to 64KB for the regular version).

 

Core:


The Core language elements implement some aspects of the inner functionality of the system.

This variable stores the Armpit Scheme version number (here: 050).

Performs garbage collection and returns the number of free bytes of the heap.

This variable is used to bind the winders used by r5rs' dynamic-wind function in extended environment frames.

This is the top-level startup program (typically the rep) and can be listed with (cdddr _prg).

This function gives access to the internal global scheme vector (see implementation). It can be used to modify interrupt callbacks, unlock the file system, view the list of open files and view the user environment, among others.

These functions implement the user-level error reporting system. Throw generates an error (a list of the form '(source throw value)) and invokes _catch with this error as input. The startup program sets _catch to the continuation of a write process that preceded entry to the rep such that catching an error displays it on screen and restarts the rep.

Performs a file-system cleanup whereby all user files are copied to the bottom of file FLASH and the freed up FLASH space is erased (not available on Live-SD versions).

This function returns the address, in RAM or FLASH, of a scheme object, with an added offset (mandatory) or shifts a base-address (integer) left by 4 bits and adds the offset (mandatory) to it. The resulting 32-bit address is returned as a bytevector (little-endian). For example, (address-of + 0) gives the address of the header of the built-in (+ ...) function.

These functions allow movement of scheme objects from their current location (eg. heap) to other locations (eg. above the heap, to FLASH library space or to another MCU). The pack function copies an object and all its dependencies into a position-independent version stored in a bytevector. The unpack function performs the opposite conversion and stores the result in the heap if the destination is unspecified or null, above the heap if the destination is a positive integer (eg. 1) or into library FLASH if the destination is a negative integer (if such FLASH is available). The packed-data-set! function is used to modify the contents of a packed object prior to unpacking (used for example in the ARMSchembler).

These functions implement a subset of the R6RS library functionality (R6RS Section 7). The library form is used to define a new library that is automatically written to FLASH library space (where available) or RAM (where FLASH library space is non-existent). The import function is used to load libraries at top-level into a running system or to load libraries into other libraries when they are defined (i.e. within a (library ...) form). The export form is used within a library form to specify which symbols of a library are to be available when the library is imported (i.e. which symbols will be public). Other R6RS library functionality is not available (eg. renaming exported symbols).

These symbols are bound to entry points for internal Armpit Scheme functions that may be used by a compiler or ARMSchembler to avoid external duplication of built-in functionality.

 

Base:


These functions work as in the r5rs standard except for the addition of a potential base-address/offset pair as source or destination (both are integers). If such a pair is specified then a single byte is read from or written to the memory address given by 16*base-address+offset. For read-char, the byte is returned as a scheme character while for write-char it is obtained from the ascii code of the specified character and written to memory (use char->integer and integer->char for appropriate conversions if needed). ARM's ldrb and strb instructions are used for these bytewise operations.

This function unlocks the file system if it was inadvertently left locked by an aborted file operation.

This function initializes communication with an attached SD-card (if available). It returns #f if communication cannot be established. It must be used (once, after card insertion) before performing any file listing or file input/output operations with the SD-card.

This function is used to list user files on the default file storage unit or on the unit specified by the port-model. There are 2 possible port-models for this operation: FILE and SDFT, where SDFT is an attached SD-card (if available).

This function, used without input argument, erases all user files on the on-chip or on-board flash. The optional input argument, opt-arg, if a positive integer, specifies the start address of a single flash sector to be erased (shifted right by 4-bits, i.e. one hexadecimal digit). If a negative integer, it specifies the number of flash libraries to be erased from the system (eg. -1 to erase the most recently added library).

This function can be used to write a page of data to on-chip flash. It may be useful to repair a broken installation.

These functions operate as in r5rs, with some extensions. First, the file-ports returned by the open-*-file functions are integers and it is those same integers that are given as port identifiers to the close-*-port functions (as well as to read/write functions). A given input file can be opened simultaneously several times and each instance gets its own unique file-port number. Opening an output file that is already opened for input or output generates an error. The optional port-model in the open-*-file functions can be either FILE for on-chip or on-board flash files or SDFT (where available) for files on a FAT formatted SD-card (max of 2GB). The optional mode in close-output-port, if non-null, closes the file port but does not complete the file writing operation (it is meant to be used for file deletion whereby a file would be opened for output and then immediately closed with a non-null mode. Alternatively, it could be closed as an input-port to achieve the same effect).

These language items are used by the pattern language sub-component of Armpit Scheme and exposed at top-level. Expand is a syntax procedure that expands the macros in the expression it receives as input, without evaluating that expression (eg. (expand (and 1 2 3))). Match forms bindings between a pattern and a form and substitute substitutes these bindings into a template. An example of the usage of match is:
    (define form         '(plus 2 3 4))
    (define pattern      '(_ x ...))
    (define old-bindings '((z . 1)))
    (define literals     '(else =>))
    (define new-bindings (eval `(match ,form ,pattern ,old-bindings ,literals)))
    new-bindings         ; -> ((x 4 3 2) (_ . plus) (z . 1)) == an a-list of bindings
Subsequently, substitute can be used as follows:
    (define template '(+ z x ...))
    (define new-expr (eval `(substitute ,new-bindings ,template)))
    new-expr         ; -> (+ 1 2 3 4)
    (eval new-expr)  ; -> 10

 

Ports:


These variables are bound to port models (scheme vectors) used by Armpit Scheme to identify the base address of a port (if any) and the sub-functions used to open, close, read from and write to, or list files on, ports of that type.

 

Read-Write:


Displays the prompt on the current output port.

Converts a string into an s-expression. For example:
    (parse "(+ 2 3 4)")        ; -> (+ 2 3 4)
    (eval (parse "(+ 2 3 4)")) ; -> 9

Load works as in r5rs but allows an additional port-model parameter that can be either FILE or SDFT to load code from on-chip/on-board flash or from an attached SD-card, respectively.

The read and write functions implement the r5rs standard and provide extensions to access MCU registers (or memory addresses at large) and to communicate via an i2c interface (where available). The port, if specified, is typically a small integer (for files) or the base address of a perihperal, shifted right by 4-bits (one hexadecimal digit).

Register oriented read/write operations are specified by replacing the port with a base-address and offset that define the source/target memory location as: 16 * base-address + aligned-absolute-value-of offset. The value obtained from a register read is a scheme integer if the offset is non-negative and a bytevector if the offset is negative. The bytevector contains all 32-bits from the source location but the integer contains only the lower 30 bits (the upper 2 bits are lost when the value is tagged). Similarly, the object written to a memory location can be either an integer or a bytevector provided that the offset is set accordingly to a non-negative or negative integer. In the case of an integer, the upper 2 bits written to memory are a copy of the upper bit (2's complement sign bit) of the integer. The offset is automatically aligned (truncated) to a multiple of 4 such that an offset of -1 can be used to write a 32-bit bytevector (4 bytes) to a base-address with zero offset.

The i2c functionality is invoked either by specifying the i2c-base-address as port, or by specifying the i2c-base-address as port and an address vector as 2nd or 3rd argument to the read-write functions, followed by an optional number of bytes to receive or send. The first form is used to read-write scheme objects as an i2c slave device interacting with a remote master. The objects are automatically packed and unpacked during this transfer. The second form is used to transmit either 1 to 3 bytes of data or scheme objects via the i2c interface. The transfer is done in master mode if the i2c-remote-address-vector is non-null (or in slave mode if it is null). The transfer is performed for a limited number of data bytes (1 to 3) if that number (n) is specified, or for whole scheme objects if n is omitted or null. In the first case, the data bytes are obtained as a single scheme integer on read and specified as a single scheme integer on write (little endian, LSB sent first if I remember -- check examples or source code to make sure if needed). For master transfers, the i2c-remote-address-vector must be a scheme vector whose 1st element (index 0) is the i2c address of the remote device and whose subsequent elements specify internal registers of the remote device (typically there is just one of those and it is used only for data transfers of 1 to 3 bytes, i.e. with a value given to the optional input n). The i2c-base-address is the base address of the i2c peripheral block in the MCU, shifted right by 4-bits (i.e. by one hexadecimal digit). It can be obtained using, for example: (vector-ref I2C0 0). Note that this flexible i2c read-write functionality, which enables multiprocessing, is not implemented in all MCUs at this time.

 

Library:


This function can be used in ARMSchembled code to make a promise using internal functionality.

These symbols are used in built-in macros (let, cond, case, ...) to represent distinct variables. An example of their use can be seen in the implementaion of the and macro, viewed with: (cddr and). They can be re-used in user code.

These functions work as in r5rs except for the option to add a port-model to the list of input arguments. The port-model can be FILE or SDFT to direct file operations towards an on-chip/on-board flash file or towards a file on an attached SD-card, respectively.

This function is used to assess whether a given variable is defined in the currently applicable lexical environment, without producing a CORE throw. The following example illustrates:
    (defined? 'xyz)    ; -> #f
    (define xyz 10)
    (defined? 'xyz)    ; -> #t

The link function links ARMSchembled code to built-in functions, variables, libraries and possibly other (installed) ARMSchembled code on a given MCU. In other words, it resolves addresses and values of external objects used in ARMSchembled code bytevectors and inserts them into that code. Code may be ARMSchembled (or compiled) on one MCU and the resulting bytevector may be linked, unpacked and run on a separate MCU where built-in functions have different addresses (provided that the target objects exist on the destination MCU and that the code was ARMSchembled for the correct target type: Cortex-M3 (Thumb-2) or ARM).

This function is used to list the installed libraries, available for import, on a given system.

This function is a convenient specialization of the erase function for the case where all flash libraries are to be erased.

These functions are convenient specializations of the unpack function of the Core category for specific destinations.

 

R6RS Library:


These functions constitute the subset of the Bytevectors library of R6RS (Standard Libraries, Section 2) found in Armpit Scheme. The bytevector-s32-native-ref/set! functions are limited by the 30-bit internal representation of integers in Armpit Scheme such that the upper 2 bits of the bytevector's MSB are dropped by the -ref function and these bits are set to copies of the MSb of the input integer in the -set! function. Examples for these corner cases are:

    ap> (bytevector-s32-native-ref #vu8(0 0 0 #xc0) 0)  ; -> 0
    ap> (let ((bv #vu8(0 0 0 0)))
           (bytevector-s32-native-set! bv 0 #x20000000)
           bv)                                          ; -> #vu8(0 0 0 224)

These functions represent the subset of R6RS Exact bitwise arithmetic library functionality (R6RS Standard Libraries, Section 11.4) implemented in Armpit Scheme. They have been extended so that the inputs that are operated upon can be either exact integers or 4-byte-long bytevectors. Operations involving bytevectors return bytevectors as output, where applicable. Bytevectors and exact integers can be mixed, for example: (bitwise-ior #vu8(1 2 4 8) #xf0). Note that bytevectors are presented in little-endian format.

These functions perform operations similar to bitwise functions of equivalent names but do so on the contents of memory locations with address: 16*base-address+offset. All inputs must be exact integers. Internally, a read-modify-write process is applied to the contents of the specified memory location (or register).

These functions represent the subset of R6RS Fixnum library functionality (R6RS Standard Libraries, Section 11.2) implemented in Armpit Scheme. Their input arguments must be 2 exact integers, for example: (fx+ 3 4). They are included conditionally in the system and their purpose is to speed-up integer computations relative to generic numeric functions like +, *, max and min.

 

System 0:


The System 0 category contains symbols and functions whose definitions are specific to each MCU family and potentially vary between MCUs within a family as well. It is best to check the examples as well as the source code in the mcu_specific directories, in files named *_system_0.s to ascertain their specific uses. Some of the most commonly used items are described here.

These symbols, where available, are bound to the base address of a system control register and of the MCU's interrupt controller, shifted right by 4-bits (one hexadecmal digit).

These variables, if available, are bound to the base addresses of the corresponding peripheral registers of the MCU, shifted right by 4-bits (i.e. one hexadecimal digit). The numbering may differ between Armpit Scheme and the MCU reference and can be checked using, for example: (number->string gio1 16).

Where available, these functions are used to power-up peripherals or pin pads, configure the multiplexed functionality of pins and set the direction of gpio pins as eiher input or output.

These functions are used to set (high), clear (low) and identify the state of gpio pins.

These functions are used to start or stop a timer, where available.

Where available, these functions are used to send and receive data through an SPI interface.



Last updated January 14, 2012

bioe-hubert-at-sourceforge.net