STM3 ESC dual brushless motor controller. 10-60v, motor power rating tiny to kW. Ganged or independent motor control As used in 'The Brute' locomotive - www.jons-workshop.com

Dependencies:   mbed BufferedSerial Servo FastPWM

cli_BLS_nortos.cpp

Committer:
JonFreeman
Date:
2019-03-04
Revision:
12:d1d21a2941ef
Parent:
11:bfb73f083009

File content as of revision 12:d1d21a2941ef:

/**
*   Code in this file : -
*
*   STM3_ESC board uses two serial ports. 
*       One (use pc.printf etc) provides 9600 baud comms to a pc or other terminal. Essential for test and setup, not used in everyday use.
*
*       com2 provides 19200 baud comms via opto-isolators to Touch Screen Controller (see "Brute_TS_Controller_2018_11"). Opto isolation allows
*       for several boards to parallel up on this port, each STM3_ESC board having a unique single byte ID char in range '0' to '9'.
*       This enables Touch Screen controller to address ESC boards individually e.g. for requesting speed, RPM etc
*       while also allowing broadcast of commands not requiring responses
*
*   Code implements a Command Line Interpreter (see class   cli_2019)
*   Two instantiations of class   cli_2019 are used, 'pcc' for pc comms and 'tsc' for touch screen comms
*   These read incoming commands and execute code functions accordingly. These functions are all of type
*       void    func    (struct parameters &)  ;
*   This allows any command line parameters to pass to 'func'
*/
//  Brushless_STM3_Ctrl_2018_11
#include "mbed.h"
#include "BufferedSerial.h"
#include "DualBLS.h"
#include "brushless_motor.h"

#include <cctype>
using namespace std;

extern  eeprom_settings     mode     ;
extern  error_handling_Jan_2019     ESC_Error    ;         //  Provides array usable to store error codes.
extern  int     WatchDog;
extern  bool    WatchDogEnable;
extern  double  rpm2mph ;

extern  brushless_motor MotorA, MotorB;     //  Controlling two motors together or individually
extern  char   const_version_string[]   ;

extern  BufferedSerial com2, pc;            //  The two com ports used. There is also an unused com port, com3 setup @ 1200 baud
extern  void    setVI   (double v, double i)  ;     //  Set motor voltage limit and current limit
extern  double  Read_DriverPot  ();
extern  double  Read_BatteryVolts   ();
extern  void    mode_set_both_motors   (int mode, double val)  ;   //  called from cli to set fw, re, rb, hb
extern  void    rcin_report ()  ;

//  All void    func    (struct parameters &)  ; functions addressed by command line interpreter are together below here

/**
void    ver_cmd (struct parameters & a)
    Responds YES, causes action NO
    PC or TS able to read software / firmware / hardware version string
*/
void    ver_cmd (struct parameters & a)
{
    if  (a.source == SOURCE_PC)
        pc.printf   ("Version %s\r\n", const_version_string);
    else    {
        if  (a.source == SOURCE_TS)
            if (a.respond)     //  Only respond if this board addressed
                a.com->printf   ("%s\r", const_version_string);
        else
            pc.printf   ("Crap source %d in ver_cmd\r\n", a.source);
    }
}

/**
void    pot_cmd (struct parameters & a)
    Responds YES, causes action NO
    pc reads DriverPot. No sense in TS reading as STM3_ESC uses either/or TS, DriverPot
*/
void    pot_cmd (struct parameters & a)
{   if  (a.source == SOURCE_PC)
        pc.printf   ("Driver's pot %.3f\r\n", Read_DriverPot  ());
    else
        pc.printf   ("Wrong use of pot_cmd\r\n");
}

/**
*   Do nothing command, but does report board ID code '0' to '9'
*/
void    null_cmd (struct parameters & a)
{
    if  (a.respond) 
        a.com->printf   ("At null_cmd, board ID %c\r\n", mode.rd(BOARD_ID));
}

