Touch screen drivers control dashboard for miniature locomotive. Features meters for speed, volts, power. Switches for lights, horns. Drives multiple STM3_ESC brushless motor controllers for complete brushless loco system as used in "The Brute" - www.jons-workshop.com

Dependencies:   TS_DISCO_F746NG mbed Servo LCD_DISCO_F746NG BSP_DISCO_F746NG QSPI_DISCO_F746NG AsyncSerial FastPWM

main.cpp

Committer:
JonFreeman
Date:
2018-05-09
Revision:
8:5945d506a872
Parent:
7:3b1f44cd4735
Child:
9:644867052318

File content as of revision 8:5945d506a872:

/*
April 2018  -   Jon Freeman

Touch Screen controller communicates with 1, 2, ... n Twin BLDC Controller boards via opto-isolated serial port.

9 pid D connector retained but wiring NOT compatible with 2017.
This time pins are : -
1   Not Used on TS2018, connection from Twin BLDC Controller only - Pot wiper
2   Not Used on TS2018, connection from Twin BLDC Controller only - GND
3   Not Used on TS2018, connection from Twin BLDC Controller only - Weak +5 (top of pot)
4   Not Used on TS2018, connection from Twin BLDC Controller only - Possible Fwd / Rev switched between pins 2 and 3 above
5   TS2018 high voltage output - power up signal to Twin BLDC Controllers, +10 to + 70 Volt (full supply via 3k3 0.5W safety resistor)
6   Twin BLDC Rx- <- TS2018 Tx data     ||GND to avoid exposing TS +5v rail to the outside world
7   Twin BLDC Rx+ <- TS2018 +5v         ||Tx\ to avoid exposing TS +5v rail to the outside world, INVERTED Txd idles lo
8   Twin BLDC Tx- <- TS2018 GND
9   Twin BLDC Tx+ <- TS2018 Rx data with 1k pullup to +5, line idles hi
*/
#include "mbed.h"
#include "Electric_Loco.h"
#include "AsyncSerial.hpp"
#include "Servo.h"
#include "TS_DISCO_F746NG.h"
#include "LCD_DISCO_F746NG.h"
#include <cctype>
//#include <cerrno>

LCD_DISCO_F746NG    lcd;
TS_DISCO_F746NG     touch_screen;

//FastPWM     maxv    (D12, 1), 
//            maxi    (D11, 1); //  pin, prescaler value
Serial      pc      (USBTX, USBRX);    //  AsyncSerial does not work here. Comms to 'PuTTY' or similar comms programme on pc

DigitalOut  reverse_pin     (D7);   //  These pins no longer used to set mode and direction, now commands issued to com
DigitalOut  forward_pin     (D9);   //was D6, these two decode to fwd, rev, regen_braking and park

DigitalOut  I_sink1          (D14);  //  a horn
DigitalOut  I_sink2          (D15);  //  lamp
DigitalOut  I_sink3          (D3);  //  lamp
DigitalOut  I_sink4          (D4);
DigitalOut  I_sink5          (D5);
DigitalOut led_grn           (LED1); //  the only on board user led

DigitalIn   f_r_switch      (D2);   //  was D0, Reads position of centre-off ignition switch
//DigitalIn   spareio_d8      (D8);
//DigitalIn   spareio_d9      (D9);
DigitalIn   spareio_d10     (D10);  //  D8, D9, D10 wired to jumper on pcb - not used to Apr 2017

AnalogIn    ht_volts_ain    (A0);  //  Jan 2017
AnalogIn    ht_amps_ain     (A1);  //  Jan 2017
//AnalogIn    spare_ain2      (A2);
//AnalogIn    spare_ain3      (A3);
//AnalogIn    spare_ain4      (A4);   //  hardware on pcb for these 3 spare analogue inputs - not used to Apr 2017
//AnalogIn    spare_ain5      (A5); //  causes display flicker !


AsyncSerial  com (A4, A5);   //  Com port to opto isolators on Twin BLDC Controller boards
//AsyncSerial  ir  (D1, D0);    //  Second port does work, but gives the old broken-up display flicker nonsense problem

Servo   servo1    (D6);     //  Now used to adjust Honda speed

extern  uint32_t    odometer_out  ()    ;   //  Read latest total of metres travelled ever
extern  bool    odometer_zero   ()  ;   //  Returns true on success
extern  bool    odometer_update  (uint32_t pulsetot, uint16_t pow, uint16_t volt)  ;   //  Hall pulse total updated once per sec and saved in blocks of 4096 bytes on QSPI onboard memory

