Electric Locomotive control system. Touch screen driver control, includes regenerative braking, drives 4 brushless motors, displays speed MPH, system volts and power

Dependencies:   BSP_DISCO_F746NG FastPWM LCD_DISCO_F746NG SD_DISCO_F746NG TS_DISCO_F746NG mbed

graphics.cpp

Committer:
JonFreeman
Date:
11 months ago
Revision:
1:8ef34deb5177
Parent:
0:23cc72b18e74

File content as of revision 1:8ef34deb5177:

#include "mbed.h"
#include "TS_DISCO_F746NG.h"
#include "LCD_DISCO_F746NG.h"
#include "Electric_Loco.h"

#define VOLTMETER_X 68      //  Voltmeter screen position
#define VOLTMETER_Y 68
#define AMMETER_X   68      //  Ammeter screen position - Now replaced by Power meter
#define AMMETER_Y   202
#define SPEEDO_X    274     //  Speedometer screen position
#define SPEEDO_Y    135
#define V_A_SIZE    54      //  Size of voltmeter and ammeter
#define SPEEDO_SIZE 112

#define SPEEDO_BODY_COLOUR  LCD_COLOR_BLACK
#define SPEEDO_DIAL_COLOUR  LCD_COLOR_WHITE
#define SPEEDO_TEXT_COLOUR  LCD_COLOR_BLUE

#define VMETER_BODY_COLOUR  LCD_COLOR_BLACK
#define VMETER_DIAL_COLOUR  LCD_COLOR_WHITE
#define VMETER_TEXT_COLOUR  LCD_COLOR_BLUE

#define AMETER_BODY_COLOUR  LCD_COLOR_BLACK
#define AMETER_DIAL_COLOUR  LCD_COLOR_WHITE
#define AMETER_TEXT_COLOUR  LCD_COLOR_BLUE

extern  LCD_DISCO_F746NG    lcd;
extern  TS_DISCO_F746NG     touch_screen;
extern  Serial pc;

static const int    char_widths[]   = {5, 7, 11, 14, 17, 17}    ,
                    meter_radius_min = 30, meter_radius_max = 120;


//  Uses our own generated sine and cosines from lookup table.  For some unexplained reason, using inbuilt sin and cos fns cause display flicker !
extern double  jcos  (double angle);    //  Used in DrawNeedle, plain sin and cos functions cause display flicker !!
extern double  jsin  (double angle);

/*void    costabgen  (int points)  {
    double  angle = 0.0;
    while   (angle < 2.1 * PI)  {
        pc.printf   ("Angle %f, my cos %+f, c cos %+f\r\n", angle, jcos(angle), cos(angle));
//        pc.printf   ("Angle %f, my sin %+f, c sin %+f\r\n", angle, jsin(angle), sin(angle));
        angle += PI / 24;
    }
//    double  angle;
*//*    int step, perline = 0;
    double  interval = PI / 2.0 / (double)points;
    pc.printf   ("//At costabgen with %d points\r\n", points);
    pc.printf   ("static const double costab[] = {\r\n");
    for (step = 0; step <= points; step++)  {
        angle   = interval * (double)step;
//        pc.printf   ("cos %+.3f = %+.3f\r\n", angle, cos(angle));
        if  (++perline == 8)  {
            pc.printf   ("%+.6f,\r\n", cos(angle));
            perline = 0;
        }
        else
            pc.printf   ("%+.6f, ", cos(angle));
        wait    (0.025);
    }
    pc.printf   ("0.0\t}\t;\r\n//End of costab\r\n");
*/
//}

/**
  * @brief  Fills a triangle (between 3 points).
  * @param  x1: Point 1 X position
  * @param  y1: Point 1 Y position
  * @param  x2: Point 2 X position
  * @param  y2: Point 2 Y position
  * @param  x3: Point 3 X position
  * @param  y3: Point 3 Y position
  * @retval None
  */
