Running an Arduino sketch with a simulavr script

In order to run a sketch, the values of the input pins over time has to be specified. A simulation script allows doing it: it can set some input pins high and some low, let the sketch run for some time and then change the input pin values, possibly depending on the values of the output pins.

In a simulation script, one can:

Some of these features require a patched version of simulavr. The patch is simulavr-1.0.0.patch. Apply by moving it in the root of the simulavr source, changing to that directory, running patch -p1 < simulavr-1.0.0.patch and compiling the program as usual: configure --enable-tcl; make.

Each sketch uses a different set of pins and is meant to be run with a different kind of input. As a result, each sketch requires its own simulation script. Still better, each simulation test for each sketch requires its own simulation script, written for the sketch and including a possible way in which the input pins change over time.

The following example is for simulating the sketch Arduino.ino, which uses digital pins 13 and 12 as output and 4 as input, and transfer data over the UART. The simulation script simulate.tcl makes some changes on pin 4 and prints how and when the sketch changes the value of pin 13, then writes a character on the serial port and reads one.

The script requires libsimulavr.tcl in the current directory. Its procedures of main interest are:

setPin
change the value of an input pin
getPin
find the value of an output pin
runClock
run the simulation for the given number of microseconds

The simulation results is in the file log.txt. The following table explains what the simulation script does, and how it is reported in the log file.

tcl simulation script log explanation
#!/usr/bin/tclsh
#
# ...
source libsimulavr.tcl

# parameters

set program Arduino
set interface "netcat -l -p 7777"
set frequency 16000000
set model atmega328

# set up device, ui and clock

set dev [createDevice $model \
	$frequency applet/$program.elf]
set ui [createUi $interface]
set sc [createClock $dev $ui]
clock cycle: 62 nanoseconds
No connect to socket possible now... retry Connection refused
User Interface Connection opened by host 127.0.0.1 port 7777
create UpdateControl dummy dummy 
initialization, nothing to be changed except the name of the sketch (in this case, Arduino.ino)
# make two pins accessible 
# D4 = digital pin 4, B5 = digital pin 13

createPin D4 $dev $ui
createPin B5 $dev $ui
create Net D4 .D4 
set D4 t 0
create Net B5 .B5 
set B5 t 0

pins D4 (digital pin 4) and B5 (digital pin 13) are made accessible to the simulation script
# stop simulation whenever the sketch
# changes B4 (digital pin 12)

stopPin B4 $dev $sc
*** pinstop created
the simulation is frozen and the control is handled back to the simulation script whenever the sketch changes the value of pin B4 (digital pin 12)

this has two possible uses: as in this script, the otherwise unused digital pin 12 is configured so that digitalWrite(12, opposite_value) stops the simulation and return to this script; otherwise, configure this way all pins that require some action to be taken on the part of the simulation script (like changing some input pins in response)

# create the serial line

createRx D0 $dev $ui 115200 false
createTx D1 $dev $ui 115200 false true
create SerialTx rxD0 rxD0
create SerialRx txD1 txD1
the simulation script can read from and write to the sketch through the UART at pins D0 and D1 (digital pin 0 and 1)
# set the initial value of input pin

setPin D4 L
<<<<<< setvalue: D4 L <<<<<<
set D4 L 0
the simulation script can change the value of an input pin via the setPin command; this is logged as "setvalue: ..."; the following line "set D4 L 0" means: pin D4 (digital pin 4) changed to LOW after 0 nanoseconds of simulation; this is correct, as the simulation has not been started yet
# run for at least 2000 microseconds;
# in practice, the run terminates earlier
# because of the StopPin change

runClock $sc 2000
simulavr_port_print: 0x20
>>> simulation started
set B5 L 217868
*** pin state changed; time=222084
runClock is the command that starts the simulation and makes it run for a given number of microseconds

these lines of the log are generated by the sketch running, not by the simulation script; in particular, set B5 L 217868 means: pin B5 (digital pin 13) changed to LOW after 217868 nanosecond of simulation; the following line pin state changed; time=222084 reported the time the simulation was stopped because of change in a PinStop pin

# now pulse the input pin 4
# for 10 microseconds

setPin D4 H
runClock $sc 10
setPin D4 L
<<<<<< setvalue: D4 H <<<<<<
set D4 H 222084
<<<<<< setvalue: D4 L <<<<<<
set D4 L 232128
changing the input pins at fixed intervals is realized by running the simulation for the interval time and then changing the pin value

