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
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
Generated on Thu Jul 14 2022 06:50:34 by 1.7.2