Write an Environmental Sensing Application
To start we will focus on creating a sensing application that can collect data by reading sensors.
Setup
You will need the libtock
library to provide the library functions for calling
system calls provided by the Tock kernel. We will use libtock-c
, which you can
clone:
git clone https://github.com/tock/libtock-c
Make sure you can compile an application:
cd libtock-c/examples/blink
make
Create a Hello World
Application
Create a new folder in the libtock-c/examples
folder called simsense
. Copy
the Makefile
from the blink app.
cd examples
mkdir simsense
cp blink/Makefile simsense
Now create main.c
in simsense/
and create a basic hello world application:
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
}
Background on
printf()
The code uses the standard C library routine
printf
to compose a message using a format string and print it to the console. Let's break down what the code layers are here:
printf()
is provided by the C standard library (implemented by newlib). It takes the format string and arguments, and generates an output string from them. To actually write the string to standard out,printf
calls_write
._write
(inlibtock-c
'ssys.c
) is a wrapper for actually writing to output streams (in this case, standard out a.k.a. the console). It calls the Tock-specific console writing functionputnstr
.putnstr
(inlibtock-c
'sconsole.c
) is a buffers data to be written, callsputnstr_async
, and acts as a synchronous wrapper, yielding until the operation is complete.- Finally,
putnstr_async
(inlibtock-c
'sconsole.c
) performs the actual system calls, calling toallow
,subscribe
, andcommand
to enable the kernel to access the buffer, request a callback when the write is complete, and begin the write operation respectively.The application could accomplish all of this by invoking Tock system calls directly, but using libraries makes for a much cleaner interface and allows users to not need to know the inner workings of the OS.
Loading the Application
Okay, let's build and load this simple program.
-
Erase all other applications from the development board:
tockloader erase-apps
-
Build the application and load it (Note:
tockloader install
automatically searches the current working directory and its subdirectories for Tock binaries.)make tockloader install
-
Check that it worked with a separate terminal:
tockloader listen
The output should look something like:
$ tockloader listen No device name specified. Using default "tock" Using "/dev/cu.usbserial-c098e5130012 - Hail IoT Module - TockOS" Listening for serial output. Hello, World!
Checkpoint: You can compile and run your own Hello World app.
Discovering Sensors
Now we want to go beyond printing fixed strings and sample onboard sensors. Because Tock separates apps from the kernel, an application doesn't necessarily know which sensors are available. To start, we will test for various sensors and see which are available.
Background
Tock apps use system calls to communicate with the kernel. Drivers for various kernel drivers (e.g. accessing sensors, controlling LEDs, or printing serial messages) are identified by a
DRIVER_NUM
. Apps can then callCommand
s for each driver, where commands are identified by aCOMMAND_NUM
.To aid with discovery,
COMMAND_NUM == 0
is reserved as an existence check. Userspace apps can call aCommand
syscall with theCOMMAND_NUM
of 0 and check the return value. IfSUCCESS
, that driver exists.
Check for Ambient Light Sensor
Let's start by checking if our board has an ambient light sensor. The library
interface for
ambient light
is in the libtock-c/libtock
folder.
We can use the ambient_light_exists()
function. In main.c of our simsense app:
#include <stdio.h>
#include <ambient_light.h>
int main(void) {
printf("Checking for ambient light sensor.\n");
printf("Ambient Light: ");
if (ambient_light_exists()) {
printf("Exists!\n");
} else {
printf("Does not exist.\n");
}
}
Compile and run your updated app.
Tip: To see which apps are loaded on a board, run
tockloader list
.
Checkpoint: You can check if you have an ambient light sensor. What is the result for your hardware?
Check for Additional Sensors
The next step is to check for other sensor types (you might not have a light sensor). Expand your application to check for other sensors. Some you might use:
Checkpoint: Your app now checks for the presence of several sensors. Which are available on your board?
Sampling Data from Available Sensors
Now that we know which sensors are available, we want to get data from the sensors that exist.
Use the libtock libraries to sample the sensors. For simplicity, you want to use
the functions which end in _sync
so you can avoid writing the asynchronous
code.
Print the readings to the serial console. As a starting point, consider the following code:
int take_measurement(void) {
int val;
int ret;
ret = sensor_sample_sync(&val);
if (ret == RETURNCODE_SUCCESS) {
printf("Sensor Reading: %d\n", val);
}
}
Example: Ambient Light
The interface in libtock/ambient_light.h
is used to measure ambient light
conditions in lux. imix uses the
ISL29035
sensor, but the userland library is abstracted from the details of particular
sensors. It contains the function:
#include <ambient_light.h>
int ambient_light_read_intensity_sync(int* lux);
Note that the light reading is written to the location passed as an argument, and the function returns non-zero in the case of an error.
Example: Temperature
The interface in libtock/temperature.h
is used to measure ambient temperature
in degrees Celsius, times 100. imix uses the
SI7021
sensor. It contains the function:
#include <temperature.h>
int temperature_read_sync(int* temperature);
Again, this function returns non-zero in the case of an error.
Checkpoint: Your app prints readings from all available sensors.
Take Multiple Readings
Finally to complete our sensor we want to take multiple sensor readings. Put
your sampling code in a loop. Use the delay_ms()
function to sample only
periodically.
You'll find the interface for timers in libtock/timer.h
. The function you'll
find useful today is:
#include <timer.h>
void delay_ms(uint32_t ms);
This function sleeps until the specified number of milliseconds have passed, and then returns. So we call this function "synchronous": no further code will run until the delay is complete.
An example loop structure:
int main(void) {
while (1) {
take_measurement();
delay_ms(2000);
}
}
Checkpoint: You app prints readings from each sensors multiple times.
Blink the LED on Samples
To be able to see if our device is sampling periodically without observing the console output, we will add an LED toggle on each sample. This is straightforward in Tock:
#include <led.h>
int main(void) {
while (1) {
take_measurement();
led_toggle(0);
delay_ms(2000);
}
}
Checkpoint: You have an environmental sensing application!