static void FillTriangle(uint16_t x1, uint16_t x2, uint16_t x3, uint16_t y1, uint16_t y2, uint16_t y3)
{
    int16_t deltax = 0, deltay = 0, x = 0, y = 0, xinc1 = 0, xinc2 = 0,
            yinc1 = 0, yinc2 = 0, den = 0, num = 0, num_add = 0, num_pixels = 0,
            curpixel = 0;

    deltax = abs(x2 - x1);        /* The difference between the x's */
    deltay = abs(y2 - y1);        /* The difference between the y's */
    x = x1;                       /* Start x off at the first pixel */
    y = y1;                       /* Start y off at the first pixel */

    if (x2 >= x1) {               /* The x-values are increasing */
        xinc1 = 1;
        xinc2 = 1;
    } else {                      /* The x-values are decreasing */
        xinc1 = -1;
        xinc2 = -1;
    }

    if (y2 >= y1) {               /* The y-values are increasing */
        yinc1 = 1;
        yinc2 = 1;
    } else {                      /* The y-values are decreasing */
        yinc1 = -1;
        yinc2 = -1;
    }

    if (deltax >= deltay) {       /* There is at least one x-value for every y-value */
        xinc1 = 0;                  /* Don't change the x when numerator >= denominator */
        yinc2 = 0;                  /* Don't change the y for every iteration */
        den = deltax;
        num = deltax / 2;
        num_add = deltay;
        num_pixels = deltax;         /* There are more x-values than y-values */
    } else {                      /* There is at least one y-value for every x-value */
        xinc2 = 0;                  /* Don't change the x for every iteration */
        yinc1 = 0;                  /* Don't change the y when numerator >= denominator */
        den = deltay;
        num = deltay / 2;
        num_add = deltax;
        num_pixels = deltay;         /* There are more y-values than x-values */
    }

    for (curpixel = 0; curpixel <= num_pixels; curpixel++) {
        lcd.DrawLine(x, y, x3, y3);

        num += num_add;              /* Increase the numerator by the top of the fraction */
        if (num >= den) {           /* Check if numerator >= denominator */
            num -= den;               /* Calculate the new numerator value */
            x += xinc1;               /* Change the x as appropriate */
            y += yinc1;               /* Change the y as appropriate */
        }
        x += xinc2;                 /* Change the x as appropriate */
        y += yinc2;                 /* Change the y as appropriate */
    }
}

double  anglefix    (double a)  {       //  Ensures 0.0 <= angle <= + two PI
    while   (a > PI) a   -= 2.0 * PI;
    while   (a < 0.0) a   += 2.0 * PI;
    return  a;
}

class    moving_coil_meter
{
    int     meter_radius, cent_x, cent_y, needle_len, scale_ticks,
            disc_colour,    needle_colour,  scale_colour,   text_colour, body_colour, dec_places;
    double  start_angle, end_angle, old_angle, value_min, value_max, rad_per_value, swept_angle, value_range;
    double  Value;  //  This is the one that determines pointer angle

    void    DrawNeedle          (double alpha, int colour)  ;
    void    DrawScaleGraduations(int colour)  ;
    double  get_pointer_angle   (double value)  ;
    int     get_font            ()  ;

public:

    moving_coil_meter   ()  {   //  constructor
        meter_radius = 100;
        value_min = -1.0;
        value_max = 1.0;
        cent_x = cent_y = 150;
        disc_colour     = LCD_COLOR_BLACK;
        needle_colour   = LCD_COLOR_WHITE;
        scale_colour    = LCD_COLOR_MAGENTA;
        text_colour     = LCD_COLOR_RED;
        body_colour     = LCD_COLOR_CYAN;
        old_angle       = 0.0;
    }

    bool    setup   (int cx, int cy, int size, double lo, double hi, double start_ang, double end_ang, int scaleticks, char * units, int decimal_places)  ;
    void    set_colours   (int bod_colour, int bgcol, int needlecol, int textcolour, int scalecol)  ;
    void    set_value   (double v)  ;
}   Voltmeter,  Powermeter,    Speedo;     //  3 instances of moving coil meter graphic

void    moving_coil_meter::set_colours   (int bod_col, int bgcol, int needlecol, int textcol, int scalecol)  {
    body_colour = bod_col;
    disc_colour = bgcol;
    needle_colour   = needlecol;
    text_colour = textcol;
    scale_colour    = scalecol;
}

