Wireless communication¶
Learning goals: The fundamentals of wireless communication and implementation of a program utilizing wireless data transfer by using Pico SDK and course library.
Pico's wireless communication is based on Infineon's CYW43439-chip. Actually, this chip consists of two microcontrollers, both of which have their own memory and other peripherals to perform wireless data transfer and to communicate with Pico's RP2040-microcontroller. We don't need to dive deeper into the internals of this complex device, because for us it is enough to only know its properties and the libraries needed to utilize these properties. CYW43439 supports Wi-Fi-connections according to the IEEE 802.11n-standard, and Bluetooth 5.4. In practice this means that we can connect Pico to almost any modern wireless netowork without problem.
CYW43439 block diagram
Network connection¶
To utilize this outstanding chip in our program as efficiently (and as easily) as possible, we have the Pico SDK's library
cyw43_arch at our disposal. With the help of this library, we can initialize the CYW43439 and after that, we can connect to a wireless network. Below is an example, where we connect the Pico to the open panoulu-network available at university campus....
#include <pico/cyw43_arch.h>
...
void wirelessTask(void *pvParams) {
// Initializing CYW43439
if(cyw43_arch_init()) {
printf("Wireless init failed\n");
}
else {
// Enabling "Station"-mode, where we can connect to wireless networks
cyw43_arch_enable_sta_mode();
// Connecting to the open panoulu-network (no password needed)
// We try to connect for 30 seconds before printing an error message
if(cyw43_arch_wifi_connect_timeout_ms("panoulu", NULL, CYW43_AUTH_OPEN, 30*1000)) {
printf("Failed to connect\n");
}
else {
printf("Connected to panoulu\n");
}
}
...
}
...
In this example, we initialize wireless data transmission using the
cyw43_arch_init function call. If the initialization is successful, the function returns 0. If something goes wrong, the initialization function returns a non-zero error code. However, in our program, we don’t need to handle this further. In case of an error, we will print an error message and do not perform any other wireless data transfer-related actions.If the return value of the initialization function is zero, we proceed to enable the CYW43439 station mode (STA). In practice, this means we become a client device that connects to available networks. The CYW43439 could also operate in access point mode (AP), where it appears as an open network for devices in station mode (although the number of connectable devices is only to 4 due to its limited hardware resources).
Once we have activated the correct mode, we move on to the main task: establishing a connection to a wireless network. We achieve this by using the
cyw43_arch_wifi_connect_timeout_ms function, which takes parameters such as the network name, password, authentication method, and the time we are willing to wait for the connection. In this case, our access point is an open network that doesn't require a password. Therefore, we set the password value to NULL and the authentication method to CYW43_AUTH_OPEN. When connecting to other networks, the situation may differ, so it is essential to set these parameters correctly for successful connection. Another possible security method is WPA2-PSK, commonly used in home networks (constant value: CYW43_AUTH_WPA2_AES_PSK). More information about connecting to wireless networks is available in Pico SDK's documentation, which is worth a look.For connecting to a wireless network, additional functions are available in the
cyw43_arch.h library (as documented). One of the most useful for us might be cyw43_arch_wifi_connect_blocking, which connects Pico to the Wi-Fi network similarly to the previously used function. However, in this case, the function call is made without a waiting time, continuing until the connection succeeds or an authentication error occurs (e.g., incorrect password). Using this function in a task means that no network-related actions are performed until we know for sure that the connection has been established or is impossible with the current information. For your project, you can choose the most suitable function from the ones presented or explore other functions in the documentation.Now that we have a network connection, what can we do with it? The answer is: quite a lot! Through the wireless network, we can directly communicate with a server from Pico. For example, we can send the latest sensor readings or perform many other tasks. Let’s explore one way to leverage this feature in the following section.
Pico and a server¶
On the internet, information flows between client devices and servers. This data can be practically anything - videos, images, or even sensor data. Now, that last example is relevant because it is precisely what we transmit from Pico to the server, leveraging its wireless network connection. But how does this data transfer actually happen? For the purposes of this course, we only need to know that data transfer over the network occurs using the TCP/IP protocol stack. This stack provides us with an interface and standardized message structures for transferring data between different systems. Pico's SDK also includes an implementation of the TCP/IP protocol stack, known as
lwIP (lightweight IP). Therefore, we can utilize this protocol stack and all its features by using the pico_lwip library. This is a high-level and somewhat imprecise explanation of protocol stacks and TCP/IP, as they are not the main focus of this course. For more in-depth knowledge, consider exploring the topic further in the course Introduction to Internet.In order to stablish a communication you would need to have a look to following libraries and material
- [https://www.raspberrypi.com/documentation/pico-sdk/networking.html#group_pico_lwip|pico-lwip wrapper|]: A wrapper for the LWIP library that is supported by the CYW43439.
- lwip-freertos: Functions that help integrate LWIP in FreeRTOS
HTTP Client / Server¶
You can check examples at official examples page:
//TODO: Check if Mongoose is possible.
TCP Sockets¶
You can check examples at official examples page:
//TODO: Bidirectional communication between two devices. Check if mongoose or directly lwit.
MQTT Server¶
To make using the lwIP library with all its features as straightforward as possible, the course staff has developed their own library that leverages lwIP's functions. Specifically, this library utilizes one of lwIP's (and TCP/IP's) applications: MQTT. MQTT is a widely used communication protocol in embedded systems, designed to minimize resource usage and bandwidth. In practice, MQTT enables message exchange between the server and the client device, provided that the client authenticates by for example using a username and password (other methods also exist). When devices send messages to the server, they are directed to specific topics. The server publishes the messages it receives from client devices under the relevant topics, allowing other listening devices to access them.
Although the course library simplifies many of MQTT's features, users are responsible for providing the correct server address and configuring the necessary username and password for authentication. Once the connection is established, messages can be sent and received with a single function call. Convenient, isn't it?
Next, we will look at a code example, which shows us how to use this library
#include <wireless.h>
#include <string.h>
...
// Constants for establishing a connection
#define SERVER_IP "server.server.com"
#define SERVER_PORT 8000
#define USERNAME "user"
#define PASSWORD "supersecretpassword"
...
void wirelessTask(void *pvParams) {
// Initializing the library
wireless_connect(SERVER_IP, SERVER_PORT, USERNAME, PASSWORD);
// A buffer for sensor data
char dataBuffer[10];
// Eternal life
while(true) {
...
// Take the lux value from a global variable and place it to the buffer
sprintf(dataBuffer, "%.2f", lux);
// Sending a message that contains the sensor data
if(wireless_send_message(dataBuffer, strlen(dataBuffer)) == WIRELESS_OK) {
printf("Message sent!");
}
else {
printf("Failed to send message\n");
}
...
}
}
...
In the example, we bring the wireless data transfer to our program through a library, and after that define the address and port for a server, along with a username and a password that are used to authenticate with the server.
...
#include <wireless.h>
...
#define SERVER_IP "server.server.com"
#define SERVER_PORT 8000
#define USERNAME "user"
#define PASSWORD "supersecretpassword"
...
When implementing your own program, you of course need to change these to correspond with the course server and the user account given to your group
Next, inside of the
wirelessTask before we being the functionality of the task, the library is initialized. We use the constants that were defined earlier to do this: wireless_init(SERVER_IP, SERVER_PORT, USERNAME, PASSWORD);
It is important that we remember to initialize the library in our program before we try to send any messages. This way, we can avoid many strange bugs and errors. (Well, in this case the library itself is aware of its own state, and it will refuse to send any messages before being initialized, but this is more of a general advice)
When the library is ready for use, we can move to the main part of our task, the
while-loop, where we send our messages to a server. In this example, we have the value of ambient light in a global variable lux, that we wish to send to a server. sprintf(dataBuffer, "%.2f", lux);
if(wireless_send_message(dataBuffer, strlen(dataBuffer)) == WIRELESS_OK) {
printf("Message sent!\n");
}
else {
printf("Failed to send message\n");
}
The data contained by the message is first converted into a string with the function
sprintf, after which it can be given to the library to handle. We also give the library function wireless_send_message the length of the string to send.If sending the data to the server was successful, the send function returns the constant value
WIRELESS_OK. We can react to this in our program by printing a message that informs about the success, or not react at all, in which case we don't need to check the return value of the function with the if-clause. In the example we will also print an error message, if the transmission was unsuccessful.Collaboration¶
Now we have all the bits and pieces we need to send data from Pico to a remote server, completely wireless! For this to work, we need to put together the functionalities which we use to first connect to a wireless network and then send messages to server. Fortunately this all has been made relatively easy with the help of libraries, because the things happening inside of them include many different levels of abstraction and detail, which would require their own course.
Let's look at this unified functionality with the help of an example.
...
#define SERVER_IP "server.server.com"
#define SERVER_PORT 8000
#define USERNAME "user"
#define PASSWORD "supersecretpassword"
...
void wirelessTask(void *pvParameters) {
// Initializing CYW43439
if(cyw43_arch_init()) {
printf("Wireless init failed\n");
}
else {
// Enabling "Station"-mode, where we can connect to wireless networks
cyw43_arch_enable_sta_mode();
// We are connecting to the open panoulu-network
// Trying to connect for 30 seconds before printing an error message
if(cyw43_arch_wifi_connect_timeout_ms("panoulu", NULL, CYW43_AUTH_OPEN, 30*1000)) {
printf("Failed to connect\n");
}
else {
printf("Connected to panoulu\n");
// Initializing the library
wireless_init(SERVER_IP, SERVER_PORT, USERNAME, PASSWORD);
}
}
char dataBuffer[10];
while(true) {
...
// Take the lux value from a global variable and place it to the buffer
sprintf(dataBuffer, "%.2f", lux);
// Sending a message that contains the sensor data
if(wireless_send_message(dataBuffer, strlen(dataBuffer)) == WIRELESS_OK) {
printf("Message sent!");
}
else {
printf("Failed to send message\n");
}
...
}
}
In practice, here we have combined the functionalities introduced in earlier stages. First, we connect to a Wi-Fi network, and after the connection is established, we are ready to transmit some data.
Now we have learned to use one of the many technologies based on wireless data transfer and TCP/IP, that Pico provides us with. If you became interested or want to know more about the topic, the documentation of Pico's wireless data transfer can be found here.
Just like was earlier concluded, the TCP/IP protocol stack used in Pico is called lwIP. If you want to familiarize yourself with its documentation, or want to use some other application of TCP/IP in Pico, you can explore the documentation of lwIP here