Clion Raspberry Pi-Pico REPL Walk-through over USB - Then Use it to Control a Variable Frequency PWM Square-Wave Oscillator Generator!

In this guide we write a command controllable square-wave generator capable of controlled accurate PWM to 400KHz!

Clion Raspberry Pi-Pico REPL Walk-through over USB - Then Use it to Control a Variable Frequency PWM Square-Wave Oscillator Generator!

Before we Start - Music of the Day: Chill Flow Coding Music Full shout-out to Chill-Flow!

Part 1: Understand the Basics / Foundation / Setup / picocom

  • REPL effectively allows keyboard communication from your PC to your Raspberry Device (both ways!)
  • If you can understand this you can build all kinds of command based controllers for your Raspberry Pi-Pico like the old AT command sets for modems!
  • It is derived from the older Serial RTTY communications but in this case the drivers will enable it over the USB Bus.
  • If you want a simple review guide on REPL for micro-python consider this guide to get a handle on the basics.
  • This article is built off the excellent forum coverage from here
Micro-Python / Repl Setup W/Pycharm in a Linux Envirnoment
In this quick guide we go over setting up the Repl Enviroment for Pycharm for quick little one-off programs on your edge MCU’s
  • You definitely also want step-debugging (with a CMSIS-DAP probe) for your project (we prefer Clion - another guide for that)
CLion Support for Step-Debugging w/ Raspberry Pi-Pico
In this guide we get stepper-debugging to work for the Raspberry Pi Pico with a $25 CMSIS probe!
  • We are building in C / CPP

One can watch to see where your pico program  will show up in terms of /dev/ttyACM0 (or /dev/ttyACM1)

sudo dmesg -w

And by having this open in a terminal - we can see the device come up when it is running with :

Install your picocom + example how to communicate with the device:

sudo apt-get install picocom
sudo picocom -b 115200 /dev/ttyACM0 (sometimes /dev/ttyACM1.. etc)

Part 2: Setup Basic Files

  • The project goal basically will look like:
<pico> cf:5000:1000 <enter>
<pico> Setting clock counter to 5000 high 1000 low..  
  • Space bar will stop / start the pulser and accept a new command.
  • We are trying to simplify this and not use interrupts etc.
  • If you need a review of CMakeLists, etc check the main page for many guides

Make a new folder and put in it a file named CMakeLists.txt

cmake_minimum_required(VERSION 3.13)
add_compile_options("-O0")
include(/home/c/coding/pico-sdk/pico_sdk_init.cmake)
project(test C CXX ASM)
pico_sdk_init()
add_executable(rtr_in rtr_in.cpp)
target_link_libraries(rtr_in pico_stdlib )
pico_add_extra_outputs(rtr_in)
pico_enable_stdio_usb(rtr_in 1)
pico_enable_stdio_uart(rtr_in 0)

Note: add_compile_options("-O0") is important so the compiler does not optimize it away too far.

Note: include(/home<where lives pico_sdk) must tell Clion / CMake where lives pico-sdk  (Again if this is confusing please study the numerous guides on the main page)

Make your board config file (openocd.cfg) and put this in it:

source [find interface/cmsis-dap.cfg]
transport select swd
adapter speed 1000
source [find target/rp2040.cfg]

And We will start with some basic input handler code. Create a file named rtr_in.cpp and put the code below into it. You can use main.cpp but change the CMakeLists.txt above for anything that says 'rtr_in' to 'main'  Then call the CMakeLists.txt refresh button in CLion..

We continue:

#include "pico/stdlib.h"
#include <stdio.h>


#define  OUTPUT_PIN 0
#define LED_PIN 25


int main()
{
  gpio_init(OUTPUT_PIN);
  gpio_init(LED_PIN);
  gpio_set_dir(OUTPUT_PIN, GPIO_OUT);
  gpio_set_dir(LED_PIN, GPIO_OUT);
  stdio_init_all();

  bool is_active = false;
  char c;
  char cmd[40] = {};
  char cpos = 0;

  while (true)
  {
    c = getchar_timeout_us(0);
    if (c == ' ')
    {
      for (int t = 0; t < 40; t++)
        cmd[t] = 0;  // Clear the command out for the next
      printf("<pico> ");
      while (c != 0x13)
      {
        c = getchar();
        if ((c != 0) && (c != 0x13))
        {
          printf("%c", c);
          cmd[cpos] = c;
          cpos++;
        }
      }
      // To add: Cmd Decoder:
    }
  }
}

If you are having trouble getting CLion to compile it do look at a working example from:

CLion Support for Step-Debugging w/ Raspberry Pi-Pico
In this guide we get stepper-debugging to work for the Raspberry Pi Pico with a $25 CMSIS probe!
  • Hitting space will at any time activate the command input:

Part 3: Add Command Parser

The command parser was pretty easy to do, just at the

      // To add: Cmd Decoder:

