Home |  GPIO |  PWM |  ADC |  LCD |  Threads |  Sonar |  Clock |  I2C |  Expert |  KANREN |  Candy


A Scheme Interpreter for ARM Microcontrollers:
STR7 Program Examples

SourceForge.net Logo
 

GPIO:


The status of General Purpose Input/Output (gpio, PIO) lines of the MCU can be read and modified using Armpit Scheme's extended read and write functions. These functions read/write from/to a register when both a port and a register offset are included in their argument list. The port is an integer representing the MCU-defined base address of the IO line to read (eg. #xE0004000 for IOPORT1, from the STR711 user's manual). The register offset is also an integer and equals the offset of the register to read/write from/to relative to the port's base address (#x0C for PD, the Pin Data register). These ports and registers are defined as scheme variables at the top of the code below. They are then used in functions that: 1) identify whether a given pin is set in a given gpio port; 2) set a given pin in a given gpio port, and; 3) clear a given pin in a gpio port. These basic functions are used in a 4th function that toggles a given pin on a given gpio port. An Example of its use to toggle the green LED of the STR-H711 board (pin 8), on and off, is given. Note that, except for the first 2 lines and the last line, the code is identical to that used with other MCUs (LPC2000 and AT91SAM7).




; Armpit Scheme GPIO Example
; tested on STR-H711

