--- title: Sensors/Actuators using SAUL description: Learn how to use sensors and actuators with RIOT code_folder: examples/guides/saul/ --- In the previous chapter we learned how to interact with the GPIOs directly, but RIOT provides a more abstract way to interact with sensors and actuators. RIOT calls this the SAUL (Sensors/Actuators Abstraction Layer) system. The availability of sensors and actuators can vary greatly between different boards, so the SAUL system provides a way to interact with them in a uniform way, regardless of the underlying hardware. So consulting the [documentation](https://doc.riot-os.org/group__drivers__saul.html) is always a good idea. ## The Makefile First we need to include the necessary module in the `Makefile`, to do this add the following line to the `Makefile`: ```makefile USEMODULE += saul USEMODULE += saul_default USEMODULE += ztimer USEMODULE += ztimer_msec ``` ```makefile title="Makefile" {21-27} # name of your application APPLICATION = saul_example # Change this to your board if you want to build for a different board BOARD ?= arduino-feather-nrf52840-sense # This has to be the absolute path to the RIOT base directory: # If you are following the tutorial, your RIOT base directory will # most likely be something like RIOTBASE ?= $(CURDIR)/RIOT # instead of this RIOTBASE ?= $(CURDIR)/../../.. # Comment this out to disable code in RIOT that does safety checking # which is not needed in a production environment but helps in the # development process: DEVELHELP ?= 1 # This board requires a start sleep to actually catch the printed output USEMODULE += shell # Add the SAUL module to the application USEMODULE += saul USEMODULE += saul_default # Enable the milliseconds timer. USEMODULE += ztimer USEMODULE += ztimer_msec # Change this to 0 show compiler invocation lines by default: QUIET ?= 1 include $(RIOTBASE)/Makefile.include ``` ## Including the Headers Next we need to include the necessary headers in our `main.c` file. Add the following lines to the top of the file: ```c #include #include "board.h" #include "saul_reg.h" #include "ztimer.h" ``` We need: - `stdio.h` for the `printf` function, - `board.h` for the board specific configuration, - `ztimer.h` for the ztimer module so we can sleep for a while, - and `saul_reg.h` for the SAUL registry and related functions. The code should now look like this: ```c title="main.c" #include #include "board.h" #include "saul_reg.h" #include "ztimer.h" int main(void) { } ``` ## Registering a Sensor To create a SAUL registry entry RIOT provides a function called `saul_reg_find_type` which searches for the first device on our board that matches the description we provide. In this example we will register a temperature sensor, as such we need to simply tell it to search for `SAUL_SENSE_TEMP` devices. ```c /* Define our temperature sensor */ saul_reg_t *temperature_sensor = saul_reg_find_type(SAUL_SENSE_TEMP); ``` This doesn't actually guarantee that the sensor is available, which is why we also need to check if the sensor truly exists. To do this we create a simple if statement that checks whether the result of the function was `NULL` or not. ```c /* Exit if we can't find a temperature sensor */ if (!temperature_sensor) { puts("No temperature sensor found"); return 1; } else { /* * Otherwise print the name of the temperature sensor * and continue the program */ printf("Temperature sensor found: %s\n", temperature_sensor->name); } ``` The code should now look like this: ```c title="main.c" {9-27} #include #include "board.h" #include "saul_reg.h" #include "ztimer.h" int main(void) { /* We sleep for 5 seconds to allow the system to initialize */ ztimer_sleep(ZTIMER_MSEC, 5000); puts("Welcome to SAUL magic!"); /* Define our temperature sensor */ saul_reg_t *temperature_sensor = saul_reg_find_type(SAUL_SENSE_TEMP); /* Exit if we can't find a temperature sensor */ if (!temperature_sensor) { puts("No temperature sensor found"); return 1; } else { /* * Otherwise print the name of the temperature sensor * and continue the program */ printf("Temperature sensor found: %s\n", temperature_sensor->name); } } ``` Congratulations, by this point your program should be able to find a temperature sensor on your board. ## Reading the Sensor Here is where SAUL really shines, to read the sensor we simply call the `saul_reg_read` function which then stores the result in a `phydat_t` struct we provide. ```c /* We start an infinite loop to continuously read the temperature */ while (1) { /* Define a variable to store the temperature */ phydat_t temperature; /* * Read the temperature sensor * and store the result in the temperature variable * saul_reg_read returns the dimension of the data read (1 in this case) */ int dimension = saul_reg_read(temperature_sensor, &temperature); ``` Once again, since C doesn't have exceptions, we need to check if the sensor was read correctly. In this case we simply need to check if the dimension is greater than 0. ```c /* If the read was successful (1+ Dimensions), print the temperature */ if (dimension <= 0) { puts("Error reading temperature sensor"); return 1; } ``` Now all that is left is to print the temperature to the console and go to sleep. RIOT provides a simple function to solve this problem, `phydat_dump` which prints the data in a `phydat_t` struct to the console. ```c /* Dump the temperature to the console */ phydat_dump(&temperature, dimension); /* Sleep for 1 seconds */ ztimer_sleep(ZTIMER_MSEC, 1000); ``` The final code should now look like this: ```c title="main.c" {29-52} #include #include "board.h" #include "saul_reg.h" #include "ztimer.h" int main(void) { /* We sleep for 5 seconds to allow the system to initialize */ ztimer_sleep(ZTIMER_MSEC, 5000); puts("Welcome to SAUL magic!"); /* Define our temperature sensor */ saul_reg_t *temperature_sensor = saul_reg_find_type(SAUL_SENSE_TEMP); /* Exit if we can't find a temperature sensor */ if (!temperature_sensor) { puts("No temperature sensor found"); return 1; } else { /* * Otherwise print the name of the temperature sensor * and continue the program */ printf("Temperature sensor found: %s\n", temperature_sensor->name); } /* We start an infinite loop to continuously read the temperature */ while (1) { /* Define a variable to store the temperature */ phydat_t temperature; /* * Read the temperature sensor * and store the result in the temperature variable * saul_reg_read returns the dimension of the data read (1 in this case) */ int dimension = saul_reg_read(temperature_sensor, &temperature); /* If the read was successful (1+ Dimensions), print the temperature */ if (dimension <= 0) { puts("Error reading temperature sensor"); return 1; } /* Dump the temperature to the console */ phydat_dump(&temperature, dimension); /* Sleep for 1 seconds */ ztimer_sleep(ZTIMER_MSEC, 1000); } } ``` ## Building and Running the Program As always, we need to build and run our program. To do this we use the following commands: ```bash make flash ``` and then to see the output: ```bash make term ``` If everything went well, you should see the temperature being printed to the console every second like this: ```log 2024-10-14 15:31:29,610 # Data: 24.50 °C 2024-10-14 15:31:30,134 # Data: 24.50 °C 2024-10-14 15:31:31,134 # Data: 24.50 °C 2024-10-14 15:31:32,134 # Data: 24.50 °C 2024-10-14 15:31:33,135 # Data: 24.50 °C 2024-10-14 15:31:34,135 # Data: 24.50 °C 2024-10-14 15:31:35,136 # Data: 24.50 °C ``` ## Conclusion Congratulations! You have now learned how to use the SAUL system to interact with sensors and actuators in RIOT and how to read the temperature from a temperature sensor. 🎉 :::note The source code for this tutorial can be found [HERE](https://github.com/RIOT-OS/RIOT/tree/master/examples/guides/saul). If your project is not working as expected, you can compare your code with the code in this repository to see if you missed anything. :::