Internet of Things  Board Simulator

An embedded system with interrupts is presented here:

S21 with interrupt specification

Extension of S21 for IoT board

embed system

The main module contains S2.1 processor with memory.  The board has four interrupts (int0, int1, int2, int3) and four input ports (10, 11, 12, 13).  I made a set of tools for practicing with this kind of programming.  The tool consists of the compiler, the assembler and the simulator of the processor (S2.1 processor).  The language I used can be found here  (Rz language).  The interrupt program looks like this:

// simple interrupt
// a simple loop run for 10000 inst.
// when interrupt occurs, it prints out cnt


cnt        // global counter

int0()
  print(cnt," ")
  cnt = cnt + 1

main()
  cnt = 0
  while( 1 )   // wait for int
    doze()     // sleep and wait for interrupt

This program has an empty main loop. It waits for interrupt.  The interrupt service routine increments a counter and print it out.  The simulation runs for 1 second, or 1,000,000 time unit at the speed of the processor 1MHz.  The sequence of commands to run the experiment is, compile the program (simple-int.txt), assemble the assembly file, run the simulator.  The output of compiler will be an assembly language file  (simple-int-s.txt).  This file is assembled into an executable object file (simple-int-s.obj) suitable for the processor simulator. 

Here is the console session (assuming you set path to python interpreter appropriately)
C:\iot-rz\test>python rz36.py
input file: simple-int.txt
(output simple-int-s.txt)
C:\iot-rz\test>python as21.py
input file: simple-int-s.txt
(output simple-int-s.obj )
C:\iot-rz\test>python sim21.py
input file:simple-int-s.obj

load program, last address 37
int 201
<0> int 402
<1> int 603
<2> int 804
. . .
<94> int 19296
<95> int 19497
<96> int 19698
<97> int 19899
<98> stop, clock 20000, execute 1897 instructions

C:\iot-rz\test>

Currently, the simulator is set to run for 10,000 time unit.  If you comment out the "sleep and wait for interrupt", the program will run its full cycle.  Observe the amount of instruction executed.   The length of simulation can be change by changing the constant  MAXSIM  in the header file of the simulator  "s21.h".  You will need to recompile the simulator.

Multiple Interrupt

There are four interrupts, int0, int1, int2, int3.  Only one interrupt service routine (ISR) is allowed to run at a time and it runs to completion.  No nested interrupt is allowed.  (This assumption simplifies the design of the system greatly) . When two interrupts occur simultaneously only the higher priority is acknowledge, all other interrupt have to wait until the first interrupt is returned.  The priority is int0 > int1 > int2 > int3.  An interrupt can be "enable" or "disable", this masks CPU to allow/ignore the interrupt. 

Interrupt can be enable and disable by
ei(n)   // n  0,1,2,3      
di(n)   // n  0,1,2,3     


int0 is bound to timer0 (explained below)
int1 is bound to timer1 (explained below)
int2 not used
int3 not used

Timer

There are two timers, when the timer is set off, an interrupt occurs.  Timer0 is bound to int0.  Timer1 is bound to int1.  The interval of interrupt can be set by
settimer0(k)        // equivalent to  mov r1 #k, trap r1 #13
settimer1(k)        // equivalent to  mov r1 #k, trap r1 #14


Port

There are four ports available to use in the simulator
port 10  is an analog input port.  The simulator is wired to a sine wave, with the period 1000, amplitude 50.
port 11  is a "digital" input port.  It is a square wave (hence "digital") with the period 1000, amplitude 5.
port 12  is a 31-bit positive random number
port 13  is tie to the global clock.
These ports can be read by
x = readport(m)   // m 10,11,12,13

Example

This example shows how to read sine wave port and print the value out.  Using int0.

int0()
  x = readport(10)
  print(x)

main()
  while(1)
     doze()      // sleep and wait for interrupt


In this example, the default timer0 is set to 100. The sine wave table has the period 1000. The default simulation runs for 10,000 cycles.  Hence, there will be 100 interrupts. You will see ten sine waves.

The next example shows how to change the timer0 and use random number.  It also give a trick to do modulo arithmetic.  The port 12 (random number) generates a positive 31-bit integer using a very good random number generator, Mersenne Twister.

int0()
  x = readport(12)
  y = x - ((x / 256) * 256)  // modulo 256
  print(y)

main()
  settimer0(200)            // set timer0 to 200
  while(1)
     doze()   

Wave table

For analog (and also digital) port, the simulator creates "wave" table of a fixed period  (1,000).  You can visualise that the wave occurs in "real" time, that is, one sampling point per one instruction cycle.  There is a multiplier to change this period (default to 1).  The multiplier will "lenghtening" the period of the wave.  It can be changed  (also the amplitude) in the simulator header file "io.h".  The "digital" wave table can be created to be used as the input data for your application. (you need to modify "gendig()" in "io.c"). You need to recompile the simulator to change it. The analog port has the sine wave of period 1000 and amplitude +-50.  The default digital wave is a square wave of amplitude (0,5).

Example

How to use interrupt service routine to run state-based programs

