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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 /*
00002 November 2018  -   Jon Freeman
00003 Cloned from Loco_TS_2018_06 on 23rd November 2018
00004 
00005 Touch Screen controller communicates with 1, 2, ... n Brushless STM3 Controller boards via opto-isolated serial port.
00006 
00007 9 pid D connector retained but wiring NOT compatible with 2017.
00008 This time pins are : -
00009 1   Not Used on TS2018, connection from Twin BLDC Controller only - Pot wiper
00010 2   Not Used on TS2018, connection from Twin BLDC Controller only - GND
00011 3   Not Used on TS2018, connection from Twin BLDC Controller only - Weak +5 (top of pot)
00012 4   Not Used on TS2018, connection from Twin BLDC Controller only - Fwd / Rev switched between pins 2 and 3 above
00013 5   TS2018 high voltage output - power up signal to Twin BLDC Controllers, +10 to + 70 Volt (full supply via 3k3 0.5W safety resistor)
00014 6   Twin BLDC Rx- <- TS2018 Tx data     ||GND to avoid exposing TS +5v rail to the outside world
00015 7   Twin BLDC Rx+ <- TS2018 +5v         ||Tx\ to avoid exposing TS +5v rail to the outside world, INVERTED Txd idles lo
00016 8   Twin BLDC Tx- <- TS2018 GND
00017 9   Twin BLDC Tx+ <- TS2018 Rx data with 1k pullup to +5, line idles hi
00018 */
00019 #include "mbed.h"
00020 #include "Electric_Loco.h"
00021 #include "AsyncSerial.hpp"
00022 #include "Servo.h"
00023 #include "TS_DISCO_F746NG.h"
00024 #include "LCD_DISCO_F746NG.h"
00025 
00026 char   const_version_string[] = {"1.0.0\0"};  //  Version string, readable from serial ports
00027 
00028 LCD_DISCO_F746NG        lcd ;               //  LCD display
00029 TS_DISCO_F746NG         touch_screen    ;   //  Touch Screen
00030 screen_touch_handler    slider  ;           //  Loco driver's slider control
00031 
00032 //  see movingcoilmeter.h ffi
00033 moving_coil_meter   Voltmeter   (   LCD_COLOR_BLACK, LCD_COLOR_WHITE, LCD_COLOR_RED, LCD_COLOR_BLUE, LCD_COLOR_MAGENTA,
00034                                     VOLTMETER_X, VOLTMETER_Y, V_A_SIZE, 22.0, 61.0, 1.25 * PI, -0.25 * PI , 20, "V", ONE_DP, false),  
00035                     Powermeter  (   LCD_COLOR_BLACK, LCD_COLOR_WHITE, LCD_COLOR_RED, LCD_COLOR_BLUE, LCD_COLOR_BLUE,
00036                                     AMMETER_X, AMMETER_Y, V_A_SIZE, -1400.0, 1400.0, 1.25 * PI, -0.25 * PI , 14, "Watt", NO_DPS, false),    
00037                     Speedo      (   SPEEDO_BODY_COLOUR, SPEEDO_DIAL_COLOUR, LCD_COLOR_RED, SPEEDO_TEXT_COLOUR, LCD_COLOR_BLACK,
00038                                     SPEEDO_X, SPEEDO_Y, SPEEDO_SIZE, 0.0, 12.0, 1.25 * PI, -0.25 * PI , 12, "MPH", ONE_DP, false);
00039                                 //  3 instances of moving coil meter graphic
00040 
00041 error_handling_Jan_2019     Controller_Error    ;         //  Provides array usable to store error codes.
00042 STM3_ESC_Interface          My_STM3_ESC_boards   ;
00043 
00044 extern  command_line_interpreter_core   pcli, ploco;    //  pcli handles comms with pc, ploco handles comms with STM3_ESC boards
00045 
00046 /*
00047 STRANGE BEHAVIOUR WARNING !
00048 This project requires two serial ports.
00049 The following combination of one 'Serial' and one 'AsyncSerial' is the only combination found to work !
00050 MODSERIAL has not been adapted to F746NG, will not compile.
00051 Does compile with BufferedSerial but crashes the whole thing. No startup.
00052 */
00053 
00054 Serial      pc      (USBTX, USBRX);    //  AsyncSerial does not work here. Comms to 'PuTTY' or similar comms programme on pc
00055 AsyncSerial com2escs (A4, A5);   //  Com port to opto isolators on Twin BLDC Controller boards. Only AsyncSerial works here
00056 
00057 //DigitalOut  reverse_pin     (D7);   //  These pins no longer used to set mode and direction, now commands issued to com
00058 //DigitalOut  forward_pin     (D9);   //was D6, these two decode to fwd, rev, regen_braking and park
00059 
00060 DigitalOut  I_sink1         (D14);  //  a horn
00061 DigitalOut  I_sink2         (D15);  //  lamp
00062 DigitalOut  I_sink3         (D3);  //  lamp
00063 DigitalOut  I_sink4         (D4);
00064 DigitalOut  I_sink5         (D5);
00065 DigitalOut  led_grn         (LED1); //  the only on board user led
00066 
00067 DigitalIn   f_r_switch      (D2);   //  was D0, Reads position of centre-off ignition switch
00068 //DigitalIn   spareio_d8      (D8);
00069 //DigitalIn   spareio_d9      (D9);
00070 DigitalIn   spareio_d10     (D10);  //  D8, D9, D10 wired to jumper on pcb - not used to Apr 2017
00071 
00072 AnalogIn    ht_volts_ain    (A0);  //  Jan 2017
00073 AnalogIn    ht_amps_ain     (A1);  //  Jan 2017
00074 //AnalogIn    spare_ain2      (A2);
00075 //AnalogIn    spare_ain3      (A3);
00076 //AnalogIn    spare_ain4      (A4);   //  hardware on pcb for these 3 spare analogue inputs - not used to Apr 2017
00077 //AnalogIn    spare_ain5      (A5); //  causes display flicker !
00078 
00079 Servo   servo1    (D6);     //  Model control servo used to adjust Honda engine speed
00080 
00081 extern  bool    test_qspi   ()  ;
00082 extern  bool    odometer_zero   ()  ;   //  Returns true on success
00083 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
00084 
00085 extern  void    setup_buttons    ()  ;
00086 extern  void    rewrite_odometer    ()  ;
00087 
00088 static  const   int
00089     MAF_PTS             = 140,      //  Moving Average Filter points. Filters reduce noise on volatage and current readings
00090     FWD                 = 0,
00091     REV                 = ~FWD;
00092 
00093 int32_t     V_maf[MAF_PTS + 2],    I_maf[MAF_PTS + 2],  maf_ptr = 0;    //
00094 volatile    uint32_t    sys_timer_32Hz = 0;
00095 double      recent_distance = 0.0;
00096 
00097 bool        qtrsec_trig             = false;
00098 bool        trigger_current_read    = false;
00099 volatile    bool    trigger_32ms    = false;
00100 
00101 double  read_voltmeter   ()
00102 {
00103     int32_t a = 0;
00104     for (int b = 0; b < MAF_PTS; b++)
00105         a += V_maf[b];
00106     a /= MAF_PTS;
00107     double v = (double) a;
00108     return  (v / 932.0) + 0.0;  //  fiddled to suit resistor values
00109 }
00110 
00111 double  read_ammeter ()     //  Returns amps taken by STM3escs - amps dumped due to over-voltage
00112 {                           //  Could make sense to read this at up to 32 times per second
00113     int32_t a = 0;          //  MAF data almost completely renewed at this rate
00114     for (int b = 0; b < MAF_PTS; b++)   //  MAF updated every 250us, MAF size = MAF_PTS (once set to 140, probably still is)
00115         a += I_maf[b];
00116     a /= MAF_PTS;
00117     a -= 0x4000;
00118     double i = (double) (0 - a);
00119     return  i / 200.0;      //  Fiddled to get current reading close enough
00120 }
00121 
00122 const   int BIGMAFSIZ = 8;
00123 
00124 class   ammeter_reading  {
00125     double  bigImaf[BIGMAFSIZ];
00126     int     bigImafptr;
00127     double  amps_longave,   //  internal use only
00128             amps_latest,    //  update() called @ 32Hz. Value stored here is average over most recent 3125us
00129             amps_filtered,  //  Average of the BIGMAFSIZ most recent samples stored in latest
00130             amps_filtered2;  //  Average of the BIGMAFSIZ most recent samples stored in latest
00131 public:    
00132     ammeter_reading ()  {   //  constructor
00133         bigImafptr = 0;
00134         amps_longave = amps_latest = amps_filtered = amps_filtered2 = 0.0;
00135         for (int i = 0; i < BIGMAFSIZ; i++)
00136             bigImaf[i] = 0.0;
00137     }
00138     void    update  ()  ;   //  Read ammeter core, store most recent 32ms or so worth in amps_latest, 250ms average in amps_filtered
00139     double  latest  ()  ;
00140     double  filtered()  ;
00141     double  filtered2()  ;
00142 }   Ammeter ;
00143 
00144 double  ammeter_reading::latest     ()  {
00145     return  amps_latest;
00146 }
00147 
00148 double  ammeter_reading::filtered2   ()  {
00149 //  could use filter without buffer, weights result more towards more frecent samples
00150     return  amps_filtered2;
00151 }
00152 
00153 double  ammeter_reading::filtered   ()  {
00154     return  amps_filtered;
00155 }
00156 
00157 void  ammeter_reading::update   ()  {
00158     amps_latest = read_ammeter();
00159     amps_longave -= bigImaf[bigImafptr];
00160     bigImaf[bigImafptr] = amps_latest;
00161     amps_longave += amps_latest;
00162     bigImafptr++;
00163     if  (bigImafptr >= BIGMAFSIZ)
00164         bigImafptr = 0;
00165     amps_filtered = amps_longave / BIGMAFSIZ;
00166 const   double  sampweight  = (double)(1) / (double)BIGMAFSIZ;
00167 const   double  shrinkby    = 1.0 - sampweight;
00168     amps_filtered2 *= shrinkby;
00169     amps_filtered2 += amps_latest * sampweight;
00170 }
00171 
00172 
00173 //  Interrupt Service Routines
00174 
00175 void    ISR_current_reader  (void)      //  FIXED at 250us
00176 {
00177     static  int ms32 = 0, ms250 = 0;
00178     trigger_current_read    = true; //  every 250us, i.e. 4kHz  NOTE only sets trigger here, readings taken in main loop
00179     ms32++;
00180     if  (ms32 >= 125)    {   //  31.25ms, not 32ms, is 32Hz
00181         ms32 = 0;
00182         sys_timer_32Hz++;   //  , usable anywhere as general measure of elapsed time
00183         trigger_32ms = true;
00184         ms250++;
00185         if  (ms250 >= 8) {
00186             ms250 = 0;
00187             qtrsec_trig = true;
00188         }
00189     }
00190 }
00191 
00192 //  End of Interrupt Service Routines
00193 
00194 void    throttle    (double p)  {            // New Apr 2018 ; servo adjusts throttle lever on Honda GX120
00195     const   double  THR_MAX = 0.92;     //  Values tweaked to suit servo and linkage fitted to loco power unit
00196     const   double  THR_MIN = 0.09;
00197     const   double  RANGE = (THR_MAX - THR_MIN);
00198     if  (p > 1.0)
00199         p = 1.0;
00200     if  (p < 0.0)
00201         p = 0.0;
00202     //  p = 1.0 - p;    //  if direction needs swapping
00203     p *= RANGE;
00204     p += THR_MIN;
00205     servo1 = p;
00206 }
00207 
00208 
00209 void    horn    (int which, int onoff)  {
00210     if  (which == HI_HORN)
00211         I_sink5 = onoff;
00212     else
00213         I_sink4 = onoff;
00214 }
00215 
00216 
00217 void    lights  (int onoff)  {
00218     I_sink2 = onoff;    //  lamp right
00219     I_sink3 = onoff;    //  lamp left
00220 }
00221 
00222 
00223 void    draw_normal_run_screen  ()  {
00224     lcd.Clear(LCD_COLOR_LIGHTGRAY);
00225     setup_buttons(); //  draws buttons
00226     slider.DrawSlider ();
00227     //  Draw 3 analogue meter movements, speedo, voltmeter, ammeter
00228     Speedo.redraw();
00229     Voltmeter.redraw();
00230     Powermeter.redraw();
00231 }
00232 
00233 
00234 int main()  //  Programme entry point
00235 {
00236     int     qtr_sec = 0, seconds = 0, minutes = 0;
00237     double  electrical_power_Watt = 0.0, volts = 0.0;
00238 
00239     pc.baud (9600);
00240     com2escs.baud (19200);
00241 
00242     I_sink1 = 0;    //  turn outputs off
00243     I_sink2 = 0;    //  lamp right
00244     I_sink3 = 0;    //  lamp left
00245     I_sink4 = 0;    //  low horn
00246     I_sink5 = 0;    //  high horn
00247     spareio_d10.mode(PullUp);
00248 
00249     Ticker  tick250us;      //  Master 4kHz interrupt timebase
00250 
00251 //  Setup User Interrupt Vectors
00252     tick250us.attach_us (&ISR_current_reader, 250);    //  count 125 interrupts to trig 31.25ms
00253 
00254 //  QSPI memory is now in constant use for odometer
00255     if  (!test_qspi())
00256         Controller_Error.set    (FAULT_QSPI, -1);   //        pc.printf   ("Problem with qspimemcheck\r\n");
00257 
00258     slider.direction = f_r_switch ? REV : FWD;      //  Only place in the code where direction gets set. Centre-Off power switch REV-OFF-FWD.
00259 
00260     My_STM3_ESC_boards.set_I_limit (0.0);
00261     My_STM3_ESC_boards.set_V_limit (0.0);   //  zero power to motors
00262     My_STM3_ESC_boards.message  ("rb\r");   //  regen brake mode
00263     throttle    (0.0);                      //  Set revs to idle. Start engine and warm up before powering up control
00264     pc.printf   ("\r\n\n\nJon's Loco_TS_2018 Loco Controller ver %s starting, direction %s\r\n", const_version_string, slider.direction ? "Forward":"Reverse");
00265 
00266     uint8_t lcd_status = touch_screen.Init(lcd.GetXSize(), lcd.GetYSize());
00267     if (lcd_status != TS_OK) {
00268         Controller_Error.set    (FAULT_TS, -1);
00269     } 
00270     lcd.Clear(LCD_COLOR_DARKBLUE);
00271     lcd.SetBackColor(LCD_COLOR_GREEN);
00272     lcd.SetTextColor(LCD_COLOR_WHITE);
00273     lcd.DisplayStringAt(0, LINE(5), (uint8_t *)"TOUCHSCREEN INIT OK", CENTER_MODE);
00274 
00275 //    if  (odometer_zero   ())
00276 //        pc.printf   ("TRUE from odometer_zero\r\n");
00277 //    else
00278 //        pc.printf   ("FALSE from odometer_zero\r\n");
00279 
00280     lights  (1);    //  Headlights ON!
00281 
00282     My_STM3_ESC_boards.search_for_escs ();  //  Build list of connected STM3_ESC IDs
00283 
00284 /*    Controller_Error.set    (3, 99);
00285     pc.printf   ("%lx red\r\n", LCD_COLOR_RED);     //LCD_COLOR is 0xffrrggbb
00286     pc.printf   ("%lx grn\r\n", LCD_COLOR_GREEN);
00287     pc.printf   ("%lx blu\r\n", LCD_COLOR_BLUE);
00288     pc.printf   ("%lx blk\r\n", LCD_COLOR_BLACK);
00289     pc.printf   ("%lx white\r\n", LCD_COLOR_WHITE);
00290 */
00291     draw_normal_run_screen  ();
00292 
00293     pc.printf   ("Controller_Error.all_good() ret'd %s\r\n", Controller_Error.all_good() ? "true" : "false");
00294 
00295     while (1) {     //  main prog loop
00296 
00297         pcli.sniff  ();   //  Do any actions from command line serial port via usb link
00298 
00299         if  (trigger_current_read)  {     //  flag set in interrupt handler every 250us
00300             trigger_current_read = false;
00301             I_maf[maf_ptr] = ht_amps_ain.read_u16();    //  Read raw ACS709 ammeter module
00302             V_maf[maf_ptr] = ht_volts_ain.read_u16();   //  Read raw system voltage
00303             maf_ptr++;
00304             if  (maf_ptr > MAF_PTS - 1)
00305                 maf_ptr = 0;
00306         }                       //  endof stuff to do every 250us
00307 
00308         if  (trigger_32ms == true)  {       //  Stuff to do every 31.25 milli secs (32Hz)
00309             trigger_32ms = false;
00310             ploco.sniff ();         //  Only call within main loop, checks message responses from STM3_ESC boards
00311             Ammeter.update ();      //  This updates Ammeter 'latest' and 'filtered' variables every 31.25ms
00312             slider.HandleFingerInput   ();  //  Do everything concerning fingers on touch screen
00313         }                                  //  endof doing 32Hz stuff
00314 
00315         if  (qtrsec_trig == true)  {    //  do every quarter second stuff here
00316             qtrsec_trig = false;
00317             volts = read_voltmeter();    //  voltage and current readings updated @ 250us, these are averaged over 35ms or so
00318             electrical_power_Watt = volts * Ammeter.filtered();   //  visible throughout main
00319             //  Update meters
00320             Powermeter.set_value(electrical_power_Watt);
00321             Voltmeter.set_value (volts);
00322             Speedo.set_value    (My_STM3_ESC_boards.mph);
00323 
00324             led_grn = !led_grn;
00325             My_STM3_ESC_boards.request_mph   (); //  issues "'n'mph\r", takes care of cycling through available boards in sequence
00326 //            switch  (qtr_sec)   {   //  Can do sequential stuff quarter second apart here
00327 //            }   //  End of switch qtr_sec
00328             qtr_sec++;
00329             //  Can do stuff once per second here
00330             if(qtr_sec > 3) {
00331                 qtr_sec = 0;
00332                 seconds++;
00333                 if  (seconds > 59)  {
00334                     seconds = 0;
00335                     minutes++;
00336                     //  do once per minute stuff here
00337                     Controller_Error.report_any (false);    //  Reset error having reported it once
00338                 }   //  fall back into once per second
00339                 if  (seconds & 1)
00340                     Speedo.LED  (0, LCD_COLOR_DARKGRAY);
00341                 else
00342                     Speedo.LED  (0, LCD_COLOR_RED);
00343 //                pc.printf   ("Filter test %.3f, %.3f\r\n", Ammeter.filtered(), Ammeter.filtered2());
00344                 My_STM3_ESC_boards.message  ("kd\r");       //  Kick the WatchDog timers in the Twin BLDC drive boards
00345                 recent_distance += (My_STM3_ESC_boards.mph * (111.76 * 4.0));    //  Convert mph to distance mm travelled in one second
00346                 uint32_t    new_metres = ((uint32_t)recent_distance) / 1000;
00347                 recent_distance -= (double)(new_metres * 1000);
00348                 if  (!odometer_update (new_metres, (uint16_t)electrical_power_Watt, (uint16_t)(volts * 500.0))) {
00349                     pc.printf   ("Probs with odometer_update");
00350                     Controller_Error.set    (FAULT_ODOMETER, 1);
00351                 }
00352                 rewrite_odometer    ()  ;   //  Update text on speedo dial face
00353             }   //  endof if(qtr_sec > 3
00354         }       //  endof if  (qtrsec_trig == true)  {
00355     }           //  endof while(1) main programme loop
00356 }               //  endof main ()
00357