UP | HOME

Raspberry Pico and PWM (Servo)

Table of Contents

rpi.png

Working with PWM

Overview

Pulse Width Modulation is a technique for getting analog results with digital means. The principle is to reduce the avrage power deliviered by an electrical signal. This can be done by chopping the signal into discrete parts. In the case of servos the amount in which the signal is enabled indicates in what position the servo is to be placed . In this overview I will use a micro-servo sg90 which bascially has a range of \(180^\circ\) (\(-90 \rightarrow 90\)).

Lets dive right into it.

RPI2040 - period

The RP2040 has a clock frequency \(\approx 125 Mhz\). That would in the case of PWM look something like.

     Duty Cycle                                         Duty cycle
<----------------->                                 <----------------->
+-----------------+                                 +-----------------+
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
+-----------------+---------------------------------+-----------------+-------------

<-------------------------------------------------->
                 Pulse Width (125 Mhz, 8ns)

As can see in the above diagram we have a frequency of 125 Mhz which gives us \({1 \over 125} = 8 * 10^{-9}\) (8ns)

Duty Cycle

A duty cycle is the amount of time that the pulse is on for each period. One way to see it is that the RP2040 is split into slices. One slice is 8ns , which can be turned either "Low" or "High".

freq = 125e6
period = 1/freq
print(period)


Lets say we want a period of \(1\mu s\) that means we have \(1e^{-6}/8e^{-9}=125\) rp2040 periods. If we continue and want a Duty cycle (in other words when its high) to be 120ns. \(120e^{-9}/8e^{-9}=15\). That gives a pulse looking like the following picture:

8e-09
| 120ns                                            120ns
|<--->                                            <---->
+----+                                            +----+
|    |                                            |    |
|    |                                            |    |
|    |                                            |    |
|    |                                            |    |
+----+|-----|------------|-----------|------------+----+
     125    250ns        500ns      750ns         1us

<-------------------------------------------------> 125 periods
<----> 15 periods

Now to enable this pulse, we need to tell RP2040 that the wrap time is 125 and that it needs to have a duty cycle of 15. This can be done using

.
.
.
pwm_set_wrap(slice, 125);
.
.
.
pwm_set_chan_level(slice, channel, 15);
.
.
.

More on this later.

Micro servo sg90

The servo im using is working on a period of 20 ms with a duty cycle of a range from \(1\ldots 2\) ms Its able to turn 180 ° as the table shows

Duty Position %
cycle in degree of duty
(ms)   cycle
1 -90 5.
1.5 0 7.5
2 90 10.

If we now take a look at table original period, we see that the periods of RP2040 and quite much faster than the servo period. Fortunatly one can set a counter (wrap counter see Duty cycle) for how many RP2040 periods is a period.

Servo Period
|     DC 1...2
|<------------->
+--------+--+--+                                                       +-------------+
|        |  |  |                                                       |             |
|        |  |  |                                                       |             |
|        |  |  |                                                       |             |
|        |  |  |                                                       |             |
|        |  |  |                                                       |             |
|        |  |  |                                                       |             |
+--------v--v--+-------------------------------------------------------+-------------+--
                          Period 20 ms
<---------------------------------------------------------------------->

RP2040
|
++ ++ ++ ++
|| || || ||
|| || || ||
|| || || || ..................................................................
|| || || ||
|| || || ||
++-++-++-++-----------------------------------------------------------------------------
   period 8ns

If we calculate the how many cycles the raspberry pi periods for each servo period:

rpi_period = 8e-9
servo_period = 20e-3

print( servo_period / rpi_period)

2500000.0

That means we get 2.5 million periods for each servo period (\(20ms\)). And if we calculate the Duty cycle period for \(-90^\circ\)

duty_cycle = 1e-3
print( duty_cycle / rpi_period)
125000.0

This gives us 125 000, as discussed in the Duty cycle section, we could set the wrap time to \(2.5e^6\) and the channel level to \(125000\). which would solve the problem?

So in our case we need the wrap value to be set to \(2.5*10^6\) . So that gives us a equation

\begin{equation} {wanted\_period \over rpi\_period } = cycles \end{equation}

if we now want the servo to be placed at \(-90^\circ \rightarrow 1 ms\) we can calculate the precentage of the period in which the signal should be high, we did that in the servo table above, for \(0^\circ\) we see that it correponds to \(4%\) of the complete cycle. that means \(2.5*0.05=125000\) of the 2.5million it needs to be active.

