Using WiFi in the Raspberry Pi Pico (Part 2)
As I mentioned before, I decided to start my study of the Pi Picos's C/C++ SDK function for WiFi communication implementing an SNTP client. This just an exercise, as it is also available in both the lwIP library and the SDK examples. So we start by looking how it is done in the SDK example (code here).
The Pico example output |
My first read of the code was not encouraging. To understand it we will examine it a part at a time. I am assuming you know the basics of TCP/IP and recommend you read my comments alongside the source code.
The first block to analyze is the the main() function. It contains calls to the following cyw43_arch functions:
- cyw43_arch_init() - initializes the WiFi communication librarties
- cyw43_arch_enable_sta_mode() - puts the WiFi in "station" mode, as we are going to connect to an WiFi AP
- cyw43_arch_wifi_connect_timeout_ms() - try to connect to the AP
- cyw43_arch_deinit() - ends the use of the WiFi communication librarties
All these functions are documented in the SDK manual.
So far, so good. Let's look now at run_ntp_test(), where we find the main loop. The first step of this routine is to call ntp_init(). The program is built around an structure (NTP_T) that holds all its state. ntp_init() allocates this structure and call two functions from the lwIP library (documented here):
- udp_new_ip_type() - this routine returns a pointer to a udp_pcb structure. "pcb" stands for Protocol Control Block, the pcbs in lwIP contain the needed information to control the communication for the various protocols.
- udp_recv() - Here we define the routine that will be called when a UDP packet is received. As we did not specify a protocol port in the pcb, this routine will be called when data is received at any port. We also define that this routine will receive the state as its parameter.
These functions are parte of the raw lwIP API. lwIP has others, higher level, APIs but they can be used only along with an RTOS (Real Time Operating System). The raw API works mainly through callbacks (don't call me, I will call you when there are news).
Going back to the main loop (and ignoring the error treatments), the following functions are called:
- cyw43_arch_lwip_begin()
- dns_gethostbyname() - finds out the AP address of a hostname
- cyw43_arch_lwip_end()
The begin/end surround the lwIP calls to protect against concurrency problems. There are two ways to use lwIP. In the simple way is works by polling: the cyw43_arch_poll() routine must be called periodically and the begin/end routines do nothing. In the background mode, lwIP will transparently work concurrently with your code anda begin/end are essentials.
There are many possible returns from dns_gethostbyname(). The firs time it will return ERR_INPROGRESS, meaning it is still processing the request; the final result will came through a callback. If everything goes fine, the next calls (for a limited time) will use the previous result and return immediately with ERR_OK. Values different from these two indicate errors. In the example, the ntp_dns_found() callback stores the IP address and calls ntp_request(). This same routine is called if dns_gethostbyname() returns ERR_OK immediatly.
Now we get to ntp_request(), where the actual SNTP request is sent. Here is the full routine:
// Make an NTP request static void ntp_request(NTP_T *state) { cyw43_arch_lwip_begin(); struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, NTP_MSG_LEN, PBUF_RAM); uint8_t *req = (uint8_t *) p->payload; memset(req, 0, NTP_MSG_LEN); req[0] = 0x1b; udp_sendto(state->ntp_pcb, p, &state->ntp_server_address, NTP_PORT); pbuf_free(p); cyw43_arch_lwip_end(); }
A few points to highlight:
- The transmitted packet is allocated using pbuf_alloc(), an lwIP routine that reserves space for the protocol headers and makes sure the packet is allocated in the appropriate memory area according to the system (in the RP2040 case, this can be anywhere in the SRAM).
- The actual transmission is done by udp_sendto(), properly surrounded by cw43_arch_lwip_begin/end
- The packet can be discarded as soon as udp_sendto() returns.
In this example there is no association betwen send and received packets. Received packets are treated in the receive callback.
From an SNTP standard viewpoint this implementation is not robust. Let's see if I can do better in the next post.
Comments
Post a Comment