新建会话
Ctrl
K
Kimi+
历史会话

Electric Vehicle Motor Control Script

explain async def task_control_motor(): global vars global cfg global button_press_state_previous global front_motor global rear_motor global throttle_1 global throttle_2 global brake_sensor while True: ########################################################################################## # Throttle # read 1st and 2nd throttle, and use the max value of both throttle_1_value = throttle_1.value throttle_2_value = throttle_2.value throttle_value = max(throttle_1_value, throttle_2_value) #print(throttle_1.adc_value, throttle_1.value) #print(throttle_2.adc_value, throttle_2.value) #print(rear_motor.data.battery_voltage_x10) #print() # check to see if throttle is over the suposed max error value, # if this happens, that probably means there is an issue with ADC and this can be dangerous, # as this did happen a few times during development and motor keeps running at max target / current / speed!! # the raise Exception() will reset the system if throttle_1_value > cfg.throttle_1_adc_over_max_error or \ throttle_2_value > cfg.throttle_2_adc_over_max_error: # send 3x times the motor current 0, to make sure VESC receives it # VESC set_motor_current_amps command will release the motor front_motor.set_motor_current_amps(0) rear_motor.set_motor_current_amps(0) front_motor.set_motor_current_amps(0) rear_motor.set_motor_current_amps(0) front_motor.set_motor_current_amps(0) rear_motor.set_motor_current_amps(0) if throttle_1_value > cfg.throttle_1_adc_over_max_error: message = f'throttle 1 value: {throttle_1_value} -- is over max, this can be dangerous!' else: message = f'throttle 2 value: {throttle_2_value} -- is over max, this can be dangerous!' raise Exception(message) pass # Apply cruise control throttle_value = cruise_control( vars, rear_motor.data.wheel_speed, throttle_value) # Calculate target speed front_motor.data.motor_target_speed = simpleio.map_range( throttle_value, 0.0, 1000.0, 0.0, front_motor.data.cfg.motor_erpm_max_speed_limit) rear_motor.data.motor_target_speed = simpleio.map_range( throttle_value, 0.0, 1000.0, 0.0, rear_motor.data.cfg.motor_erpm_max_speed_limit) # Limit mins and max values if front_motor.data.motor_target_speed < 500.0: front_motor.data.motor_target_speed = 0.0 if rear_motor.data.motor_target_speed < 500.0: rear_motor.data.motor_target_speed = 0.0 if front_motor.data.motor_target_speed > \ front_motor.data.cfg.motor_erpm_max_speed_limit: front_motor.data.motor_target_speed = front_motor.data.cfg.motor_erpm_max_speed_limit if rear_motor.data.motor_target_speed > \ rear_motor.data.cfg.motor_erpm_max_speed_limit: rear_motor.data.motor_target_speed = rear_motor.data.cfg.motor_erpm_max_speed_limit # Set motor min and max currents front_motor.set_motor_current_limits( front_motor.data.motor_target_current_limit_min, front_motor.data.motor_target_current_limit_max) rear_motor.set_motor_current_limits( rear_motor.data.motor_target_current_limit_min, rear_motor.data.motor_target_current_limit_max) # Set battery min and max currents front_motor.set_battery_current_limits( front_motor.data.battery_target_current_limit_min, front_motor.data.battery_target_current_limit_max) rear_motor.set_battery_current_limits( rear_motor.data.battery_target_current_limit_min, rear_motor.data.battery_target_current_limit_max) # Check if brakes are active vars.brakes_are_active = True if brake_sensor.value else False # If motor state is disabled, set motor current to 0 to release the motor if vars.motors_enable_state == False: front_motor.set_motor_current_amps(0) rear_motor.set_motor_current_amps(0) else: # If brakes are active, set motor speed to 0 to have the highest brake / regen if vars.brakes_are_active: front_motor.set_motor_speed_rpm(0) rear_motor.set_motor_speed_rpm(0) # If brakes are not active, set the motor speed else: front_motor.set_motor_speed_rpm(front_motor.data.motor_target_speed) rear_motor.set_motor_speed_rpm(rear_motor.data.motor_target_speed) # We just updated the motor target, so let's feed the watchdog to avoid a system reset watchdog.feed() # avoid system reset because watchdog timeout gc.collect() # https://learn.adafruit.com/Memory-saving-tips-for-CircuitPython await asyncio.sleep(0.02)
This is the “brain” of an electric skateboard / e-bike / similar vehicle.
It runs forever (while True) at 50 Hz (20 ms period) and does only one job: decide how fast each motor should spin and how much current they may draw, while keeping the rider safe.

  1. Read the rider’s request

  • Two independent throttle potentiometers are sampled.
  • The bigger of the two values is kept (“dual throttle” safety).
  • If either ADC value is higher than a hard-wired maximum that the hardware can never reach under normal conditions, the code assumes the ADC or wiring is fried.
    – It instantly sends six “0 A” commands to the two VESC motor controllers (three times each to be sure the packet gets through).
    – Then it raises an exception, which reboots the whole microcontroller (watchdog will fire if the exception is somehow missed).
    Rebooting is the safest fallback because it guarantees the motors stop.

  1. Cruise-control overlay