|         0         1         2                2500000   0
+----+    +----+    +----+    +----+           +----+    +----+
|    |    |    |    |    |    |    |           |    |    |    |
|    |    |    |    |    |    |    |   ......  |    |    |    |
|    |    |    |    |    |    |    |           |    |    |    |
+----+----+----+----+----+----+----+-----------+----+----+----+---------------------------

All we need to do is to set the first 125000 cycles to be enabled and the rest (\(2.4*10^6\)) to be disabled. Well, that is all nice, but there is a problem. The function static inline void pwm_set_wrap(uint slice_num, uint16_t wrap) takes a 16 bits unsigned integer, meaning \(2^{16}=65535\) is the highest number, which means \(2.6*10^6\) will not fit. Ok, so we got a problem. But there is a solution to this problem. The divider!

Fortunatly there is something called a clock divider built into the hardware and the SDK. The divider can be used to slow down the frequency by dividing it. Lets consider the divisior in the range between \(1\ldots6\) the new frequency is easily calculated by doing.

\begin{equation} {RPI\_period \over divisor}=new frequency \end{equation}
Divisor Freq period
  (Mhz)  
1 125 8e-9
2 62.5 1.6e-8
3 41.666667 2.4000000e-8
4 31.25 3.2e-8
5 25 4e-8
6 20.833333 4.8000001e-8
2500000 5e-5 0.02

The divider works in a range of \(1.f \leq value < 256.f\) So in the if we divide the clock-frequency by 255 we get. \({125 \over 255} = 490.196khz \rightarrow{1 \over 490.196}\approx 2\mu sec\)

So we slow down the clockrate to \(\approx2\mu sec\) If we really want to have a slowest possible period. We could set the wrap counter on max (65535). \(2\mu s * 65535=0.13421568 s\) which would give us a frequency of \(1/0.13421568=7.45 hz\) Which is lower than the threshold for human hearing.

     Duty Cycle                                         Duty cycle
<----------------->                                 <----------------->
+-----------------+                                 +-----------------+
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
|                 |                                 |                 |
+-----------------+---------------------------------+-----------------+-------------

<-------------------------------------------------->
                 Pulse Width ( 488 kHz, 2 us)

Back to the servo: its now possible to calculate the the cycles by \({20*10^{-3} \over 2.048*10^{-6}}=9803.9216\) So if we set the wrap time to 9804 we get almost the right value (0.02000016 secods).

So our duty cycle should be \(0.05*9804=490.2\) for \(-90^\circ\) and \(0.075*9764=735.3\) for \(0^\circ\)

degrees ms Percentage cycles
    of 20 ms (percent*9764)
-90 0.6 0.03 294.12
-90 1.0 0.05 490.2
0 1.5 0.075 735.3
90 2.0 0.1 980.4
90 2.4 0.12 1176.48

This gives us a pulse looking something like :


  Duty cycle                                        Wrap Point
  490 periods                                       9804 periods
<----------------->
+--+--+-------+---+                                 +-----------------+
|  |  |       |   |                                 |                 |
|  |  |       |   |                                 |                 |
|  |  | 488   |   |                                 |                 |
|  |  | ....  |   |                                 | ..... 488 ....  |
|  |  |       |   |                                 |                 |
|  |  |       |   |                                 |                 |
|  |  |       |   |                                 |                 |
+--+--+-------+---+---------------------------------+-----------------+-------------
                     9804 periods
<-------------------------------------------------->
                 Pulse Width ( 50 Hz  , 20ms)

There is actually a better numbers to divide with (e.g 125 which gives us a \(1\mu sec\) period) , but lets stick to this and do a test implementation.

Implementation

After some initial tests I noticed that it didn't quite get to the end points (-90, 90) correctly. So I added some new values (0.6 ms and 2.4 ms) as can be seen in the table. They seem to be more correct. Anyhow the implemtation is pretty straight forward.

#include "hardware/clocks.h"
#include "hardware/pwm.h"
#include "pico/stdlib.h"

constexpr uint PIN_OUT = 0;

