/* Copyright 2020 Marcos Chaparro mchaparro@powerdesigns.ca Copyright 2021 Maximiliano Cordoba mcordoba@powerdesigns.ca This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "conf_general.h" #ifdef HW_HAS_LUNA_CANBUS_DISPLAY #include "hw.h" #include "luna_m600_display.h" #include "app.h" #include "ch.h" #include "hal.h" #include "packet.h" #include "commands.h" #include "mc_interface.h" #include "utils.h" #include #include #include "comm_can.h" #include "shutdown.h" #include "datatypes.h" #include "timeout.h" #include "mcpwm_foc.h" // for encoder angle error #define LUNA_TORQUE_SENSOR_MINIMUM_RANGE 0x02EE #define LUNA_TORQUE_SENSOR_MAXIMUM_RANGE 0x0600 #define LUNA_TORQUE_SENSOR_DEADBAND 0.03 typedef enum { PAS_LEVEL_0 = 0x00, PAS_LEVEL_1 = 0x01, PAS_LEVEL_2 = 0x0B, PAS_LEVEL_3 = 0x0C, PAS_LEVEL_4 = 0x0D, PAS_LEVEL_5 = 0x02, PAS_LEVEL_6 = 0x15, PAS_LEVEL_7 = 0x16, PAS_LEVEL_8 = 0x17, PAS_LEVEL_9 = 0x03, PAS_LEVEL_WALK = 0x06, } LUNA_PAS_LEVEL; typedef enum { WRITE_LOW_BATTERY_ERROR = 0x00, WRITE_MAX_CURRENT_ERROR = 0x01, WRITE_ASSIST_LEVEL_ERROR = 0x02, WRITE_ASSIST_SPEED_ERROR = 0x0C, WRITE_SPEEDOMETER_ERROR = 0x16, WRITE_SPEEDOMETER_SIGNAL_ERROR = 0x17, WRITE_BASIC_SUCCESS = 0x18 } CMD_WRITE_BASIC_RESPONSE; typedef enum { LUNA_DISPLAY_ID1 = 0x03106300, LUNA_TORQUE_SENSOR_ID = 0x01F83100, } LUNA_CAN_IDs; typedef enum { LUNA_DISPLAY_ID1_LENGTH_BYTES = 4, LUNA_TORQUE_SENSOR_ID_LENGTH_BYTES = 4, } LUNA_CAN_IDs_LENGTH_BYTES; typedef enum { LUNA_LIGHT_MODE_OFF = 0, LUNA_LIGHT_MODE_ON = 1, LUNA_LIGHT_MODE_TURNING_OFF = 2, LUNA_LIGHT_MODE_TURNING_ON = 3, } LUNA_LIGHT_CONTROL_MODE; typedef enum { LUNA_ERROR_NONE = 0x00, LUNA_ERROR_BRAKES = 0x03, LUNA_ERROR_THROTTLE = 0x05, LUNA_ERROR_UNDER_VOLTAGE = 0x06, LUNA_ERROR_HIGH_VOLTAGE = 0x07, LUNA_ERROR_ENCODER = 0x08, LUNA_ERROR_MOTOR_OVERTEMP = 0x10, LUNA_ERROR_MOSFET_OVERTEMP = 0x11, LUNA_ERROR_CURRENT_SENSOR = 0x12, LUNA_ERROR_BATTERY_TEMPERATURE = 0x13, LUNA_ERROR_WHEEL_SPEED_DETECTION = 0x21, LUNA_ERROR_BMS_COMMUNICATION = 0x22, LUNA_ERROR_TORQUE_SENSOR = 0x25, LUNA_ERROR_SPEED_SENSOR = 0x26, LUNA_ERROR_COMMUNICATION = 0x30, LUNA_ERROR_TORQUE_SENSOR_OUT_OF_RANGE = 0x100 } LUNA_ERROR_CODES; typedef enum{ SEND_BATTERY_RANGE_STATE, SEND_SPEED_CURRENT_VOLTAGE_STATE, SEND_SPEED_LIMIT_WHEEL_SIZE_STATE, SEND_CALORIES_STATE, SEND_ERROR_STATE, SEND_SHUTDOWN_STATE, }can_display_process_states_t; typedef struct{ LUNA_LIGHT_CONTROL_MODE light_mode; LUNA_PAS_LEVEL pas_level; LUNA_ERROR_CODES error_code; bool torque_sensor_is_active; int32_t torque_sensor_output; float torque_sensor_output_filtered; int32_t torque_sensor_upper_range; int32_t torque_sensor_lower_range; float torque_sensor_deadband; uint8_t assist_code; }luna_settings_t; static volatile luna_settings_t luna_settings = { .light_mode = LUNA_LIGHT_MODE_OFF, .pas_level = PAS_LEVEL_0, .error_code = LUNA_ERROR_NONE, .torque_sensor_is_active = false, .torque_sensor_upper_range = LUNA_TORQUE_SENSOR_MAXIMUM_RANGE, .torque_sensor_lower_range = LUNA_TORQUE_SENSOR_MINIMUM_RANGE, .torque_sensor_deadband = LUNA_TORQUE_SENSOR_DEADBAND, .torque_sensor_output_filtered = 0.0 }; static volatile bool display_thread_is_running = false; static volatile bool display_uart_is_running = false; // Threads static THD_WORKING_AREA(display_process_thread_wa, 1024); static THD_FUNCTION(display_process_thread, arg); static bool check_light_mode(uint8_t light_mode); static bool check_assist_level(uint8_t assist_code); static void set_assist_level(uint8_t assist_code); static void can_bus_display_process(uint32_t dt_ms); static bool can_bus_rx_callback(uint32_t id, uint8_t *data, uint8_t len); /** * Initialize the display and torque sensor for M600 drive units */ void luna_canbus_start(void) { if (!display_thread_is_running) { chThdCreateStatic(display_process_thread_wa, sizeof(display_process_thread_wa), NORMALPRIO, display_process_thread, NULL); display_thread_is_running = true; } } /** * Get torque applied to the crank arms * * @return * 0.0 for no torque applied, 1.0 for maximum torque applied */ float luna_canbus_get_PAS_torque(void){ return luna_settings.torque_sensor_output_filtered; } int32_t get_torque_sensor_output(void){ return luna_settings.torque_sensor_output; } float get_torque_sensor_deadband(void){ return luna_settings.torque_sensor_deadband; } int32_t set_torque_sensor_upper_range(int32_t new_upper_range) { uint32_t ret; // Check if the new_upper_range is within the expected range if ( (new_upper_range > LUNA_TORQUE_SENSOR_MAXIMUM_RANGE) || (new_upper_range < LUNA_TORQUE_SENSOR_MINIMUM_RANGE) ) { ret = LUNA_ERROR_TORQUE_SENSOR_OUT_OF_RANGE; } else { luna_settings.torque_sensor_upper_range = new_upper_range; ret = LUNA_ERROR_NONE; } return ret; } int32_t set_torque_sensor_lower_range(int32_t new_lower_range) { uint32_t ret; // Check if the new_lower_range is within the expected range if ( (new_lower_range > LUNA_TORQUE_SENSOR_MAXIMUM_RANGE) || (new_lower_range < LUNA_TORQUE_SENSOR_MINIMUM_RANGE) ) { ret = LUNA_ERROR_TORQUE_SENSOR_OUT_OF_RANGE; } else { luna_settings.torque_sensor_lower_range = new_lower_range; ret = LUNA_ERROR_NONE; } return ret; } int32_t get_torque_sensor_lower_range(void) { return luna_settings.torque_sensor_lower_range; } int32_t get_torque_sensor_upper_range(void) { return luna_settings.torque_sensor_upper_range; } // calibration procedure: make sure that the user is not pressing the pedals, then // sample the TS for 3 full second and use that as as the new lower range baseline int32_t measure_torque_sensor_offset(void) { float average = 0.0; for(uint32_t samples = 0; samples < 100 ; samples++) { average += luna_settings.torque_sensor_output; chThdSleep(MS2ST(30)); } average /= 100.0; return (int32_t)average; } /** * Get the current Pedal Assist level * * @return * Assist level from 0 (min) to 9 (max power). */ LUNA_PAS_LEVEL luna_canbus_get_pas_level(void){ return luna_settings.pas_level; } /** * checks if the light mode is valid * * @param light_mode * Parameter to check * * @return * true if valid, false if not. */ static bool check_light_mode(uint8_t light_mode) { bool ret = false; switch (light_mode) { case LUNA_LIGHT_MODE_OFF: case LUNA_LIGHT_MODE_ON: case LUNA_LIGHT_MODE_TURNING_OFF: case LUNA_LIGHT_MODE_TURNING_ON: ret = true; break; default: break; } return ret; } /** * Check if the assist code corresponds to a current level * * @param assist_code * Parameter to check * * @return * true if valid, false if not. */ static bool check_assist_level(uint8_t assist_code) { bool ret = false; switch (assist_code) { case PAS_LEVEL_0: case PAS_LEVEL_1: case PAS_LEVEL_2: case PAS_LEVEL_3: case PAS_LEVEL_4: case PAS_LEVEL_5: case PAS_LEVEL_6: case PAS_LEVEL_7: case PAS_LEVEL_8: case PAS_LEVEL_9: case PAS_LEVEL_WALK: ret = true; break; default: break; } return ret; } /** * Set the PAS level according to the assist code * * @param assist_code * Assist level to apply */ static void set_assist_level(uint8_t assist_code) { float current_scale; volatile mc_configuration *mcconf = (volatile mc_configuration*) mc_interface_get_configuration(); luna_settings.assist_code = assist_code; switch (assist_code) { case PAS_LEVEL_0: current_scale = 0.0; break; case PAS_LEVEL_1: current_scale = 1.0 / 9.0; break; case PAS_LEVEL_2: current_scale = 2.0 / 9.0; break; case PAS_LEVEL_3: current_scale = 3.0 / 9.0; break; case PAS_LEVEL_4: current_scale = 4.0 / 9.0; break; case PAS_LEVEL_5: current_scale = 5.0 / 9.0; break; case PAS_LEVEL_6: current_scale = 6.0 / 9.0; break; case PAS_LEVEL_7: current_scale = 7.0 / 9.0; break; case PAS_LEVEL_8: current_scale = 8.0 / 9.0; break; case PAS_LEVEL_9: current_scale = 1.0; break; case PAS_LEVEL_WALK: current_scale = 1.0; break; default: return; } if( hw_m600_has_fixed_throttle_level() ) { mcconf->l_current_max_scale = 1.0; app_pas_set_current_sub_scaling(current_scale); } else { mcconf->l_current_max_scale = current_scale; } // In level 0, both PAS and throttle should be disabled if(current_scale == 0.0) { mcconf->l_current_max_scale = current_scale; } } float distance; float time_since_display_change=0; uint16_t distance_display; /** * State machine for display functions and error handling * * @param dt_ms * Time since last call to this function */ static void can_bus_display_process(uint32_t dt_ms){ static uint8_t can_tx_buffer[8]; volatile mc_configuration *mcconf = (volatile mc_configuration*) mc_interface_get_configuration(); static can_display_process_states_t can_display_process_state = SEND_BATTERY_RANGE_STATE; static uint32_t delay_between_states = 0; static uint32_t delay_between_torque_sensor_message = 0; //check if torque sensor is active or not if(luna_settings.torque_sensor_is_active){ luna_settings.torque_sensor_is_active = false; delay_between_torque_sensor_message = 0; if( luna_settings.error_code == LUNA_ERROR_TORQUE_SENSOR){ luna_settings.error_code = LUNA_ERROR_NONE; } }else{ //fault if sensor data stops for >500ms, but also allow 3sec for sensor to boot delay_between_torque_sensor_message += dt_ms; float uptime = (float)chVTGetSystemTimeX() / (float)CH_CFG_ST_FREQUENCY; if(delay_between_torque_sensor_message > 500 && uptime > 3.0){ delay_between_torque_sensor_message = 0; //luna_settings.error_code = LUNA_ERROR_TORQUE_SENSOR; } } if(luna_settings.error_code == LUNA_ERROR_NONE){ //check if vesc has got any faults mc_fault_code vesc_fault = mc_interface_get_fault(); switch(vesc_fault){ case FAULT_CODE_NONE:{ break; } case FAULT_CODE_ABS_OVER_CURRENT: case FAULT_CODE_HIGH_OFFSET_CURRENT_SENSOR_1: case FAULT_CODE_HIGH_OFFSET_CURRENT_SENSOR_2: case FAULT_CODE_HIGH_OFFSET_CURRENT_SENSOR_3: case FAULT_CODE_UNBALANCED_CURRENTS:{ luna_settings.error_code = LUNA_ERROR_CURRENT_SENSOR; break; } case FAULT_CODE_OVER_TEMP_FET: luna_settings.error_code = LUNA_ERROR_MOSFET_OVERTEMP; break; case FAULT_CODE_OVER_TEMP_MOTOR:{ luna_settings.error_code = LUNA_ERROR_MOTOR_OVERTEMP; break; } case FAULT_CODE_OVER_VOLTAGE:{ luna_settings.error_code = LUNA_ERROR_HIGH_VOLTAGE; break; } case FAULT_CODE_UNDER_VOLTAGE:{ luna_settings.error_code = LUNA_ERROR_UNDER_VOLTAGE; break; } case FAULT_CODE_ENCODER_SPI: case FAULT_CODE_ENCODER_NO_MAGNET:{ luna_settings.error_code = LUNA_ERROR_ENCODER; break; } default: ;//luna_settings.error_code = LUNA_ERROR_COMMUNICATION; } } if(mc_interface_get_configuration()->foc_encoder_offset == 400.0) { luna_settings.error_code = LUNA_ERROR_ENCODER; } if(hw_luna_m600_shutdown_button_down()) { can_display_process_state = SEND_SHUTDOWN_STATE; } delay_between_states += dt_ms; //packets are typically sent every 11 msec if(delay_between_states > 50){ delay_between_states = 0; switch(can_display_process_state){ case SEND_BATTERY_RANGE_STATE:{ // This packet is sent every 250ms float wh_left; float battery_level = mc_interface_get_battery_level(&wh_left) * 100.0; utils_truncate_number((float*)&battery_level, 0.0, 100.0); static float last_distance = 0.0; float distance_abs = mc_interface_get_distance_abs(); distance = distance_abs - last_distance; // what is sent is 1 / 10 of the value in meters distance_display = (uint16_t) (distance / 10.0); static uint16_t time_at_last_distance_change = 0; static uint16_t last_display_distance = 0; // if distance_display doesn't change for 5 seconds, we have to reset it to zero if (distance_display != last_display_distance) { time_at_last_distance_change = (float)chVTGetSystemTimeX() / (float)CH_CFG_ST_FREQUENCY; } else { float current_time = (float)chVTGetSystemTimeX() / (float)CH_CFG_ST_FREQUENCY; time_since_display_change = current_time - time_at_last_distance_change;//for debug if(current_time - time_at_last_distance_change > 5.0){ time_at_last_distance_change = (float)chVTGetSystemTimeX() / (float)CH_CFG_ST_FREQUENCY; last_distance = distance_abs; distance = 0; distance_display = 0; } } last_display_distance = distance_display; if(distance_display < 5) { //make sure the first zero is not skipped. Apparently it's important and needs 4 consecutive zeroes //(or maybe its 1 full second of zeroes) distance_display = 0; } static float distance_old = 0; if(distance_abs != distance_old) { // commands_printf("time_since_display_change: %.2f",(double)time_since_display_change); // commands_printf("display_distance: %d",distance_display); // commands_printf("distance: %.2f",(double)distance); // commands_printf("odometer: %.2f\n", (double)distance_abs); distance_old = distance_abs; } memset(can_tx_buffer, 0, 8); can_tx_buffer[0] = (uint8_t) battery_level; can_tx_buffer[1] = (uint8_t)(distance_display & 0x00ff); can_tx_buffer[2] = (uint8_t) 0; can_tx_buffer[3] = (uint8_t) 0; can_tx_buffer[4] = (uint8_t) 0; can_tx_buffer[5] = (uint8_t) 0; can_tx_buffer[6] = (uint8_t) 0; can_tx_buffer[7] = (uint8_t) 0; // If the distance value exceeds 1000 meters, it overflows back to 0 meter if(distance > 1020) { last_distance += distance; } //TODO: support RANGE parameter. 0x1FF = 511 sets RANGE as 5.11 km or 3 miles. //can_tx_buffer[6] = RANGE LSB //can_tx_buffer[7] = RANGE MSB comm_can_transmit_eid(0x02F83200, can_tx_buffer, 8); can_display_process_state = SEND_SPEED_CURRENT_VOLTAGE_STATE; break; } case SEND_SPEED_CURRENT_VOLTAGE_STATE:{ //this packet is sent every 280ms memset(can_tx_buffer, 0, 8); #ifdef HW_HAS_WHEEL_SPEED_SENSOR //float wheel_rpm = hw_get_wheel_rpm(); //float wheelsize_in_meters = mcconf->si_wheel_diameter / 1000.0; //float speed_km_h = (uint16_t)(wheel_rpm * wheelsize_in_meters * 60.0 / 1000.0); //uint16_t speed_display = (uint16_t)(speed_km_h * 100.0);//the display needs [km_h * 100] uint16_t speed_display = (uint16_t)(mc_interface_get_speed() * 3600.0 / 1000.0 * 100.0 ); //the display needs [km_h * 100] can_tx_buffer[0] = (uint8_t)(speed_display & 0x00ff); can_tx_buffer[1] = (uint8_t)((speed_display >> 8 ) & 0x00ff); #endif float current = mc_interface_get_tot_current_in_filtered(); uint16_t current_display = (uint16_t)(current * 100.0); can_tx_buffer[2] = (uint8_t)(current_display & 0x00ff); can_tx_buffer[3] = (uint8_t)((current_display >> 8 ) & 0x00ff); float voltage = mc_interface_get_input_voltage_filtered(); uint16_t voltage_display = voltage * 100; can_tx_buffer[4] = (uint8_t)(voltage_display & 0x00ff); can_tx_buffer[5] = (uint8_t)((voltage_display >> 8 ) & 0x00ff); can_tx_buffer[6] = (uint8_t) (mc_interface_temp_fet_filtered() - 40.0); // 10°C = 10+40=50(32Hex) = 32 can_tx_buffer[7] = (uint8_t) (mc_interface_temp_motor_filtered() - 40.0); // 20°C = 20+40=60(3CHex) = 3C comm_can_transmit_eid(0x02F83201, can_tx_buffer, 8); can_display_process_state = SEND_SPEED_LIMIT_WHEEL_SIZE_STATE; break; } case SEND_SPEED_LIMIT_WHEEL_SIZE_STATE:{ memset(can_tx_buffer, 0, 8); //"speed limit" parameter [kmh *100] float speed_limit = 32.2;// dummy 32.2km/h (20mph) uint16_t speed_limit_display = speed_limit * 100; can_tx_buffer[0] = (uint8_t)(speed_limit_display & 0x00ff); can_tx_buffer[1] = (uint8_t)((speed_limit_display >> 8 ) & 0x00ff); // Arbitrary wheel size definitions from the display uint16_t wheelsize_display; if(mcconf->si_wheel_diameter <= 0.7){ wheelsize_display = 416; //26" } if(mcconf->si_wheel_diameter >= 0.7){ wheelsize_display = 437; //27.5" } if(mcconf->si_wheel_diameter >= 0.75){ wheelsize_display = 464; //29" } can_tx_buffer[2] = (uint8_t)(wheelsize_display & 0x00ff); can_tx_buffer[3] = (uint8_t)((wheelsize_display >> 8 ) & 0x00ff); //these values are fixed can_tx_buffer[4] = 182; can_tx_buffer[5] = 8; comm_can_transmit_eid(0x02F83203, can_tx_buffer, 6); can_display_process_state = SEND_CALORIES_STATE; break; } case SEND_CALORIES_STATE:{ // this packet is sent every 300msec memset(can_tx_buffer, 0, 8); //TODO: support "KCAL" parameter in [kmh *100] //conversion rate: KCAL = value * 0.621368. //For example: 0xFFFF = 65535 sets KCAL as 40722 comm_can_transmit_eid(0x02F83205, can_tx_buffer, 2); can_display_process_state = SEND_ERROR_STATE; break; } case SEND_ERROR_STATE:{ // this packet is sent every 500msec static uint8_t send_error_counter = 0; memset(can_tx_buffer, 0, 8); if(luna_settings.error_code != LUNA_ERROR_NONE){ can_tx_buffer[0] = luna_settings.error_code ; comm_can_transmit_eid(0x02FF1200, can_tx_buffer, 1); send_error_counter++; if(send_error_counter > 3){ send_error_counter = 0; can_display_process_state = SEND_BATTERY_RANGE_STATE; } }else{ send_error_counter = 0; can_display_process_state = SEND_BATTERY_RANGE_STATE; } comm_can_transmit_eid(0x02FF1200, can_tx_buffer, 1); break; } case SEND_SHUTDOWN_STATE:{ memset(can_tx_buffer, 0, 8); can_tx_buffer[0] = 0; comm_can_transmit_eid(0x02FF1204, can_tx_buffer, 1); can_display_process_state = SEND_BATTERY_RANGE_STATE; break; } } } } static bool can_bus_rx_callback(uint32_t id, uint8_t *data, uint8_t len) { bool used_data = false; LUNA_CAN_IDs cmd_id = id; switch(cmd_id){ case LUNA_DISPLAY_ID1:{ if(len == LUNA_DISPLAY_ID1_LENGTH_BYTES){ if(data != NULL){ used_data = true; if(check_assist_level(data[1])){ luna_settings.pas_level = data[1]; set_assist_level(luna_settings.pas_level); } if(check_light_mode(data[2])){ luna_settings.light_mode = data[2]; } } } break; } case LUNA_TORQUE_SENSOR_ID:{ if(len == LUNA_TORQUE_SENSOR_ID_LENGTH_BYTES){ if(data != NULL){ used_data = true; luna_settings.torque_sensor_is_active = true; uint16_t torque_sensor_output = (((uint16_t)data[1] << 8 ) & 0xff00) | ((uint16_t)data[0]&0x00ff); luna_settings.torque_sensor_output = torque_sensor_output; float normalized_torque_sensor_output = utils_map((float)torque_sensor_output, (float)luna_settings.torque_sensor_lower_range, (float)luna_settings.torque_sensor_upper_range, 0.0, 1.0); utils_truncate_number(&normalized_torque_sensor_output, 0.0, 1.0); utils_deadband(&normalized_torque_sensor_output, luna_settings.torque_sensor_deadband, 1.0); UTILS_LP_FAST(luna_settings.torque_sensor_output_filtered, normalized_torque_sensor_output, 0.1); } } break; } } return used_data; } bool luna_display_shutdown_request(void) { static uint16_t counter = 0; bool button_down = hw_luna_m600_shutdown_button_down(); static bool first_press = true; // With the bike off, if you press the power button and never release it // the bike shouldn't turn on and then power off, so ignore the first release if (button_down && first_press) { return false; } else { first_press = false; } // while button is pressed, increment a counter. If released, after 500ms reset the counter to 0 if(button_down) { counter++; } else { if (counter > 0) { counter--; if (counter < 50 ) { counter = 0; } } } return (counter > 100); } // If user long-presses the (-) button, walk mode is engaged. // Controller will target a specific RPM and hold it until the button is released // The speed control must have a slow ramp, the allowed current is TBD. bool luna_display_walk_mode_long_pressed(void) { static systime_t time_last_button_down = 0; const float long_press_time_seconds = 0.5; // bafang uses 2 sec, lets make it more responsive if( time_last_button_down == 0) { time_last_button_down = chVTGetSystemTimeX(); } if(hw_luna_m600_minus_button_down() == false) { time_last_button_down = chVTGetSystemTimeX(); } return (UTILS_AGE_S(time_last_button_down) > long_press_time_seconds); } // lets try using the sensorless observer to check if the encoder // offset has been set correctly. float encoder_error(void) { static float angle_diff_filtered = 0.0; float angle_diff = 0.0; // some batches have only 2 current sensors, so better rely only in // the phase voltage tracker which runs with no modulation and is // accurate at mid-high rpm if(/*mc_interface_get_state() != MC_STATE_OFF &&*/ mc_interface_get_duty_cycle_now() > 0.4) { angle_diff = utils_angle_difference(mcpwm_foc_get_phase_encoder(), mcpwm_foc_get_phase_observer()); } UTILS_LP_FAST(angle_diff_filtered, angle_diff, 0.01); return angle_diff_filtered; } static THD_FUNCTION(display_process_thread, arg) { (void)arg; chRegSetThreadName("Luna CANbus display"); event_listener_t el; chEvtRegisterMaskWithFlags(&HW_UART_DEV.event, &el, EVENT_MASK(0), CHN_INPUT_AVAILABLE); // Set default power level set_assist_level(PAS_LEVEL_1); comm_can_set_eid_rx_callback( can_bus_rx_callback ); for(;;) { float uptime = (float)chVTGetSystemTimeX() / (float)CH_CFG_ST_FREQUENCY; chThdSleep(MS2ST(5)); can_bus_display_process(5); static bool encoder_recovery_done = false; if(encoder_recovery_done) { // default motor config is set to an invalid 400° encoder offset. Make clear // to the users that they need to run the encoder offset detection if(mc_interface_get_configuration()->foc_encoder_offset == 400.0 /*|| fabsf(hw_get_encoder_error()) > 10.0*/) { ;//mc_interface_fault_stop(FAULT_CODE_ENCODER_SPI, false, false); } } else { if (uptime > 1.0 && !encoder_recovery_done) { // recover encoder offset across fw updates hw_recover_encoder_offset(); encoder_recovery_done = true; } } // when PAS level set to 0, the system would shut down after 10 minutes of non-assisted pedaling // so we force it to stay ON if there is pedal activity if(luna_canbus_get_PAS_torque() > 0.25) { shutdown_reset_timer(); } // consider shutting down in case of battery undervoltage. Power button can turn it on only if Vin>25V // BMS typically trips at 2.8V/cell if(luna_display_shutdown_request()) { //Turn off the display. Introduced in Rev8 palSetPadMode(DISPLAY_PWR_GPIO, DISPLAY_PWR_PIN, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST); palClearPad(DISPLAY_PWR_GPIO, DISPLAY_PWR_PIN); conf_general_store_backup_data(); while(hw_luna_m600_shutdown_button_down()); //stay here until button is released HW_SHUTDOWN_HOLD_OFF(); // night night } if (luna_settings.assist_code == PAS_LEVEL_WALK) { //disable ADC & PAS apps for 50 millisec app_disable_output(50); if(luna_display_walk_mode_long_pressed()) { //send speed command. Sloow 500rpm/sec ramp mc_interface_set_pid_speed(2500.0); timeout_reset(); } else { //quickly release the motor mc_interface_release_motor(); } } } } #endif