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
Radio_Control_In.cpp@16:d1e4b9ad3b8b, 2020-06-09 (annotated)
- Committer:
- JonFreeman
- Date:
- Tue Jun 09 09:20:19 2020 +0000
- Revision:
- 16:d1e4b9ad3b8b
- Parent:
- 14:acaa1add097b
About to tidy i2c stuff
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
JonFreeman | 11:bfb73f083009 | 1 | #include "mbed.h" |
JonFreeman | 11:bfb73f083009 | 2 | #include "BufferedSerial.h" |
JonFreeman | 11:bfb73f083009 | 3 | #include "Radio_Control_In.h" |
JonFreeman | 16:d1e4b9ad3b8b | 4 | #include "STM3_ESC.h" |
JonFreeman | 11:bfb73f083009 | 5 | /**class RControl_In |
JonFreeman | 11:bfb73f083009 | 6 | Jon Freeman |
JonFreeman | 11:bfb73f083009 | 7 | Jan 2019 |
JonFreeman | 11:bfb73f083009 | 8 | |
JonFreeman | 11:bfb73f083009 | 9 | Checks for __-__ duration 800-2200us |
JonFreeman | 11:bfb73f083009 | 10 | Checks repetition rate in range 5-25ms |
JonFreeman | 11:bfb73f083009 | 11 | */ |
JonFreeman | 11:bfb73f083009 | 12 | extern BufferedSerial pc; |
JonFreeman | 16:d1e4b9ad3b8b | 13 | //extern eeprom_settings user_settings ; |
JonFreeman | 11:bfb73f083009 | 14 | |
JonFreeman | 11:bfb73f083009 | 15 | // RControl_In::RControl_In () { // Default Constructor |
JonFreeman | 11:bfb73f083009 | 16 | // pulse_width_us = period_us = pulse_count = 0; |
JonFreeman | 11:bfb73f083009 | 17 | // lost_chan_return_value = 0.0; |
JonFreeman | 11:bfb73f083009 | 18 | // } ; |
JonFreeman | 11:bfb73f083009 | 19 | // RControl_In::RControl_In (PinName inp) : pulse_in(inp) { // Default Constructor |
JonFreeman | 11:bfb73f083009 | 20 | // pulse_width_us = period_us = pulse_count = 0; |
JonFreeman | 11:bfb73f083009 | 21 | // lost_chan_return_value = 0.0; |
JonFreeman | 11:bfb73f083009 | 22 | // } ; |
JonFreeman | 11:bfb73f083009 | 23 | /** |
JonFreeman | 11:bfb73f083009 | 24 | */ |
JonFreeman | 11:bfb73f083009 | 25 | void RControl_In::set_lost_chan_return_value (double d) { |
JonFreeman | 11:bfb73f083009 | 26 | lost_chan_return_value = d; |
JonFreeman | 11:bfb73f083009 | 27 | } |
JonFreeman | 11:bfb73f083009 | 28 | |
JonFreeman | 11:bfb73f083009 | 29 | uint32_t RControl_In::pulsewidth () |
JonFreeman | 11:bfb73f083009 | 30 | { |
JonFreeman | 11:bfb73f083009 | 31 | return pulse_width_us; |
JonFreeman | 11:bfb73f083009 | 32 | } |
JonFreeman | 11:bfb73f083009 | 33 | |
JonFreeman | 11:bfb73f083009 | 34 | uint32_t RControl_In::pulsecount () |
JonFreeman | 11:bfb73f083009 | 35 | { |
JonFreeman | 11:bfb73f083009 | 36 | return pulse_count; |
JonFreeman | 11:bfb73f083009 | 37 | } |
JonFreeman | 11:bfb73f083009 | 38 | |
JonFreeman | 11:bfb73f083009 | 39 | uint32_t RControl_In::period () |
JonFreeman | 11:bfb73f083009 | 40 | { |
JonFreeman | 11:bfb73f083009 | 41 | return period_us; |
JonFreeman | 11:bfb73f083009 | 42 | } |
JonFreeman | 11:bfb73f083009 | 43 | |
JonFreeman | 11:bfb73f083009 | 44 | bool RControl_In::validate_rx () |
JonFreeman | 11:bfb73f083009 | 45 | { // Tests for pulse width and repetition rates being believable |
JonFreeman | 11:bfb73f083009 | 46 | return !((period_us < 5000) || (period_us > 25000) || (pulse_width_us < 800) || (pulse_width_us > 2200)); |
JonFreeman | 11:bfb73f083009 | 47 | } |
JonFreeman | 11:bfb73f083009 | 48 | |
JonFreeman | 16:d1e4b9ad3b8b | 49 | bool RControl_In::energise (struct RC_stick_info & stick, struct brushless_motor & motor) { // December 2019 |
JonFreeman | 16:d1e4b9ad3b8b | 50 | if (stick.active) { |
JonFreeman | 16:d1e4b9ad3b8b | 51 | if (stick.zone == ZONE_DRIVE) { |
JonFreeman | 16:d1e4b9ad3b8b | 52 | motor.set_mode (stick.stick_implied_motor_direction == 1 ? MOTOR_FORWARD : MOTOR_REVERSE); |
JonFreeman | 16:d1e4b9ad3b8b | 53 | motor.set_V_limit (stick.drive_effort); |
JonFreeman | 16:d1e4b9ad3b8b | 54 | motor.set_I_limit (stick.drive_effort); // This could be 1.0, or other options |
JonFreeman | 16:d1e4b9ad3b8b | 55 | } |
JonFreeman | 16:d1e4b9ad3b8b | 56 | if (stick.zone == ZONE_BRAKE) { |
JonFreeman | 16:d1e4b9ad3b8b | 57 | motor.brake (stick.brake_effort); |
JonFreeman | 16:d1e4b9ad3b8b | 58 | } |
JonFreeman | 16:d1e4b9ad3b8b | 59 | } |
JonFreeman | 16:d1e4b9ad3b8b | 60 | return stick.active; |
JonFreeman | 16:d1e4b9ad3b8b | 61 | } |
JonFreeman | 16:d1e4b9ad3b8b | 62 | |
JonFreeman | 16:d1e4b9ad3b8b | 63 | bool RControl_In::read (class RC_stick_info & stick) { // December 2019 |
JonFreeman | 16:d1e4b9ad3b8b | 64 | double dtmp; |
JonFreeman | 16:d1e4b9ad3b8b | 65 | uint32_t old_zone = stick.zone; |
JonFreeman | 16:d1e4b9ad3b8b | 66 | stick.chan_mode = get_chanmode(); // 0 disabled, 1 uni-dir, or 2 bi-dir |
JonFreeman | 16:d1e4b9ad3b8b | 67 | stick.active = validate_rx(); // True if RC Rx delivering believable pulse duration and timing |
JonFreeman | 16:d1e4b9ad3b8b | 68 | if (stick.active && (stick.chan_mode < 1 || stick.chan_mode > 2)) { // Should signal an error here |
JonFreeman | 16:d1e4b9ad3b8b | 69 | stick.active = false; |
JonFreeman | 16:d1e4b9ad3b8b | 70 | } |
JonFreeman | 16:d1e4b9ad3b8b | 71 | if (stick.active) { |
JonFreeman | 16:d1e4b9ad3b8b | 72 | stick.raw = (double) (pulse_width_us - 1000); // Read pulse width from Rx, left with -200.0 to + 1200.0 allowing for some margin |
JonFreeman | 16:d1e4b9ad3b8b | 73 | stick.raw /= 1000.0; // pulse width varies between typ 1000 to 2000 micro seconds |
JonFreeman | 16:d1e4b9ad3b8b | 74 | stick.raw += range_offset; // range now normalised to 0.0 <= raw <= 1.0 |
JonFreeman | 16:d1e4b9ad3b8b | 75 | if (stick.raw > 1.0) stick.raw = 1.0; |
JonFreeman | 16:d1e4b9ad3b8b | 76 | if (stick.raw < 0.0) stick.raw = 0.0; // clipped to strict limits 0.0 and 1.0 |
JonFreeman | 16:d1e4b9ad3b8b | 77 | if (stick_sense != 0) |
JonFreeman | 16:d1e4b9ad3b8b | 78 | stick.raw = 1.0 - stick.raw; // user setting allows for stick sense reversal |
JonFreeman | 16:d1e4b9ad3b8b | 79 | stick.deflection = stick.raw; |
JonFreeman | 16:d1e4b9ad3b8b | 80 | stick.stick_implied_motor_direction = +1; // -1 Reverse, 0 Stopped, +1 Forward |
JonFreeman | 16:d1e4b9ad3b8b | 81 | if (stick.chan_mode == 2) { // Bi-directional centre zero stick mode selected by user |
JonFreeman | 16:d1e4b9ad3b8b | 82 | stick.deflection = (stick.raw * 2.0) - 1.0; // range here -1.0 <= deflection <= +1.0 |
JonFreeman | 16:d1e4b9ad3b8b | 83 | if (stick.deflection < 0.0) { |
JonFreeman | 16:d1e4b9ad3b8b | 84 | stick.deflection = 0.0 - stick.deflection; // range inverted if negative, direction info separated out |
JonFreeman | 16:d1e4b9ad3b8b | 85 | stick.stick_implied_motor_direction = -1; // -1 Reverse, 0 Stopped, +1 Forward (almost never 0) |
JonFreeman | 16:d1e4b9ad3b8b | 86 | } // endof deflection < 0.0 |
JonFreeman | 16:d1e4b9ad3b8b | 87 | } // endof if chan_mode == 2 |
JonFreeman | 16:d1e4b9ad3b8b | 88 | // Now find zone from deflection |
JonFreeman | 16:d1e4b9ad3b8b | 89 | stick.zone = ZONE_COAST; |
JonFreeman | 16:d1e4b9ad3b8b | 90 | if (stick.deflection < (brake_segment - 0.02)) // size of brake_segment user settable |
JonFreeman | 16:d1e4b9ad3b8b | 91 | stick.zone = ZONE_BRAKE; |
JonFreeman | 16:d1e4b9ad3b8b | 92 | if (stick.deflection > (brake_segment + 0.02)) // Tiny 'freewheel' COAST band between drive and brake |
JonFreeman | 16:d1e4b9ad3b8b | 93 | stick.zone = ZONE_DRIVE; |
JonFreeman | 16:d1e4b9ad3b8b | 94 | if (old_zone != ZONE_COAST && old_zone != stick.zone) // |
JonFreeman | 16:d1e4b9ad3b8b | 95 | stick.zone = ZONE_COAST; // Ensures transitions between BRAKE and DRIVE go via COAST |
JonFreeman | 16:d1e4b9ad3b8b | 96 | switch (stick.zone) { |
JonFreeman | 16:d1e4b9ad3b8b | 97 | case ZONE_COAST: |
JonFreeman | 16:d1e4b9ad3b8b | 98 | stick.drive_effort = 0.0; |
JonFreeman | 16:d1e4b9ad3b8b | 99 | stick.brake_effort = 0.0; |
JonFreeman | 16:d1e4b9ad3b8b | 100 | break; |
JonFreeman | 16:d1e4b9ad3b8b | 101 | case ZONE_BRAKE: |
JonFreeman | 16:d1e4b9ad3b8b | 102 | stick.brake_effort = (brake_segment - stick.deflection) / brake_segment; // 1.0 at zero deflection, reducing to 0.0 on boundary with DRIVE |
JonFreeman | 16:d1e4b9ad3b8b | 103 | stick.drive_effort = 0.0; |
JonFreeman | 16:d1e4b9ad3b8b | 104 | break; |
JonFreeman | 16:d1e4b9ad3b8b | 105 | case ZONE_DRIVE: |
JonFreeman | 16:d1e4b9ad3b8b | 106 | stick.brake_effort = 0.0; |
JonFreeman | 16:d1e4b9ad3b8b | 107 | dtmp = (stick.deflection - brake_segment) / (1.0 - brake_segment); |
JonFreeman | 16:d1e4b9ad3b8b | 108 | if (dtmp > stick.drive_effort) { // Stick has moved in increasing demand direction |
JonFreeman | 16:d1e4b9ad3b8b | 109 | stick.drive_effort *= (1.0 - stick_attack); // Apply 'viscous damping' to demand increases for smoother operation |
JonFreeman | 16:d1e4b9ad3b8b | 110 | stick.drive_effort += (dtmp * stick_attack); // Low pass filter, time constant variable by choosing 'stick_attack' value %age |
JonFreeman | 16:d1e4b9ad3b8b | 111 | } |
JonFreeman | 16:d1e4b9ad3b8b | 112 | else // Reduction or no increase in demanded drive effort |
JonFreeman | 16:d1e4b9ad3b8b | 113 | stick.drive_effort = dtmp; // Reduce demand immediately, i.e. no viscous damping on reduced demand |
JonFreeman | 16:d1e4b9ad3b8b | 114 | break; |
JonFreeman | 16:d1e4b9ad3b8b | 115 | } // endof switch |
JonFreeman | 16:d1e4b9ad3b8b | 116 | } // endof if active |
JonFreeman | 16:d1e4b9ad3b8b | 117 | else { // stick Not active |
JonFreeman | 16:d1e4b9ad3b8b | 118 | stick.zone = ZONE_BRAKE; |
JonFreeman | 16:d1e4b9ad3b8b | 119 | stick.raw = 0.0; |
JonFreeman | 16:d1e4b9ad3b8b | 120 | stick.deflection = 0.0; |
JonFreeman | 16:d1e4b9ad3b8b | 121 | } // endof not active |
JonFreeman | 16:d1e4b9ad3b8b | 122 | return stick.active; |
JonFreeman | 16:d1e4b9ad3b8b | 123 | } |
JonFreeman | 16:d1e4b9ad3b8b | 124 | |
JonFreeman | 16:d1e4b9ad3b8b | 125 | |
JonFreeman | 16:d1e4b9ad3b8b | 126 | void RControl_In::set_offset (signed char offs, char brake_pcent, char attack) { // Takes user_settings[RCIx_TRIM] |
JonFreeman | 16:d1e4b9ad3b8b | 127 | brake_segment = ((double) brake_pcent) / 100.0; |
JonFreeman | 16:d1e4b9ad3b8b | 128 | stick_attack = ((double) attack) / 100.0; |
JonFreeman | 16:d1e4b9ad3b8b | 129 | range_offset = (double) offs; |
JonFreeman | 16:d1e4b9ad3b8b | 130 | range_offset /= 1000.0; // This is where to set range_offset sensitivity |
JonFreeman | 16:d1e4b9ad3b8b | 131 | // pc.printf ("In RControl_In::set_offset, input signed char = %d, out f %.3f\r\n", offs, range_offset); |
JonFreeman | 16:d1e4b9ad3b8b | 132 | } |
JonFreeman | 16:d1e4b9ad3b8b | 133 | |
JonFreeman | 16:d1e4b9ad3b8b | 134 | uint32_t RControl_In::get_chanmode () { |
JonFreeman | 16:d1e4b9ad3b8b | 135 | return chan_mode; |
JonFreeman | 16:d1e4b9ad3b8b | 136 | } |
JonFreeman | 16:d1e4b9ad3b8b | 137 | |
JonFreeman | 16:d1e4b9ad3b8b | 138 | void RControl_In::set_chanmode (char c, char polarity) { |
JonFreeman | 16:d1e4b9ad3b8b | 139 | chan_mode = ((uint32_t) c); |
JonFreeman | 16:d1e4b9ad3b8b | 140 | stick_sense = (uint32_t) polarity; |
JonFreeman | 11:bfb73f083009 | 141 | } |
JonFreeman | 11:bfb73f083009 | 142 | |
JonFreeman | 14:acaa1add097b | 143 | void RControl_In::RadC_fall () // December 2018 - Could not make Servo port bidirectional, fix by using PC_14 and 15 as inputs |
JonFreeman | 14:acaa1add097b | 144 | { // 30th November 2019 - Swapped _rise and _fall as now using Schmitt inverters on input |
JonFreeman | 11:bfb73f083009 | 145 | period_us = t.read_us (); |
JonFreeman | 11:bfb73f083009 | 146 | t.reset (); |
JonFreeman | 11:bfb73f083009 | 147 | t.start (); |
JonFreeman | 11:bfb73f083009 | 148 | } |
JonFreeman | 11:bfb73f083009 | 149 | |
JonFreeman | 14:acaa1add097b | 150 | void RControl_In::RadC_rise () |
JonFreeman | 11:bfb73f083009 | 151 | { |
JonFreeman | 11:bfb73f083009 | 152 | pulse_width_us = t.read_us (); |
JonFreeman | 11:bfb73f083009 | 153 | pulse_count++; |
JonFreeman | 11:bfb73f083009 | 154 | } |
JonFreeman | 11:bfb73f083009 | 155 | // end of RControl_In class |