int main()
{
    stdio_init_all();
    sleep_ms(2000);                             /// Just to wait some..
    gpio_set_function(PIN_OUT, GPIO_FUNC_PWM);  /// Set the pin 0 to be PWM
    auto slice   = pwm_gpio_to_slice_num(PIN_OUT);
    auto channel = pwm_gpio_to_channel(PIN_OUT);

    pwm_set_clkdiv(slice, 256.0f);  /// Setting the divider to slow down the clock
    pwm_set_wrap(slice, 9804);      /// setting the Wrap time to 9764 (20 ms)
    pwm_set_enabled(slice, true);

    for (uint i = 0; i < 10; ++i)
    {

        pwm_set_chan_level(slice, channel, 490);  /// Setting the duty period (0.6 ms)
        sleep_ms(2000);
        pwm_set_chan_level(slice, channel, 735);  /// Setting the duty period (1.5 ms)
        sleep_ms(2000);
        pwm_set_chan_level(slice, channel, 1176);  /// Setting the duty period (2.4 ms)
        sleep_ms(2000);
    }
    return 0;
}

Maybe not the most useful program, but it proves how this works. Since it works and we have a pretty good understanding of how it works. it should be pretty straight forward to have a function to get the number of duty periods.

Extending functionality

An extension to the program above is to have some function that do the calculation. Instead of sending in the number of periods that the channel is to be active, it would be nice to have function that returns it and as argument it takes the degree. e.g \(f(0)=735\) or \(f(90)=1176\) or as put in a more generic ways

\begin{equation} f(D)=Rpi\_Periods \end{equation}

All we need to do is to figure out what \(F(D)\) is ..

Looking at the table Servo cycles we could calculate the amount slices per degree. That would mean we could based on the degree we could get the amount of slices which should be enabled.

\begin{equation} {735-490 \over 90} = 2.72 \end{equation}

That gives us

\begin{equation} f(d) = { (90+ d )*2.72 + 488 \\ } \end{equation}
def degree2slice(degree):
    assert(degree >= -90 and degree <= 90)
    val = degree + 90
    return round(val*2.72+488)

out =  "|-----|------|\n"
out += "| deg |slices|\n"
out += "|-----|------|\n"

for deg in [-90,-45,-1,-0.5,0,0.5,1,45,90]:
    out += "| {}|{}|\n".format(deg,degree2slice(deg))
print(out)
deg slices
-90 488
-45 610
-1 730
-0.5 731
0 733
0.5 734
1 736
45 855
90 978

That works when we have a division of 255, but lets say we have some other division.

What we know is that RP2040 has a clock period of \(125Mhz \rightarrow 8ns\) But if that changes the whole function changes. But its a constant expression

CLK_SYS=125e6                   # Speed of the bus (125Mhz)

def get_period_spd(bus_speed):
    return 1/bus_speed

print(f"|{CLK_SYS}|{get_period_spd(CLK_SYS)}|\n")


125000000.0 8e-09

if we use a divider the Bus speed will change, we could however change the clkdiv dynamically, that means we need a function to get the current bus speed.

def get_clk_speed_freq(bus_speed, clk_div):
    return bus_speed/clk_div


print("|Div|Freq|Period|")
print("|----|----|-----|")
for div in [1,2,8,16,32,64,125,255]:
    freq = get_clk_speed_freq(CLK_SYS,div)
    period_time = get_period_spd(freq)
    print(f"|{div}|{freq}|{period_time}|")

Div Freq Period
1 125000000.0 8e-09
2 62500000.0 1.6e-08
8 15625000.0 6.4e-08
16 7812500.0 1.28e-07
32 3906250.0 2.56e-07
64 1953125.0 5.12e-07
125 1000000.0 1e-06
255 490196.07843137253 2.04e-06

FIX THIS

The next calculation is to amount of "slices" during one period. What we need is the RP2040 period time, and the period time of the PWM.

Cycles per period using divider

If we continue on using the period time of \(20ms\) and change the divider we get a table with frequency, period time and how many cycles. in the above section Micro servo

\begin{equation} slices_{per\_period}(RP2040_{period},PWM_{period}) = {PWM_{period} \over RP2040_{period}}\\ \end{equation}
PWM_PERIOD_TIME = 20e-3

def cycles_per_period(rp_period,pwm_period):
    return pwm_period/rp_period

print("|Div|Freq|Period|cycles")
print("|----|----|-----|-----|")



for div in [1,2,8,16,32,64,125,255]:
    freq = get_clk_speed_freq(CLK_SYS,div)
    period_time = get_period_spd(freq)
    cycles = cycles_per_period(period_time,PWM_PERIOD_TIME)
    print(f"|{div}|{freq}|{period_time}|{cycles}|")