void    moving_coil_meter::DrawNeedle  (double alpha, int colour)
{
    point  pixpts[4];
    int save_colour, ssa, sca;
    alpha   = anglefix  (alpha);
    double  shortln = (needle_len / 18.7),
            sina    = jsin(alpha),
            cosa    = jcos(alpha);

    save_colour = lcd.GetTextColor  ();
    ssa = (int)(shortln * sina);
    sca = (int)(shortln * cosa);
    old_angle = alpha;
    pixpts[0].x = cent_x - ssa;//(int)(shortln * sin(alpha));
    pixpts[0].y = cent_y - sca;//(int)(shortln * cos(alpha));
    pixpts[1].x = cent_x + (int)(needle_len * cosa);
    pixpts[1].y = cent_y - (int)(needle_len * sina);  //  - as increasing y is downwards
    pixpts[2].x = cent_x + ssa;//(int)(shortln * sin(alpha));
    pixpts[2].y = cent_y + sca;//(int)(shortln * cos(alpha));
    lcd.SetTextColor    (colour);
    lcd.FillCircle      (cent_x, cent_y, (int)(needle_len / 15.0));
    FillTriangle    (pixpts[0].x, pixpts[1].x, pixpts[2].x, pixpts[0].y, pixpts[1].y, pixpts[2].y);
    lcd.SetTextColor    (save_colour);
}

void    moving_coil_meter::DrawScaleGraduations (int colour)
{
    int save_colour = lcd.GetTextColor  ();
    int i, radius_inner = (int) meter_radius - 2, radius_outer = (int) (meter_radius * 0.9);
    double  ang, cosang, sinang, angle_step;
    lcd.SetTextColor    (colour);
    ang = start_angle;
    angle_step  = (start_angle - end_angle) / scale_ticks;
    for (i = 0; i <= scale_ticks; i++)   {   //
        cosang  = cos(ang);
        sinang  = sin(ang);
        lcd.DrawLine    (cent_x + radius_outer * cosang, cent_y - radius_outer * sinang, cent_x + radius_inner * cosang, cent_y - radius_inner * sinang);
        ang -= angle_step;
    }
    lcd.SetTextColor    (save_colour);
}

void    displaytext    (int x, int y, const int font, char * txt)   ;

bool    moving_coil_meter::setup   (int cx, int cy, int size, double lo, double hi, double start_ang, double end_ang, int scaleticks, char * units, int decimal_places)
{
    bool    retval = true;
    int font, charwid, x_offset;
    if  (size < meter_radius_min || size > meter_radius_max)
        return  false;
    meter_radius = size;
    if  (meter_radius > cx || meter_radius > cy)
        return  false;
    int corner_rad          = meter_radius / 6,
        screw_hole_offset   = meter_radius * 92 / 100,
        screw_rad           = meter_radius / 13;
    cent_x = cx;
    cent_y = cy;

    start_angle = start_ang;
    end_angle = end_ang;
    value_min = lo;
    value_max = hi;
    scale_ticks = scaleticks;
    swept_angle = abs(start_angle - end_angle);
    value_range = (value_max - value_min);
    rad_per_value = swept_angle / value_range;
    dec_places  = decimal_places;

    needle_len = (int)(0.87 * (double)meter_radius);
    int oldcolour1 = lcd.GetTextColor   ();
    int oldcolour2 = lcd.GetBackColor   ();
    lcd.SetTextColor    (body_colour);
    //  Draw meter body as solid square with rounded corners, complete with mounting screw holes !
    lcd.FillRect    (cent_x - meter_radius, cent_y - meter_radius - corner_rad, meter_radius * 2, corner_rad);
    lcd.FillRect    (cent_x - meter_radius, cent_y + meter_radius, meter_radius * 2, corner_rad + 1);
    lcd.FillRect    (cent_x - meter_radius - corner_rad, cent_y - meter_radius, 1 +(meter_radius + corner_rad) * 2, meter_radius * 2);
    lcd.FillCircle  (cent_x - meter_radius, cent_y - meter_radius, corner_rad);  //  meter box has rounded corners
    lcd.FillCircle  (cent_x - meter_radius, cent_y + meter_radius, corner_rad);
    lcd.FillCircle  (cent_x + meter_radius, cent_y - meter_radius, corner_rad);
    lcd.FillCircle  (cent_x + meter_radius, cent_y + meter_radius, corner_rad);
    lcd.SetTextColor    (LCD_COLOR_DARKGRAY);
    lcd.FillCircle  (cent_x - screw_hole_offset, cent_y - screw_hole_offset, screw_rad);  //  panel mounting screw holes near corners
    lcd.FillCircle  (cent_x - screw_hole_offset, cent_y + screw_hole_offset, screw_rad);
    lcd.FillCircle  (cent_x + screw_hole_offset, cent_y - screw_hole_offset, screw_rad);
    lcd.FillCircle  (cent_x + screw_hole_offset, cent_y + screw_hole_offset, screw_rad);
    lcd.SetTextColor    (disc_colour);
    lcd.FillCircle      (cent_x, cent_y, meter_radius);
    DrawScaleGraduations (scale_colour);   //drew the green trace around active needle-sweep angle

    font = get_font ();
    charwid = char_widths[font];
    x_offset = charwid * strlen(units) / 2;
    lcd.SetTextColor    (text_colour);
    lcd.SetBackColor    (disc_colour);
//    displaytext (cent_x - x_offset, cent_y + (meter_radius * 7) / 19, font, units);
    displaytext (cent_x - x_offset, cent_y + (meter_radius * 6) / 19, font, units);
    lcd.SetBackColor    (oldcolour2);
    lcd.SetTextColor    (oldcolour1);
    return  retval;
}