Instead becomes:

      printf("\n");
      cmd[cpos] = 13;  // Added EOL character for the parser following.
      if ((cmd[0] == 'c') && (cmd[1] == 'f') && (cmd[2] == ':'))
      {
        char spos = 3;
        offvalue = 0;
        onvalue = 0;
        while (cmd[spos] != ':')
        {
          onvalue *= 10;
          char conv = cmd[spos] - '0';
          onvalue += conv;
          spos++;
        }
        spos++;  /// bump over the ':' character
        while (cmd[spos] != 13)
        {
          offvalue *= 10;
          char conv = cmd[spos] - '0';
          offvalue += conv;
          spos++;
        }
      }
      else
      {
        printf("Command error.. \n");
        c = ' '; // Force another run.

      }
      printf("SET ON CYCLES: %d OFF CYCLES %d\n", onvalue, offvalue);

And then from there we simply add the pin driver:

    if ((onvalue > 0) && (offvalue > 0))
    {
      gpio_put(OUTPUT_PIN, true);
      gpio_put(LED_PIN, true);
      for (unsigned int a = 0; a < onvalue; a++)
      {};
      gpio_put(OUTPUT_PIN, false);
      gpio_put(LED_PIN, false);
      for (unsigned int a = 0; a < offvalue; a++)
      {};
    }

The entire code:

//
// Created by c on 6/9/24.
//


#include "pico/stdlib.h"
#include <stdio.h>


#define  OUTPUT_PIN 0
#define LED_PIN 25


int main()
{
  gpio_init(OUTPUT_PIN);
  gpio_init(LED_PIN);
  gpio_set_dir(OUTPUT_PIN, GPIO_OUT);
  gpio_set_dir(LED_PIN, GPIO_OUT);
  stdio_init_all();

  bool is_active = false;
  char c;
  char cmd[40] = {};
  char cpos = 0;
  unsigned int onvalue = 0;
  unsigned int offvalue = 0;


  while (true)
  {
    c = getchar_timeout_us(0);
    if (c == ' ')
    {
      cpos = 0;
      for (int t = 0; t < 40; t++)
        cmd[t] = 0;  // Clear the command out for the next
      printf("<pico> ");
      while (c != 13)
      {
        c = getchar();
        if (c != 13)
        {
          printf("%c", c);
          cmd[cpos] = c;
          cpos++;
        }
      }
      printf("\n");
      cmd[cpos] = 13;  // Added EOL character for the parser following.
      if ((cmd[0] == 'c') && (cmd[1] == 'f') && (cmd[2] == ':'))
      {
        char spos = 3;
        offvalue = 0;
        onvalue = 0;
        while (cmd[spos] != ':')
        {
          onvalue *= 10;
          char conv = cmd[spos] - '0';
          onvalue += conv;
          spos++;
        }
        spos++;  /// bump over the ':' character
        while (cmd[spos] != 13)
        {
          offvalue *= 10;
          char conv = cmd[spos] - '0';
          offvalue += conv;
          spos++;
        }
      }
      else
      {
        printf("Command error.. \n");
        c = ' '; // Force another run.

      }
      printf("SET ON CYCLES: %d OFF CYCLES %d\n", onvalue, offvalue);
    }
    if ((onvalue > 0) && (offvalue > 0))
    {
      gpio_put(OUTPUT_PIN, true);
      gpio_put(LED_PIN, true);
      for (unsigned int a = 0; a < onvalue; a++)
      {};
      gpio_put(OUTPUT_PIN, false);
      gpio_put(LED_PIN, false);
      for (unsigned int a = 0; a < offvalue; a++)
      {};
    }
  }
}

Let's try it out!

Ok can it change after the fact to another setting??

  • DEL does not work so if so just hit ' ' to start a fresh command!
  • Still slightly buggy on subsequent commands:

Fixed it!

Error was simply not resetting cpos on a cycle. The code above was already corrected for it:

How does it look! - Using your typical low cost logic analyzer (on Amazon for about $15).

Gives us:!!

Zooming a pulse:

It works! Some notes:

  • Recalling that c = getchar_timeout_us(0); is being called after every single cycle is lopsiding the offtime significantly. To remedy this one simply changed the cycles so that it runs about 100 loops before bothering to check for a character (which introduces fast-mode)
  • {} = NOP (No Operation) in C/C++;
      for (unsigned int x = 0; x < 100; x++)
      {
        gpio_put(OUTPUT_PIN, true);
        gpio_put(LED_PIN, true);
        for (unsigned int a = 0; a < onvalue; a++)
        {};
        gpio_put(OUTPUT_PIN, false);
        gpio_put(LED_PIN, false);
        for (unsigned int a = 0; a < offvalue; a++)
        {};
      }

Now when we torque this right up we get:

<pico> cf:1:1

We get a roughly 400 Khz Generator - that can also set all kinds of PWM signals for training..

  • To Add feature potentials (but would take some code changes) is to have the command piped from the command line (allowing remote device cycling)
  • Potentials from there would be remote control for water sprinklers, timers, fireworks, remote opening of garage doors the possiblities are endless!
  • One can see the gapping after the 100 cycles - so one can suite to taste:
  • Some other very interesting notes - Changing your optimizations did not help.  Compiler optimizations caused jittery on the bits as if one loop was being compiled and another was not.  Therefore it was left as 'O0' vs 'O3'
Linux Rocks Every Day