When we want to run a program under ISR, we need to design it in such a way that it "run-and-stop" and later it must come back to continue where it left.  This means it needs to use "state", that is, a persistent value to keep some value that will be used in the next "run-and-stop". 

Example 1

Turn on a water pump for 10 minutes then turn it off.

Normal program

turn-on-pump
t = 0
while( t < 10 )
  delay(1 sec)
  t = t + 1
turn-off-pump

Interrupt driven

t       // global

int()
  t = t + 1
  if( t == 10 )
     turn-off-pump
     disableint()

main()
  settimer(1 sec)
  turn-on-pump
  t = 0
  while(1)
     doze()

The interrupt interval is set to one second.  The ISR will be invoke every one second.  When ISR counts to 10, it turn off the water pump and disable any further interrupt (or if you want it to repeat, you can reset t = 0).

Example 2

Frequency counter.  We measure the frequency of a signal (sine wave) by observing its "zero crossing".  Let say it changes from + to -, the first time, we record this time (t0).  Then, it changes from - to +, we record the second time (t1).  2 * (t1 - t0) is the period of this signal.  The frequency is 1/period . 

Normal program

i = 0

while( i < 2 )
  x = readwave()
  if x > 0 then
     while(1)
        x = readwave()
        if x < 0 then       // zero cross from + to -
           t0 = readclock()
           break
  else if x < 0 then
     while(1)
        x = readwave()
        if x > 0 then
           t1 = readclock()
           break
  i = i + 1          // we now have two zero cross

if t1 > t0 then
  dt = t1 - 10
else
  dt = t0 - t1
f = 1 / (2*dt)       // compute the frequency

Interrupt driven

We should not use "loop" (while 1) in an ISR.  So we break down the zero cross detection into states.  This is the state table.

state input  next-state  action
 
 1    +      2    
 2    +      2          it is + + ... stay on 2
 2    -      3          zero cross + to -
 3    -      3          it is - - ... stay on 3
 3    +      4          zero cross - to +

 1    -      5          start - 
 5    -      5          it is - - ... stay on 5
 5    +      2          goto start of +


We record zero crossing time at 2 to 3, and 3 to 4.  Here is the program

state          // global
t0, t1         // global

int()
  x = readwave()
  if state == 1
     if( x > 0 ) then state = 2
     else state = 5      
  else if state == 2
     if( x > 0 ) then state = 2
     else
         state = 3
         t0 = readclock()    // record time of zero cross + to -
  else if state == 3
     if( x < 0 ) then state = 3
     else
         state = 4
         t1 = readclock()    // record time of zero cross - to +
  else if state == 5
     if( x < 0 ) then state = 5
     else state = 2

main()
  state = 1
  while(1)
    doze()
    if state == 4 then
       dt = t1 - t0
       f = 1 / (2*dt)         // compute the frequency
       state = 1              // reset for next cycle

The interrupt interval comes as the rate of sampling the input signal. The ISR is just a state machine that run once each interrupt. The beauty of this program is that, no matter how the signal start (+ or -) it will correctly recognise that and when it reaches the terminal state (state 4) the correct record of period will be already acquired.  The main program just observe this state and compute the frequency then reset the state = 1 to continue the next cycle.

S21 extension

S21 instructions are extended to include:

int #n          software interrupt, xop 24
ei #n           enable interrupt, xop 25
di #n           disable interrupt, xop 26
reti            return from interrupt, xop 27
wfi             wait for interrupt, xop 28

#n resides in r2 field. It is 5-bit, 0..31 .  These instructions are in format-X.

int #n  acts the same as physical interrupt. When an interrupt occurs the program will jumps to the address pointed by the interrupt vector stored at M[1000]. int #0 at M[1000], int #1 at M[1001] and so on.  The return address from interrupt is stored in R[31]. So, R[31] must not be used for other purpose. reti must be used to return from ISR as it is also enable master interrupt to allow other ISR to run.

ei/di enable/disable the interrupt #n.  wfi puts CPU to sleep mode and will wake up when an interrupt occurs (which then will run ISR) then continue to run the next instruction.

sim21  trap functions to support simulation of devices (ports and timers)

0  stop
1  print int
2  print char
3  print string (array of char, terminate by 0)
4  input returns address of string
13  settimer0   (iot board)
14  settimer1   (iot board)
18  readport (iot board)
19  malloc 

RZ 3.6  with IoT board

Rz 3.6 has the following reserve words to use IoT board

asm(s)             // insert assembly string into asm source
print()            // print int and constant string "..."
printc(c)          // print char
settimer0(t)  
settimer1(t)
asm("di #n")       //  diable int n
asm("ei #n")       //  enable int n
doze()             //  sleep wait int
x = readport(k)    // read iot-board ports
x = input()        // input returns string
x = malloc(16)     // allocate 16 words from heap

Code Release (source + executable)

Rz compiler, S21 assembler, IoT board simulator   (now all in Python, run in both Windows and MacOS)

iot-rz-py-3.zip         Rz executable for Windows, as21 assembler, sim21 simulator in Python
iot-rz-py-xxx           Rz 3.6 compiler, as21 assembler, sim21 in Python (coming soon)

last update 11 Mar 2025