int     moving_coil_meter::get_font ()
{
    int font = meter_radius - meter_radius_min;
    font /= 17;
    if  (font > 4)
        font = 4;
    if  (font < 2)
        font = 2;
    return  font;
}

double  moving_coil_meter::get_pointer_angle    (double v)
{
    double   vabvmin, retval;
    if   (v < value_min) v = value_min;
    if   (v > value_max) v = value_max;
    Value = v;   //  clipped copy of supplied value
    vabvmin = v - value_min;
    retval   = start_angle - (vabvmin * rad_per_value);
    return   anglefix   (retval);
}

void    moving_coil_meter::set_value   (double meter_read_value)
{
    char    txt[32];
    int x_offset, font, charwid, lenchk;//,
    DrawNeedle  (old_angle, disc_colour);                   //  un-draw needle
    DrawNeedle  (get_pointer_angle   (meter_read_value), needle_colour)  ; //  re-draw needle
    if  (dec_places == ONE_DP)
        sprintf (txt, " %+.1f \0", meter_read_value);
    else
        sprintf (txt, " %+.0f \0", meter_read_value);
    lenchk = strlen(txt);
    font = get_font();
    charwid = char_widths[font];
    x_offset = charwid * lenchk / 2;
    lcd.SetTextColor    (text_colour);
    lcd.SetBackColor    (disc_colour);
    if  (lenchk > 0 && lenchk < 9)
        displaytext (cent_x - x_offset, cent_y + (meter_radius * 11) / 19, font, txt);
}
//bool    moving_coil_meter::setup   (int cx, int cy, int size, double lo, double hi, double start_ang, double end_ang,
//                                    int scale_ticks, char * units)
void    vm_set  ()   //x   y  size minv  maxv  min angle   max angle,
{
    Speedo.set_colours  (SPEEDO_BODY_COLOUR, SPEEDO_DIAL_COLOUR, LCD_COLOR_RED, SPEEDO_TEXT_COLOUR, LCD_COLOR_BLACK);
    Speedo.setup    (SPEEDO_X, SPEEDO_Y, SPEEDO_SIZE, 0.0, 12.0, 1.25 * PI, -0.25 * PI , 12, "MPH", ONE_DP);
    Voltmeter.set_colours  (LCD_COLOR_BLACK, LCD_COLOR_WHITE, LCD_COLOR_RED, LCD_COLOR_BLUE, LCD_COLOR_MAGENTA);
    Voltmeter.setup (VOLTMETER_X, VOLTMETER_Y, V_A_SIZE, 22.0, 59.0, 1.25 * PI, -0.25 * PI , 30, "V", ONE_DP);
    Powermeter.set_colours  (LCD_COLOR_BLACK, LCD_COLOR_WHITE, LCD_COLOR_RED, LCD_COLOR_BLUE, LCD_COLOR_BLUE);
    Powermeter.setup   (AMMETER_X, AMMETER_Y, V_A_SIZE, -1400.0, 1400.0, 1.25 * PI, -0.25 * PI , 14, "Watt", NO_DPS);
}