#ifdef  USING_DC_MOTORS
/**
void    mt_cmd (struct parameters & a)
    PC Only
    Responds YES, causes action NO
    report_motor_types  ()      //  Reports 'Brushless' if Hall inputs read 1 to 6, 'DC' if no Hall sensors connected, therefore DC motor assumed
*/
void    mt_cmd (struct parameters & a)
{
    if  (a.source == SOURCE_PC)
        pc.printf   ("Mot A is %s, Mot B is %s\r\n", MotorA.dc_motor ? "DC":"Brushless", MotorB.dc_motor ? "DC":"Brushless");
    else
        pc.printf   ("Wrong use of mt_cmd\r\n");
}
#endif

/**
*   void    rdi_cmd (struct parameters & a)  //  read motor currents (uint32_t) and BatteryVolts (double)
*/
void    rdi_cmd (struct parameters & a)  //  read motor currents (uint32_t) and BatteryVolts (double)
{   //  Voltage reading true volts, currents only useful as relative values
    if  (a.respond) 
//        a.com->printf ("rdi%d %d %.1f\r%s", MotorA.I.ave, MotorB.I.ave, Read_BatteryVolts  (), a.source == SOURCE_PC ? "\n" : "");
        a.com->printf ("rdi%.1f %.1f %.1f\r%s", MotorA.Idbl, MotorB.Idbl, Read_BatteryVolts  (), a.source == SOURCE_PC ? "\n" : "");
          //  Format good to be unpicked by cli in touch screen controller
}

/**
*   void    rvi_cmd (struct parameters & a)  //  read last normalised motor voltage and current values sent to pwms
*
*/
void    rvi_cmd (struct parameters & a)  //  read last normalised values sent to pwms
{
    if  (a.respond) 
        a.com->printf ("rvi%.2f %.2f %.2f %.2f\r%s", MotorA.last_V, MotorA.last_I, MotorB.last_V, MotorB.last_I, a.source == SOURCE_PC ? "\n" : "");
}

/**
*   void    fw_cmd (struct parameters & a)  //  Forward command
*   Broadcast to all STM3_ESC boards, required to ACT but NOT respond
*/
void    fw_cmd (struct parameters & a)  //  Forward command
{
    mode_set_both_motors   (MOTOR_FORWARD, 0.0);
    if  (a.source == SOURCE_PC)
        pc.printf   ("fw\r\n");     //  Show response to action if command from pc terminal
}

/**
*   void    re_cmd (struct parameters & a)  //  Reverse command
*   Broadcast to all STM3_ESC boards, required to ACT but NOT respond
*/
void    re_cmd (struct parameters & a)  //  Reverse command
{
    mode_set_both_motors   (MOTOR_REVERSE, 0.0);
    if  (a.source == SOURCE_PC)
        pc.printf   ("re\r\n");
}

/**
*   void    rb_cmd (struct parameters & a)      //  Regen brake command
*   Broadcast to all STM3_ESC boards, required to ACT but NOT respond
*/
void    rb_cmd (struct parameters & a)      //  Regen brake command
{
    mode_set_both_motors   (MOTOR_REGENBRAKE, a.dbl[0] / 100.0);
    if  (a.source == SOURCE_PC)
        pc.printf   ("rb %.2f\r\n", a.dbl[0] / 100.0);
}

/**
*   void    hb_cmd (struct parameters & a)      //  Hand brake command
*   Broadcast to all STM3_ESC boards, required to ACT but NOT respond
*
*   NOTE    Jan 2019 Hand brake not implemented
*
*/
void    hb_cmd (struct parameters & a)      //  Hand brake command
{
    if  (a.respond) {
        a.com->printf   ("numof params = %d\r\n", a.numof_dbls);
        a.com->printf   ("Hand Brake : First %.3f, second %.3f\r\n", a.dbl[0], a.dbl[1]);
    }
    mode_set_both_motors   (MOTOR_HANDBRAKE, 0.0);
}