Div Freq Period cycles
1 125000000.0 8e-09 2500000.0
2 62500000.0 1.6e-08 1250000.0
8 15625000.0 6.4e-08 312500.0
16 7812500.0 1.28e-07 156250.0
32 3906250.0 2.56e-07 78125.0
64 1953125.0 5.12e-07 39062.5
125 1000000.0 1e-06 20000.0
255 490196.07843137253 2.04e-06 9803.921568627451

slices per cycles

The actual number of slices for a specific period is dependent on what system clock rate , which in it self is dependent on the clkdiv So there are a couple of variables that needs to be taken into account.

#PERIOD_TIME=20e-3

def get_slice_period(period_time, clk_speed, clkdiv):
    freq = get_clk_speed_freq(clk_speed,clkdiv) # Get the frequency for a clock_speed, div
    sys_period_time = get_period_spd(freq)     # Time speent for each slice
    return period_time/sys_period_time

period_times = [1e-6,50e-6,500e-6,1e-5,50e-5,500e-5,1e-4,50e-4,500e-4,\
                1e-3,20e-3,500e-3]

print("|--|")
print("|  | Period Times |")
print("|--|")

times = "| div |"
times += "|".join(map(str,period_times))
times += "|"

print(times)
print("|--|")

for div in [1,2,8,16,32,64,125,255]:
    out=f"|{div}"
    #print(f"|{div}")
    for period_time in period_times:
        period_slices = get_slice_period(period_time,CLK_SYS,div)
        out+=f"|{period_slices}"
        #print(f"{period_slices}")
    print(out)



  Period Times                      
div 1e-06 5e-05 0.0005 1e-05 0.0005 0.005 0.0001 0.005 0.05 0.001 0.02 0.5
1 124.99999999999999 6250.0 62500.0 1250.0 62500.0 625000.0 12500.0 625000.0 6250000.0 125000.0 2500000.0 62499999.99999999
2 62.49999999999999 3125.0 31250.0 625.0 31250.0 312500.0 6250.0 312500.0 3125000.0 62500.0 1250000.0 31249999.999999996
8 15.624999999999998 781.25 7812.5 156.25 7812.5 78125.0 1562.5 78125.0 781250.0 15625.0 312500.0 7812499.999999999
16 7.812499999999999 390.625 3906.25 78.125 3906.25 39062.5 781.25 39062.5 390625.0 7812.5 156250.0 3906249.9999999995
32 3.9062499999999996 195.3125 1953.125 39.0625 1953.125 19531.25 390.625 19531.25 195312.5 3906.25 78125.0 1953124.9999999998
64 1.9531249999999998 97.65625 976.5625 19.53125 976.5625 9765.625 195.3125 9765.625 97656.25 1953.125 39062.5 976562.4999999999
125 1.0 50.00000000000001 500.00000000000006 10.000000000000002 500.00000000000006 5000.0 100.00000000000001 5000.0 50000.00000000001 1000.0000000000001 20000.0 500000.0
255 0.49019607843137253 24.50980392156863 245.0980392156863 4.901960784313726 245.0980392156863 2450.9803921568628 49.01960784313726 2450.9803921568628 24509.80392156863 490.1960784313726 9803.921568627451 245098.0392156863

Duty cycle calculation

The duty cycle is a dynamic calculation, meaning that it might change during the runtime of the program . This means that we need to dynamically calculate the cycles per degree for a certain frequency. As was discussed earlier in section slices per cycles , we used some magic numbers (2.72 and 488) these of course were dependent on what divisor and frequency used. Now we need to calculate the same value but using dynamically.

First what we need is the range on which we are operating on. In the above case we used \(-90 \ldots 90\) but this might be something else..e.g \(0 \ldots 360\) or even \(-180 \ldots 180\) we also need the amount of time which the Pulse should be high

This becomes a 3 dimensional table, so instead we restrict ourself using the period time to be \(20ms\)

def pwm_range(low_tuple,high_tuple):


%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np



print("Hello Numpy")
# Out[1]:

c++ Implementation

PWM

#include <cstdint>
#include <iostream>
#include <chrono>



template<double CLK_SYS, uint8_t CLK_DIV=1>
struct PWM
{
    static constexpr double clk_sys = CLK_SYS;
    static constexpr uint8_t divider = CLK_DIV;

