My latest progress with the Raspberry Pi Pico: the PicoW. I have purchased a new Pimoroni Automation 2040W board for experimenting with process automation. The 2040W board is a base/carrier board for the PicoW. The PicoW is pretty much the same as the Pico board – except there is an onboard Infineon CYW43439 WIFI chip. The Pi Pico C/C++ SDK provides support for the CYW43 and an IP stack using the open source package: lwIP.
The first thing after integrating the PicoW into the Colony.pico repository’s build environment was to provide an implementation for the CPL stream interfaces (think platform independent file descriptors) using the PicoW WIFI. I wanted the implementation to not be dependent on an RTOS since the CPL library supports threading built on top the Pi Pico SDK directly without an RTOS. This constraint meant that the PicoW socket implementation had to based on lwIP’s raw TCP interfaces which are asynchronous.
Click here for additional details about the CPL threading on the PI Pico.
Click here for additional details about the CPL non-blocking sockets
The next step was to create a example project to show off my new toy 😉.
Project Overview
In the Colony.pico repository the project located under projects/example/cpl_2threads/
directory is a simple blinky-LED application with a command shell that uses two threads.
The application blinks the PicoW board’s LED at a runtime settable frequency and optionally outputs status messages every time the LED state is toggled. The LED application code executes on core0. The application also contains the CPL ‘TShell’ command processor (think Debug console) that runs over a TCP socket connection. The TShell command processor executes on core1.
The LED flash frequency and the verbose state of the application is set by the developer entering TShell commands.
Below is the STDOUT output when the application starts-up and an external client makes a TCP connection. I am using Putty to connect to the board’s UART which is where STDOUT is routed to.
Here is the output from my Putty session where I am connected to the target board using sockets.
Notes:
- The
help
command displays a list of shell/console commands. I use this to verify that the connection is working. - The
trace here
command routes the application’s printf-tracing output to the command shell’s output (instead of STDOUT) - The
dm write {name:"delayTime",val:500}
command sets the LED delay time to 500ms, aka 1Hz - The
dm write {name:"verbose", val:true}
anddm write {name:"verbose", val:false}
commands controls the output messages from the application. - The
bye
command exits the command shell and closes the socket connection. The application will then accept a new connection and restart the command shell.
Code Snippets
The example application was an existing application. All I needed to do was to
create a new main()
function that does the board/platform specific setup and then call the application code. Almost. Since servicing the TCP connection is done asynchronously I needed hooks into the application’s periodic scheduling for the background TCP processing. This required refactoring the application to add three hook functions that are called as part of the periodic processing for the two threads/cores. Below are code snippets for main() and the run_application() method it calls. The core0MBox_
and core1Mbox_
objects are Cpl::System::Runnable
instances (think the main/forever loop for each thread/core).
int main(void)
{
// Initialize CPL
Cpl::System::Api::initialize();
// Initialize the board
Bsp_Api_initialize();
// Enable Tracing
CPL_SYSTEM_TRACE_ENABLE();
CPL_SYSTEM_TRACE_ENABLE_SECTION( MY_APP_TRACE_SECTION );
CPL_SYSTEM_TRACE_SET_INFO_LEVEL( Cpl::System::Trace::eINFO );
// Start the Console/Trace output: Accepting the default UART Config parameters, e.g. 115200, 8N1
Cpl::System::RP2040::startConsole();
Cpl::System::RP2040::getConsoleStream().write( "\n**** APPLICATION START-UP *****\n" );
Cpl::System::RP2040::getConsoleStream().write( "Cpl and the Bsp have been initialized.\n" );
// Enable WIFI station mode
cyw43_arch_enable_sta_mode();
// Attempt to connect to the WIFI network
CPL_SYSTEM_TRACE_MSG( "_0test", ("Connecting to WiFi...\n") );
if ( cyw43_arch_wifi_connect_timeout_ms( WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000 ) )
{
Cpl::System::RP2040::getConsoleStream().write("FAILED to connect to the WIFI network.");
}
else
{
Cpl::System::RP2040::getConsoleStream().write( tmpString_, "CONNECTED to the wife network: %s\n", WIFI_SSID );
Cpl::System::RP2040::getConsoleStream().write( tmpString_, "My IP address: %s\n", ip4addr_ntoa( netif_ip4_addr( netif_list ) ) );
}
// Set the stream for the console
g_consoleInputFdPtr = &wifiFd_;
g_consoleOutputFdPtr = &wifiFd_;
// Start the application
runApplication(); // This method should never return
return 0;
}
void platformHook_core0_beginThread()
{
// NOT USED
}
void platformHook_core1_beginThread()
{
listener_.startListening( wifiFd_, OPTION_PORT_NUM );
}
void platformHook_core1_idleThread()
{
listener_.poll();
}
/// Application Code
void runApplication()
{
CPL_SYSTEM_TRACE_MSG( MY_APP_TRACE_SECTION, ("Hello. I am the Bob exampleapplication.") );
// Create mock application thread
Cpl::System::Thread::create( core0Mbox_, "APP-BOB" );
// Create a 'Scheduler' thread for the TShell to run in
Cpl::System::Thread::create( core1Mbox_, "TSHELL" );
// Start scheduling
CPL_SYSTEM_TRACE_MSG( MY_APP_TRACE_SECTION, ("Enabling scheduling") );
Cpl::System::Api::enableScheduling();
}