extern  int numof_eeprom_options2    ;
extern  struct  optpar const option_list2[]  ;
/**void    mode_cmd (struct parameters & a)       //  With no params, reads eeprom contents. With params sets eeprom contents
*   mode_cmd called only from pc comms. No sense calling from Touch Screen Controller
*
*   Called without parameters - Lists to pc terminal current settings
*
*/
void    mode19_cmd (struct parameters & a)       //  With no params, reads eeprom contents. With params sets eeprom contents
{
    char            temps[36];
    int i;
    double  topspeed;   //  New Jan 2019 - set max loco speed
    pc.printf   ("\r\nmode - Set system data in EEPROM - Jan 2019\r\nSyntax 'mode' with no parameters lists current state.\r\n");
    if  (a.numof_dbls)  {           //  If more than 0 parameters supplied
        for (i = 0; i < a.numof_dbls; i++)
            temps[i] = (char)a.dbl[i];          //  recast doubles to char
        while   (i < 33)
            temps[i++] = 0;
        switch  ((int)a.dbl[0]) {
            case    0:      //  MotorA_dir [0 or 1], MotorB_dir [0 or 1]
                if  (temps[1] == 0 || temps[1] == 1)
                    mode.wr(temps[1], MOTADIR);
                if  (temps[2] == 0 || temps[2] == 1)
                    mode.wr(temps[2], MOTBDIR);
                break;
            case    1:      //  MotorA_poles [4,6,8], MotorB_poles [4,6,8]
                if  (temps[1] == 4 || temps[1] == 6 || temps[1] == 8)
                    mode.wr(temps[1], MOTAPOLES);
                if  (temps[2] == 4 || temps[2] == 6 || temps[2] == 8)
                    mode.wr(temps[2], MOTBPOLES);
                break;
            case    2:      //  MotorA_ current sense resistors [1 to 4], MotorB_ current sense resistors [1 to 4]
                if  (temps[1] > 0 && temps[1] < 5) 
                    mode.wr(temps[1], MOTADIR);
                if  (temps[2] > 0 && temps[2] < 5)  
                    mode.wr(temps[2], MOTBDIR);
                break;
            case    3:      //  2 Servo1 [0 or 1], Servo2 [0 or 1]
                if  (temps[1] == 0 || temps[1] == 1)
                    mode.wr(temps[1], SVO1);
                if  (temps[2] == 0 || temps[2] == 1)
                    mode.wr(temps[2], SVO2);
                break;
            case    4:      //  3 RCIn1 [0 or 1], RCIn2 [0 or 1]
                if  (temps[1] == 0 || temps[1] == 1)
                    mode.wr(temps[1], RCIN1);
                if  (temps[2] == 0 || temps[2] == 1)
                    mode.wr(temps[2], RCIN2);
                break;
            case    5:      //  4 Board ID '0' to '9'
                if  (temps[1] <= 9)    //  pointless to compare unsigned integer with zero
                    mode.wr('0' | temps[1], BOARD_ID);
                break;
            case    6:      //  TOP_SPEED
                topspeed = a.dbl[1];
                if  (topspeed > 25.0)   topspeed = 25.0;
                if  (topspeed < 1.0)    topspeed = 1.0;
                mode.wr((char)(topspeed * 10.0), TOP_SPEED);
                break;
            case    7:      //  5 Wheel dia mm, Motor pinion teeth, Wheel gear teeth
                mode.wr(temps[1], WHEELDIA);
                mode.wr(temps[2], MOTPIN);
                mode.wr(temps[3], WHEELGEAR);
                break;
            case    8:      //    {2, 5, 2, "Command source 2= COM2 (Touch Screen), 3= Pot, 4= RC Input1, 5= RC Input2"},
                if  (temps[1] > 1 && temps[1] < 6)
                    mode.wr(temps[1], COMM_SRC);
                break;
            case    83: //  set to defaults
                mode.set_defaults   ();
                break;
            case    9:      //  9 Save settings
                mode.save   ();
                pc.printf   ("Saving settings to EEPROM\r\n");
                break;
            default:
                break;
        }       //  endof   switch
    }           //  endof   //  If more than 0 parameters supplied
    else    {
        pc.printf   ("No Changes\r\n");
    }
    pc.printf   ("mode 0\tMotorA_dir [0 or 1]=%d, MotorB_dir [0 or 1]=%d\r\n", mode.rd(MOTADIR), mode.rd(MOTBDIR));
    pc.printf   ("mode 1\tMotorA_poles [4,6,8]=%d, MotorB_poles [4,6,8]=%d\r\n", mode.rd(MOTAPOLES), mode.rd(MOTBPOLES));
    pc.printf   ("mode 2\tMotorA I 0R05 sense Rs [1to4]=%d, MotorB I 0R05 sense Rs [1to4]=%d\r\n", mode.rd(ISHUNTA), mode.rd(ISHUNTB));
    pc.printf   ("mode 3\tServo1 [0 or 1]=%d, Servo2 [0 or 1]=%d\r\n", mode.rd(SVO1), mode.rd(SVO2));
    pc.printf   ("mode 4\tRCIn1 [0 or 1]=%d, RCIn2 [0 or 1]=%d\r\n", mode.rd(RCIN1), mode.rd(RCIN2));
    pc.printf   ("mode 5\tBoard ID ['0' to '9']='%c'\r\n", mode.rd(BOARD_ID));
    pc.printf   ("mode 6\tTop Speed MPH [1.0 to 25.0]=%.1f\r\n", double(mode.rd(TOP_SPEED)) / 10.0);
    pc.printf   ("mode 7\tWheel dia mm=%d, Motor pinion teeth=%d, Wheel gear teeth=%d\r\n", mode.rd(WHEELDIA), mode.rd(MOTPIN), mode.rd(WHEELGEAR));
    pc.printf   ("mode 8\tCommand Src [%d] - 2=COM2 (Touch Screen), 3=Pot, 4=RC In1, 5=RC In2\r\n", mode.rd(COMM_SRC));
    pc.printf   ("mode 83\tSet to defaults\r\n");
    pc.printf   ("mode 9\tSave settings\r\r\n");

}

