Dual Brushless Motor ESC, 10-62V, up to 50A per motor. Motors ganged or independent, multiple control input methods, cycle-by-cycle current limit, speed mode and torque mode control. Motors tiny to kW. Speed limit and other parameters easily set in firmware. As used in 'The Brushless Brutalist' locomotive - www.jons-workshop.com. See also Model Engineer magazine June-October 2019.
Dependencies: mbed BufferedSerial Servo PCT2075 FastPWM
Update 17th August 2020 Radio control inputs completed
Diff: main.cpp
- Revision:
- 10:e40d8724268a
- Parent:
- 9:ac2412df01be
- Child:
- 11:bfb73f083009
--- a/main.cpp Sat Nov 10 17:08:21 2018 +0000 +++ b/main.cpp Tue Jan 15 09:03:57 2019 +0000 @@ -1,3 +1,4 @@ +// Cloned from 'DualBLS2018_06' on 23 November 2018 #include "mbed.h" //#include "users/mbed_official/code/mbed-dev/file/707f6e361f3e/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F401xE/device/stm32f401xe.h" #include "stm32f401xe.h" @@ -5,10 +6,13 @@ #include "BufferedSerial.h" #include "FastPWM.h" #include "Servo.h" +#include "brushless_motor.h" /* -New 29th May 2018 - YET TO CODE FOR - Fwd/Rev line from possible remote hand control box has signal routed to T5 - Also new LMT01 temperature sensor routed to T1 - and rerouted to PC_13 as InterruptIn on T1 (ports A and B I think) not workable +Brushless_STM3 board + +New 29th May 2018 ** + LMT01 temperature sensor routed to T1 - and rerouted to PC_13 as InterruptIn on T1 (ports A and B I think) not workable */ @@ -35,7 +39,7 @@ #include "F401RE.h" // See here for warnings about Servo InterruptIn not working #endif #if defined (TARGET_NUCLEO_F446ZE) // CPU in 144 pin LQFP -#include "F446ZE.h" +#include "F446ZE.h" // A thought for future version #endif /* Global variable declarations */ volatile uint32_t fast_sys_timer = 0; // gets incremented by our Ticker ISR every VOLTAGE_READ_INTERVAL_US @@ -106,47 +110,74 @@ } +/**class RControl_In + Checks for __-__ duration 800-2200us + Checks repetition rate in range 5-25ms +*/ class RControl_In { // Read servo style pwm input Timer t; - int32_t clock_old, clock_new; - int32_t pulse_width_us, period_us; + int32_t pulse_width_us, period_us, pulse_count; public: - RControl_In () { - pulse_width_us = period_us = 0; - com2.printf ("Setting up Radio_Congtrol_In\r\n"); + RControl_In () { // Default Constructor + pulse_width_us = period_us = pulse_count = 0; + rx_active = false; + com2.printf ("Setting up Radio_Control_In\r\n"); } ; bool validate_rx () ; - void rise () ; + void rise () ; // InterruptIn ISRs redirected to these void fall () ; + uint32_t pulsecount () ; uint32_t pulsewidth () ; uint32_t period () ; + double normalised (); // Returns 0.0 <= p <= 1.0 or something else when rc not active bool rx_active; } ; + uint32_t RControl_In::pulsewidth () { return pulse_width_us; } +uint32_t RControl_In::pulsecount () +{ + return pulse_count; +} + uint32_t RControl_In::period () { return period_us; } bool RControl_In::validate_rx () -{ - if ((clock() - clock_new) > 4) +{ // Tests for pulse width and repetition rates being believable + if ((period_us < 5000) || (period_us > 25000) || (pulse_width_us < 800) || (pulse_width_us > 2200)) + { rx_active = false; + pc.printf ("RC per=%d, pw=%d\r\n", period_us, pulse_width_us); + } else rx_active = true; return rx_active; } -void RControl_In::rise () // These may not work as use of PortB as port may bugger InterruptIn use -{ - t.stop (); +double RControl_In::normalised () { + if (!validate_rx()) + return 0.0; // ** WHAT TO RETURN WHEN RC NOT ACTIVE ? ** + double norm = (double) (pulse_width_us - 1000); // left with -200 to + 1200 allowing for some margin + norm /= 1000.0; + if (norm > 1.0) + norm = 1.0; + if (norm < 0.0) + norm = 0.0; + return norm; +} + +void RControl_In::rise () // These may not work as use of PortB as port may bugger InterruptIn use ** THIS IS SO ** +{ // December 2018 - ** FIXED ** by using PC_14 and 15 instead +// t.stop (); period_us = t.read_us (); t.reset (); t.start (); @@ -154,14 +185,28 @@ void RControl_In::fall () { pulse_width_us = t.read_us (); - clock_old = clock_new; - clock_new = clock(); - if ((clock_new - clock_old) < 4) - rx_active = true; + pulse_count++; +} +// end of RControl_In class + + RControl_In RC_chan_1, RC_chan_2; // Declare two radio control input channels + +// Radio Control Input Interrupt Handlers +void RC_chan_1rise () { + RC_chan_1.rise (); } - +void RC_chan_1fall () { + RC_chan_1.fall (); +} +void RC_chan_2rise () { + RC_chan_2.rise (); +} +void RC_chan_2fall () { + RC_chan_2.fall (); +} +// End of Radio Control Input Interrupt Handlers -Servo * Servos[2]; +//Servo * Servos[2]; // Is possible to create pointers to Servo but no need to. //uint32_t HAtot = 0, HBtot = 0, A_Offset = 0, B_Offset = 0; /* @@ -182,8 +227,8 @@ */ const uint16_t A_tabl[] = { // Origial table 0, 0, 0, 0, 0, 0, 0, 0, // Handbrake - 0, AWV,AVU,AWU,AUW,AUV,AVW,AUW, // Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0, // JP, FR, SG, PWM = 1 0 1 1 Forward1 - 0, AVW,AUV,AUW,AWU,AVU,AWV,AWU, // Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0, // JP, FR, SG, PWM = 1 1 0 1 Reverse1 + 0, AWHVL,AVHUL,AWHUL,AUHWL,AUHVL,AVHWL,AUHWL, // Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0, // JP, FR, SG, PWM = 1 0 1 1 Forward1 + 0, AVHWL,AUHVL,AUHWL,AWHUL,AVHUL,AWHVL,AWHUL, // Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0, // JP, FR, SG, PWM = 1 1 0 1 Reverse1 0, BRA,BRA,BRA,BRA,BRA,BRA,BRA, // Regenerative Braking KEEP_L_MASK_A, KEEP_H_MASK_A // [32 and 33] } ; @@ -194,8 +239,8 @@ } ; const uint16_t B_tabl[] = { 0, 0, 0, 0, 0, 0, 0, 0, // Handbrake - 0, BWV,BVU,BWU,BUW,BUV,BVW,BUW, // Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0, // JP, FR, SG, PWM = 1 0 1 1 Forward1 - 0, BVW,BUV,BUW,BWU,BVU,BWV,BWU, // Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0, // JP, FR, SG, PWM = 1 1 0 1 Reverse1 + 0, BWHVL,BVHUL,BWHUL,BUHWL,BUHVL,BVHWL,BUHWL, // Forward 0, WV1, VU1, WU1, UW1, UV1, VW1, 0, // JP, FR, SG, PWM = 1 0 1 1 Forward1 + 0, BVHWL,BUHVL,BUHWL,BWHUL,BVHUL,BWHVL,BWHUL, // Reverse 0, VW1, UV1, UW1, WU1, VU1, WV1, 0, // JP, FR, SG, PWM = 1 1 0 1 Reverse1 0, BRB,BRB,BRB,BRB,BRB,BRB,BRB, // Regenerative Braking KEEP_L_MASK_B, KEEP_H_MASK_B } ; @@ -205,256 +250,9 @@ &MBH3 } ; -class motor -{ - uint32_t Hall_total, visible_mode, inner_mode, edge_count_table[MAIN_LOOP_ITERATION_Hz]; // to contain one seconds worth - uint32_t latest_pulses_per_sec, Hall_tab_ptr, direction, ppstmp; - bool moving_flag; - const uint16_t * lut; - FastPWM * maxV, * maxI; - PortOut * Motor_Port; - InterruptIn * Hall1, * Hall2, * Hall3; -public: - bool dc_motor; - struct currents { - uint32_t max, min, ave; - } I; - int32_t angle_cnt; - uint32_t current_samples[CURRENT_SAMPLES_AVERAGED]; // Circular buffer where latest current readings get stored - uint32_t Hindex[2], tickleon, encoder_error_cnt; - uint32_t RPM, PPS; - double last_V, last_I; - motor () {} ; // Default constructor - motor (PortOut * , FastPWM * , FastPWM * , const uint16_t *, InterruptIn **) ; - void set_V_limit (double) ; // Sets max motor voltage - void set_I_limit (double) ; // Sets max motor current - void Hall_change () ; // Called in response to edge on Hall input pin - void motor_set () ; // Energise Port with data determined by Hall sensors - void direction_set (int) ; // sets 'direction' with bit pattern to eor with FORWARD or REVERSE in set_mode - bool set_mode (int); // sets mode to HANDBRAKE, FORWARD, REVERSE or REGENBRAKE - bool is_moving () ; // Returns true if one or more Hall transitions within last 31.25 milli secs - void current_calc () ; // Updates 3 uint32_t I.min, I.ave, I.max - uint32_t pulses_per_sec () ; // call this once per main loop pass to keep count = edges per sec - int read_Halls () ; // Returns 3 bits of latest Hall sensor outputs - bool motor_is_brushless (); - void high_side_off () ; - void low_side_on () ; - void raw_V_pwm (int); -} ; //MotorA, MotorB, or even Motor[2]; -motor MotorA (&MotA, &A_MAX_V_PWM, &A_MAX_I_PWM, A_tabl, AHarr); -motor MotorB (&MotB, &B_MAX_V_PWM, &B_MAX_I_PWM, B_tabl, BHarr); - -//motor * MotPtr[8]; // Array of pointers to some number of motor objects - -motor::motor (PortOut * P , FastPWM * _maxV_ , FastPWM * _maxI_ , const uint16_t * lutptr, InterruptIn ** Hall) // Constructor -{ - // Constructor - maxV = _maxV_; - maxI = _maxI_; - Hall_total = 0; // mode can be only 0, 8, 16 or 24, lut row select for Handbrake, Forward, Reverse, or Regen Braking - latest_pulses_per_sec = 0; - for (int i = 0; i < MAIN_LOOP_ITERATION_Hz; i++) - edge_count_table[i] = 0; - if (lutptr != A_tabl && lutptr != B_tabl) - pc.printf ("Fatal in 'motor' constructor, Invalid lut address\r\n"); - Hall_tab_ptr = 0; - Motor_Port = P; - pc.printf ("In motor constructor, Motor port = %lx\r\n", P); - maxV->period_ticks (MAX_PWM_TICKS + 1); // around 18 kHz - maxI->period_ticks (MAX_PWM_TICKS + 1); - maxV->pulsewidth_ticks (MAX_PWM_TICKS / 20); - maxI->pulsewidth_ticks (MAX_PWM_TICKS / 30); - visible_mode = REGENBRAKE; - inner_mode = REGENBRAKE; - lut = lutptr; - Hindex[0] = Hindex[1] = read_Halls (); - ppstmp = 0; - tickleon = 0; - direction = 0; - angle_cnt = 0; // Incremented or decremented on each Hall event according to actual measured direction of travel - encoder_error_cnt = 0; // Incremented when Hall transition not recognised as either direction - Hall1 = Hall[0]; - Hall2 = Hall[1]; - Hall3 = Hall[2]; - PPS = 0; - RPM = 0; - last_V = last_I = 0.0; - int x = read_Halls (); - if (x == 7) - dc_motor = true; - else - dc_motor = false; -} - -bool motor::motor_is_brushless () -{ - /* int x = read_Halls (); - if (x < 1 || x > 6) - return false; - return true; - */ - return !dc_motor; -} - -/** -void motor::direction_set (int dir) { -Used to set direction according to mode data from eeprom -*/ -void motor::direction_set (int dir) -{ - if (dir != 0) - dir = FORWARD | REVERSE; // bits used in eor - direction = dir; -} - -int motor::read_Halls () -{ - int x = 0; - if (*Hall1 != 0) x |= 1; - if (*Hall2 != 0) x |= 2; - if (*Hall3 != 0) x |= 4; - return x; -} - -void motor::high_side_off () -{ - uint16_t p = *Motor_Port; - p &= lut[32]; // KEEP_L_MASK_A or B - *Motor_Port = p; -} - -void motor::low_side_on () -{ -// uint16_t p = *Motor_Port; -// p &= lut[31]; // KEEP_L_MASK_A or B - *Motor_Port = lut[31]; -} - -void motor::current_calc () -{ - I.min = 0x0fffffff; // samples are 16 bit - I.max = 0; - I.ave = 0; - uint16_t sample; - for (int i = 0; i < CURRENT_SAMPLES_AVERAGED; i++) { - sample = current_samples[i]; - I.ave += sample; - if (I.min > sample) - I.min = sample; - if (I.max < sample) - I.max = sample; - } - I.ave /= CURRENT_SAMPLES_AVERAGED; -} - - -void motor::raw_V_pwm (int v) -{ - if (v < 1) v = 1; - if (v > MAX_PWM_TICKS) v = MAX_PWM_TICKS; - maxV->pulsewidth_ticks (v); -} - -void motor::set_V_limit (double p) // Sets max motor voltage. Use set_V_limit (last_V) to restore previous setting -{ - if (p < 0.0) - p = 0.0; - if (p > 1.0) - p = 1.0; - last_V = p; // for read by diagnostics - 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 to MCP1630 inverted motor pwm as MCP1630 inverts -} - -void motor::set_I_limit (double p) // Sets max motor current. pwm integrated to dc ref voltage level -{ - int a; - if (p < 0.0) - p = 0.0; - if (p > 1.0) - p = 1.0; - last_I = p; - 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 -} - -uint32_t motor::pulses_per_sec () // call this once per 'MAIN_LOOP_REPEAT_TIME_US= 31250' main loop pass to keep count = edges per sec -{ - // Can also test for motor running or not here - if (dc_motor) - return 0; - if (ppstmp == Hall_total) { -// if (dc_motor || ppstmp == Hall_total) { - moving_flag = false; // Zero Hall transitions since previous call - motor not moving - tickleon = TICKLE_TIMES; - } else { - moving_flag = true; - ppstmp = Hall_total; - } - latest_pulses_per_sec = ppstmp - edge_count_table[Hall_tab_ptr]; - edge_count_table[Hall_tab_ptr] = ppstmp; - Hall_tab_ptr++; - if (Hall_tab_ptr >= MAIN_LOOP_ITERATION_Hz) - Hall_tab_ptr = 0; - PPS = latest_pulses_per_sec; - RPM = (latest_pulses_per_sec * 60) / 24; - return latest_pulses_per_sec; -} - -bool motor::is_moving () -{ - return moving_flag; -} - -/** -bool motor::set_mode (int m) -Use to set motor to one mode of HANDBRAKE, FORWARD, REVERSE, REGENBRAKE. -If this causes change of mode, also sets V and I to zero. -*/ -bool motor::set_mode (int m) -{ - if ((m != HANDBRAKE) && (m != FORWARD) && (m != REVERSE) && (m !=REGENBRAKE)) { - pc.printf ("Error in set_mode, invalid mode %d\r\n", m); - return false; - } - if (visible_mode != m) { // Mode change, kill volts and amps to be safe - set_V_limit (0.0); - set_I_limit (0.0); - visible_mode = m; - } - if (m == FORWARD || m == REVERSE) - m ^= direction; - inner_mode = m; // idea is to use inner_mode only in lut addressing, keep 'visible_mode' true regardless of setup data in eeprom - return true; -} - -void motor::Hall_change () -{ - const int32_t delta_theta_lut[] = { // Looks up -1 for forward move detected, +1 for reverse move detected, 0 for error or unknown - 0, 0, 0, 0, 0, 0, 0, 0, // Previous Hindex was 0 - 0, 0, 0,-1, 0, 1, 0, 0, // Previous Hindex was 1 - 0, 0, 0, 1, 0, 0,-1, 0, // Previous Hindex was 2 - 0, 1,-1, 0, 0, 0, 0, 0, // Previous Hindex was 3 - 0, 0, 0, 0, 0,-1, 1, 0, // Previous Hindex was 4 - 0,-1, 0, 0, 1, 0, 0, 0, // Previous Hindex was 5 - 0, 0, 1, 0,-1, 0, 0, 0, // Previous Hindex was 6 - 0, 0, 0, 0, 0, 0, 0, 0, // Previous Hindex was 7 - } ; - int32_t delta_theta = delta_theta_lut[(Hindex[1] << 3) | Hindex[0]]; - if (delta_theta == 0) - encoder_error_cnt++; - else - angle_cnt += delta_theta; - *Motor_Port = lut[inner_mode | Hindex[0]]; // changed mode to inner_mode 27/04/18 - Hall_total++; - Hindex[1] = Hindex[0]; -} +brushless_motor MotorA (&MotA, &A_MAX_V_PWM, &A_MAX_I_PWM, A_tabl, AHarr); +brushless_motor MotorB (&MotB, &B_MAX_V_PWM, &B_MAX_I_PWM, B_tabl, BHarr); void report_motor_types () // not very good test, shows 'Brushless' if Hall inputs read 1 to 6, 'DC' otherwise @@ -499,12 +297,6 @@ // End of Interrupt Service Routines -void motor::motor_set () -{ - Hindex[0] = read_Halls (); - *Motor_Port = lut[inner_mode | Hindex[0]]; -} - void setVI (double v, double i) { MotorA.set_V_limit (v); // Sets max motor voltage @@ -606,9 +398,40 @@ } } +/** double Read_Servo1_In () +* driverpot_reading is a global 16 bit unsigned int updated in interrupt service routine +* ISR also filters signal +* This function returns normalised double (range 0.0 to 1.0) representation of driver pot position +*/ +double Read_Servo1_In () +{ + const double xjoin = 0.5, + yjoin = 0.35, + slope_a = yjoin / xjoin, + slope_b = (1.0 - yjoin)/(1.0 - xjoin); +// centre = 0.35, // For pot, first (1/3)ish in braking area, position = 1/3 drift, > (1/3)ish drive +// halfish = 0.5; // RC stick in the centre value + // Bottom half of RC stick movement maps to lowest (1/3)ish pot movement + // Higher half of RC stick movement maps to highest (2/3)ish pot movement + double t; + double demand = RC_chan_1.normalised(); + // apply demand = 1.0 - demand here to swap stick move polarity +// return pow (demand, SERVOIN_PWR_BENDER); + if (demand < 0.0) demand = 0.0; + if (demand > 1.0) demand = 1.0; + if (demand < xjoin) { + demand *= slope_a; + } + else { + t = yjoin + ((demand - xjoin) * slope_b); + demand = t; + } + return demand; +} + /** double Read_DriverPot () * driverpot_reading is a global 16 bit unsigned int updated in interrupt service routine -* ISR also filters signal +* ISR also filters signal by returning average of most recent two readings * This function returns normalised double (range 0.0 to 1.0) representation of driver pot position */ double Read_DriverPot () @@ -689,16 +512,6 @@ } //int ttime = 3; // resettable via cli 'stt', used to determine suitable force low on period for driving dc motor -bool worth_the_bother (double a, double b) { - double c = a - b; - if (c < 0.0) - c = 0.0 - c; - if (c > 0.02) { -// pc.printf ("%.3f\r\n", c); - return true; - } - return false; -} void prscfuck (int v) { /* @@ -720,54 +533,110 @@ pc.printf ("Attempt setting prescaler %d returned %d\r\n", v, w); } -enum { HAND_CONT_STATE_BEGIN, - HAND_CONT_STATE_POWER_CYCLE_TO_EXIT, - HAND_CONT_STATE_INTO_BRAKING, - HAND_CONT_STATE_INTO_DRIVING, - HAND_CONT_STATE_BRAKING, - HAND_CONT_STATE_DRIVING - } ; +void rcin_report () { + double c1 = RC_chan_1.normalised(); + double c2 = RC_chan_2.normalised(); + uint32_t pc1 = RC_chan_1.pulsecount(); + uint32_t pc2 = RC_chan_2.pulsecount(); + pc.printf ("At rcin_report, Ch1=%.3f, Ch2=%.3f, cnt1 %d, cnt2 %d\r\n", c1, c2, pc1, pc2); +// if (c1 < -0.0001) + pc.printf ("\t1 period %d, pulsewidth %d\r\n", RC_chan_1.period(), RC_chan_1.pulsewidth()); +// if (c2 < -0.0001) + pc.printf ("\t2 period %d, pulsewidth %d\r\n", RC_chan_2.period(), RC_chan_2.pulsewidth()); +} -void hand_control_state_machine () { - static int new_hand_controller_state = HAND_CONT_STATE_BEGIN; -// static int old_hand_controller_state = HAND_CONT_STATE_BEGIN; - static int old_hand_controller_direction = T5, t = 0; // Nov 2018 confirms Rob and Quentin obs, direction read at powerup +bool worth_the_bother (double a, double b) { + double c = a - b; + if (c < 0.0) + c = 0.0 - c; + if (c > 0.02) + return true; + return false; +} + +void hand_control_state_machine (int source) { // if hand control mode '3', get here @ approx 30Hz to read drivers control pot + // if servo1 mode '4', reads input from servo1 instead +enum { // states used in hand_control_state_machine() + HAND_CONT_IDLE, + HAND_CONT_BRAKE_WAIT, + HAND_CONT_BRAKE_POT, + HAND_CONT_STATE_INTO_BRAKING, + HAND_CONT_STATE_BRAKING, + HAND_CONT_STATE_INTO_DRIVING, + HAND_CONT_STATE_DRIVING + } ; + + static int hand_controller_state = HAND_CONT_IDLE; +// static int old_hand_controller_direction = T5; // Nov 2018 reworked thanks to feedback from Rob and Quentin static double brake_effort, drive_effort, pot_position, old_pot_position = 0.0; - if (T5 != old_hand_controller_direction) { // 1 Forward, 0 Reverse - pc.printf ("Direction change! Power off then on again to resume\r\n"); - mode_set_both_motors (REGENBRAKE, 1.0); -// old_hand_controller_state = new_hand_controller_state; - new_hand_controller_state = HAND_CONT_STATE_POWER_CYCLE_TO_EXIT; + static int dirbuf[5] = {100,100,100,100,100}; // Initialised with invalid direction such that 'change' is read from switch + static int dirbufptr = 0, direction_old = -1, direction_new = -1; + const int buflen = sizeof(dirbuf) / sizeof(int); + const double Pot_Brake_Range = 0.35; //pow (0.5, SERVOIN_PWR_BENDER); //0.353553 for SERVOIN_PWR_BENDER = 1.5; + + direction_old = direction_new; + +// Test for change in direction switch setting. +// If whole buffer NEWLY filled with all Fwd or all Rev, state = brake_wait + int diracc = 0; + if (dirbufptr >= buflen) + dirbufptr = 0; + dirbuf[dirbufptr++] = T5; // Read direction switch into circular debounce buffer + for (int i = 0; i < buflen; i++) + diracc += dirbuf[i]; // will = 0 or buflen if direction switch stable + if (diracc == buflen || diracc == 0) // if was all 0 or all 1 + direction_new = diracc / buflen; + if (direction_new != direction_old) + hand_controller_state = HAND_CONT_BRAKE_WAIT; // validated change of direction switch + + switch (source) { + case 3: // driver pot is control input + pot_position = Read_DriverPot (); // Only place read in the loop, rteads normalised 0.0 to 1.0 + break; + case 4: // servo 1 input is control input + break; + default: + pot_position = 0.0; + break; } - pot_position = Read_DriverPot (); // gets to here on first pass before pot has been read - switch (new_hand_controller_state) { - case HAND_CONT_STATE_BEGIN: - pot_position = Read_DriverPot (); - if (t++ > 2 && pot_position < 0.02) { - pc.printf ("In BEGIN, pot %.2f\r\n", pot_position); - new_hand_controller_state = HAND_CONT_STATE_INTO_BRAKING; + + switch (hand_controller_state) { + case HAND_CONT_IDLE: // Here for first few passes until validated direction switch reading + break; + + case HAND_CONT_BRAKE_WAIT: + pc.printf ("At HAND_CONT_BRAKE_WAIT\r\n"); + brake_effort = 0.05; // Apply braking not too fiercely + mode_set_both_motors (REGENBRAKE, brake_effort); // Direction change + hand_controller_state = HAND_CONT_BRAKE_POT; + break; + + case HAND_CONT_BRAKE_POT: + if (brake_effort < 0.9) { + brake_effort += 0.05; // ramp braking up to max over about one sec + mode_set_both_motors (REGENBRAKE, brake_effort); // Direction change + pc.printf ("Brake effort %.2f\r\n", brake_effort); + } + else { // once braking up to quite hard + if (pot_position < 0.02) { // Driver has turned pot fully anticlock + hand_controller_state = HAND_CONT_STATE_BRAKING; + } } break; - case HAND_CONT_STATE_POWER_CYCLE_TO_EXIT: - break; + case HAND_CONT_STATE_INTO_BRAKING: brake_effort = (Pot_Brake_Range - pot_position) / Pot_Brake_Range; + if (brake_effort < 0.0) + brake_effort = 0.5; mode_set_both_motors (REGENBRAKE, brake_effort); old_pot_position = pot_position; - new_hand_controller_state = HAND_CONT_STATE_BRAKING; + hand_controller_state = HAND_CONT_STATE_BRAKING; pc.printf ("Brake\r\n"); break; - case HAND_CONT_STATE_INTO_DRIVING: - new_hand_controller_state = HAND_CONT_STATE_DRIVING; - pc.printf ("Drive\r\n"); - if (T5) - mode_set_both_motors (FORWARD, 0.0); // sets both motors - else - mode_set_both_motors (REVERSE, 0.0); - break; + case HAND_CONT_STATE_BRAKING: if (pot_position > Pot_Brake_Range) // escape braking into drive - new_hand_controller_state = HAND_CONT_STATE_INTO_DRIVING; + hand_controller_state = HAND_CONT_STATE_INTO_DRIVING; else { if (worth_the_bother(pot_position, old_pot_position)) { old_pot_position = pot_position; @@ -777,9 +646,19 @@ } } break; + + case HAND_CONT_STATE_INTO_DRIVING: + pc.printf ("Drive\r\n"); + if (direction_new == 1) + mode_set_both_motors (FORWARD, 0.0); // sets both motors + else + mode_set_both_motors (REVERSE, 0.0); + hand_controller_state = HAND_CONT_STATE_DRIVING; + break; + case HAND_CONT_STATE_DRIVING: if (pot_position < Pot_Brake_Range) // escape braking into drive - new_hand_controller_state = HAND_CONT_STATE_INTO_BRAKING; + hand_controller_state = HAND_CONT_STATE_INTO_BRAKING; else { if (worth_the_bother(pot_position, old_pot_position)) { old_pot_position = pot_position; @@ -789,7 +668,9 @@ } } break; + default: + pc.printf ("Unhandled Hand Controller state %d\r\n", hand_controller_state); break; } // endof switch (hand_controller_state) { } @@ -797,6 +678,7 @@ int main() { int eighth_sec_count = 0; + double servo_angle = 0.0; // For testing servo outs MotA = 0; // Output all 0s to Motor drive ports A and B MotB = 0; @@ -805,6 +687,18 @@ Temperature_pin.fall (&temp_sensor_isr); Temperature_pin.mode (PullUp); +#ifdef RC1IN + RC_1_in.rise (& RC_chan_1rise) ; + RC_1_in.fall (& RC_chan_1fall) ; + RC_1_in.mode (PullDown); +#endif +#ifdef RC2IN + RC_2_in.rise (& RC_chan_2rise) ; + RC_2_in.fall (& RC_chan_2fall) ; + RC_2_in.mode (PullDown); +#endif +// Servo1_i.mode (PullUp); // These never worked, December 2018 re-trying using PC_14 and 15 +// Servo2_i.mode (PullUp); MAH1.rise (& MAH_isr); // Set up interrupt vectors MAH1.fall (& MAH_isr); @@ -826,8 +720,6 @@ MBH1.mode (PullUp); MBH2.mode (PullUp); MBH3.mode (PullUp); - Servo1_i.mode (PullUp); - Servo2_i.mode (PullUp); // Setup system timers to cause periodic interrupts to synchronise and automate volt and current readings, loop repeat rate etc tick_vread.attach_us (&ISR_voltage_reader, VOLTAGE_READ_INTERVAL_US); // Start periodic interrupt generator @@ -898,15 +790,17 @@ // prscfuck (PWM_PRESECALER_DEFAULT); // Test fucking with fastpwm prescaler - Servos[0] = Servos[1] = NULL; +// Servos[0] = Servos[1] = NULL; // NOTE The ONLY way to get both servos working properly is to NOT use any if (bla) Servo ervo1(PB_8); // Only works with unconditional inline code // However, setup code for Input by default, // This can be used to monitor Servo output also ! + +// December 2018 ** NEED TO PROVE SERVO OUT WORKS ** YES, DONE. Servo Servo1 (PB_8) ; - Servos[0] = & Servo1; +// Servos[0] = & Servo1; Servo Servo2 (PB_9) ; - Servos[1] = & Servo2; +// Servos[1] = & Servo2; // pc.printf ("last_temp_count = %d\r\n", last_temp_count); // Has had time to do at least 1 conversion if ((last_temp_count > 160) && (last_temp_count < 2400)) // in range -40 to +100 degree C @@ -945,6 +839,11 @@ } pc.printf ("About to start!, mode_bytes[COMM_SRC]= %d\r\n", mode_bytes[COMM_SRC]); +// const double pwr = 1.5;SERVOIN_PWR_BENDER +// for (double i = 0.0; i < 1.05; i+= 0.1) +// pc.printf ("%f ^ %f = %f\r\n", i, SERVOIN_PWR_BENDER, pow(i, SERVOIN_PWR_BENDER)); + +// pc.printf ("PortA=%lx\r\n", PortA); while (1) { // Loop forever, repeats synchroised by waiting for ticker Interrupt Service Routine to set 'loop_flag' true while (!loop_flag) { // Most of the time is spent in this loop, repeatedly re-checking for commands from pc port @@ -952,7 +851,11 @@ command_line_interpreter_loco () ; // Proceed beyond here once loop_timer ticker ISR has set loop_flag true AtoD_reader (); // Performs A to D conversions at rate set by ticker interrupts } - loop_flag = false; // Clear flag set by ticker interrupt handler + loop_flag = false; // Clear flag set by ticker interrupt handler. WHEN LAST CHECKED this was about every 32ms + + RC_chan_1.validate_rx(); + RC_chan_2.validate_rx(); + switch (mode_bytes[COMM_SRC]) { // Look to selected source of driving command, act on commands from wherever case 0: // Invalid break; @@ -961,9 +864,10 @@ case 2: // COM2 - Primarily for testing, bypassed by command line interpreter break; case 3: // Put all hand controller input stuff here - hand_control_state_machine (); + hand_control_state_machine (3); break; // endof hand controller stuff case 4: // Servo1 + hand_control_state_machine (4); break; case 5: // Servo2 break; @@ -1015,6 +919,15 @@ }*/ // com2.printf ("V=%+.2f, Pot=%+.2f, HA %d, HB %d, IAmin %d, IAave %d, IAmax %d, IB %d, Arpm %d, Brpm %d\r\n", Read_BatteryVolts(), Read_DriverPot(), MotorA.read_Halls (), MotorB.read_Halls (), MotorA.I.min, MotorA.I.ave, MotorA.I.max, MotorB.I.ave, (Apps * 60) / 24, (Bpps * 60) / 24); } + + // servo out test here December 2018 + servo_angle += 0.01; + if (servo_angle > TWOPI) + servo_angle -= TWOPI; + Servo1 = ((sin(servo_angle)+1.0) / 2.0); + Servo2 = ((cos(servo_angle)+1.0) / 2.0); + // Yep! Both servo outs work lovely December 2018 + } // End of if(flag_8Hz) } // End of main programme loop }