    static constexpr double get_clk_frequency()
    {
        return clk_sys/divider;
    }

    static  auto get_ratio_frequency()
    {

        return std::ratio<clk_sys,divider>{};
    }

    static constexpr double get_clk_speed()
    {
        return 1.0/get_clk_frequency();
    }


    static constexpr double cycles_per_period(double pwm_period)
    {
        return pwm_period/get_clk_speed();
    }



};

NOTE: could use ratio!!

#include <cstdint>
#include <iostream>
#include <chrono>



template<double CLK_SYS, uint8_t CLK_DIV=1>
struct PWM
{
    static constexpr double clk_sys = CLK_SYS;
    static constexpr uint8_t divider = CLK_DIV;

    static constexpr double get_clk_frequency()
    {
        return clk_sys/divider;
    }

    static  auto get_ratio_frequency()
    {

        return std::ratio<clk_sys,divider>{};
    }

    static constexpr double get_clk_speed()
    {
        return 1.0/get_clk_frequency();
    }


    static constexpr double cycles_per_period(double pwm_period)
    {
        return pwm_period/get_clk_speed();
    }



};


int main()
{
    {
        PWM<125e6> pwm;
        std::cout << pwm.clk_sys << "," << pwm.get_clk_speed() << "\n";
    }
    {
        PWM<125e6,255> pwm;
        std::cout << pwm.get_clk_speed() << "," << pwm.get_clk_frequency() << \
                ","<< pwm.cycles_per_period(20e-3) << ","<< \
                pwm.cycles_per_period(1e-3) << "\n";
    }
    {
        PWM<125e6,125> pwm;
        std::cout << pwm.get_clk_speed() << "," << pwm.get_clk_frequency() << \
                ","<< pwm.cycles_per_period(20e-3) << ","<< \
                pwm.cycles_per_period(1e-3) << "\n";
    }
    return 0;

}


The Range

#include <cstdint>
#include <iostream>
#include <chrono>



template<double CLK_SYS, uint8_t CLK_DIV=1>
struct PWM
{
    static constexpr double clk_sys = CLK_SYS;
    static constexpr uint8_t divider = CLK_DIV;

    static constexpr double get_clk_frequency()
    {
        return clk_sys/divider;
    }

    static  auto get_ratio_frequency()
    {

        return std::ratio<clk_sys,divider>{};
    }

    static constexpr double get_clk_speed()
    {
        return 1.0/get_clk_frequency();
    }


    static constexpr double cycles_per_period(double pwm_period)
    {
        return pwm_period/get_clk_speed();
    }



};


struct DegreePoint
{
    constexpr Point(int8_t degree, std::chrono::duration duration):
            degree_(degree),
            ms_(duration)
    {

    }
    int8_t degree_{};
    std::chrono::duration ms_{};



};

template <typename T, typename PWM>
struct Range
{

    constexpr Range(T Low, T High)
    {


    }
    static constexpr double degree2slice(DegreePoint pt, PWM pwm)
            {

    /// static constexpr double degree2slice(, PWM range)
    /// {
    ///     return low_cycles_;
    /// }


    double cycles_per_degree_{0};
    double low_cycles_{};
    /// T Low_{};
    /// T High_{};
};


#include <cstdint>
#include <iostream>
#include <chrono>



template<double CLK_SYS, uint8_t CLK_DIV=1>
struct PWM
{
    static constexpr double clk_sys = CLK_SYS;
    static constexpr uint8_t divider = CLK_DIV;

    static constexpr double get_clk_frequency()
    {
        return clk_sys/divider;
    }

    static  auto get_ratio_frequency()
    {

        return std::ratio<clk_sys,divider>{};
    }

    static constexpr double get_clk_speed()
    {
        return 1.0/get_clk_frequency();
    }


    static constexpr double cycles_per_period(double pwm_period)
    {
        return pwm_period/get_clk_speed();
    }



};


struct DegreePoint
{
    constexpr Point(int8_t degree, std::chrono::duration duration):
            degree_(degree),
            ms_(duration)
    {

    }
    int8_t degree_{};
    std::chrono::duration ms_{};



};

template <typename T, typename PWM>
struct Range
{