extern  uint32_t    last_temperature_count;
/**
*   void    temperature_cmd  (struct parameters & a)  {
*   Few boards have temperature sensor fitted. Non-preferred feature
*/
void    temperature_cmd  (struct parameters & a)  {
    if  (a.respond) {
        a.com->printf ("tem%c %d\r\n", mode.rd(BOARD_ID), (last_temperature_count / 16) - 50);
    }
}

/**
*void    rpm_cmd (struct parameters & a) //  to report e.g. RPM 1000 1000 ; speed for both motors
*/
void    rpm_cmd (struct parameters & a) //  to report e.g. RPM 1000 1000 ; speed for both motors
{
    if  (a.respond) 
//        a.com->printf  ("rpm%c %d %d\r%s", mode.rd(BOARD_ID), MotorA.RPM, MotorB.RPM, a.source == SOURCE_PC ? "\n" : "");
        a.com->printf  ("rpm%c %.0f %.0f\r%s", mode.rd(BOARD_ID), MotorA.dRPM, MotorB.dRPM, a.source == SOURCE_PC ? "\n" : "");
}

/**
*void    mph_cmd (struct parameters & a) //  to report miles per hour
*/
void    mph_cmd (struct parameters & a) //  to report miles per hour to 1 decimal place
{
    if  (a.respond) 
//        a.com->printf ("mph%c %.1f\r%s", mode.rd(BOARD_ID), (double)(MotorA.RPM + MotorB.RPM) * rpm2mph / 2.0, a.source == SOURCE_PC ? "\n" : "");
        a.com->printf ("mph%c %.1f\r%s", mode.rd(BOARD_ID), (double)(MotorA.dMPH + MotorB.dMPH) / 2.0, a.source == SOURCE_PC ? "\n" : "");
}