extern  int     get_button_press    (struct point & pt) ;
extern  void    displaytext    (int x, int y, const int font, uint32_t BCol, uint32_t TCol, char * txt) ;
extern  void    displaytext    (int x, int y, const int font, char * txt)   ;
extern  void    displaytext    (int x, int y, char * txt)   ;
extern  void    setup_buttons    ()  ;
extern  void    draw_numeric_keypad (int    colour) ;
extern  void    draw_button_hilight     (int bu, int colour)  ;
extern  void    read_presses    (int * a)   ;
extern  void    read_keypresses    (struct ky_bd & a)   ;
extern  void    SliderGraphic (struct slide & q)   ;
extern  void    vm_set  ()  ;
extern  void    update_meters  (double, double, double)  ;

extern  void    setup_pccom ()  ;
extern  void    setup_lococom ()  ;
extern  void    clicore (struct parameters & a) ;
extern  struct  parameters pccom, lococom;

static  const   int
    DAMPER_DECAY        = 42,   //  Small num -> fast 'viscous damper' on dead-mans function with finger removed from panel
    MAF_PTS             = 140,      //  Moving Average Filter points. Filters reduce noise on volatage and current readings
    FWD                 = 0,
    REV                 = ~FWD;

//from .h    struct  slide   {   int position;    int    oldpos; int state; int direction;   bool recalc_run;    bool handbrake_slipping;    double handbrake_effort;   double   loco_speed  }   ;
struct slide    slider  ;


int     V_maf[MAF_PTS + 2],    I_maf[MAF_PTS + 2],  maf_ptr = 0;    //  ********* These should be uint16_t
uint32_t    sys_timer_32Hz = 0;
double  last_V = 0.0, last_I = 0.0, recent_distance = 0.0;

bool    qtrsec_trig                 = false;
bool    trigger_current_read        = false;
volatile    bool    trigger_32ms    = false;

class   speed_2018
{
    static const int    SPEED_AVE_PTS   = 5;    //  AVE_PTS - points in moving average filters
    uint32_t    speed_maf_mem   [(SPEED_AVE_PTS + 1) * 2][8],   //  Allow for up to 8 axles
                axle_total  [8],    //  allow up to 8 axles
                mafptr,
                board_IDs   [4],    //  allow up to 4 boards
                board_count,
                b, reqno;
public:
    speed_2018  ()  {   //  Constructor
        memset(speed_maf_mem, 0, sizeof(speed_maf_mem));
        for (int i = 0; i < sizeof(axle_total) / sizeof(uint32_t); i++)
            axle_total[i] = 0;
        mafptr = 0;
        board_count = 0;
        b = 0;
        reqno = 0;
    }
    bool    request_rpm ()  ;
    void    rpm_update(struct parameters & a)  ;
    void    set_board_IDs (uint32_t *)   ;
    double  mph ()  ;
}
   speed  ;

void    speed_2018::set_board_IDs (uint32_t * a)   {
    board_count = 0;
    while   (a[board_count])  {
        board_IDs[board_count] = a[board_count];
        board_count++;
    }
    pc.printf   ("set_board_IDs %d\r\n", board_count);
}

double  speed_2018::mph ()  {
    if  (!board_count)  {
//        pc.printf   ("No boards\r\n");
        return  0.0;
    }
    int t[8] = {0,0,0,0,0,0,0,0};
    for (int i = 0; i < SPEED_AVE_PTS; i++) {
        for (int j = 0; j < board_count * 2; j++)
            t[j] += speed_maf_mem[i][j];
    }
    int j = 0;
    for (int i = 0; i < board_count * 2; i++) {
        j += t[i];
        axle_total[i] = t[i];
    }
    return  (rpm2mph * ((double) j) / (SPEED_AVE_PTS * board_count * 2));
}

bool    speed_2018::request_rpm ()  {   //  Issue "'n'rpm\r" to BLDC board to request RPM
    if  (board_IDs[0] == 0)
        return  false;      //  No boards identified
    if  (board_IDs[reqno] == 0)
        reqno = 0;
    com.putc    (board_IDs[reqno++]);
    com.printf  ("rpm\r");
    return  true;
}

void    speed_2018::rpm_update(struct parameters & a)  {    //  Puts new readings into mem
    speed_maf_mem   [mafptr][b++] = (uint32_t)a.dbl[0];
    speed_maf_mem   [mafptr][b++] = (uint32_t)a.dbl[1];
    if  ((b + 1) >= (board_count * 2))    {
        b = 0;
        mafptr++;
        if  (mafptr >= SPEED_AVE_PTS)   
            mafptr = 0;
    }
}


