Colony.Pico gone Wireless

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} and dm 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();
}