/**
*void    sysV_report (struct parameters & a) //  to report system voltage
*   Reports system link voltage to one decimal place
*/
void    sysV_report (struct parameters & a) //  
{
    if  (a.respond) 
        a.com->printf ("?v%c %.1f\r%s", mode.rd(BOARD_ID), Read_BatteryVolts(), a.source == SOURCE_PC ? "\n" : "");
}

/**
*void    sysI_report (struct parameters & a) //  to report motor currents
*   Reports doubles for each motor current amps to 2 decimal places
*/
void    sysI_report (struct parameters & a) //  
{
    if  (a.respond) //  Calibration, refinement of 6000.0 (not miles out) first guess possible.
//        a.com->printf ("?i%c %.2f %.2f\r%s", mode_bytes[BOARD_ID], (double)MotorA.I.ave / 6000.0, (double)MotorB.I.ave / 6000.0, a.source == SOURCE_PC ? "\n" : "");
//        a.com->printf ("?i%c %.2f %.2f\r%s", mode.rd(BOARD_ID), (double)MotorA.I.ave / 6000.0, MotorA.I.dblave2 / 6000.0, a.source == SOURCE_PC ? "\n" : "");
        a.com->printf ("?i%c %.2f %.2f\r%s", mode.rd(BOARD_ID), MotorA.Idbl, MotorB.Idbl, a.source == SOURCE_PC ? "\n" : "");
}


/**void    vi_cmd (struct parameters & a)
*
*   For setting motor voltage and current limits from pc terminal for test   
*/
void    vi_cmd (struct parameters & a)
{
    setVI   (a.dbl[0] / 100.0, a.dbl[1] / 100.0);
    pc.printf   ("setVI from %s\r\n", a.source == SOURCE_PC ? "PC" : "Touch Screen");
}

/**
*void    v_cmd (struct parameters & a)
*    Set motor voltage limit from either source without checking for addressed board
*/
void    v_cmd (struct parameters & a)
{
    MotorA.set_V_limit  (a.dbl[0] / 100.0);
    MotorB.set_V_limit  (a.dbl[0] / 100.0);
}

/**
*void    i_cmd (struct parameters & a)
*    Set motor current limit from either source without checking for addressed board
*/
void    i_cmd (struct parameters & a)
{
    MotorA.set_I_limit  (a.dbl[0] / 100.0);
    MotorB.set_I_limit  (a.dbl[0] / 100.0);
}

/**
*void    kd_cmd (struct parameters & a)  //  kick and enable the watch dog
*
*/
void    kd_cmd (struct parameters & a)  //  kick the watchdog. Reached from TS or pc.
{
    WatchDog = WATCHDOG_RELOAD + (mode.rd(BOARD_ID) & 0x0f);   //  Reload watchdog timeout. Counted down @ 8Hz
    WatchDogEnable = true;                          //  Receipt of this command sufficient to enable watchdog
}

/**
*void    who_cmd (struct parameters & a)     //  Reachable always from pc. Only addressed board responds to TS
*
*   When using STM3_ESC boards together with 'Brute Touch Screen Controller', controller needs to identify number and identity
*   of all connected STM3_ESC boards. To do this the Touch Screen issues '0who', '1who' ... '9who' allowing time between each
*   for an identified STM3_ESC to respond with 'who7' (if it was number 7) without causing contention of paralleled serial opto isolated bus
*/
void    who_cmd (struct parameters & a)     //  Reachable always from pc. Only addressed board responds to TS
{
    if  (a.source == SOURCE_PC || mode.rd(BOARD_ID) == a.target_unit)
        a.com->printf ("who%c\r%s", mode.rd(BOARD_ID), a.source == SOURCE_PC ? "\n" : "");
}