; define GPIO ports and offsets
(define ioport1    #xE0004000) ; IOPORT1
(define pin-status #x0C)       ; Pin Data Register offset (PD)

; function to check the status of a pin on a gpio port
(define (is-set? port pin)
  (not (zero? (logand (read port pin-status) (ash 1 pin)))))

; function to set a pin on a gpio port
(define (set-pin port pin)
  (write (logior (ash 1 pin) (read port pin-status)) port pin-status))

; function to clear a pin on a gpio port
(define (clear-pin port pin)
  (write (logxor (ash 1 pin) (read port pin-status)) port pin-status))

; function to toggle a pin on a gpio port
(define (toggle port pin)
  (if (is-set? port pin)
      (clear-pin port pin)
      (set-pin port pin)))

; toggle MCU board's LED(s)
(toggle ioport1 8)


 

PWM:


This example illustrates how to perform Pulse-Width Modulation (PWM) using Armpit Scheme on the STR-H711. PWM is a timer function on this MCU. The example uses Timer1 for this purpose and outputs the PWM wave to pin P1.7 (i.e. T1.OCMPA). IOPORT1 is used to set P1.7 to its Alternate Function (AF) with Push-Pull output. The PWM high and total periods are set by writing to TIM1_OCAR and TIM1_OCBR. The PWM frequency is set and the function is then enabled using TIM_CR2 and TIM_CR1. The output can be visualized by connecting a LED and 150 ohm resistor between P1.7 and ground.




; Armpit Scheme PWM Example
; tested on STR-H711

; define useful ports and configure timer1 for PWM
(begin
  (define ioport1 #xE0004000) ; IOPORT1 base address
  (define timer1  #xE000A000) ; TIMER1 base address
  (write (logior (ash 1 7) (read ioport1 0)) ioport1 0) ; set alternate function Push-Pull for OCMPA (part 1)
  (write (logior (ash 1 7) (read ioport1 4)) ioport1 4) ; set alternate function Push-Pull for OCMPA (part 2)
  (write (logior (ash 1 7) (read ioport1 8)) ioport1 8) ; set alternate function Push-Pull for OCMPA (part 3)
  (write #x0200 timer1 #x08)  ; set timer1 PWM high  period count to 512  via TIMn_OCAR
  (write #x0400 timer1 #x0C)  ; set timer1 PWM total period count to 1024 via TIMn_OCBR
  (write #x77   timer1 #x18)  ; set timer1 PWM frequency to 400kHz (for 48 MHz clk) via TIMn_CR2
  (write #x8250 timer1 #x14)) ; enable timer1 PWM function with output on OCMPA Pin via TIMn_CR1

; function to set the Timer1 PWM duty cycle to a given value (0 to 1023)
(define (set-pwm value)
  (write value timer1 #x08)) ; set duty in Match register TIMn_OCAR

; set duty-cycle of Timer1 PWM (pin P1.7) to 1000/1023
(set-pwm 1000)

; set duty-cycle of Timer1 PWM (pin P1.7) to 250/1023
(set-pwm 250)


 

ADC:


Reading from the Analog to Digtal Converter (ADC) is straightforward once the peripheral has been powered (see Note below). The 12-bit ADC of the STR711 has 4 input channels (AIN0 to AIN3) connected to pins P1.0 to P1.3. IOPORT1 is used to select the appropriate Alternate Function (AF) (0/0/0) for these pins. The ADC prescaler (in ADC_CPR) is then set to obtain a sampling rate of 500 Hz per channel (4 channels sampled at 1MHz with 512 ticks per conversion). The ADC is read by waiting for a completed conversion (indicated by ADC_CSR bits 0:3) and then reading the appropriate channel (ADC_DATA1 - ADC_DATA3). The data is in the 12 MSBs of the 16 low bits of the value read and have 2's complement values from -2048 to 2047. They are converted to positive integers (0-4096) via arithmetic shifts (ash).

Note: In Armpit Scheme 00.0098, the STR711 ADC is not powered-up by default and cannot be powered up via Scheme statements. To get the ADC powered up at reset, you need to add the line below to the source code, at line 10896 (approx), just before the initialization of gpio pins, and then re-assemble and re-upload the code. The source code of future releases will be updated accordingly.


    .word	0xA0000050,	0x20		@ PRCCU PCU_BOOTCR	turn ADC on



; Armpit Scheme ADC Example
; tested on STR-H711

; define useful ports and configure AIN 0-3
(begin
  (define ioport1 #xE0004000) ; IOPORT1 base address
  (define adc     #xE0007000) ; ADC base address
  (write (logand #xFFFFFFF0 (read ioport1 0)) ioport1 0) ; set alternate function for AIN0-3 (part 1)
  (write (logand #xFFFFFFF0 (read ioport1 4)) ioport1 4) ; set alternate function for AIN0-3 (part 2)
  (write (logand #xFFFFFFF0 (read ioport1 8)) ioport1 8) ; set alternate function for AIN0-3 (part 3)
  (write #x18 adc #x30))      ; set ADC pre-scaler for 1MHz conversion via ADC_CPR

; function to read a value from an input channel in adc
(define (read-adc channel)
  (write #x00 adc #x20) ; clear prior adc conversion status via ADC_CSR
  (let loop ((status 0))
    (if (zero? (logand status (ash 1 channel))) ; check if conversion is done
	(loop (read adc #x20))                  ; if not, loop with new status read from ADC_CSR
	(+ 2048 (ash (ash (read adc (ash channel 3)) 14) -18))))) ; if so, read and return value from ADC_DATAn

; read a value from adc channel 0, i.e. AIN0 (P1.0)
(read-adc 0)


 

Serial LCD on uart1:


Not yet implemented/tested on this MCU.


 

MultiTasking:


Armpit Scheme supports multitasking by allowing its user to define a process-queue switched by the MCU's timer 0 or timer 1 interrupt callbacks. The code below examplifies. It starts by defining useful ports and register offsets, as well as timer management functions (stop, restart). It then configures timer 0 to operate with a 1 micro-second tick. An empty process queue is then defined and a function that switches tasks from this queue is installed as the timer 0 callback by writing it to timer 0 port, offset #x010000. The timer is then configured to generate interrupts every 10 ms and is started. Two utility functions are then defined: spawn and toggler. The first is used to spawn a thunk onto the process queue and the other produces a thunk that toggles a GPIO pin every time its internal counter reaches zero. The toggler uses the functions defined earlier in the GPIO example (above) which are therefore also needed for running this multitasking example. The last line of code shows how to spawn a LED toggler on the STR-H711 board. Note that, except for the first 9 lines and the last line, the code is identical to that used with other MCUs (LPC2000 and AT91SAM7).




; Armpit MultiTasking Example
; Tested on STR-H711 [requires the GPIO example, above]

; define useful ports and offsets
(define timer0 #xE0009000) ; TIMER0
(define timer-period #x08) ; TIMn_OCAR offset
(define timer-count  #x10) ; TIMn_CNTR offset (resets to FFFC)

; function to stop a timer
(define (stop timer)
  (write 0 timer #x14))     ; disable timer via TIMn_CR1

; function to restart a timer
(define (restart timer)
  (write 0 timer timer-count) ; reset timer count
  (write #x8000 timer #x14))  ; start timer via TIMn_CR1

; configure timer0
(write #x402F timer0 #x18) ; set timer0 period to 1 us (for 48 MHz clk) and generate interrupt on match via TIMn_CR2

; Initialize the process queue
(define *queue* '())

; Set timer 0 callback to switch queued thunks
(write
 (lambda ()
   (call/cc
    (lambda (resume)
      (restart timer0)
      (if (null? *queue*)
	  (resume #t)
	  ((car *queue*) (set! *queue* (append (cdr *queue*) (list resume))))))))
 timer0 #x010000)

; Start timer 0 to produce interrupts every 10 ms for task switching
(begin
  (stop timer0)
  (write 10000 timer0 timer-period)
  (restart timer0))

; read the timer count (to check proper operation, read it a few times)
(read timer0 timer-count)

; function to spawn a new thunk (task)
(define (spawn thunk)
  (stop timer0)
  (set! *queue*
	(cons
	 (lambda ()
	   (thunk)
	   ((car *queue*) (set! *queue* (cdr *queue*))))
	 *queue*))
  (restart timer0))

; function to toggle a gpio pin every "ticks" countdown ticks
(define (toggler port pin ticks)
  (lambda ()
    (let go ((n ticks))
      (if (> n 0)
	  (go (- n 1))
	  (begin
	    (toggle port pin)
	    (go ticks))))))

; spawn toggler on MCU board's LED(s)
(spawn (toggler ioport1 8 500))



 

Ultrasonic Ranger:


Armpit Scheme and the STR-H711 can measure distances using the SRF-04 Ultrasonic Range Finder from Devantech (eg. sensors section at Acroname). In the example below, Timer1 is configured to generate 20 micro-second pulses to trigger the range finder and the code then reads the echo pulse length to determine distance. Timer1 is set to operate in PWM mode with 50ms period and minimum pulse width of 1 micro-second. To initiate a reading the timer is enabled and set to produce the 20 us trigger pulse on P1.7 (TIM1_OCMPA). When the echo pulse on the input capture pin P1.4 (TIM1_ICAPA) terminates, the code stops the Timer and reads the echo time from the input capture register (TIM1_ICAR).




; Armpit Scheme Ultrasonic Ranger Example
; tested on STR-H711

; define useful ports and configure timer1
(begin
  (define ioport1 #xE0004000) ; IOPORT1 base address
  (define timer1  #xE000A000) ; TIMER1 base address
  (write (logior (ash 1 7) (read ioport1 0)) ioport1 0) ; set alternate function Push-Pull for OCMPA (part 1)
  (write (logior (ash 1 7) (read ioport1 4)) ioport1 4) ; set alternate function Push-Pull for OCMPA (part 2)
  (write (logior (ash 1 7) (read ioport1 8)) ioport1 8) ; set alternate function Push-Pull for OCMPA (part 3)
  (write (logior (ash 1 4) (read ioport1 0)) ioport1 0) ; set TTL IN for ICAPA (part 1)
  (write (logand    #xFFEF (read ioport1 4)) ioport1 4) ; set TTL IN for ICAPA (part 2)
  (write (logand    #XFFEF (read ioport1 8)) ioport1 8) ; set TTL IN for ICAPA (part 3)
  (write    47 timer1 #x18)  ; set timer1 Prescale Register for 1 micro-secs timing (at 48MHZ) via TIM1_CR2
  (write    20 timer1 #x08)  ; set timer1 pulse high period to 20 us (sonar trigger) via TIMn_OCAR
  (write 50000 timer1 #x0C)) ; set timer1 period to 50 ms between readings via TIMn_OCAR

; function to get sonar reading as round-trip echo time in micro-seconds
(define (read-sonar)
  (write   #x00 timer1 #x1C) ; clear timer status register via TIM1_SR
  (write #x8250 timer1 #x14) ; start timer1 for PWM/Capture via TIMn_CR1
  (let loop ((status 0))
    (if (zero? (logand status #x8000))  ; still waiting for echo return (ICFA bit in TIMn_SR)?
	(loop (read timer1 #x1C))       ; if so jump to loop with updated timer status (TIMn_SR)
	(begin
	  (write 0 timer1 #x14)         ; disable timer via TIMn_CR1
	  (- (read timer1 #x00) 20))))) ; return pulse length in us from TIMn_ICAR

; function to get sonar reading and return it as a distance in centimeters
(define (sonar-cm)
  (/ (read-sonar) 2 29.033))

; read the sonar
(sonar-cm)



 

Real-Time Clock:


The STR711 Real-Time Clock (RTC) is enabled by default and produces a 1 second uptime count for the MCU. The 32-bit count is split over two pseudo 16-bit registers (RTC_CNTH and RTC_CNTL) that can be shifted and or-ed to get the full count. The example below illustrates how these registers are read and implements a function named (time) to read and set the time of day in hours, minutes and seconds.




; Armpit Real-Time Clock Example
; tested on STR-H711

; define useful port
(define rtc    #xE000D000) ; RTC base address

; read MCU uptime in seconds from RTC_CNTH (#x18) and RTC_CNTL (#x1c)
(logior (ash (read rtc #x18) 16) (read rtc #x1c))

; function to read/set the time
(define time
  (lambda opt-time-list
    (if (null? opt-time-list)
	(let* ((curtim (logior (ash (read rtc #x18) 16) (read rtc #x1c)))
	       (hour (remainder (quotient  curtim 3600) 24))
	       (rema (remainder curtim 3600))
	       (minu (quotient  rema 60))
	       (seco (remainder rema 60)))
	  (list hour minu seco))
	(let ((settim
	       (+ (* 3600 (car   opt-time-list))
		  (* 60 (cadr  opt-time-list))
		  (caddr opt-time-list))))
	  (let loop ((status 0))
	    (if (zero? (logand status #x20))   ; is last rtc write complete (RTOFF)?
		(loop (read rtc #x04))         ; if not, wait with updated rtc status from RTC_CRL
		(begin
		  (write #x10 rtc #x04)        ; set rtc to configuration mode (CNF bit in RTC_CRL)
		  (write (ash settim -16) rtc #x18)       ; set high part of time in rtc (RTC_CNTH)
		  (write (logand #xffff settim) rtc #x1c) ; set low  part of time in rtc (RTC_CNTL)
		  (write #x00 rtc #x04)))))))) ; reset rtc to running mode (clear CNF bit in RTC_CRL)

; setting the time to 1:32:40am
(time 1 32 40)

; reading the time
(time)


 

I2C Communication:


Armpit Scheme allows I2C communication as master and slave on the STR711. The default MCU address is 100 and can be modified. I2C communication typically requires at least two devices (one master, one slave) and is examplified in the two code examples below (one for each mode).

The first example illustrates the configuration and use of the I2C interface in master mode. Once configured, the (write ...) function can be used to send data or expressions to connected I2C slaves and (read ...) can be used to retrieve data from slaves. The I2C1 interface is used because on the STR-H711 board one I2C0 line is connected to a 4MHz clock rather than free.

The second example illustrates I2C slave mode where the MCU receives scheme expressions form the I2C interface, evaluates them and writes the result back to the Master via I2C. The code first configures and enables the I2C1 peripheral block and then sets the Slave device's address to 20 using the statement: (write 40 i2c #x10) in which 40 represents twice the desired address and #x10 is the offset of the "own address" register (OAR1) in the I2C block. An infinite loop is then entered that continuously reads the I2C channel (read as slave) evaluates the expresion received in the interaction-environment and writes the result back into the I2C buffer (write as slave) where it can be read by the Master device. This program is stored into the MCU's FLASH as a scheme startup program (i.e. in a file named "boot") so that it starts automatically when the MCU is reset.




; Armpit Scheme I2C Communication Example 1: Master Mode
; Tested on STR-H711

; configuration and initialization of i2c1 -- I1.SCL on Pin 0.2, SDA on Pin 0.3
; (i2c0 apparently has a 4MHZ clock attached to it on this board (on ext2-10) and is not used here)
; the code keeps 100 (default) as the MCU's I2C address
(begin
  (define i2c     #xC0002000) ; I2C1    port base address
  (define ioport0 #xE0003000) ; IOPORT0
  (write (logand #xFFFFFFF3 (read ioport0 0)) ioport0 0) ; set alternate function for i2c1 (part 1)
  (write (logior #x0000000C (read ioport0 4)) ioport0 4) ; set alternate function for i2c1 (part 2)
  (write (logior #x0000000C (read ioport0 8)) ioport0 8) ; set alternate function for i2c1 (part 3)
  (write #x00 i2c #x00)  ; clear i2c CR
  (write #x80 i2c #x14)  ; set frequency bits for 40-53MHz in OAR2
  (write #xA5 i2c #x0C)  ; set fast mode at 400kHz in CCR (+ #x80 #x25)
  (write #x20 i2c #x00)  ; enable i2c peripheral in CR (part 1)
  (write #x25 i2c #x00)) ; enable i2c ack and interrupts in CR (part 2)

; send an integer to the Slave device at i2c address 20
(write 123 i2c '#(20))

; read the data back from the Slave device at i2c address 20
(read i2c '#(20))




; Armpit Scheme I2C Communication Example 2: Slave Mode
; Tested on STR-H711

; write i2c start-up code to FLASH boot file
; the code sets the MCU's I2C address to 20
(let ((port (open-output-file "boot")))
  (if (zero? port) #f
      (begin
	(define current-output-port (lambda () port))
	(write 
	 '(begin
            (write 40 i2c #x10)
	    (define i2c     #xC0002000)
	    (define ioport0 #xE0003000)
	    (write (logand #xFFFFFFF3 (read ioport0 0)) ioport0 0)
	    (write (logior #x0000000C (read ioport0 4)) ioport0 4)
	    (write (logior #x0000000C (read ioport0 8)) ioport0 8)
	    (write #x00 i2c #x00)
	    (write #x80 i2c #x14)
	    (write #xA5 i2c #x0C)
	    (write #x20 i2c #x00)
	    (write #x25 i2c #x00)))
	(write
	 '(let rep ()
	    (write (eval (read) (interaction-environment)))
	    (rep)))
	(close-output-port (current-output-port)))))



 

Expert System:


The Expert System example for this MCU is the same as that for other MCUs and is provided here .


 

miniKANREN:


Sample modifications for Mini-Kanren are the same as for other MCUs and are provided here .


 

Brain-Candy:


The Y-combinator example is the same as for other MCUs and is presented here .



Last updated October 9, 2007

bioe-hubert-at-sourceforge.net