//void    update_meters  (double speed, double current, double voltage)
void    update_meters  (double speed, double power, double voltage)
{
//    Powermeter.set_value(voltage * current);
    Powermeter.set_value(power);
    Voltmeter.set_value (voltage);
    Speedo.set_value    (speed);
}



struct  rect    {   struct point a, b; }   ;

struct  butt_on  {
    struct  rect    area;
    int border_colour,  body_colour;
    bool    in_use, pressed;//, released;
    char txt1[12];
    char txt2[12];
}   ;

struct  butt_on   button[NUMOF_BUTTONS];

int get_button_press    (struct point & pt) ;
int get_but_p   (int x, int y)
{
    struct  point   p;
    p.x = x;
    p.y = y;
    return  get_button_press    (p);
}


void    read_keypresses    (struct ky_bd & a)
{
    int x;
    a.count = 0;
    a.sli   = false;
    for (x = 0; x < MAX_TOUCHES; x++)
        a.ky[x].keynum = -1;
    int touches, but;
    TS_StateTypeDef TS_State;
    touch_screen.GetState(&TS_State);
    touches = TS_State.touchDetected;
    for (int h = 0; h < touches; h++)   {
        but = get_but_p  (TS_State.touchX[h], TS_State.touchY[h]);
        if  (but > - 1) {
            a.ky[a.count].keynum = but;
            a.ky[a.count].x     = TS_State.touchX[h];
            a.ky[a.count].y     = TS_State.touchY[h];
            if  (but == SLIDER) {
                a.sli   = true;
                a.slider_y  = a.ky[a.count].y;
            }
            a.count++;
        }
    }
}


void    displaytext    (int x, int y, char * txt)
{
    lcd.DisplayStringAt(x, y, (uint8_t *)txt, LEFT_MODE);
}

void    displaytext    (int x, int y, const int font, char * txt)
{
    sFONT * const fp[] = {&Font8, &Font12, &Font16, &Font20, &Font24};
    lcd.SetFont(fp[font]);
    displaytext (x, y, txt);
}

void    displaytext    (int x, int y, const int font, uint32_t BCol, uint32_t TCol, char * txt)
{
    uint32_t otc, obc;
    otc = lcd.GetTextColor();
    obc = lcd.GetBackColor();
    lcd.SetTextColor(TCol);
    lcd.SetBackColor(BCol);
    displaytext (x, y, font, txt);
    lcd.SetTextColor(otc);
    lcd.SetBackColor(obc);
}

void    draw_button (struct butt_on & bu)
{
    int oldbgcolour;
    lcd.SetTextColor    (bu.body_colour);
    lcd.FillRect(bu.area.a.x + 2, bu.area.a.y + 2, bu.area.b.x - bu.area.a.x - 2, bu.area.b.y - bu.area.a.y - 2);   //, bu.body_colour);
    oldbgcolour = lcd.GetBackColor();
    lcd.SetBackColor(bu.body_colour);
    lcd.SetTextColor(LCD_COLOR_BLACK);
    if  (strlen(bu.txt2) == 0)   {
        displaytext     (bu.area.a.x + 4, bu.area.a.y + 14, 4, bu.txt1); //  largest font 4
    } else    {
        displaytext     (bu.area.a.x + 4, bu.area.a.y + 4, 3, bu.txt1); //  not so large font 3
        displaytext     (bu.area.a.x + 4, bu.area.a.y + 26, bu.txt2);
    }
    lcd.SetBackColor(LCD_COLOR_BLACK);
    lcd.SetTextColor(bu.border_colour);
    lcd.DrawRect(bu.area.a.x, bu.area.a.y, bu.area.b.x - bu.area.a.x, bu.area.b.y - bu.area.a.y);   //, bu.border_colour);
    lcd.DrawRect(bu.area.a.x + 1, bu.area.a.y + 1, bu.area.b.x - bu.area.a.x - 1, bu.area.b.y - bu.area.a.y - 1);   //, bu.border_colour);
    lcd.SetBackColor(oldbgcolour);
}