/**
*void    rcin_pccmd (struct parameters & a)
*
*   For test, reports to pc terminal info about radio control input channels
*/
void    rcin_pccmd (struct parameters & a)
{
    rcin_report ();
}

void    scmd (struct parameters & a)    //  filter coefficient fiddler
{
    switch  ((int)a.dbl[0]) {
        case    1:
            MotorA.s[1] = MotorB.s[1] = a.dbl[1];
            break;
        case    2:
            MotorA.s[2] = MotorB.s[2] = a.dbl[1];
            break;
        case    3:
            MotorA.s[3] = MotorB.s[3] = a.dbl[1];
            break;
        case    4:
            MotorA.s[4] = MotorB.s[4] = a.dbl[1];
            break;
        case    5:
            MotorA.set_speed    (a.dbl[1]);
            MotorB.set_speed    (a.dbl[1]);
            break;
        default:
            pc.printf   ("Wrong use of scmd %f\r\n", a.dbl[0]);
    }
    pc.printf   ("Filter Coeffs 1 to 4\r\n");
    pc.printf   ("1 %.3f\tPscale 0.01-0.5\r\n",     MotorA.s[1]);
    pc.printf   ("2 %.3f\tP_gain 1.0-1000.0\r\n",   MotorA.s[2]);
    pc.printf   ("3 %.3f\tDscale 0.01-0.5\r\n",     MotorA.s[3]);
    pc.printf   ("4 %.3f\tD_gain 1.0-1000.0\r\n",   MotorA.s[4]);
    pc.printf   ("5 Set target_speed\r\n");
}

    struct kb_command  {    //  Commands tabulated as list of these structures as seen below
    const char * cmd_word;              //  points to text e.g. "menu"
    const char * explan;                //  very brief explanation or clue as to purpose of function
    void (*f)(struct parameters &);     //  points to function
}  ;                                    //  Positioned in code here as knowledge needed by following menucmd

/**
*   void    menucmd (struct parameters & a)
*
*   List available terminal commands to pc terminal. No sense in touch screen using this
*/
void    menucmd (struct parameters & a)
{
    if  (a.respond) {
        a.com->printf("\r\n\nDual BLDC ESC type STM3 2018\r\nAt menucmd function - listing commands, source %s:-\r\n", a.source == SOURCE_PC ? "PC" : "TS");
        for(int i = 0; i < a.numof_menu_items; i++)
            a.com->printf("[%s]\t\t%s\r\n", a.command_list[i].cmd_word, a.command_list[i].explan);
        a.com->printf("End of List of Commands\r\n");
    }
}

/********************** END OF COMMAND LINE INTERPRETER COMMANDS *************************************/


/**
*   struct  kb_command const loco_command_list[] = {
*   List of commands accepted from external controller through opto isolated com port 19200, 8,n,1
*/
struct  kb_command const loco_command_list[] = {    //  For comms between STM3_ESC and 'Brute Touch Screen Controller'
    //  ***** Broadcast commands for all STM3_ESC boards to execute. Boards NOT to send serial response *****
    {"fw", "forward", fw_cmd},
    {"re", "reverse", re_cmd},
    {"rb", "regen brake 0 to 99 %", rb_cmd},
    {"hb", "hand brake", hb_cmd},
    {"v", "set motors V percent RANGE 0 to 100", v_cmd},
    {"i", "set motors I percent RANGE 0 to 100", i_cmd},
    {"vi", "set motors V and I percent RANGE 0 to 100", vi_cmd},
    {"kd", "kick the dog, reloads WatchDog", kd_cmd},
    //  ***** Endof Broadcast commands for all STM3_ESC boards to execute. Boards NOT to send serial response *****

    //  ***** Following are rx'd requests for serial response from addressed STM3_ESC only
    {"?v", "Report system bus voltage", sysV_report},
    {"?i", "Report motor both currents", sysI_report},
    {"who", "search for connected units, e.g. 3who returs 'who3' if found", who_cmd},
    {"tem", "report temperature", temperature_cmd},
    {"mph", "read loco speed miles per hour", mph_cmd},
//    {"rvi", "read most recent values sent to pwms", rvi_cmd},
//    {"rdi", "read motor currents and power voltage", rdi_cmd},
    //  ***** Endof
}   ;