void    rpm_push    (struct parameters & a)  {  //  Latest RPM reports from Dual BLDC Motor Controllers arrive here
    speed.rpm_update   (a);
//    pc.printf   ("RPM%d %d, mph %.1f\r\n", (int)a.dbl[0], (int)a.dbl[1], speed2.mph());
}



void    set_V_limit (double p)  //  Sets max motor voltage
{
    if  (p < 0.0)
        p = 0.0;
    if  (p > 1.0)
        p = 1.0;
    last_V = p;
    com.printf  ("v%d\r", (int)(p * 99.0));
}

void    set_I_limit (double p)     //  Sets max motor current
{
    if  (p < 0.0)
        p = 0.0;
    if  (p > 1.0)
        p = 1.0;
    last_I = p;     //  New 30/4/2018 ; no use for this yet, included to be consistent with V
    com.printf  ("i%d\r", (int)(p * 99.0));
}

double  read_ammeter ()
{
    int32_t a = 0;
    for (int b = 0; b < MAF_PTS; b++)
        a += I_maf[b];
    a /= MAF_PTS;
    a -= 0x4000;
    double i = (double) (0 - a);
    return  i / 200.0;      //  Fiddled to get current reading close enough
}

double  read_voltmeter   ()
{
    int a = 0;
    for (int b = 0; b < MAF_PTS; b++)
        a += V_maf[b];
    a /= MAF_PTS;
    double v = (double) a;
    return  (v / 932.0) + 0.0;  //  fiddled to suit resistor values
}

//  Interrupt Service Routines

void    ISR_current_reader  (void)      //  FIXED at 250us
{
    static  int ms32 = 0, ms250 = 0;
    trigger_current_read    = true; //  every 250us, i.e. 4kHz  NOTE only sets trigger here, readings taken in main loop
    ms32++;
    if  (ms32 > 124)    {   //  31.25ms, not 32ms, is 32Hz
        ms32 = 0;
        sys_timer_32Hz++;   //  , usable anywhere as general measure of elapsed time
        trigger_32ms = true;
        ms250++;
        if  (ms250 > 7) {
            ms250 = 0;
            qtrsec_trig = true;
        }
    }
}

//  End of Interrupt Service Routines


bool    inlist  (struct ky_bd & a, int key)
{
    int i = 0;
    while   (i < a.count)  {
        if  (key == a.ky[i].keynum)
            return  true;
        i++;
    }
    return  false;
}


void    stuff_to_do_every_250us  ()     //  Take readings of system voltage and current
{
    if  (!trigger_current_read)
        return;
    trigger_current_read = false;
    int ch;
    ch++;
    I_maf[maf_ptr] = ht_amps_ain.read_u16();    //  Read ACS709 ammeter module
    V_maf[maf_ptr] = ht_volts_ain.read_u16();   //  Read system voltage
    maf_ptr++;
    if  (maf_ptr > MAF_PTS - 1)
        maf_ptr = 0;
}

void    set_run_mode    (int    mode)
{   //  NOTE Nov 2017 - Handbrake not implemented
    if  (mode == HANDBRAKE_SLIPPING)    slider.handbrake_slipping = true;
    else                                slider.handbrake_slipping = false;
    switch  (mode) {
            //            STATES, INACTIVE, RUN, NEUTRAL_DRIFT, REGEN_BRAKE, PARK};
//        case    HANDBRAKE_SLIPPING:
//            break;
        case    PARK:     //  PARKED new rom code IS now finished.
//            forward_pin = 0;
//            reverse_pin = 0;
            slider.state = mode;
            set_V_limit     (0.075); // was 0.1
            set_I_limit (slider.handbrake_effort);
//            char    tmp[16];
//            sprintf (tmp, "vi7 %d\r", (int)(slider.handbrake_effort * 99.0));
//            com.printf  ("%s", tmp);
            break;
        case    REGEN_BRAKE:    //  BRAKING, pwm affects degree
            com.printf  ("rb\r");
//            forward_pin = 1;
//            reverse_pin = 1;
            slider.state = mode;
            break;
        case    NEUTRAL_DRIFT:
            slider.state = mode;
            set_I_limit (0.0);      //  added after first test runs, looking for cause of mechanical startup snatch
            set_V_limit (0.0);      //  added after first test runs, looking for cause of mechanical startup snatch
            break;
        case    RUN:
            if  (slider.direction)  {
                com.printf  ("fw\r");
//                forward_pin = 0;
//                reverse_pin = 1;
            } else    {
                com.printf  ("re\r");
//                forward_pin = 1;
//                reverse_pin = 0;
            }
            slider.state = mode;
            break;
        default:
            break;
    }
}


