diff --git a/diy_main_board/firmware/.vscode/settings.json b/diy_main_board/firmware/.vscode/settings.json index b437d44..42746bc 100644 --- a/diy_main_board/firmware/.vscode/settings.json +++ b/diy_main_board/firmware/.vscode/settings.json @@ -8,7 +8,7 @@ "python.analysis.extraPaths": [ "", "/home/cas/.vscode/extensions/joedevivo.vscode-circuitpython-0.2.0-linux-x64/stubs", - "/home/cas/.config/Code/User/globalStorage/joedevivo.vscode-circuitpython/bundle/20240402/adafruit-circuitpython-bundle-py-20240402/lib" + "/home/cas/.config/Code/User/globalStorage/joedevivo.vscode-circuitpython/bundle/20240428/adafruit-circuitpython-bundle-py-20240428/lib" ], - "circuitpython.board.version": "8.2.0-5-gfad86bfb2-dirty" + "circuitpython.board.version": "9.0.3" } \ No newline at end of file diff --git a/diy_main_board/firmware/boot.py b/diy_main_board/firmware/boot.py index a81a080..aad5295 100644 --- a/diy_main_board/firmware/boot.py +++ b/diy_main_board/firmware/boot.py @@ -1,4 +1,4 @@ -# import storage -# storage.disable_usb_drive() -# storage.remount("/", readonly=False) -# storage.enable_usb_drive() +import storage +storage.disable_usb_drive() +storage.remount("/", readonly=False) +storage.enable_usb_drive() diff --git a/diy_main_board/firmware/ebike_bafang_m500/.vscode/settings.json b/diy_main_board/firmware/ebike_bafang_m500/.vscode/settings.json new file mode 100644 index 0000000..8cdec8c --- /dev/null +++ b/diy_main_board/firmware/ebike_bafang_m500/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "python.languageServer": "Pylance", + "python.analysis.diagnosticSeverityOverrides": { + "reportMissingModuleSource": "none", + "reportShadowedImports": "none" + }, + "python.analysis.extraPaths": [ + "", + "/home/cas/.vscode/extensions/joedevivo.vscode-circuitpython-0.2.0-linux-x64/stubs", + "/home/cas/.config/Code/User/globalStorage/joedevivo.vscode-circuitpython/bundle/20240402/adafruit-circuitpython-bundle-py-20240402/lib" + ], + "circuitpython.board.version": null +} \ No newline at end of file diff --git a/diy_main_board/firmware/escooter_fiido_q1_s/.vscode/settings.json b/diy_main_board/firmware/escooter_fiido_q1_s/.vscode/settings.json new file mode 100644 index 0000000..df02be7 --- /dev/null +++ b/diy_main_board/firmware/escooter_fiido_q1_s/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "python.languageServer": "Pylance", + "python.analysis.diagnosticSeverityOverrides": { + "reportMissingModuleSource": "none", + "reportShadowedImports": "none" + }, + "python.analysis.extraPaths": [ + "", + "/home/cas/.vscode/extensions/joedevivo.vscode-circuitpython-0.2.0-linux-x64/stubs", + "/home/cas/.config/Code/User/globalStorage/joedevivo.vscode-circuitpython/bundle/20240411/adafruit-circuitpython-bundle-py-20240411/lib" + ], + "circuitpython.board.version": null +} \ No newline at end of file diff --git a/diy_main_board/firmware/escooter_fiido_q1_s/display_espnow.py b/diy_main_board/firmware/escooter_fiido_q1_s/display_espnow.py index bbbd21e..f10aafe 100644 --- a/diy_main_board/firmware/escooter_fiido_q1_s/display_espnow.py +++ b/diy_main_board/firmware/escooter_fiido_q1_s/display_espnow.py @@ -6,8 +6,8 @@ class Display(object): """Display""" - def __init__(self, display_mac_address, system_data): - self._system_data = system_data + def __init__(self, display_mac_address, vars): + self._vars = vars self._espnow = ESPNow.ESPNow() peer = ESPNow.Peer(mac=bytes(display_mac_address), channel=1) self._espnow.peers.append(peer) @@ -31,15 +31,15 @@ def process_data(self): # only process packages for us # must have 3 elements: message_id + 2 variables if int(data_list[0]) == int(BoardsIds.MAIN_BOARD) and len(data_list) == 3: - self._system_data.motor_enable_state = True if data_list[1] != 0 else False - self._system_data.button_power_state = data_list[2] + self._vars.motor_enable_state = True if data_list[1] != 0 else False + self._vars.button_power_state = data_list[2] except: pass def update(self): if self._espnow is not None: try: - brakes_are_active = 1 if self._system_data.brakes_are_active else 0 - self._espnow.send(f"{int(BoardsIds.DISPLAY)} {int(self._system_data.battery_voltage_x10)} {int(self._system_data.battery_current_x100)} {int(self._system_data.motor_current_x100)} {int(self._system_data.wheel_speed * 10)} {int(brakes_are_active)} {int(self._system_data.vesc_temperature_x10)} {int(self._system_data.motor_temperature_x10)}") + brakes_are_active = 1 if self._vars.brakes_are_active else 0 + self._espnow.send(f"{int(BoardsIds.DISPLAY)} {int(self._vars.battery_voltage_x10)} {int(self._vars.battery_current_x100)} {int(self._vars.motor_current_x100)} {int(self._vars.wheel_speed * 10)} {int(brakes_are_active)} {int(self._vars.vesc_temperature_x10)} {int(self._vars.motor_temperature_x10)}") except: pass diff --git a/diy_main_board/firmware/escooter_fiido_q1_s/main.py b/diy_main_board/firmware/escooter_fiido_q1_s/main.py index 8b97dca..c696c60 100644 --- a/diy_main_board/firmware/escooter_fiido_q1_s/main.py +++ b/diy_main_board/firmware/escooter_fiido_q1_s/main.py @@ -3,7 +3,7 @@ import supervisor import asyncio import simpleio -import system_data as SystemData +import vars as Vars import vesc as Vesc import Brake import throttle as Throttle @@ -21,9 +21,16 @@ wifi.radio.enabled = True class MotorControlScheme: - CURRENT = 0 - SPEED = 1 - SPEED_NO_REGEN = 2 + SPEED = 0 + SPEED_NO_REGEN = 1 + CURRENT = 2 + +# MotorControlScheme_len = len([attr for attr in dir(MotorControlScheme) if not attr.startswith('__')]) +MotorControlScheme_len = 3 + +class MotorSingleDual: + SINGLE = 0 + DUAL = 1 # Tested on a ESP32-S3-DevKitC-1-N8R2 @@ -44,11 +51,14 @@ class MotorControlScheme: # throttle value of original Fiido Q1S throttle throttle_adc_min = 16400 # this is a value that should be a bit superior than the min value, so if throttle is in rest position, motor will not run throttle_adc_max = 50540 # this is a value that should be a bit lower than the max value, so if throttle is at max position, the calculated value of throttle will be the max -throttle_adc_over_max_error = 51500 # this is a value that should be a bit superior than the max value, just to protect is the case there is some issue with the signal and then motor can keep run at max speed!! +throttle_adc_over_max_error = 54500 # this is a value that should be a bit superior than the max value, just to protect is the case there is some issue with the signal and then motor can keep run at max speed!! -motor_min_current_start = 15.0 # to much lower value will make the motor vibrate and not run, so, impose a min limit (??) motor_max_current_limit = 135.0 # max value, be careful to not burn your motor -motor_max_current_regen = -50.0 # max regen current +motor_min_current_start = 1.0 # to much lower value will make the motor vibrate and not run, so, impose a min limit (??) +motor_max_current_regen = -70.0 # max regen current + +battery_max_current_limit = 31.0 # about 2200W at 72V +battery_max_current_regen = -14.0 # about 1000W at 72V # To reduce motor temperature, motor current limits are higher at startup and low at higer speeds # motor current limits will be adjusted on this values, depending on the speed @@ -56,18 +66,35 @@ class MotorControlScheme: # up to the 'motor_current_limit_max_min', when wheel speed is # 'motor_current_limit_max_min_speed' motor_current_limit_max_max = 135.0 -motor_current_limit_max_min = 40.0 -motor_current_limit_max_min_speed = 10.0 -motor_current_limit_max_max_speed = 30.0 +motor_current_limit_max_min = 60.0 +motor_current_limit_max_min_speed = 25.0 # this are the values for regen -motor_current_limit_min_min = -50.0 -motor_current_limit_min_max = -30.0 -motor_current_limit_min_max_speed = 30.0 +motor_current_limit_min_min = -70.0 +motor_current_limit_min_max = -50.0 +motor_current_limit_min_max_speed = 25.0 + +## Battery currents +battery_current_limit_max_max = 31.0 # about 2200W at 72V +battery_current_limit_max_min = 27.0 # about 20% less +battery_current_limit_max_min_speed = 25.0 + +# this are the values for regen +battery_current_limit_min_min = -14.0 +battery_current_limit_min_max = -12.0 # about xx% less +battery_current_limit_min_max_speed = 25.0 + # default as speed control motor_control_scheme = MotorControlScheme.SPEED +# Single or Dual motor setup +# motor_single_dual = MotorSingleDual.SINGLE +motor_single_dual = MotorSingleDual.DUAL +motor_1_dual_factor = 0.6 +motor_2_dual_factor = 0.4 +motor_can_id = 101 + # MAC Address value needed for the wireless communication with the display display_mac_address = [0x68, 0xb6, 0xb3, 0x01, 0xf7, 0xf3] @@ -87,12 +114,12 @@ class MotorControlScheme: min = throttle_adc_min, # min ADC value that throttle reads, plus some margin max = throttle_adc_max) # max ADC value that throttle reads, minus some margin -sdata = SystemData.SystemData() +vars = Vars.Vars() vesc = Vesc.Vesc( board.IO13, # UART TX pin that connect to VESC board.IO14, # UART RX pin that connect to VESC - sdata) + vars) throttle_lowpass_filter_state = None def lowpass_filter(sample, filter_constant): @@ -126,7 +153,7 @@ def utils_step_towards(current_value, target_value, step): return value # don't know why, but if this objects related to ESPNow are started earlier, system will enter safemode -display = DisplayESPnow.Display(display_mac_address, sdata) +display = DisplayESPnow.Display(display_mac_address, vars) async def task_vesc_refresh_data(): while True: @@ -160,7 +187,7 @@ def cruise_control(throttle_value): global cruise_control_button_press_previous_state global cruise_control_throttle_value - button_long_press_state = sdata.button_power_state & 0x0200 + button_long_press_state = vars.button_power_state & 0x0200 # Set initial variables values if cruise_control_state == 0: @@ -169,27 +196,23 @@ def cruise_control(throttle_value): # Wait for conditions to start cruise control if cruise_control_state == 1: - if (button_long_press_state != cruise_control_button_long_press_previous_state) and sdata.wheel_speed > 4.0: + if (button_long_press_state != cruise_control_button_long_press_previous_state) and vars.wheel_speed > 4.0: cruise_control_button_long_press_previous_state = button_long_press_state cruise_control_throttle_value = throttle_value - cruise_control_state = 3 - - if cruise_control_state == 2: - cruise_control_button_press_previous_state = sdata.button_power_state & 0x0100 - cruise_control_state = 3 + cruise_control_state = 2 # Cruise control is active - if cruise_control_state == 3: + if cruise_control_state == 2: # Check for button pressed cruise_control_button_pressed = False - button_press_state = sdata.button_power_state & 0x0100 + button_press_state = vars.button_power_state & 0x0100 if button_press_state != cruise_control_button_press_previous_state: cruise_control_button_press_previous_state = button_press_state cruise_control_button_pressed = True # Check for conditions to stop cruise control - if sdata.brakes_are_active or cruise_control_button_pressed or throttle_value > (cruise_control_throttle_value * 1.15): + if vars.brakes_are_active or cruise_control_button_pressed or throttle_value > (cruise_control_throttle_value * 1.15): cruise_control_button_long_press_previous_state = button_long_press_state cruise_control_state = 1 else: @@ -202,17 +225,37 @@ def cruise_control(throttle_value): # initialize here this variables - they hold values that will change real time depending on the wheel speed, like 'motor_target_current_limit_max' will be max at startup motor_target_current_limit_max = motor_max_current_limit motor_target_current_limit_min = motor_max_current_regen -button_press_state_previous = True +battery_target_current_limit_max = battery_max_current_limit +battery_target_current_limit_min = battery_max_current_regen +button_press_state_previous = False async def task_control_motor(): global motor_target_current_limit_max global motor_target_current_limit_min + global battery_target_current_limit_max + global battery_target_current_limit_min global button_press_state_previous global motor_control_scheme # let's limit the wheel speed, so even in current control mode, the speed will be limited vesc.set_motor_limit_speed(motor_max_speed_limit) + if motor_single_dual == MotorSingleDual.DUAL: vesc.set_can_motor_limit_speed(motor_max_speed_limit, motor_can_id) while True: + # See if we want to switch the motor_control_scheme + button_press_state = True if vars.button_power_state & 0x0100 else False + if button_press_state != button_press_state_previous: + button_press_state_previous = button_press_state + + if motor_control_scheme < (MotorControlScheme_len - 1): + motor_control_scheme += 1 + else: + motor_control_scheme = 0 + + # force speed limit + if motor_control_scheme == MotorControlScheme.CURRENT: + vesc.set_motor_limit_speed(motor_max_speed_limit) + if motor_single_dual == MotorSingleDual.DUAL: vesc.set_can_motor_limit_speed(motor_max_speed_limit, motor_can_id) + ########################################################################################## # Throttle # map torque value @@ -232,105 +275,161 @@ async def task_control_motor(): if throttle_adc_previous_value > throttle_adc_over_max_error: # send 3x times the motor current 0, to make sure VESC receives it vesc.set_motor_current_amps(0) + if motor_single_dual == MotorSingleDual.DUAL: vesc.set_can_motor_current_amps(0, motor_can_id) vesc.set_motor_current_amps(0) + if motor_single_dual == MotorSingleDual.DUAL: vesc.set_can_motor_current_amps(0, motor_can_id) vesc.set_motor_current_amps(0) + if motor_single_dual == MotorSingleDual.DUAL: vesc.set_can_motor_current_amps(0, motor_can_id) raise Exception(f'throttle value: {throttle_adc_previous_value} -- is over max, this can be dangerous!') # Apply cruise control throttle_value_filtered = cruise_control(throttle_value_filtered) - - # If button was pressed, switch the motor_control_scheme - button_press_state = True if sdata.button_power_state & 0x0100 else False - if button_press_state != button_press_state_previous: - button_press_state_previous = button_press_state - - if motor_control_scheme == MotorControlScheme.CURRENT: - motor_control_scheme = MotorControlScheme.SPEED - elif motor_control_scheme == MotorControlScheme.SPEED: - motor_control_scheme = MotorControlScheme.SPEED_NO_REGEN - elif motor_control_scheme == MotorControlScheme.SPEED_NO_REGEN: - motor_control_scheme = MotorControlScheme.CURRENT - sdata.motor_target_current = simpleio.map_range( - throttle_value_filtered, - 0.0, - 1000.0, - 0.0, - motor_target_current_limit_max) + # map throttle value to target currents and wheel speed + vars.motor_target_current = simpleio.map_range(throttle_value_filtered, 0.0, 1000.0, 0.0, motor_target_current_limit_max) + vars.battery_target_current = simpleio.map_range(throttle_value_filtered, 0.0, 1000.0, 0.0, battery_target_current_limit_max) - sdata.motor_target_current_regen = motor_target_current_limit_min + vars.motor_target_current_regen = motor_target_current_limit_min + vars.battery_target_current_regen = battery_target_current_limit_min - sdata.motor_target_speed = simpleio.map_range( - throttle_value_filtered, - 0.0, - 1000.0, - 0.0, - motor_erpm_max_speed_limit) + vars.motor_target_speed = simpleio.map_range(throttle_value_filtered, 0.0, 1000.0, 0.0, motor_erpm_max_speed_limit) ########################################################################################## + ## Limit mins and max values # impose a min motor target value, as to much lower value will make the motor vibrate and not run (??) - if sdata.motor_target_current < motor_min_current_start: - sdata.motor_target_current = 0.0 - - if sdata.motor_target_speed < 500.0: - sdata.motor_target_speed = 0.0 - - # let's limit the value - if sdata.motor_target_current > motor_max_current_limit: - sdata.motor_target_current = motor_max_current_limit - - if sdata.motor_target_current_regen < motor_max_current_regen: - sdata.motor_target_current_regen = motor_max_current_regen + if vars.motor_target_current < motor_min_current_start: vars.motor_target_current = 0.0 + if vars.motor_target_current > motor_max_current_limit: vars.motor_target_current = motor_max_current_limit + if vars.motor_target_current_regen < motor_max_current_regen: vars.motor_target_current_regen = motor_max_current_regen + + if vars.battery_target_current > battery_max_current_limit: vars.battery_target_current = battery_max_current_limit + if vars.battery_target_current_regen < battery_max_current_regen: vars.battery_target_current_regen = battery_max_current_regen - if sdata.motor_target_speed > motor_erpm_max_speed_limit: - sdata.motor_target_speed = motor_erpm_max_speed_limit + if vars.motor_target_speed < 500.0: vars.motor_target_speed = 0.0 + if vars.motor_target_speed > motor_erpm_max_speed_limit: vars.motor_target_speed = motor_erpm_max_speed_limit # limit very small and negative values - if sdata.motor_target_current < 1.0: - sdata.motor_target_current = 0.0 + if vars.motor_target_current < 1.0: vars.motor_target_current = 0.0 + if vars.motor_target_current_regen > -1.0: vars.motor_target_current_regen = 0.0 + + if vars.battery_target_current < 0.1: vars.battery_target_current = 0.0 + if vars.battery_target_current_regen > -0.1: vars.battery_target_current_regen = 0.0 - if sdata.motor_target_speed < 500.0: # 2 kms/h - sdata.motor_target_speed = 0.0 + if vars.motor_target_speed < 500.0: vars.motor_target_speed = 0.0 # 2 kms/h # if brakes are active, set values to try stop the wheel if brake_sensor.value: - sdata.brakes_are_active = True + vars.brakes_are_active = True # set specific motor current when braking if motor_control_scheme == MotorControlScheme.CURRENT: - if sdata.wheel_speed > 5.0: - sdata.motor_target_current = sdata.motor_target_current_regen + if vars.wheel_speed > 2.0: + vars.motor_target_current = vars.motor_target_current_regen + vars.battery_target_current = vars.battery_target_current_regen else: # avoid making the wheel / motor rotate backwards - sdata.motor_target_current = 0.0 + vars.motor_target_current = 0.0 + vars.battery_target_current = 0.0 + + # set specific motor current when braking + elif motor_control_scheme == MotorControlScheme.SPEED_NO_REGEN: + vars.motor_target_current = vars.motor_target_current_regen + vars.battery_target_current = vars.battery_target_current_regen + vars.motor_target_speed = 0.0 # set specific motor speed when braking - else: # motor_control_scheme == MotorControlScheme.SPEED or \ - # motor_control_scheme == MotorControlScheme.SPEED_NO_REGEN: - sdata.motor_target_speed = 0.0 + else: + vars.motor_target_speed = 0.0 # brakes not active else: - sdata.brakes_are_active = False + vars.brakes_are_active = False # in SPEED_NO_REGEN mode, set motor_current_limit_min to 0 to disable regen + motor_current_limit_min = vars.motor_target_current_regen + battery_current_limit_min = vars.battery_target_current_regen if motor_control_scheme == MotorControlScheme.SPEED_NO_REGEN: - if brake_sensor.value: - vesc.set_motor_current_limit_min(sdata.motor_target_current_regen) - else: - vesc.set_motor_current_limit_min(0.0) - + if brake_sensor.value == False: + vars.motor_target_current = 0.0 + vars.battery_target_current = 0.0 + # if motor state is off, set our motor target as zero - if sdata.motor_enable_state == False: - sdata.motor_target_current = 0.0 - sdata.motor_target_speed = 0.0 - - if motor_control_scheme == MotorControlScheme.CURRENT: - vesc.set_motor_current_amps(sdata.motor_target_current) - else: # motor_control_scheme == MotorControlScheme.SPEED or \ - # motor_control_scheme == MotorControlScheme.SPEED_NO_REGEN: - vesc.set_motor_speed_rpm(sdata.motor_target_speed) - + if vars.motor_enable_state == False: + vars.motor_target_current = 0.0 + vars.battery_target_current = 0.0 + vars.motor_target_speed = 0.0 + motor_current_limit_min = 0.0 + battery_current_limit_min = 0.0 + + # apply the current values + if motor_control_scheme == MotorControlScheme.CURRENT: + if motor_single_dual == MotorSingleDual.SINGLE: + vesc.set_motor_current_limit_max(motor_target_current_limit_max) + vesc.set_motor_current_limit_min(motor_current_limit_min) + vesc.set_battery_current_limit_max(battery_target_current_limit_max) + vesc.set_battery_current_limit_min(battery_current_limit_min) + vesc.set_motor_current_amps(vars.motor_target_current) + else: + vesc.set_motor_current_limit_max(motor_target_current_limit_max * motor_1_dual_factor) + vesc.set_can_motor_current_limit_max(motor_target_current_limit_max * motor_2_dual_factor, motor_can_id) + + vesc.set_motor_current_limit_min(motor_current_limit_min) + vesc.set_can_motor_current_limit_min(motor_current_limit_min * motor_2_dual_factor, motor_can_id) + + vesc.set_battery_current_limit_max(battery_target_current_limit_max) + vesc.set_can_battery_current_limit_max(battery_target_current_limit_max * motor_2_dual_factor, motor_can_id) + + vesc.set_battery_current_limit_min(battery_current_limit_min) + vesc.set_can_battery_current_limit_min(battery_current_limit_min * motor_2_dual_factor, motor_can_id) + + vesc.set_motor_current_amps(vars.motor_target_current * motor_1_dual_factor) + vesc.set_can_motor_current_amps(vars.motor_target_current * motor_2_dual_factor, motor_can_id) + + elif motor_control_scheme == MotorControlScheme.SPEED: + if motor_single_dual == MotorSingleDual.SINGLE: + vesc.set_motor_current_limit_max(motor_target_current_limit_max) + vesc.set_motor_current_limit_min(motor_current_limit_min) + vesc.set_battery_current_limit_max(battery_target_current_limit_max) + vesc.set_battery_current_limit_min(battery_current_limit_min) + vesc.set_motor_speed_rpm(vars.motor_target_speed) + else: + vesc.set_motor_current_limit_max(motor_target_current_limit_max * motor_1_dual_factor) + vesc.set_can_motor_current_limit_max(motor_target_current_limit_max * motor_2_dual_factor, motor_can_id) + + vesc.set_motor_current_limit_min(motor_current_limit_min * motor_1_dual_factor) + vesc.set_can_motor_current_limit_min(motor_current_limit_min * motor_2_dual_factor, motor_can_id) + + vesc.set_battery_current_limit_max(battery_target_current_limit_max * motor_1_dual_factor) + vesc.set_can_battery_current_limit_max(battery_target_current_limit_max * motor_2_dual_factor, motor_can_id) + + vesc.set_battery_current_limit_min(battery_current_limit_min * motor_1_dual_factor) + vesc.set_can_battery_current_limit_min(battery_target_current_limit_max * motor_2_dual_factor, motor_can_id) + + vesc.set_motor_speed_rpm(vars.motor_target_speed) + vesc.set_can_motor_speed_rpm(vars.motor_target_speed, motor_can_id) + + elif motor_control_scheme == MotorControlScheme.SPEED_NO_REGEN: + if motor_single_dual == MotorSingleDual.SINGLE: + vesc.set_motor_current_limit_max(motor_target_current_limit_max) + vesc.set_motor_current_limit_min(vars.motor_target_current) + vesc.set_battery_current_limit_max(battery_target_current_limit_max) + vesc.set_battery_current_limit_min(vars.battery_target_current) + vesc.set_motor_speed_rpm(vars.motor_target_speed) + else: + vesc.set_motor_current_limit_max(motor_target_current_limit_max * motor_1_dual_factor) + vesc.set_can_motor_current_limit_max(motor_target_current_limit_max * motor_2_dual_factor, motor_can_id) + + vesc.set_motor_current_limit_min(vars.motor_target_current * motor_1_dual_factor) + vesc.set_can_motor_current_limit_min(vars.motor_target_current * motor_2_dual_factor, motor_can_id) + + vesc.set_battery_current_limit_max(battery_target_current_limit_max * motor_1_dual_factor) + vesc.set_can_battery_current_limit_max(battery_target_current_limit_max * motor_2_dual_factor, motor_can_id) + + vesc.set_battery_current_limit_min(vars.battery_target_current * motor_1_dual_factor) + vesc.set_can_battery_current_limit_min(vars.battery_target_current * motor_2_dual_factor, motor_can_id) + + vesc.set_motor_speed_rpm(vars.motor_target_speed) + vesc.set_can_motor_speed_rpm(vars.motor_target_speed, motor_can_id) + # 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 @@ -342,53 +441,63 @@ async def task_control_motor(): async def task_control_motor_limit_current(): global motor_target_current_limit_max global motor_target_current_limit_min + global battery_target_current_limit_max + global battery_target_current_limit_min while True: ########################################################################################## motor_target_current_limit_max = simpleio.map_range( - sdata.wheel_speed, + vars.wheel_speed, + 5.0, motor_current_limit_max_min_speed, - motor_current_limit_max_max_speed, motor_current_limit_max_max, motor_current_limit_max_min) motor_target_current_limit_min = simpleio.map_range( - sdata.wheel_speed, - motor_current_limit_max_min_speed, - motor_current_limit_max_max_speed, + vars.wheel_speed, + 5.0, + motor_current_limit_min_max_speed, motor_current_limit_min_min, motor_current_limit_min_max) - vesc.set_motor_current_limit_max(motor_target_current_limit_max) + battery_target_current_limit_max = simpleio.map_range( + vars.wheel_speed, + 5.0, + battery_current_limit_max_min_speed, + battery_current_limit_max_max, + battery_current_limit_max_min) + + battery_target_current_limit_min = simpleio.map_range( + vars.wheel_speed, + 5.0, + battery_current_limit_min_max_speed, + battery_current_limit_min_min, + battery_current_limit_min_max) - # In SPEED_NO_REGEN mode, the motor_current_limit_min should not be set here - if motor_control_scheme != MotorControlScheme.SPEED_NO_REGEN: - vesc.set_motor_current_limit_min(motor_target_current_limit_min) - gc.collect() # https://learn.adafruit.com/Memory-saving-tips-for-CircuitPython - # idle 1 second - await asyncio.sleep(1.0) - + # idle 100 ms + await asyncio.sleep(0.1) + wheel_speed_previous_motor_speed_erpm = 0 -async def task_various_0_5s(): +async def task_various(): global wheel_speed_previous_motor_speed_erpm while True: - if sdata.motor_speed_erpm != wheel_speed_previous_motor_speed_erpm: - wheel_speed_previous_motor_speed_erpm = sdata.motor_speed_erpm + if vars.motor_speed_erpm != wheel_speed_previous_motor_speed_erpm: + wheel_speed_previous_motor_speed_erpm = vars.motor_speed_erpm # Fiido Q1S with installed Luneye motor 2000W # calculate the wheel speed wheel_radius = 0.165 # measured as 16.5cms perimeter = 6.28 * wheel_radius # 2*pi = 6.28 - motor_rpm = sdata.motor_speed_erpm / motor_poles_pair - sdata.wheel_speed = ((perimeter / 1000.0) * motor_rpm * 60) + motor_rpm = vars.motor_speed_erpm / motor_poles_pair + vars.wheel_speed = ((perimeter / 1000.0) * motor_rpm * 60.0) - if abs(sdata.wheel_speed < 3.5): - sdata.wheel_speed = 0.0 + if abs(vars.wheel_speed < 2.0): + vars.wheel_speed = 0.0 - await asyncio.sleep(0.5) + await asyncio.sleep(0.1) async def main(): @@ -403,13 +512,14 @@ async def main(): display_refresh_data_task = asyncio.create_task(task_display_refresh_data()) control_motor_limit_current_task = asyncio.create_task(task_control_motor_limit_current()) read_sensors_control_motor_task = asyncio.create_task(task_control_motor()) - various_0_5s_task = asyncio.create_task(task_various_0_5s()) + various_task = asyncio.create_task(task_various()) await asyncio.gather(vesc_refresh_data_task, display_refresh_data_task, control_motor_limit_current_task, read_sensors_control_motor_task, - various_0_5s_task) + various_task) asyncio.run(main()) + diff --git a/diy_main_board/firmware/firmware_common/__init__.py b/diy_main_board/firmware/firmware_common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/diy_main_board/firmware/firmware_common/boards_ids.py b/diy_main_board/firmware/firmware_common/boards_ids.py new file mode 100644 index 0000000..a1385a3 --- /dev/null +++ b/diy_main_board/firmware/firmware_common/boards_ids.py @@ -0,0 +1,7 @@ +class BoardsIds: + MAIN_BOARD = 1 + DISPLAY = 2 + POWER_SWITCH = 4 + REAR_LIGHTS = 8 + FRONT_LIGHTS = 16 + \ No newline at end of file diff --git a/diy_main_board/firmware/safemode.py b/diy_main_board/firmware/safemode.py index ff584cc..63a81dd 100644 --- a/diy_main_board/firmware/safemode.py +++ b/diy_main_board/firmware/safemode.py @@ -1,2 +1,8 @@ -# import microcontroller -# microcontroller.reset() +import microcontroller +import supervisor + +if supervisor.runtime.safe_mode_reason == supervisor.SafeModeReason.BROWNOUT: + microcontroller.reset() # Reset and start over. + +# Otherwise, do nothing. The safe mode reason will be printed in the +# console, and nothing will run. diff --git a/diy_main_board/firmware/system_data.py b/diy_main_board/firmware/vars.py similarity index 89% rename from diy_main_board/firmware/system_data.py rename to diy_main_board/firmware/vars.py index cb8ee6c..7a03791 100644 --- a/diy_main_board/firmware/system_data.py +++ b/diy_main_board/firmware/vars.py @@ -1,6 +1,6 @@ import time -class SystemData(object): +class Vars(object): def __init__(self): self.vesc_fault_code = 0 @@ -20,6 +20,8 @@ def __init__(self): self.ramp_speed_last_time = self.ramp_current_last_time self.motor_target_current = 0.0 self.motor_target_current_regen = 0.0 + self.battery_target_current = 0.0 + self.battery_target_current_regen = 0.0 self.motor_target_speed = 0.0 self.assist_level = 0 self.throttle_value = 0 diff --git a/diy_main_board/firmware/vesc.py b/diy_main_board/firmware/vesc.py index 2cded76..d27ec3a 100644 --- a/diy_main_board/firmware/vesc.py +++ b/diy_main_board/firmware/vesc.py @@ -57,8 +57,8 @@ def _uart_maybe_reset_input_buffer(self): if self._uart.in_waiting: self._uart.reset_input_buffer() - def _pack_and_send(self, buf, vesc_command, response_len): - buf[0] = vesc_command + def _pack_and_send(self, buf, response_len): + vesc_command = buf[0] #start byte + len + data + CRC 16 bits + end byte lenght = len(buf) @@ -113,11 +113,10 @@ def _pack_and_send(self, buf, vesc_command, response_len): return None def refresh_data(self): - """Read VESC motor data and update vesc_motor_data""" - vesc_command = 4 # COMM_GET_VALUES = 4; 79 bytes response (firmware bldc main branch, April 2024, commit: c8be115bb5be5a5558e3a50ba82e55931e3a45c4) - - command = bytearray([4]) - response = self._pack_and_send(command, vesc_command, 79) + """Read VESC motor data and update vesc_motor_data""" + tx_command_buffer = bytearray([4]) + tx_command_buffer[0] = 4 # COMM_GET_VALUES = 4; 79 bytes response (firmware bldc main branch, April 2024, commit: c8be115bb5be5a5558e3a50ba82e55931e3a45c4) + response = self._pack_and_send(tx_command_buffer, 79) if response is not None: # for debug @@ -128,8 +127,8 @@ def refresh_data(self): # store the motor controller data self._app_data.vesc_temperature_x10 = struct.unpack_from('>h', response, 3)[0] self._app_data.motor_temperature_x10 = struct.unpack_from('>h', response, 5)[0] - self._app_data.motor_current_x100 = (struct.unpack_from('>l', response, 7)[0]) - self._app_data.battery_current_x100 = (struct.unpack_from('>l', response, 11)[0]) + self._app_data.motor_current_x100 = struct.unpack_from('>l', response, 7)[0] + self._app_data.battery_current_x100 = struct.unpack_from('>l', response, 11)[0] self._app_data.motor_speed_erpm = struct.unpack_from('>l', response, 25)[0] self._app_data.battery_voltage_x10 = struct.unpack_from('>h', response, 29)[0] self._app_data.vesc_fault_code = response[55] @@ -137,63 +136,153 @@ def refresh_data(self): def set_motor_current_amps(self, value): """Set battery Amps""" value = value * 1000 # current in mA - vesc_command = 6 # COMM_SET_CURRENT = 6; no response - command = bytearray(5) - struct.pack_into('>l', command, 1, int(value)) - self._pack_and_send(command, vesc_command, 0) - - def set_motor_current_brake_amps(self, value): - """Set battery brake / regen Amps""" + tx_command_buffer = bytearray(5) + tx_command_buffer[0] = 6 # COMM_SET_CURRENT = 6; no response + struct.pack_into('>l', tx_command_buffer, 1, int(value)) + self._pack_and_send(tx_command_buffer, 0) + + def set_can_motor_current_amps(self, value, can_id): + """Set battery Amps""" value = value * 1000 # current in mA - vesc_command = 7 # COMM_SET_CURRENT_BRAKE = 7; no response - - command = bytearray(5) - struct.pack_into('>l', command, 1, int(value)) - self._pack_and_send(command, vesc_command, 0) + + tx_command_buffer = bytearray(7) + + tx_command_buffer[0] = 34 # COMM_FORWARD_CAN + tx_command_buffer[1] = can_id + + tx_command_buffer[2] = 6 # COMM_SET_CURRENT = 6; no response + + struct.pack_into('>l', tx_command_buffer, 3, int(value)) + self._pack_and_send(tx_command_buffer, 0) def set_motor_speed_rpm(self, value): """Set motor speed in RPM""" - vesc_command = 8 # COMM_SET_RPM = 8; no response + tx_command_buffer = bytearray(5) + tx_command_buffer[0] = 8 # COMM_SET_RPM = 8; no response + struct.pack_into('>l', tx_command_buffer, 1, int(value)) + self._pack_and_send(tx_command_buffer, 0) + + def set_can_motor_speed_rpm(self, value, can_id): + tx_command_buffer = bytearray(7) + + tx_command_buffer[0] = 34 # COMM_FORWARD_CAN + tx_command_buffer[1] = can_id + + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[2] = 8 # COMM_SET_RPM = 8; no response - command = bytearray(5) - struct.pack_into('>l', command, 1, int(value)) - self._pack_and_send(command, vesc_command, 0) + struct.pack_into('>l', tx_command_buffer, 3, int(value)) + self._pack_and_send(tx_command_buffer, 0) def set_motor_current_limit_min(self, value): value = value * 1000 # current in mA - # VESC custom command on custom firmware+ - vesc_command = 200 # no response - - command = bytearray(5) - struct.pack_into('>l', command, 1, int(value)) - self._pack_and_send(command, vesc_command, 0) + + tx_command_buffer = bytearray(5) + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[0] = 200 # no response + struct.pack_into('>l', tx_command_buffer, 1, int(value)) + self._pack_and_send(tx_command_buffer, 0) + + def set_can_motor_current_limit_min(self, value, can_id): + value = value * 1000 # current in mA + + tx_command_buffer = bytearray(7) + + tx_command_buffer[0] = 34 # COMM_FORWARD_CAN + tx_command_buffer[1] = can_id + + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[2] = 200 # no response + + struct.pack_into('>l', tx_command_buffer, 3, int(value)) + self._pack_and_send(tx_command_buffer, 0) def set_motor_current_limit_max(self, value): value = value * 1000 # current in mA - # VESC custom command on custom firmware - vesc_command = 201 # no response - command = bytearray(5) - struct.pack_into('>l', command, 1, int(value)) - self._pack_and_send(command, vesc_command, 0) + tx_command_buffer = bytearray(5) + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[0] = 201 # no response + struct.pack_into('>l', tx_command_buffer, 1, int(value)) + self._pack_and_send(tx_command_buffer, 0) + + def set_can_motor_current_limit_max(self, value, can_id): + value = value * 1000 # current in mA + + tx_command_buffer = bytearray(7) + + tx_command_buffer[0] = 34 # COMM_FORWARD_CAN + tx_command_buffer[1] = can_id + + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[2] = 201 # no response + + struct.pack_into('>l', tx_command_buffer, 3, int(value)) + self._pack_and_send(tx_command_buffer, 0) + + def set_battery_current_limit_min(self, value): + value = value * 1000 # current in mA + + tx_command_buffer = bytearray(5) + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[0] = 202 # no response + struct.pack_into('>l', tx_command_buffer, 1, int(value)) + self._pack_and_send(tx_command_buffer, 0) + + def set_can_battery_current_limit_min(self, value, can_id): + value = value * 1000 # current in mA + + tx_command_buffer = bytearray(7) + + tx_command_buffer[0] = 34 # COMM_FORWARD_CAN + tx_command_buffer[1] = can_id + + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[2] = 202 # no response + + struct.pack_into('>l', tx_command_buffer, 3, int(value)) + self._pack_and_send(tx_command_buffer, 0) + + def set_battery_current_limit_max(self, value): + value = value * 1000 # current in mA + + tx_command_buffer = bytearray(5) + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[0] = 203 # no response + struct.pack_into('>l', tx_command_buffer, 1, int(value)) + self._pack_and_send(tx_command_buffer, 0) - def set_motor_limit_speed(self, value): - # VESC custom command on custom firmware - vesc_command = 202 # no response + def set_can_battery_current_limit_max(self, value, can_id): + value = value * 1000 # current in mA - command = bytearray(5) - struct.pack_into('>l', command, 1, int(value)) - self._pack_and_send(command, vesc_command, 0) + tx_command_buffer = bytearray(7) + + tx_command_buffer[0] = 34 # COMM_FORWARD_CAN + tx_command_buffer[1] = can_id - def brake(self): - """ Brake: will set the motor current to 0 amps, efectivly coasting""" - vesc_command = 7 # COMM_SET_CURRENT_BRAKE = 7; no response + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[2] = 203 # no response + + struct.pack_into('>l', tx_command_buffer, 3, int(value)) + self._pack_and_send(tx_command_buffer, 0) + + def set_motor_limit_speed(self, value): + tx_command_buffer = bytearray(5) + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[0] = 204 # no response + struct.pack_into('>l', tx_command_buffer, 1, int(value)) + self._pack_and_send(tx_command_buffer, 0) + + def set_can_motor_limit_speed(self, value, can_id): + tx_command_buffer = bytearray(7) + + tx_command_buffer[0] = 34 # COMM_FORWARD_CAN + tx_command_buffer[1] = can_id + + # VESC custom tx_command_buffer on custom firmware + tx_command_buffer[2] = 204 # no response - command = bytearray(5) - struct.pack_into('>l', command, 1, 0) + struct.pack_into('>l', tx_command_buffer, 3, int(value)) + self._pack_and_send(tx_command_buffer, 0) - # send 3x to avoid possibility of VESC missing receiving this command - self._pack_and_send(command, vesc_command, 0) - self._pack_and_send(command, vesc_command, 0) - self._pack_and_send(command, vesc_command, 0) diff --git a/firmware_common/boards_ids.py b/firmware_common/boards_ids.py index a1385a3..0740d9d 100644 --- a/firmware_common/boards_ids.py +++ b/firmware_common/boards_ids.py @@ -4,4 +4,4 @@ class BoardsIds: POWER_SWITCH = 4 REAR_LIGHTS = 8 FRONT_LIGHTS = 16 - \ No newline at end of file +