/**
struct  kb_command const loco_command_list[] = {
List of commands accepted from external pc through non-opto isolated com port 9600, 8,n,1
*/
struct  kb_command const pc_command_list[] = {
    {"ls", "Lists available commands", menucmd},
    {"?", "Lists available commands, same as ls", menucmd},
#ifdef  USING_DC_MOTORS
    {"mtypes", "report types of motors found", mt_cmd},
#endif
    {"s","1-4, filter param", scmd},
    {"pot", "read drivers control pot", pot_cmd},
    {"fw", "forward", fw_cmd},
    {"re", "reverse", re_cmd},
    {"rb", "regen brake 0 to 99 %", rb_cmd},
    {"hb", "hand brake", hb_cmd},
    {"v", "set motors V percent RANGE 0 to 100", v_cmd},
    {"i", "set motors I percent RANGE 0 to 100", i_cmd},
    {"vi", "set motors V and I percent RANGE 0 to 100", vi_cmd},
    {"?v", "Report system bus voltage", sysV_report},
    {"?i", "Report motor both currents", sysI_report},
    {"who", "search for connected units, e.g. 3who returs 'who3' if found", who_cmd},
    {"mode", "read or set params in eeprom", mode19_cmd},                                 //  Big change Jan 2019
//    {"erase", "set eeprom contents to all 0xff", erase_cmd},
    {"tem", "report temperature", temperature_cmd},                                     //  Reports -50 when sensor not fitted
    {"kd", "kick the dog, reloads WatchDog", kd_cmd},
    {"ver", "Version", ver_cmd},
    {"rcin", "Report Radio Control Input stuff", rcin_pccmd},
    {"rpm", "read motor pair speeds", rpm_cmd},
    {"mph", "read loco speed miles per hour", mph_cmd},
    {"rvi", "read most recent values sent to pwms", rvi_cmd},
    {"rdi", "read motor currents and power voltage", rdi_cmd},
//    {"bc", "bogie constants - wheel dia, motor pinion, wheel gear", bogie_constants_report_cmd},
//    {"gbb", "get bogie bytes from eeprom and report", gbb_cmd},    //  OBSOLETE, replaced by 'gbb'
    {"nu", "do nothing", null_cmd},
}   ;

//    cli_2019    (BufferedSerial * comport, kb_command const * list, int list_len, int source)  {
/**
*   cli_2019    pcc (&pc,   pc_command_list,    sizeof(pc_command_list) / sizeof(kb_command), SOURCE_PC)  ;
*   cli_2019    tsc (&com2, loco_command_list,  sizeof(loco_command_list) / sizeof(kb_command), SOURCE_TS)  ;
*
*   Instantiate two Command Line Interpreters, one for pc terminal and one for touch screen controller
*/
cli_2019    pcc (&pc,   pc_command_list,    sizeof(pc_command_list) / sizeof(kb_command), SOURCE_PC)  ;
cli_2019    tsc (&com2, loco_command_list,  sizeof(loco_command_list) / sizeof(kb_command), SOURCE_TS)  ;

/*
New - March 2018
Using opto isolated serial port, paralleled up using same pair to multiple STM3_ESC boards running this code.
New feature - commands have optional prefix digit 0-9 indicating which unit message is addressed to.
Commands without prefix digit - broadcast to all units, all to obey but none to respond.
Only units recognising its address from prefix digit may respond. This avoids bus contention.
But for BROADCAST commands, '0' may respond on behalf of the group
*/

/**
*   void    cli_2019::test ()  {
*
*   Daft check that class instantiation worked
*/
void    cli_2019::test ()  {
    pc.printf   ("At cli2019::test, source %d len %d,\r\n", a.source, a.numof_menu_items);
}