void    throttle    (double p)  {            // New Apr 2018 ; servo adjusts throttle lever on Honda GX120
const   double  THR_MAX = 0.92;
const   double  THR_MIN = 0.09;
const   double  RANGE = THR_MAX - THR_MIN;
    if  (p > 1.0)
        p = 1.0;
    if  (p < 0.0)
        p = 0.0;
    //  p = 1.0 - p;    //  if direction needs swapping
    p *= RANGE;
    p += THR_MIN;
    servo1 = p;
}


void    lights  (int onoff)  {
    I_sink2 = onoff;    //  lamp right
    I_sink3 = onoff;    //  lamp left
}

int main()
{
    int qtr_sec = 0, seconds = 0, minutes = 0;
    double  electrical_power_Watt = 0.0;
    ky_bd   kybd_a, kybd_b;
    memset  (&kybd_a, 0, sizeof(kybd_a));
    memset  (&kybd_b, 0, sizeof(kybd_b));
    pc.baud (9600);
    com.baud (19200);
    pc.printf   ("\r\n\n\nLoco_TS_2018 Loco Controller starting, testing qspi mem\r\n");
//    ir.baud (1200);

    I_sink1 = 0;    //  turn outputs off
    I_sink2 = 0;    //  lamp right
    I_sink3 = 0;    //  lamp left
    I_sink4 = 0;
    I_sink5 = 0;
//    spareio_d8.mode (PullUp); now output driving throttle servo
//    spareio_d9.mode (PullUp);
    spareio_d10.mode(PullUp);

    Ticker  tick250us;

//  Setup User Interrupt Vectors
    tick250us.attach_us (&ISR_current_reader, 250);    //  count 125 of these to trig 31.25ms
//    tick32ms.attach_us  (&ISR_tick_32ms, 32001);
//    tick32ms.attach_us  (&ISR_tick_32ms, 31250);    //  then count 8 pulses per 250ms
//    tick250ms.attach_us (&ISR_tick_250ms, 250002);
#ifdef  QSPI

extern  int qspifindfree    (uint8_t* dest, uint32_t addr)  ;
extern  int ask_QSPI_OK ()  ;
extern  bool    qspimemcheck    ()  ;
extern  int qspiinit    ()  ;
    int qspi_ok = ask_QSPI_OK   ();
//extern  int qspieraseblock  (uint32_t addr)  ;
//extern  int qspiwr  (uint8_t* src, uint32_t addr) ;
//extern  int qspiwr  (uint8_t* src, uint32_t addr, uint32_t len) ;
//extern  int qspird  (uint8_t* dest, uint32_t addr, uint32_t len)  ;

//#define BUFFER_SIZE         ((uint32_t)32)
//#define QSPI_BUFFER_SIZE         ((uint32_t)4270)           //  Big enough for 4096 byte block
//#define WRITE_READ_ADDR     ((uint32_t)0x0050)
//#define WRITE_READ_ADDR     ((uint32_t)0x0010)
//#define WRITE_READ_ADDR2    ((uint32_t)0x0020)
//#define WRITE_READ_ADDR3    ((uint32_t)0x4030)
//#define QSPI_BASE_ADDR      ((uint32_t)0x90000000)

    //                                  123456789012345
//    uint8_t WriteBuffer[QSPI_BUFFER_SIZE] = "Hello World !!!\0";
//    uint8_t ReadBuffer[QSPI_BUFFER_SIZE];
//    const uint8_t MemInitString[] = "Electric Locomotive Controller - Jon Freeman B. Eng. Hons - November 2017\0";  
//    const uint8_t Ifound_String[] = "I found the man sir, god I wish I hadn't!\0";  
    
//    pc.printf   ("[%s]\r\n", MemInitString);
//    pc.printf   ("[%s]\r\n", Ifound_String);
//    pc.printf("\n\nQSPI demo started\r\n");

    // Check initialization
    if (qspiinit() != qspi_ok)
      error("Initialization FAILED\r\n");
    else
      pc.printf("Initialization PASSED\r\n");
    
    // Check memory informations
    if  (!qspimemcheck    ())
        pc.printf   ("Problem with qspimemcheck\r\n");
/*    // Erase memory
    qspieraseblock  (WRITE_READ_ADDR);
    qspieraseblock  (WRITE_READ_ADDR2);
    qspieraseblock  (WRITE_READ_ADDR3);
    qspieraseblock  (0x02000);
    // Write memory
    qspiwr(WriteBuffer, WRITE_READ_ADDR);
    qspird(ReadBuffer, WRITE_READ_ADDR, 20);
    qspiwr((uint8_t*)"Oh what a joy it is.", 0x02000);
    qspird(ReadBuffer, 0x02000, 20);
    qspieraseblock  (0x02000);
*/    // Read memory
//    if (qspi.Read(ReadBuffer, WRITE_READ_ADDR, 11) != QSPI_OK)
/*    qspird(ReadBuffer, WRITE_READ_ADDR, 256);
    qspird(ReadBuffer, WRITE_READ_ADDR + 1, 256);
    qspird(ReadBuffer, 0, 256);

    // Jon's play with Write memory
    qspiwr  ((uint8_t*)MemInitString, WRITE_READ_ADDR2);
    qspiwr  ((uint8_t*)Ifound_String, WRITE_READ_ADDR3);

    qspird(ReadBuffer, 0, 256); //  shows correct write of "Electric Locomotive Controller" after "Hello World !!!"
    qspird(ReadBuffer, WRITE_READ_ADDR2, 250);

    qspird(ReadBuffer, WRITE_READ_ADDR3, 250);
    pc.printf   ("\r\r\r\n");
    qspiwr  ((uint8_t*)"Today I have to pack the car full of all sorts of stuff including 7.25 gauge loco and take it up to Begbrook to show members of Bristol Society of Model and Experimental Engineers!", 2000);
    qspird(ReadBuffer, 0, 4096);

    int pos = qspifindfree    (ReadBuffer, 0);
    pc.printf   ("qspifindfree reports %d\r\n", pos);
*/
//bool    odometer_update  (uint32_t pulsetot, uint16_t pow, uint16_t volt)  ;   //  Hall pulse total updated once per sec and saved in blocks of 4096 bytes on QSPI onboard memory

#endif

    if  (f_r_switch)    {
        slider.direction = FWD; //  make decision from key switch position here
        com.printf  ("fw\r");
    }
    else    {
        slider.direction = REV; //  make decision from key switch position here
        com.printf  ("re\r");
    }
//    max_pwm_ticks   = SystemCoreClock / (2 * PWM_HZ);  //  prescaler min value is 2, or so it would seem. SystemCoreClock returns 216000000 on F746NG board
//    maxv.period_ticks      (MAX_PWM_TICKS + 1);  //  around 18 kHz
//    maxi.period_ticks      (MAX_PWM_TICKS + 1);
    set_I_limit (0.0);
    set_V_limit (0.0);
    throttle    (0.0);      //  Set revs to idle. Start engine and warm up before powering up control
    setup_pccom   ();
    setup_lococom ();
    pc.printf   ("Jon's Touch Screen Loco 2018 sytem starting up %s\r\n", slider.direction ? "Forward":"Reverse");
    uint8_t lcd_status = touch_screen.Init(lcd.GetXSize(), lcd.GetYSize());
    if (lcd_status != TS_OK) {
        lcd.Clear(LCD_COLOR_RED);
        lcd.SetBackColor(LCD_COLOR_RED);
        lcd.SetTextColor(LCD_COLOR_WHITE);
        lcd.DisplayStringAt(0, LINE(5), (uint8_t *)"TOUCHSCREEN INIT FAIL", CENTER_MODE);
        wait    (20);
    } else {
        lcd.Clear(LCD_COLOR_DARKBLUE);
        lcd.SetBackColor(LCD_COLOR_GREEN);
        lcd.SetTextColor(LCD_COLOR_WHITE);
        lcd.DisplayStringAt(0, LINE(5), (uint8_t *)"TOUCHSCREEN INIT OK", CENTER_MODE);
    }

    lcd.SetFont(&Font16);
    lcd.Clear(LCD_COLOR_LIGHTGRAY);
    setup_buttons(); //  draws buttons

    slider.oldpos = 0;
    slider.loco_speed = 0.0;
    slider.handbrake_effort = 0.1;
    slider.position = MAX_POS - 2;   //  Low down in REGEN_BRAKE position - NOT to power-up in PARK
    SliderGraphic  (slider);    //  sets slider.state to value determined by slider.position
    set_run_mode    (REGEN_BRAKE);    //  sets slider.mode

    lcd.SetBackColor(LCD_COLOR_DARKBLUE);

    vm_set();   //  Draw 3 analogue meter movements, speedo, voltmeter, ammeter

//    if  (odometer_zero   ())
//        pc.printf   ("TRUE from odometer_zero\r\n");
//    else
//        pc.printf   ("FALSE from odometer_zero\r\n");
    double  torque_req = 0.0;
    bool    toggle32ms = false;
    //  Main loop

    uint32_t boards_fitted[8], bfptr = 0;
    for (int i = 0; i < 8; i++)
        boards_fitted[i] = 0;
    sys_timer_32Hz  = 0;

    int     last_digit = 0, board_cnt = 0, ch;
    while   (sys_timer_32Hz < 12)   {       //  Sniff out system, discover motor controllers connected
        while   (!trigger_32ms)
//            command_line_interpreter    ();
            clicore    (pccom);
        trigger_32ms = false;
        if  (sys_timer_32Hz < 11)   {       //  issue "0who\r", "1who\r" ... "9who\r"
            com.putc    ((sys_timer_32Hz - 1) | '0');
            com.printf  ("who\r");
        }
        while   (com.readable())  {
            ch = com.getc();
            if  (ch != '\r') {
                if  (isdigit(ch))
                    last_digit = ch;
            }
            else    {   //  got '\r' at end of line
                if  (isdigit(last_digit))
                    boards_fitted[board_cnt++] = last_digit;
                last_digit = 0;
            }
        }
    }

//    boards_fitted[0] = '4';
//    boards_fitted[1] = '5';

    while   (boards_fitted[bfptr])    { //  This works, identified BLDC Motor Controller board ID chars '0' to '9' listed in boards_fitted[]
        pc.printf   ("Board %c found\r\n", boards_fitted[bfptr++]);
    }
    speed.set_board_IDs   (boards_fitted);    //  store number and IDs of Dual BLDC boards identified
    bfptr = 0;
    clicore (pccom);
    pc.printf   ("pcmenuitems %d, commenuitems %d\r\n", pccom.numof_menu_items, lococom.numof_menu_items);
//    perror  (strerror(errno));
    //  Done setup, time to roll !
    
    lights  (1);    //  Headlights ON!

    while (1) {

        struct ky_bd * present_kybd, * previous_kybd;
        bool    sliderpress = false;
//        command_line_interpreter    ()  ;   //  Do any actions from command line via usb link
        clicore (pccom);

        stuff_to_do_every_250us     ()  ;

        if  (trigger_32ms == true)  {       //  Stuff to do every 32 milli secs
            trigger_32ms = false;
            clicore (lococom);
            toggle32ms = !toggle32ms;
            if  (toggle32ms)    {
                present_kybd = &kybd_a;
                previous_kybd = &kybd_b;
            }   else    {
                present_kybd = &kybd_b;
                previous_kybd = &kybd_a;
            }
            read_keypresses   (*present_kybd);
            sliderpress = false;
            slider.recalc_run = false;
            int j = 0;
//        if  (present2->count > previous_kybd->count)            pc.printf   ("More presses\r\n");
//        if  (present2->count < previous_kybd->count)            pc.printf   ("Fewer presses\r\n");
            if  (present_kybd->count || previous_kybd->count)   {   //  at least one key pressed this time or last time
                int k;
                double  dbl;
//            pc.printf   ("Keys action may be required");
                //  if key in present and ! in previous, found new key press to handle
                //  if key ! in present and in previous, found new key release to handle
                if  (inlist(*present_kybd, SLIDER))  {                      //  Finger is on slider, so Update slider graphic here
                    sliderpress = true;
                    k = present_kybd->slider_y;     //  get position of finger on slider
                    if  (slider.state == RUN && k != slider.position)       //  Finger has moved within RUN range
                        slider.recalc_run   = true;
                    if  (slider.state == RUN && k >= NEUTRAL_VAL)   {       //  Finger has moved from RUN to BRAKE range
                        slider.position = k = NEUTRAL_VAL;          //  kill drive for rapid reaction to braking
                        throttle    (0.0);
                    }

                    else    {           //  nice slow non-jerky glidey movement required
                        dbl = (double)(k - slider.position);
                        dbl /= 13.179;  //  Where did 13.179 come from ?
                        if  (dbl < 0.0)
                            dbl -= 1.0;
                        if  (dbl > 0.0)
                            dbl += 1.0;
                        slider.position += (int)dbl;
                    }
                    SliderGraphic   (slider);    //  sets slider.state to value determined by slider.position
                    set_run_mode    (slider.state);
                    draw_button_hilight     (SLIDER, LCD_COLOR_YELLOW)  ;

                    if  (slider.state == REGEN_BRAKE) {
                        double  brake_effort = ((double)(slider.position - NEUTRAL_VAL)
                                                / (double)(MAX_POS - NEUTRAL_VAL));
                        //  brake_effort normalised to range 0.0 to 1.0
                        brake_effort *= 0.97;  //  upper limit to braking effort, observed effect before was quite fierce
                        pc.printf   ("Brake effort %.2f\r\n", brake_effort);
                        /* set_pwm (brake_effort); */
                        set_V_limit (sqrt(brake_effort));   //  sqrt gives more linear feel to control
                        set_I_limit (1.0);
                        throttle    (0.0);
                    }
                }   else    {   //                pc.printf   ("Slider not touched\r\n");
                }

                j = 0;
                while   (j < present_kybd->count)   {   //  handle new key presses
                    k = present_kybd->ky[j++].keynum;
                    if  (inlist(*present_kybd, k))    {
                        switch  (k)    {    //  Here for auto-repeat type key behaviour
                            case    21:     //  key is 'voltmeter'
//                                set_V_limit (last_V * 1.002 + 0.001);
                                break;
                            case    22:     //  key is 'ammeter'
//                                set_V_limit (last_V * 0.99);
                                break;
                        }   //  endof switch (k)
                    }       //  endof if (inlist(*present2, k)) {
                    if  (inlist(*present_kybd, k) && !inlist(*previous_kybd, k))    {   //  New key press detected
                        pc.printf   ("Handle Press %d\r\n", k);
                        draw_button_hilight     (k, LCD_COLOR_YELLOW)  ;
                        switch  (k)    {    //  Handle new touch screen button presses here - single action per press, not autorepeat
                            case    SPEEDO_BUT:  //
                                pc.printf   ("Speedometer key pressed %d\r\n", k);
                                break;
                            case    VMETER_BUT:  //
//                                pc.printf   ("Voltmeter key pressed %d\r\n", k);
                                I_sink5 = 1;    //  Turn on hi-horn
                                break;
                            case    AMETER_BUT:  //
//                                pc.printf   ("Ammeter key pressed %d\r\n", k);
                                I_sink4 = 1;    //  Turn on lo-horn
                                break;
                            default:
                                pc.printf   ("Unhandled keypress %d\r\n", k);
                                break;
                        }       //  endof   switch  (button)
                    }
                }           //  endof while - handle new key presses
                j = 0;
                while   (j < previous_kybd->count)   {   //  handle new key releases
                    k = previous_kybd->ky[j++].keynum;
                    if  (inlist(*previous_kybd, k) && !inlist(*present_kybd, k))    {
                        pc.printf   ("Handle Release %d\r\n", k);
                        draw_button_hilight     (k, LCD_COLOR_DARKBLUE)  ;
                        switch  (k)    {    //  Handle new touch screen button RELEASes here - single action per press, not autorepeat
                            case    SPEEDO_BUT:  //
                                pc.printf   ("Speedometer key released %d\r\n", k);
                                break;
                            case    VMETER_BUT:  //
                                I_sink5 = 0;    //  Turn hi-tone horn off
//                                pc.printf   ("Voltmeter key released %d\r\n", k);
                                break;
                            case    AMETER_BUT:  //
                                I_sink4 = 0;    //  Turn lo-tone horn off
//                                pc.printf   ("Ammeter key released %d\r\n", k);
                                break;
                            default:
                                pc.printf   ("Unhandled keyreleas %d\r\n", k);
                                break;
                        }       //  endof   switch  (button)
                    }
                }       //  endof while - handle new key releases
            }   //  endof at least one key pressed this time or last time

            if  (sliderpress == false)  {           //  need to glide dead-mans function towards neutral here
                if  (slider.position < NEUTRAL_VAL)    {
                    slider.position += 1 + (NEUTRAL_VAL - slider.position) / DAMPER_DECAY;
                    SliderGraphic   (slider);
                    slider.recalc_run = true;
                }
            }

            if  (slider.recalc_run) {   //  range of slider.position in RUN mode is min_pos_() to NEUTRAL_VAL - 1
                slider.recalc_run = false;  //  All RUN power and pwm calcs done here
                int b = slider.position;
                if  (b > NEUTRAL_VAL)
                    b = NEUTRAL_VAL;
                if  (b < MIN_POS)   //  if finger position is above top of slider limit
                    b = MIN_POS;
                b = NEUTRAL_VAL - b;    //  now got integer going positive for increasing power demand
                torque_req = (double) b;
                torque_req /= (NEUTRAL_VAL - MIN_POS);  //  in range 0.0 to 1.0
                pc.printf   ("torque_rec = %.3f, last_V = %.3f\r\n", torque_req, last_V);
                set_I_limit (torque_req);
                if  (torque_req < 0.05) {
                    set_V_limit (last_V / 2.0);
                    throttle    (torque_req * 6.0);
                }
                else    {
                    throttle    (0.3 + (torque_req / 2.0));
                    if  (last_V < 0.99)
                        set_V_limit (last_V + 0.05);   //  ramp voltage up rather than slam to max
                }
            }
        }       //  endof doing 32ms stuff

        if  (qtrsec_trig == true)  {    //  do every quarter second stuff here
            qtrsec_trig = false;
            double  speedmph = speed.mph(), amps = read_ammeter(), volts = read_voltmeter();
            slider.loco_speed = speedmph;
            electrical_power_Watt = volts * amps;   //  visible throughout main
            update_meters  (speedmph, electrical_power_Watt, volts)  ;   //  displays speed, volts and power (volts times amps)
            led_grn = !led_grn;
            if  (slider.state == PARK)   {
                if  (speedmph > LOCO_HANDBRAKE_ESCAPE_SPEED / 4.0)    {
                    slider.handbrake_effort *= 1.1;
                    if  (slider.handbrake_effort > 0.55)    slider.handbrake_effort = 0.55;
                    set_run_mode    (PARK);
                    pc.printf   ("Handbrake slipping, effort %.2f\r\n", slider.handbrake_effort);
                }
                if  (speedmph < 0.02)    {
                    slider.handbrake_effort *= 0.9;
                    if  (slider.handbrake_effort < 0.05)    slider.handbrake_effort = 0.05;
                    set_run_mode    (PARK);
                    pc.printf   ("Handbrake not slipping, effort %.2f\r\n", slider.handbrake_effort);
                }
            }
            speed.request_rpm   (); //  issues "'n'rpm\r", takes care of cycling through available boards in sequence
//            switch  (qtr_sec)   {   //  Can do sequential stuff quarter second apart here
//            }   //  End of switch qtr_sec
            qtr_sec++;
            //  Can do stuff once per second here
            if(qtr_sec > 3) {
                qtr_sec = 0;
                seconds++;
                com.printf  ("kd\r");       //  Kick the WatchDog timers in the Twin BLDC drive boards
                if  (seconds > 59)  {
                    seconds = 0;
                    minutes++;
                    //  do once per minute stuff here
                }   //  fall back into once per second
#ifdef  QSPI

//            recent_distance += 300;

            recent_distance += (speedmph * (111.76 * 4.0));    //  Convert mph to distance mm travelled in one second
            uint32_t    new_metres = ((uint32_t)recent_distance) / 1000;
            recent_distance -= (double)(new_metres * 1000);
            if  (!odometer_update (new_metres, (uint16_t)electrical_power_Watt, (uint16_t)(volts * 500.0)))
                pc.printf   ("Probs with odometer_update");
            char    dist[20];
            sprintf (dist, "%05d m", odometer_out());
            displaytext (236, 226, 2, dist);

/*
    Odometer Update stuff now needs fixing to take updagte in form of mm travelled in previous period
    
//bool    odometer_update  (uint32_t pulsetot, uint16_t pow, uint16_t volt)  ;   //  Hall pulse total updated once per sec and saved in blocks of 4096 bytes on QSPI onboard memory
bool    distance_measurement::update  (uint32_t new_metres_travelled, uint16_t powr, uint16_t volt)  {
                if  (!odometer_update (historic_distance, (uint16_t)electrical_power_Watt, (uint16_t)(volts * 500.0)))
                    pc.printf   ("Probs with odometer_update");
                char    dist[20];
//                sprintf (dist, "%05d m", speed.metres_travelled());
                displaytext (236, 226, 2, dist);
*/
#endif
//                calc_motor_amps( mva);
            }   //  endof if(qtr_sec > 3
        }       //  endof if  (qtrsec_trig == true)  {
    }           //  endof while(1) main programme loop
}