if using a PinStop, the command runClock $sc 10 may terminate earlier than ten microseconds; the script can retrieve the current simulation time with set c [$sc GetCurrentTime] and use it to run a cycle until the required time is reached

# run 8000 microseconds or
# until the PinStop changes

runClock $sc 8000
set B5 H 235352
set B5 L 1241054
set B5 H 2242602
set B5 L 3247994
set B5 H 4253510
set B5 L 5262870
SystemClock (request   1):    6312716
SystemClock (request   2):    6313150
>>> value of i: 0x001d
>>> setup complete
*** pin state changed; time=6362440
this particular sketch alternates B5 (digital pin 13) from low to high three times at fixed intervals; this is detected and reported in the lines set B5 ...; the number is the time when the pin changed, expressed in the number of nanoseconds since the start of the simulation

the time simulavr_time is called in the sketch is reported in the lines SystemClock ...; note that each call requires two clocks by itself, so the difference is actually (6313150 - 6312716) / 62 - 2 = 5 clocks

finally, the sketch prints the value of the variable i, writes that the setup is complete and stops the simulation by changing the value of the PinStop

# write something to the serial port,
# depending on the value of digital pin 13

set c [getPin B5 $dev]
puts "status of B5: $c"
if {$c == "H"} {
	puts "sending 0x45"
	rxD0 Send 0x45
} else {
	puts "sending 0x46"
	rxD0 Send 0x46
}
status of B5: L
sending 0x46
TX: F 
this is an example of how the value of the ouput pins can be read and used in the simulation script: depending on digital pin 13, what is written to the Arduino via the serial port is 0x45 or 0x46

the value that is written to the Arduino is logged as TX: F, since F is ascii 0x46

# this time the sketch runs for
# the full 2000 microseconds
# since it does not change the
# StopPin in loop()

runClock $sc 2000
>>> received on the serial port: 0x46
>>> sending back: 0x47
set txD1 0x47
the first two lines are written by the sketch itself via the simulavr_print function

the last is a consequence of Serial.write(c+1)

# read from the serial port

if {[txD1 Size]} {
	set r [txD1 Get]
	set c [format "0x%02X" $r]
	puts "received from the serial port: $c"
}
received from the serial port: 0x47
the simulation script can check if the Arduino has written something on the serial port via txD1 Size and read it via txD1 Get; this value can be then be printed (like in this case) or used for taking some other action

Simulavr-specific functions

Including Simulavr.h at the top of the sketch allows using some functions that are only meaningful when the sketch is run in the simulator, using a simulator script: simulavr_print, simulavr_newline and simulavr_time.

#define SIMULAVR
#define SIMULAVRTIME
#include "Simulavr.h"

Removing (or renaming) the macros SIMULAVR and SIMULAVRTIME makes the three functions no-ops. This is useful to save space and time when downloading the sketch to a real board, without having to change the rest of the code.

simulavr_print

This function has three arguments: simulavr_print(string, number, bits). It prints the string, then the number with the given number of bits and then a newline. If the number of bits is zero, only the string is printed. To print just a newline, the function simulavr_newline() can be used instead. Technically, these functions work by cycling over the string and the number and writing to a microcontroller register, so they take some time to be executed.

This function requires SIMULAVR to be defined before including Simulavr.h; see Arduino.ino as an example. Otherwise, it is a no-op.

simulavr_time

Whenever the simulated sketch calls simulavr_time(n), a line is printed in the form SystemClock (request n): time, where the time is in nanosecond since the start of the simulation. This can be used to check if a section of code is ever executed or how many times it is, and to measure the time it takes to execute it. The example Arduino.ino times the execution of an instruction:

	simulavr_time(1);
	i = TCNT0 + TCNT1;
	simulavr_time(2);

When running the simulator script, this fragment of code prints the time before and after executing the assigment.

SystemClock (request   1):    6312716
SystemClock (request   2):    6313150

The measure script calculates the difference in clock ticks: simulate.tcl | tee log; measure 1 2. This script requires the output of the simulation script to be redirected to file log.

Each call to simulavr_time takes two clocks, so the difference has to be subtracted 2 to obtain the number of clocks needed to run i = TCNT0 + TCNT1. Since the compiler may have moved the instructions around the calls to simulavr_time, this difference may still not be exact.

The simulavr_time function requires the macro SIMULAVRTIME to be defined before including Simulate.h; see Arduino.ino as an example. Otherwise, it is a no-op.