void    draw_button_hilight     (int but, int colour)
{
    if  (but < 0 || but > NUMOF_BUTTONS)    {
        pc.printf   ("Button out of range in draw_button_hilight %d\r\n", but)  ;
    } else    {
        struct   butt_on * bu = &button[but];
        int oldbgcolour = lcd.GetBackColor();//, minx, miny, maxx, maxy;
        lcd.SetTextColor(colour);
        lcd.DrawRect(bu->area.a.x - 1, bu->area.a.y - 1, bu->area.b.x - bu->area.a.x + 2, bu->area.b.y - bu->area.a.y + 2);
        lcd.DrawRect(bu->area.a.x - 2, bu->area.a.y - 2, bu->area.b.x - bu->area.a.x + 4, bu->area.b.y - bu->area.a.y + 4);
        lcd.DrawRect(bu->area.a.x - 2, bu->area.a.y - 3, bu->area.b.x - bu->area.a.x + 5, bu->area.b.y - bu->area.a.y + 6);
        lcd.SetBackColor(oldbgcolour);
    }
}

void    draw_button (struct butt_on & bu, int body_colour)
{
    bu.body_colour = body_colour;
    draw_button (bu);
}

void    setup_button    (struct butt_on & bu, int x1, int y1, int dx, int dy, int bord, int body, char * txt1, char * txt2)
{
    static const int margin = 3;
    int xsize = lcd.GetXSize();
    int ysize = lcd.GetXSize();
    int x2 = x1 + dx, y2 = y1 + dy;
    if  (x1 < margin) x1 = margin;
    if  (y1 < margin) y1 = margin;
    if  (x2 > xsize - margin)    x2 = xsize - margin;
    if  (y2 > ysize - margin)    y2 = ysize - margin;
    bu.area.a.x = x1;
    bu.area.a.y = y1;
    bu.area.b.x = x2;
    bu.area.b.y = y2;
    bu.border_colour = bord;
    bu.body_colour = body;
    strcpy  (bu.txt1, txt1);
    strcpy  (bu.txt2, txt2);
    bu.in_use = true;
    bu.pressed = false;
    draw_button(bu);
}

bool    ifpressed   (int key)
{
    return  button[key].pressed;
}

bool    is_button_pressed   (struct point & pt, struct butt_on & bu)
{
    if  (bu.in_use)  {
        if  (bu.area.a.x < pt.x && bu.area.b.x > pt.x
                && bu.area.a.y < pt.y && bu.area.b.y > pt.y)
            return  true;
    }
    return  false;
}

bool    keyrelease  (int key)
{
    bool    rv = false;
    if  (button[key].pressed)   {
        rv = true;
        button[key].pressed = false;
    }
    return  rv;
}
void    setpressed  (int key, bool torf)
{
    button[key].pressed = torf;
}
void    setinuse    (int key, bool torf)
{
    button[key].in_use = torf;
}


int get_button_press    (struct point & pt)
{
    for (int j = 0; j < NUMOF_BUTTONS; j++)
        if  (button[j].in_use && is_button_pressed   (pt, button[j]))
            return  j;
    return  -1;
}