The raw throttle value is passed to a cruise_control() helper.
That helper can “dial back” the throttle automatically if the wheel-speed is already above the target cruise speed (PID-style regulation).
The returned value is still in the 0-1000 range.

  1. Convert throttle → electrical RPM

simpleio.map_range() rescales:
0-1000 → 0 - motor_erpm_max_speed_limit
After that a dead-band is applied: anything below 500 eRPM is clamped to 0 so the motor is completely quiet at very small throttle openings.
Likewise, the value is hard-clamped to the configured maximum so a software bug can never request an overspeed.

  1. Current limits (battery & motor side)

The VESC controllers let you set four separate limits every cycle:
  • Motor phase-current min / max (controls torque)
  • Battery current min / max (controls power drawn from the pack)
The code simply pushes the values that were calculated elsewhere (probably from battery SOC, temperature, rider profile, etc.) to both controllers.

  1. Brake interlock & enable interlock

  • A digital brake sensor is read.
  • If the master enable flag (vars.motors_enable_state) is False the motors are immediately told to draw 0 A (coast).
  • If brakes are active and the system is enabled, the speed target is set to 0 rpm.
    On a VESC this gives maximum regenerative braking (the controller will apply the configured negative current until the wheel stops).
  • If brakes are not active and the system is enabled, the speed targets calculated in step 3 are sent to the controllers.

  1. House-keeping

  • watchdog.feed() – the hardware watchdog must be patted every cycle or the board reboots.
  • gc.collect() – CircuitPython runs on a small MCU, so an explicit garbage-collection keeps the heap from fragmenting.
  • await asyncio.sleep(0.02) – yields control back to the scheduler so other coroutines (BLE, display, logging, etc.) get CPU time.

In one sentence

Every 20 ms the loop translates the rider’s throttle (safely), applies cruise control, respects brake and enable interlocks, sets speed and current limits on dual VESCs, and then feeds the watchdog so the board never locks up.
front_motor.data.motor_target_speed = simpleio.map_range( throttle_value, 0.0, 1000.0, 0.0, front_motor.data.cfg.motor_erpm_max_speed_limit)
That line is nothing more than a linear scale / “percentage” converter.

What it literally does

simpleio.map_range(x, in_min, in_max, out_min, out_max) returns:
out_min + (x - in_min) * (out_max - out_min) / (in_max - in_min)
so with the constants you gave:
复制
front_motor.data.motor_target_speed
    = 0 + (throttle_value - 0) * (erpm_max - 0) / (1000 - 0)
    = throttle_value * erpm_max / 1000
Therefore:
  • throttle_value = 0 → 0 eRPM
  • throttle_value = 250 → 25 % of erpm_max
  • throttle_value = 1000 → 100 % of erpm_max
Any fractional value is kept (float), so you get smooth speed control rather than coarse integer steps.
After this single line the next few lines clamp the result to a dead-band (0 if < 500) and to the configured maximum, but the map_range itself is just “convert 0-1000 throttle counts to 0-max eRPM”.