    constexpr Range(T Low, T High)
    {


    }
    static constexpr double degree2slice(DegreePoint pt, PWM pwm)
            {

    /// static constexpr double degree2slice(, PWM range)
    /// {
    ///     return low_cycles_;
    /// }


    double cycles_per_degree_{0};
    double low_cycles_{};
    /// T Low_{};
    /// T High_{};
};



int main()
{
    using PWM_Micron = PWM<125e6,125>;
    PWM_Micron pwm;
    Range<int8_t,PWM_Micron> range(-90,90);

    std::cout << range.low_cycles_ << "\n";


    return 0;
}

-90000000.0

C++ library

First things first, since the RP2040 uses a frequency, I figured using std::chrono and std::ratio could make life simpler.

Birth of Pwm_ratio

Moving time

The time of movement from one degree to the next needs to be considered, there need to be a pause between sending one poistion until the servo actually reaches the next position. According to the datasheet the speed is 0.12 sec/\(60\^circ\) at 4.8 volts and 0.10 sec/\(^\circ\) at 6V. To be on the safe side we use the 0.12 second which gives \(2ms/^degree\) to be sure we actually get there in time I'll add another millisecond (0.3)

\begin{equation} M(D) = 0.03 * D \end{equation}

Where D is the distance from point A to point B.


-90        0          90
 <---A-----><----B---->

     <---------->
           D

This is calulated eassiest by \(D=\vert A - B \vert * 0.02\)

Send Command

To be able to steer the microservos its necessary some kind of interface. The eassiest one is to use the UART which is connected to USB, lets start with this one.

For now everything is synchronized half duplex, that means the client need to wait for the move to be done before going sending the next argument.

So we need some commands

Command type    
setx <int>    

Delimiting

For some reason the std::cin not working as I expected it to. for instance the std::getline is not working properly, it should wait for \n. That probably has to do something with the delimiter.. Lets try use something else for delimiter..example ';'. Ahh, that seems to works. So for example sending the command "set.x=45;" seems like a workable solution.

Then we need a split string..

#include <tuple>
#include <cassert>

auto getCommand( std::string_view str, char delim, std::size_t start=0)
{

    auto delim_pos = str.find(delim);
    if( delim_pos == std::string_view::npos)
    {
        return std::nullopt;
    }
    auto cmd = str.substr(0,delim_pos);
    str.remove_prefix(delim_pos+1);
    return std::optional{std::make_tuple(str,cmd)};
}

#include <tuple>
#include <cassert>

auto getCommand( std::string_view str, char delim, std::size_t start=0)
{

    auto delim_pos = str.find(delim);
    if( delim_pos == std::string_view::npos)
    {
        return std::nullopt;
    }
    auto cmd = str.substr(0,delim_pos);
    str.remove_prefix(delim_pos+1);
    return std::optional{std::make_tuple(str,cmd)};
}


int main()
{
    std::string test("set.x=54;");
    auto [left,cmd] = getCommand(test,'.');
    std::cout << cmd << " "<< left  << "\n";

    auto [next,type ] = getCommand(left,'=');
    std::cout << type << " " << next << "\n";

    auto [_, val] = getCommand(next,';');

    std::cout << val << " " << "EOL" << '\n';

    /// In case its garbage.
    {
        auto [str,val] = getCommand(test,',');
        assert( !str.empty() && val.empty());
        std::cout << str << " " << val << "\n";

    }
    return 0;
}
set x=54;
x 54;
54 EOL
set.x=54;  

This was all good, now lets figure out how to get even more out of one command.

auto parse_command(std::string_view command_line)
{
    get_command(command_line,'.');
}

#include <ranges>
#include <iomanip>
#include <tuple>
#include <cassert>

auto getCommand( std::string_view str, char delim, std::size_t start=0)
{

    auto delim_pos = str.find(delim);
    if( delim_pos == std::string_view::npos)
    {
        return std::nullopt;
    }
    auto cmd = str.substr(0,delim_pos);
    str.remove_prefix(delim_pos+1);
    return std::optional{std::make_tuple(str,cmd)};
}


int main()
{
    using namespace std::literals;
    constexpr std::string_view cmdLine="set.x=45,set.y=90,get.x=last"sv;

    auto [rest, command] = getCommand(cmdLine,',');

    while(!command.empty())
    {
        std::cout << command << " " << rest << '\n';
        std::tie(rest,command) = getCommand(rest,',');
        //so here we have the command, we now need to parse the command
        // The cmd
        //parseCommand( command );

    }

}


Links

Author: Calle Olsen

Created: 2021-12-01 Wed 13:27