Please, check out my previous article before continuing. As we will be relying on it.
Introduction
In this article, we'll take on an interesting Raspberry Pi Pico project, aiming to turn it into an efficient temperature
measurement device. I'll guide you through the basics of sensor interfacing and data acquisition, with a special focus
on optimizing power usage. The unique aspect of this project involves periodically putting the Raspberry Pi Pico into a
sleep state to conserve power. When it wakes up, it will make several measurements and transmit data to a remote server,
repeating this cycle. Join me as we go step by step, emphasizing the importance of power efficiency in our endeavor.
Raspberry Pico
To achieve our goals, we need a Raspberry Pico W
. The "W" in Raspberry Pico products indicates the inclusion of
wireless features such as WiFi and Bluetooth.
Connect the DHT22 sensor to your Raspberry Pico. You can find the DHT22
datasheet here. Ensure that you have the required
resistor.
Note: It can be any sensor, I will use DHT22 for demonstration purposes.
Project setup
Development environment
Before you begin, make sure to set up the necessary environment variables:
-
PICO_BOARD
:pico_w
-
PICO_SDK_PATH
:<your-path>
Raspberry Pico will connect to a server via a TCP connection, so we need to set the following CMake options:
-
TCP_SERVER_IP
: Address of the server that will listen to Raspberry Pico eg.192.168.1.XX
-
TCP_PORT
: Port on the server that is open for communication eg.8008
-
WIFI_SSID
: Your WiFi network's SSID -
WIFI_PASSWORD
: Your WiFi network's password
Note: Raspberry must connect to network where such host server exists.
Prepare project
Copying the files lwipopts.h
, lwipopts_examples_common.h
and pico_extras_import.cmake
, pico_sdk_import.cmake
is
essential for configuring and importing necessary settings and dependencies. Here's a
breakdown of each file:
lwipopts.h
andlwipopts_examples_common.h
:
These files contain configuration options for the Lightweight IP (lwIP) stack, which is crucial for networking
functionality.-
pico_extras_import.cmake
andpico_sdk_import.cmake
:- These CMake scripts are necessary for importing the Pico SDK and associated extras into your project.
-
pico_sdk_import.cmake
imports the core Pico SDK, whilepico_extras_import.cmake
imports additional extras for specific functionalities.
By copying these files, you set up the foundational configurations and dependencies needed for networking and general
development on the Raspberry Pico platform.
You can find them in the Pico SDK repository. Follow these steps:
-
lwipopts.h
andlwipopts_examples_common.h
:- Navigate to the Pico Examples repository.
- Inside the repository, locate the
pico_w/wifi
directory. - Copy the
wifi/lwipopts.h
andlwipopts_examples_common.h
files from this directory to your project.
-
pico_sdk_import.cmake
:- In the Pico SDK repository, find
the
external/pico_sdk_import.cmake
file - Copy this file into your project directory.
- In the Pico SDK repository, find
the
-
pico_extras_import.cmake
:- In the Pico Extras repository, find
the
external/pico_extras_import.cmake
file - Copy this file into your project directory.
- In the Pico Extras repository, find
the
Writing code
The code is available on
my GitHub repository
Cmake
Let's dive into the code, starting with the essential CMake configuration:
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
include(pico_extras_import.cmake)
project(raspberry_pico_c_remote_sensor C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(WIFI_SSID ${WIFI_SSID} CACHE INTERNAL "WiFi SSID")
set(WIFI_PASSWORD ${WIFI_PASSWORD} CACHE INTERNAL "WiFi password")
set(TCP_SERVER_IP ${TCP_SERVER_IP} CACHE INTERNAL "TCP server ip")
set(TCP_PORT ${TCP_PORT} CACHE INTERNAL "TCP server port")
message("Raspberry Pi Pico SDK version ${PICO_SDK_VERSION_STRING}")
pico_sdk_init()
add_executable(raspberry_pico_c_remote_sensor main.c src/dht.c src/tcp_utils.c src/sleep_utils.c)
target_include_directories(raspberry_pico_c_remote_sensor PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(raspberry_pico_c_remote_sensor
pico_cyw43_arch_lwip_poll # pico_cyw43_arch_lwip_threadsafe_background
pico_stdlib
hardware_gpio
hardware_sleep
)
target_compile_definitions(raspberry_pico_c_remote_sensor PRIVATE
WIFI_SSID=\"${WIFI_SSID}\"
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
TCP_SERVER_IP=\"${TCP_SERVER_IP}\"
TCP_PORT=\"${TCP_PORT}\"
)
pico_enable_stdio_usb(raspberry_pico_c_remote_sensor 1)
pico_enable_stdio_uart(raspberry_pico_c_remote_sensor 0)
pico_add_extra_outputs(raspberry_pico_c_remote_sensor)
In a previous article, we provided a detailed description of a similar file. Here, you'll find a comparable one, with
added environment variables
and Pico Extras that adds pico_sleep
functionality.
Main
Now, let's move on to the main C file:
#include <stdlib.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "src/sleep_utils.h"
#include "src/tcp_utils.h"
#include "src/dht.h"
int main(void) {
//...
}
Here we have included:
-
<stdlib.h>
: Standard C library for general utilities -
"pico/stdlib.h"
: Pico SDK standard library header for Raspberry Pi Pico -
"pico/cyw43_arch.h"
: Header for the Wi-Fi chip used with the Raspberry Pi Pico -
"src/...""
: Our files, which we will implement in a moment
We will also need some utils methods:
/**
* Additional PIN for debugging
*/
#define LED_PIN 22
int init_led(void) {
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
return 0;
}
int init_wifi(void) {
if (cyw43_arch_init_with_country(CYW43_COUNTRY_POLAND)) {
printf("Wi-Fi failed to initialise\n");
return 1;
}
cyw43_arch_enable_sta_mode();
printf("Connecting to Wi-Fi...\n");
if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 10000)) {
printf("Failed to connect.\n");
return 1;
}
printf("Connected.\n");
return 0;
}
Here's a brief overview of the Wi-Fi Initialization Function:
-
int init_wifi(void) { /*...*/ }
: This function initializes the Wi-Fi module using the Cypress CYW4343X chip. -
cyw43_arch_init_with_country(CYW43_COUNTRY_POLAND)
: Initializes the Wi-Fi module with the specified country code (Poland in this case). - If initialization fails, an error message is printed, and the function returns 1.
-
cyw43_arch_enable_sta_mode()
: Enables the Wi-Fi station mode. - The function attempts to connect to the Wi-Fi network using the provided SSID, password, and encryption type ( WPA2 in this case) with a timeout of 10 seconds.
- If the connection fails, an error message is printed, and the function returns 1.
- If the connection is successful, a success message is printed, and the function returns 0.
int comparator(const void *p, const void *q) {
return (int)(((dht_reading*)p)->temp_celsius - ((dht_reading*)q)->temp_celsius);
}
The provided C function, named comparator
, is designed for comparing two objects of type dht_reading
based on their
temperature values in Celsius. This function will be used as a callback function for sorting an array of dht_reading
objects.
static volatile bool awake;
static void sleep_callback(void) {
awake = true;
}
int main(void) {
/* Initialize */
stdio_init_all();
init_led();
dht_init();
/* Definitions */
dht_reading measurements[5];
uint scb_orig = scb_hw->scr;
uint clock0_orig = clocks_hw->sleep_en0;
uint clock1_orig = clocks_hw->sleep_en1;
datetime_t t = {
.year = 2020,
.month = 06,
.day = 05,
.dotw = 5, // 0 is Sunday, so 5 is Friday
.hour = 15,
.min = 00,
.sec = 00
};
rtc_init();
/* Logic */
sleep_ms(2000);
measure_freqs();
sleep_ms(1000);
while (1) {
awake = false;
gpio_put(LED_PIN, 0);
if (cyw43_is_initialized(&cyw43_state)) {
cyw43_arch_deinit();
}
sleep_run_from_xosc(); // UART will be reconfigured by sleep_run_from_xosc
rtc_set_datetime(&t);
rtc_sleep(1, 0, sleep_callback);
while (!awake) {
printf("Should be sleeping\n");
}
gpio_put(LED_PIN, 1);
recover_from_sleep(scb_orig, clock0_orig, clock1_orig);
init_wifi();
for (int c = 0; c < 5; ++c) {
dht_reading data;
read_from_dht(&data);
printf("%f %f\n", data.temp_celsius, data.humidity);
measurements[c] = data;
sleep_ms(2000);
}
qsort((void*)measurements, 5, sizeof(dht_reading), comparator);
send_measurements(&measurements[2]);
}
return 0;
}
The provided C code represents the main logic for the project involving sleep cycles, sensor measurements, and wireless
communication. Here's a breakdown of the code:
-
Global Variables:
-
static volatile bool awake;
: Declares a global boolean variable to track the wake state of the system.
-
-
Sleep Callback Function:
-
static void sleep_callback
: Defines a callback function triggered when the system wakes from sleep. It sets the globalawake
variable totrue
.
-
-
Initialization:
-
stdio_init_all()
: Initializes standard I/O for printf. -
init_led()
: Initializes an LED PIN. -
dht_init()
: Initializes the DHT sensor.
-
-
Definitions:
- Defines an array
dht_reading measurements[5]
for storing sensor readings. - Initializes variables for storing original values of certain hardware registers.
- Defines an array
-
Date and Time Configuration:
- Defines a
datetime_t
structure with a specific date and time. - Initializes the RTC (Real-Time Clock) with the provided date and time.
- Defines a
-
Main Loop:
- Initiates a loop for continuous operation.
- Sets
awake
tofalse
and turns off the LED. - Checks if the Wi-Fi module is initialized and deinitializes it if necessary.
- Initiates a sleep cycle using
rtc_sleep()
and waits for the sleep callback to setawake
totrue
. - Prints a message during the sleep cycle.
- Turns on the LED and recovers from sleep.
- Initializes the Wi-Fi module.
- Reads sensor data from the DHT sensor, sorts the measurements, and sends the data wirelessly.
- Repeats the loop.
Server
This Python server script serves as a TCP server for Raspberry Pi Pico. It listens for incoming connections, receives
temperature and humidity data in a specific format from the Pico device. The server runs indefinitely, continuously
accepting and processing incoming data.
# imports
def main(config: argparse.Namespace) -> None:
buffer_size = 8
payload_size = 8 # Specific to what PICO sends
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind((config.ip, config.port))
server.listen(1)
print('Listening for client...')
try:
while True:
try:
conn, addr = server.accept()
print(f"Connected by {addr}")
while True:
data = conn.recv(buffer_size)
if len(data) == payload_size:
humidity, temperature = struct.unpack('ff', data)
print(f"Temp: {temperature}, Humidity: {humidity}")
if not data:
break
else:
conn.close()
break
except KeyboardInterrupt:
print('Exiting...')
break
finally:
server.close()
# ...
Key functionalities include:
- Database Storage: The server can save the received temperature and humidity measurements to a local SQLite database if configured.
- Continuous Operation: The script runs continuously, maintaining an open connection for data reception.
- Connection Handling: The server handles incoming connections, receiving and processing data in the expected format.
- Graceful Exit: The script can be interrupted with a keyboard interrupt (Ctrl+C), allowing for a graceful exit.
Usage
-
-p
or--port
: Specifies the port on which the server listens (default is 8008). -
--ip
: Specifies the IP address on which the server operates (default is an empty string, allowing connections from any IP). -
--db
: Specifies the location of the SQLite database for storing temperature and humidity data.
To run the server, execute the script and provide any necessary configuration options.
Testing
-
Server Setup:
- Begin by launching the Python server script on the designated host machine. Ensure that the server is running and ready to accept connections.
-
Raspberry Pico Flashing:
- Flash the Raspberry Pico with the programmed firmware using the appropriate development environment or tool.
-
Observation:
- Observe the behavior and interactions between the Raspberry Pico and the server.
- Verify that temperature and humidity data is successfully transmitted from the Pico to the server.
- Confirm any expected actions or responses, such as database storage on the server.
-
Enjoy the Results:
- Once the testing process is complete and the interactions are confirmed, you can consider the setup successful.
Summary
In summary, this article provides a comprehensive guide for creating a temperature measurement system using the
Raspberry Pi Pico. Starting from the basics of sensor interfacing and data acquisition, we progress through the
essential steps of configuring the Pico to act as a robust temperature monitor. We extend our exploration to seamlessly
forward this valuable temperature data to a remote server, adding a layer of connectivity to our Pico project. At the
end, you have a smart temperature monitoring solution, offering practical insights for real-world applications.
Top comments (0)