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
Diff: main.cpp
- Revision:
- 0:23cc72b18e74
- Child:
- 1:8ef34deb5177
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Sun Nov 12 06:26:29 2017 +0000 @@ -0,0 +1,836 @@ +// Electric Locomotive Controller +// Jon Freeman B. Eng Hons + +// Last Updated 12 April 2017 +// Touch Screen Loco 2017 - WITH SD card data logger functions + +// This code runs on STM 32F746NG DISCO module, high performance ARM Cortex with touch screen +// ffi on ST module -> https://developer.mbed.org/platforms/ST-Discovery-F746NG/ +// Board plugs onto simple mother-board containing low voltage power supplies, interfacing buffers, connectors etc. +// See www.jons-workshop.com ffi on hardware. + +// Design provides PWM outputs to drive up to four brushless motor drive modules, each able to return speed information. +// Output signals are dual PWM, one to set max motor voltage, other to set max motor current. +// This code as supplied uses current control to drive locomotive. This means that drive fader acts as a Torque, not Speed, Demand control. +// Regenerative braking is included in the design. +// NOTE that when braking, the motor supply rail voltage will be lifted. Failure to design-in some type of 'surplus power dump' +// may result in over-voltage damage to batteries or power electronics. + + +#include "mbed.h" +#include "FastPWM.h" +#include "TS_DISCO_F746NG.h" +#include "LCD_DISCO_F746NG.h" +#include "SD_DISCO_F746NG.h" +#include "dro.h" + + + +// Design Topology +// This F746NG is the single loco control computer. +// Assumed 4 motor controllers driven from same signal set via multiple opto / buffers +// Outputs are : - +// FastPWM maxv on D12 - in drive, sets motor volts to pwm proportion of available volts. Also used in regen braking +// FastPWM maxi on D11 - used to set upper bound on motor current, used as analogue out to set current limit on motor driver +// DigitalOut reverse (D7) - D6,7 select fwd, rev, brake, parking brake +// DigitalOut forward (D6) +// Inputs are : - +// AnalogIn ht_amps_ain (A0); // Jan 2017 +// AnalogIn ht_volts_ain (A1); // Jan 2017 +// InterruptIn mot4hall (D2); +// InterruptIn mot3hall (D3); +// InterruptIn mot2hall (D4); +// InterruptIn mot1hall (D5); + +/* Feb 2017, re-thought use of FR and SG signals. Rename these FWD and REV. Truth table for actions required now : - +FWD(A5) REV(A4) PWM Action + 0 0 0 'Handbrake' - energises motor to not move + 0 0 1 'Handbrake' - energises motor to not move + 0 1 0 Reverse0 + 0 1 1 Reverse1 + + 1 0 0 Forward0 + 1 0 1 Forward1 + 1 1 0 Regen Braking + 1 1 1 Regen Braking +*/ + +LCD_DISCO_F746NG lcd; +TS_DISCO_F746NG touch_screen; +SD_DISCO_F746NG sd; + +FastPWM maxv (D12, 1), + maxi (D11, 1); // pin, prescaler value +Serial pc (USBTX, USBRX); // Comms to 'PuTTY' or similar comms programme on pc + +DigitalOut reverse_pin (D7); // +DigitalOut forward_pin (D6); //these two decode to fwd, rev, regen_braking and park +DigitalOut GfetT2 (D14); // a horn +DigitalOut GfetT1 (D15); // another horn +DigitalOut led_grn (LED1); // the only on board user led + +DigitalIn f_r_switch (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 ! + +InterruptIn mot4hall (D2); // One Hall sensor signal from each motor fed back to measure speed +InterruptIn mot3hall (D3); +InterruptIn mot2hall (D4); +InterruptIn mot1hall (D5); + +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 command_line_interpreter () ; + +static const int NUMBER_OF_MOTORS = 4, + SD_BLOCKSIZE = 512, /* SD card data Block Size in Bytes */ + DAMPER_DECAY = 42, // Small num -> fast 'viscous damper' on dead-mans function with finger removed from panel + MAF_PTS = 140, // Moving Average Filter points + PWM_HZ = 13000, +// PWM_HZ = 2000, // Used this to experiment on much bigger motor + MAX_PWM_TICKS = 108000000 / PWM_HZ, // 108000000 for F746N, due to cpu clock = 216 MHz + FWD = 0, + REV = ~FWD; + +static const double + MOTOR_PINION_T = 17.0, // motor pinion teeth, wheel gear teeth and wheel dia required to calculate speed and distance. + WHEEL_GEAR_T = 76.0, + WHEEL_DIA_MM = 147.0, + WHEEL_CIRCUMFERENCE_METRE = PI * WHEEL_DIA_MM / 1000.0, + PULSES_PER_WHEEL_REV = 32.0 * WHEEL_GEAR_T / MOTOR_PINION_T, + PULSES_PER_METRE = PULSES_PER_WHEEL_REV / WHEEL_CIRCUMFERENCE_METRE, + rpm2mph = 60.0 // = Motor Revs per hour; + * (MOTOR_PINION_T / WHEEL_GEAR_T) // = Wheel rev per hour + * WHEEL_CIRCUMFERENCE_METRE // = metres per hour + * 39.37 // = inches per hour + / (1760 * 36) // = miles per hour + ; + +// Assume SD card size is 4Gbyte, might be 8 Gbyte +// Then can use 8388608 blocks (8 * 1024 * 1024) + +uint64_t SD_blockptr = 0; +uint32_t SDBuffer[(SD_BLOCKSIZE >> 2)]; // = space for (512 / 4) uint32_t +uint8_t SD_state = SD_OK, sd_jf = 0; + +static const uint64_t GIGAB = 1024 * 1024 * 1024; +//static const uint64_t SDBLOCKS = (GIGAB / SD_BLOCKSIZE) * 4; // software drives SD up to 4Gbyte only - 8 M block +static const uint64_t SDBLOCKS = (GIGAB / SD_BLOCKSIZE) * 2; // software drives SD up to 4Gbyte only - 8 M block +// If data logger takes 2 minutes to fill 1 block, a 4G card takes 32 years run-time to fill +// If system generates approx 320 pulses per metre travelled, max distance recordable in uint32_t is 65536 * 65536 / 320 = 13421.772 km + +//from dro.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 ; + + + +//static const double mph_2_mm_per_sec = 447.04; // exact + +int V_maf[MAF_PTS + 2], I_maf[MAF_PTS + 2], maf_ptr = 0; +//uint32_t Hall_pulse[8] = {0,0,0,0,0,0,0,0}; // more than max number of motors +uint32_t Hall_pulse[8] = {1,1,1,1,1,1,1,1}; // more than max number of motors +uint32_t historic_distance = 0; + +bool qtrsec_trig = false; +bool trigger_current_read = false; +volatile bool trigger_32ms = false; + +double last_pwm = 0.0; + +bool sd_error () { // Test and Clear error code sd_jf, return true if any error bits set, false on 0 + bool retval = false; + if (sd_jf != 0) { + retval = true; + sd_jf = 0; + } + return retval; +} + +bool check_SD_block_clear (uint32_t block) { + uint32_t b[(SD_BLOCKSIZE >> 2)]; + SD_state = sd.ReadBlocks(b, (uint64_t)(SD_BLOCKSIZE * block), SD_BLOCKSIZE, 1); + if(SD_state != SD_OK) { + sd_jf = 1; + pc.printf ("Failed, not SD_OK, erasing block %d\r\n", block); + return false; + } + for (int i = 0; i < (SD_BLOCKSIZE >> 2); i++) + if (b[i] != 0) + return false; + return true; +} + +/*bool erase_block (uint32_t block2erase) { + uint64_t addr = SD_BLOCKSIZE * (uint64_t)block2erase; + SD_state = sd.Erase(addr, addr + SD_BLOCKSIZE); + if (SD_state != SD_OK) { + sd_jf = 1; // Assert error flag + pc.printf ("Failed, not SD_OK, erasing block %d\r\n", block2erase); + return false; + } + return check_SD_block_clear (block2erase); +}*/ + +bool SD_find_next_clear_block (uint64_t * blok) { // Successive approximation algorithm to quickly find next vacant SD card 512 byte block + uint64_t toaddsub = SDBLOCKS / 2, stab = SDBLOCKS - 1; + pc.printf ("At SD_find_next_clear_block \r\n"); + while (toaddsub) { + pc.printf ("stab = %lld, toadsub = %lld\r\n", stab, toaddsub); // lld for long long int + bool clear_block = true; + SD_state = sd.ReadBlocks(SDBuffer, SD_BLOCKSIZE * stab, SD_BLOCKSIZE, 1); + if(SD_state != SD_OK) { + sd_jf = 1; + pc.printf ("SD error in SD_find_next_clear_block, returning -1\r\n"); + return false; + } + for (int i = 0; i < (SD_BLOCKSIZE >> 2); i++) { + if (SDBuffer[i] != 0) { + clear_block = false; + pc.printf ("Buff at %d contains %x\r\n", i, SDBuffer[i]); + i = SD_BLOCKSIZE; // to exit loop + } + } + if (clear_block) + stab -= toaddsub; + else + stab += toaddsub; + toaddsub >>= 1; + } + if (!check_SD_block_clear(stab)) + stab++; + if (sd_error()) { // sd_error() tests and clears error bits + pc.printf ("check_SD_block_clear(%ld)returned ERROR in SD_find_next_clear_block\r\n", stab); + sd_jf = 1; // reassert error flag + return false; + } + pc.printf ("Completed find_next, stab = %d\r\n", stab); + *blok = stab; // block number of next free block + return true; +} + +bool SD_card_erase_all (void) { // assumes sd card is 4 Gbyte, erases 4 Gbyte. Called from CLI + uint64_t EndAddr = GIGAB * 4, + StartAddr = 0LL; + sd_jf = 0; + pc.printf ("Erasing SD card ... "); + // uint8_t Erase(uint64_t StartAddr, uint64_t EndAddr); + SD_state = sd.Erase(StartAddr, EndAddr); + if (SD_state != SD_OK) { + pc.printf ("SD_card_erase_all FAILED\r\n"); + sd_jf = 1; + return false; + } + pc.printf ("no error detected\r\n"); + return true; +} + + +bool mainSDtest() +{ + SD_state = sd.Init(); + if(SD_state != SD_OK) { + pc.printf ("sd.Init set SD_state to %0x\r\n", SD_state); + if(SD_state == MSD_ERROR_SD_NOT_PRESENT) { + pc.printf("SD shall be inserted before running test\r\n"); + } else { + pc.printf("SD Initialization : FAIL.\r\n"); + } + pc.printf("SD Test Aborted.\r\n"); + return false; + } +// else { // SD_state is SD_OK + pc.printf("SD Initialization : OK.\r\n"); + + + +// SD_card_erase_all(); +// if (sd_error()) +// pc.printf ("SD_card_erase_all() reports ERROR"); + + + + SD_find_next_clear_block(& SD_blockptr); + pc.printf ("SD_find_next_clear_block returned %lld\r\n\n\n", SD_blockptr); + if (sd_error()) { + pc.printf ("***** ERROR returned from SD_find_next_clear_block ***** SD ops aborted\r\n"); + return false; + } + pc.printf("SD_find_next_clear_block() returned %ld\r\n", SD_blockptr); + if (SD_blockptr < 1) { + pc.printf ("Looks like card newly erased, SD_blockptr value of %d\r\n", SD_blockptr); + SD_blockptr = 0; + historic_distance = 0; + } + else { + SD_state = sd.ReadBlocks(SDBuffer, SD_BLOCKSIZE * (SD_blockptr - 1), SD_BLOCKSIZE, 1); + if (SD_state != SD_OK) { + pc.printf ("Error reading last block from SD block %d\r\n", SD_blockptr - 1); + return false; + } + for (int i = 0; i < (SD_BLOCKSIZE >> 2); i++) + pc.printf ("%lx\t", SDBuffer[i]); + historic_distance = SDBuffer[(SD_BLOCKSIZE >> 2) - 1]; + pc.printf ("\r\nAbove, data read from last filled SD block %lld, using historic_distance = %lx\r\n", SD_blockptr - 1, historic_distance); + } + if (SD_blockptr > 2) { + for (int i = SD_blockptr - 2; i < SD_blockptr + 2; i++) { + pc.printf ("check_SD_block_clear (%d) ", i); + if (check_SD_block_clear(i)) + pc.printf ("block %ld is CLEAR\r\n", i); + else + pc.printf ("block %ld is NOT clear\r\n", i); + if (sd_error()) { + pc.printf ("ERROR from check_SD_block_clear ()\r\n"); + } + } + } + return true; +} + + + + + + + + +class speed_measurement // Interrupts at qtr sec cause read of Hall_pulse counters which are incremented by transitions of Hall inputs +{ + static const int SPEED_AVE_PTS = 9; // AVE_PTS - points in moving average filters + int speed_maf_mem [(SPEED_AVE_PTS + 1) * 2][NUMBER_OF_MOTORS], + latest_counter_read[NUMBER_OF_MOTORS], + prev_counter_read[NUMBER_OF_MOTORS], + mafptr; + int raw_filtered () ; // sum of count for all motors + +public: + + speed_measurement () { + memset(speed_maf_mem, 0, sizeof(speed_maf_mem)); + mafptr = 0; + memset (latest_counter_read, 0, sizeof(latest_counter_read)); + memset (prev_counter_read, 0, sizeof(prev_counter_read)); + } // constructor + int raw_filtered (int) ; // count for one motor + int RPM () ; + double MPH () ; + void qtr_sec_update () ; + uint32_t metres_travelled (); + uint32_t pulse_total (); +} +speed ; + +int speed_measurement::raw_filtered () // sum of count for all motors +{ + int result = 0, a, b; + for (b = 0; b < NUMBER_OF_MOTORS; b++) { + for (a = 0; a < SPEED_AVE_PTS; a++) { + result += speed_maf_mem[a][b]; + } + } + return result; +} + +int speed_measurement::raw_filtered (int motor) // count for one motor +{ + int result = 0, a; + for (a = 0; a < SPEED_AVE_PTS; a++) { + result += speed_maf_mem[a][motor]; + } + return result; +} + +double speed_measurement::MPH () +{ + return rpm2mph * (double)RPM(); +} + +int speed_measurement::RPM () +{ + int rpm = raw_filtered (); + rpm *= 60 * 4; // 60 sec per min, 4 quarters per sec, result pulses per min + rpm /= (SPEED_AVE_PTS * NUMBER_OF_MOTORS * 8); // 8 transitions counted per rev + return rpm; +} + +void speed_measurement::qtr_sec_update () // this to be called every quarter sec to read counters and update maf +{ + mafptr++; + if (mafptr >= SPEED_AVE_PTS) + mafptr = 0; + for (int a = 0; a < NUMBER_OF_MOTORS; a++) { + prev_counter_read[a] = latest_counter_read[a]; + latest_counter_read[a] = Hall_pulse[a]; + speed_maf_mem[mafptr][a] = latest_counter_read[a] - prev_counter_read[a]; + } +} + +uint32_t speed_measurement::metres_travelled () +{ + return pulse_total() / (int)PULSES_PER_METRE; +} + +uint32_t speed_measurement::pulse_total () +{ + return historic_distance + Hall_pulse[0] + Hall_pulse[1] + Hall_pulse[2] + Hall_pulse[3]; +} + +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_pwm = p; + p *= 0.95; // need limit, ffi see MCP1630 data + p = 1.0 - p; // because pwm is wrong way up + maxv.pulsewidth_ticks ((int)(p * MAX_PWM_TICKS)); // PWM output on pin D12 inverted motor pwm +} + +void set_I_limit (double p) // Sets max motor current +{ + int a; + if (p < 0.0) + p = 0.0; + if (p > 1.0) + p = 1.0; + a = (int)(p * MAX_PWM_TICKS); + if (a > MAX_PWM_TICKS) + a = MAX_PWM_TICKS; + if (a < 0) + a = 0; + maxi.pulsewidth_ticks (a); // PWM output on pin D12 inverted motor pwm +} + +double read_ammeter () +{ + int a = 0; + for (int b = 0; b < MAF_PTS; b++) + a += I_maf[b]; + a /= MAF_PTS; + double i = (double) a; + return (i * 95.0 / 32768.0) - 95.0 + 0.46; // fiddled to suit current module +} + +double read_voltmeter () +{ + int a = 0; + for (int b = 0; b < MAF_PTS; b++) + a += V_maf[b]; + a /= MAF_PTS; + double i = (double) a; + return (i / 617.75) + 0.3; // fiddled to suit current module +} + +// Interrupt Service Routines + +void ISR_mot1_hall_handler () // read motor position pulse signals from up to six motors +{ + Hall_pulse[0]++; +} +void ISR_mot2_hall_handler () +{ + Hall_pulse[1]++; +} +void ISR_mot3_hall_handler () +{ + Hall_pulse[2]++; +} +void ISR_mot4_hall_handler () +{ + Hall_pulse[3]++; +} +/*void ISR_mot5_hall_handler () +{ + Hall_pulse[4]++; +} +void ISR_mot6_hall_handler () +{ + Hall_pulse[5]++; +} +*/ + +void ISR_current_reader (void) // FIXED at 250us +{ + trigger_current_read = true; // every 250us, i.e. 4kHz NOTE only sets trigger here, readings taken in main loop +} + +void ISR_tick_32ms (void) // +{ + trigger_32ms = true; +} +void ISR_tick_250ms (void) +{ + 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; + I_maf[maf_ptr] = ht_amps_ain.read_u16(); + V_maf[maf_ptr] = ht_volts_ain.read_u16(); + maf_ptr++; + if (maf_ptr > MAF_PTS - 1) + maf_ptr = 0; +} +/* Feb 2017, re-thought use of FR and SG signals. Rename these FWD and REV. Truth table for actions required now : - +FWD(A5) REV(A4) PWM Action + 0 0 0 'Handbrake' - energises motor to not move + 0 0 1 'Handbrake' - energises motor to not move + 0 1 0 Reverse0 + 0 1 1 Reverse1 + + 1 0 0 Forward0 + 1 0 1 Forward1 + 1 1 0 Regen Braking + 1 1 1 Regen Braking +*/ +void set_run_mode (int mode) +{ + 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); + break; + case REGEN_BRAKE: // BRAKING, pwm affects degree + 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) { + forward_pin = 0; + reverse_pin = 1; + } else { + forward_pin = 1; + reverse_pin = 0; + } + slider.state = mode; + break; + default: + break; + } +} + +void update_SD_card () { // Hall pulse total updated once per sec and saved in blocks of 128 to SD card + static int index = 0; + static uint32_t buff[(SD_BLOCKSIZE >> 2) + 2]; + buff[index++] = speed.pulse_total(); // pulse_total for all time, add this to buffer to write to SD + if (index >= (SD_BLOCKSIZE >> 2)) { + pc.printf ("Writing new SD block %d ... ", SD_blockptr); + SD_state = sd.WriteBlocks(buff, SD_BLOCKSIZE * SD_blockptr, SD_BLOCKSIZE, 1); + SD_blockptr++; + if (SD_state == SD_OK) + pc.printf ("OK, distance %d\r\n", buff[index - 1] / (int)PULSES_PER_METRE); + else + pc.printf ("ERROR\r\n"); + index = 0; + } +} + +int main() +{ + int c_5 = 0, seconds = 0, minutes = 0; + ky_bd kybd_a, kybd_b; + memset (&kybd_a, 0, sizeof(kybd_a)); + memset (&kybd_b, 0, sizeof(kybd_b)); + + spareio_d8.mode (PullUp); + spareio_d9.mode (PullUp); + spareio_d10.mode(PullUp); + + Ticker tick250us; + Ticker tick32ms; + Ticker tick250ms; + +// Setup User Interrupt Vectors + mot1hall.fall (&ISR_mot1_hall_handler); + mot1hall.rise (&ISR_mot1_hall_handler); + mot2hall.fall (&ISR_mot2_hall_handler); + mot2hall.rise (&ISR_mot2_hall_handler); + mot3hall.fall (&ISR_mot3_hall_handler); + mot3hall.rise (&ISR_mot3_hall_handler); + mot4hall.fall (&ISR_mot4_hall_handler); + mot4hall.rise (&ISR_mot4_hall_handler); + + tick250us.attach_us (&ISR_current_reader, 250); // set to longer time to test + tick32ms.attach_us (&ISR_tick_32ms, 32001); + tick250ms.attach_us (&ISR_tick_250ms, 250002); + pc.baud (9600); + GfetT1 = 0; + GfetT2 = 0; // two output bits for future use driving horns + if (f_r_switch) + slider.direction = FWD; // make decision from key switch position here + else + slider.direction = REV; // make decision from key switch position here + +// 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); + + pc.printf ("Jon's Touch Screen Loco 2017 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 + + mainSDtest(); + + bool toggle32ms = false; + // Main loop + while(1) { // + struct ky_bd * present_kybd, * previous_kybd; + bool sliderpress = false; + command_line_interpreter () ; // Do any actions from command line via usb link + stuff_to_do_every_250us () ; + + if (trigger_32ms == true) { // Stuff to do every 32 milli secs + trigger_32ms = false; + 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 + + else { // nice slow non-jerky glidey movement required + dbl = (double)(k - slider.position); + dbl /= 13.179; + 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); + } + } 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_pwm * 1.002 + 0.001); + break; + case 22: // key is 'ammeter' +// set_V_limit (last_pwm * 0.99); + break; + } // endof switch (k) + } // endof if (inlist(*present2, k)) { + if (inlist(*present_kybd, k) && !inlist(*previous_kybd, k)) { + 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); + break; + case AMETER_BUT: // + pc.printf ("Ammeter key pressed %d\r\n", k); + 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) ; + } + } // 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; + double torque_req; + 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_pwm = %.3f\r\n", torque_req, last_pwm); + set_I_limit (torque_req); + if (torque_req < 0.05) + set_V_limit (last_pwm / 2.0); + else { + if (last_pwm < 0.99) + set_V_limit (last_pwm + 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; + speed.qtr_sec_update (); + double speedmph = speed.MPH(), amps = 0.0 - read_ammeter(), volts = read_voltmeter(); +//static const double mph_2_mm_per_sec = 447.04; // exact +// double mm_travelled_in_qtrsec = speedmph * mph_2_mm_per_sec / 4.0; + slider.loco_speed = speedmph; + update_meters (speedmph, amps, volts) ; +// update_meters (7.5, amps, volts) ; + 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); + } + } + c_5++; + // Can do stuff once per second here + if(c_5 > 3) { + c_5 = 0; + seconds++; + if (seconds > 59) { + seconds = 0; + minutes++; + // do once per minute stuff here + } // fall back into once per second + if(SD_state == SD_OK) { + uint32_t distance = speed.metres_travelled(); + char dist[20]; + sprintf (dist, "%05d m", distance); + displaytext (236, 226, 2, dist); + update_SD_card (); // Buffers data for SD card, writes when buffer filled + } +// calc_motor_amps( mva); + } // endof if(c_5 > 3 + } // endof if (qtrsec_trig == true) { + } // endof while(1) main programme loop +} // endof int main() { + +