/**
*   void    cli_2019::core ()  {    
*
*   Command Line Interpreter.
*   This to be called every few milli secs from main programme loop.
*   Reads any rx'd chars into command line buffer, returns when serial buffer empty.
*   If last char rx'd war '\r' end of text delimiter, apt command_list is searched for a matched command in command line
*   If matched command found, apt function is executed.
*   Parameters available to functions from 'parameters' struct.
*/
void    cli_2019::core ()  {    
    int ch, IAm = mode.rd(BOARD_ID);
    char * pEnd;//, * cmd_line_ptr;
    while  (a.com->readable()) {
        ch = a.com->getc();
        if  (clindex > MAX_CMD_LEN)  {   //  trap out stupidly long command lines
            ESC_Error.set   (FAULT_COM_LINE_LEN, 1);                      //  Set FAULT_EEPROM bit 0 if 24LC64 problem
            a.com->printf   ("Error!! Stupidly long cmd line\r\n");
            clindex = 0;
        }
        if(ch != '\r')  {   //  was this the 'Enter' key?
            if  (ch != '\n')       //  Ignore line feeds
                cmdline[clindex++] = ch;  //  added char to command being assembled
        }
        else    {   //  key was CR, may or may not be command to lookup
            a.target_unit = BROADCAST;    //  Set to BROADCAST default once found command line '\r'
            cmdline_ptr = cmdline;
            cmdline[clindex] = 0; //  null terminate command string
            if(clindex)    {   //  If have got some chars to lookup
                int i, wrdlen;
                if  (isdigit(cmdline[0]))  {   //  Look for command with prefix digit
                    cmdline_ptr++;     //  point past identified digit prefix
                    a.target_unit = cmdline[0];  //  '0' to '9'
                    //com->printf ("Got prefix %c\r\n", cmd_line[0]);
                }
                for (i = 0; i < a.numof_menu_items; i++)   {   //  Look for input match in command list
                    wrdlen = strlen(commandlist[i].cmd_word);
                    if(strncmp(commandlist[i].cmd_word, cmdline_ptr, wrdlen) == 0 && !isalpha(cmdline_ptr[wrdlen]))  {   //  If match found
                        for (int k = 0; k < MAX_PARAMS; k++)    {
                            a.dbl[k] = 0.0;
                        }
                        a.position_in_list = i;
                        a.numof_dbls = 0;
                        pEnd = cmdline_ptr + wrdlen;
                        while   (*pEnd)  {          //  Assemble all numerics as doubles
                            a.dbl[a.numof_dbls++] = strtod    (pEnd, &pEnd);
                            while   (*pEnd && !isdigit(*pEnd) && '-' != *pEnd && '+' != *pEnd)  {
                                pEnd++;
                            }
                        }
                        a.respond = a.resp_always;
                        if  (((a.target_unit == BROADCAST) && (IAm == '0')) || (IAm == a.target_unit))
                            a.respond = true; //  sorted 26/4/18
                        //  All boards to obey BROADCAST command, only specific board to obey number prefixed command
                        if  ((a.target_unit == BROADCAST) || (IAm == a.target_unit))
                            commandlist[i].f(a);   //  execute command if addressed to this unit
                        i = a.numof_menu_items + 1;    //  to exit for loop
                    }   //  end of match found
                }       // End of for numof_menu_items
                if(i == a.numof_menu_items) {
//                    a.com->printf("No Match Found for CMD [%s]\r\n", cmdline);
                    pc.printf("No Match Found for CMD [%s]\r\n", cmdline);
                    ESC_Error.set   (FAULT_COM_LINE_NOMATCH, 1);                      //  Set FAULT_EEPROM bit 0 if 24LC64 problem
                }
            }           //  End of If have got some chars to lookup
            clindex = 0;
        }       // End of else key was CR, may or may not be command to lookup
    }           //  End of while (com->readable())
}               //  end of command line interpreter core function