void    setup_buttons   ()
{
    setup_button    (button[SPEEDO_BUT],
                    SPEEDO_X - SPEEDO_SIZE, SPEEDO_Y - SPEEDO_SIZE,
                    SPEEDO_SIZE * 2, SPEEDO_SIZE * 2, SPEEDO_BODY_COLOUR,   LCD_COLOR_RED,   " X", "")  ;
    setup_button    (button[VMETER_BUT],
                    VOLTMETER_X - V_A_SIZE, VOLTMETER_Y - V_A_SIZE, V_A_SIZE * 2, V_A_SIZE * 2, VMETER_BODY_COLOUR,   LCD_COLOR_RED,   " Y", "")  ;
    setup_button    (button[AMETER_BUT],
                    AMMETER_X - V_A_SIZE, AMMETER_Y - V_A_SIZE, V_A_SIZE * 2, V_A_SIZE * 2, AMETER_BODY_COLOUR,   LCD_COLOR_RED,   " Z", "")  ;
    setup_button    (button[SLIDER],   SLIDERX, SLIDERY, SLIDERW, SLIDERH, LCD_COLOR_BLUE,   LCD_COLOR_MAGENTA,   "", "")  ;
}


void SliderGraphic (struct slide & q)   {
    int
    colr,
    oldbgcolr   = lcd.GetBackColor   (),
    oldtxtcolr  = lcd.GetTextColor   ();
    char    txt[4];
    txt[1] = 0;
    if  (q.position > MAX_POS)
        q.position = MAX_POS;
    if  (q.position < MIN_POS)
        q.position = MIN_POS;
    if  (q.position == NEUTRAL_VAL)
        q.state = NEUTRAL_DRIFT;
    if  (q.position > NEUTRAL_VAL)
        q.state = REGEN_BRAKE;
    if  (q.position < NEUTRAL_VAL)
        if  (q.state == REGEN_BRAKE)    {   //  Ensure transition from BRAKE to RUN passes through NEUTRAL
            q.position = NEUTRAL_VAL;
            q.state = NEUTRAL_DRIFT;
        }
        else
            q.state = RUN;
    if  (q.position == MAX_POS) {
        if  (q.loco_speed < LOCO_HANDBRAKE_ESCAPE_SPEED)
            q.state = PARK;
        else    {
            q.state = REGEN_BRAKE;
            q.position--;
        }
    }
    if  (q.position != q.oldpos)    {
        //  Draw slider background colour rectangle overwriting previous circles
        //  Redraw black vertical
        //  Draw new circles
        //  Write text char
        lcd.SetTextColor(LCD_COLOR_MAGENTA);
        lcd.FillRect    (SLIDERX + 1, q.oldpos - BUTTON_RAD, SLIDERW - 2, SLIDERW);
        lcd.SetTextColor(LCD_COLOR_BLACK);
        lcd.FillRect    (SLIDERX + (SLIDERW / 2) - 3, 6, 7, SLIDERH - 8);
        q.oldpos = q.position;
        lcd.SetTextColor(LCD_COLOR_WHITE);
        lcd.DrawCircle  (CIRC_CTR, q.position, BUTTON_RAD);  //  seel also FillCircle
        lcd.DrawCircle  (CIRC_CTR, q.position, BUTTON_RAD - 1);
        switch  (q.state)  {
            case    RUN:
                txt[0] = 'R';
                colr = LCD_COLOR_GREEN;
                break;
            case    NEUTRAL_DRIFT:
                txt[0] = 'N';
                colr = LCD_COLOR_BLUE;
                break;
            case    REGEN_BRAKE:
                txt[0] = 'B';
                colr = LCD_COLOR_ORANGE;
                break;
            case    PARK:
                txt[0] = 'P';
                colr = LCD_COLOR_RED;
                break;
            default:
                txt[0] = 'X';
                colr = LCD_COLOR_CYAN;
        }   //  End of switch
        lcd.SetTextColor(colr);
        lcd.FillCircle  (CIRC_CTR, q.position, BUTTON_RAD - 2);
        lcd.SetBackColor  (colr);
        lcd.SetTextColor(LCD_COLOR_YELLOW);
        displaytext(SLIDERX + 17, q.position - 10, 4, txt);   //  largest font
        lcd.SetBackColor  (LCD_COLOR_BLACK);
    }           //  End of else
    lcd.SetTextColor (oldtxtcolr);
    lcd.SetBackColor (oldbgcolr);
//    pc.printf   ("SliderG %d, %d, %d\r\n", q.position, q.oldpos, q.state);
}