/* Copyright 2019 Kirill Kostiuchenko kisel2626@gmail.com This file is part of the VESC firmware. The VESC firmware 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. The VESC firmware 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 "app.h" #include "ch.h" #include "hal.h" // Some useful includes #include "comm_can.h" #include "commands.h" #include "encoder.h" #include "hw.h" #include "mc_interface.h" #include "terminal.h" #include "timeout.h" #include "utils.h" #include #include #include #include // Delays between pereodically prints of state, position and forces static const int short_print_delay = 500; // Control loop counts, 1 millisecond about static const int long_print_delay = 3000; // Threads static THD_FUNCTION(my_thread, arg); static THD_WORKING_AREA(my_thread_wa, 2048); // Private functions static void terminal_move_tac(int argc, const char **argv); static void terminal_show_skypuff_conf(int argc, const char **argv); // Private variables static volatile bool stop_now = true; static volatile bool is_running = false; static const volatile mc_configuration *mc_conf; static int prev_abs_tac = 0; static float prev_erpm = 0; static int i; // use control loop counter in app functions static int prev_print = 0; // loop counter value of the last state print static int prev_printed_tac = 0; // do not print the same position // Units converting calculations #define METERS_PER_REV (mc_conf->si_wheel_diameter / mc_conf->si_gear_ratio * M_PI) #define STEPS_PER_REV (mc_conf->si_motor_poles * 3) // Control loop state machine typedef enum { UNINITIALIZED, // Unitialized state for application start BRAKING, // Braking zone near take off SLOWING, // Release the motor if ERPM is higher then config.slow_erpm SLOW, // PID controlled speed with slow_erpm UNWINDING, // Low force rope tension REWINDING, // High force fast rope winding to take off } rewinder_state; static rewinder_state state; typedef struct { float kg_to_amps; // winch force coefficient int braking_length; // tachometer range for braking zone int overwinding; // tachometer range to overwind braking zone int slowing_length; // range after braking zone to release and slow down float slow_erpm; // constant erpm for slow mode int rewinding_trigger_lenght; // fast rewinding after going back range int unwinding_trigger_length; // go unwinding if this range unwinded float brake_current; float unwinding_current; float rewinding_current; float slow_max_current; // On exceed max current go braking or unwinding } rewinder_config; static rewinder_config config; static char *state_str(void) { switch (state) { case UNINITIALIZED: return "unitialized"; case BRAKING: return "braking"; case UNWINDING: return "unwinding"; case REWINDING: return "rewinding"; case SLOWING: return "slowing"; case SLOW: return "slow"; default: return "unknown"; } } int meters_to_tac_steps(float meters) { float steps_per_rev = STEPS_PER_REV; float meters_per_rev = METERS_PER_REV; int steps = meters / meters_per_rev * steps_per_rev; // Low range warning if (steps == 0) { commands_printf("Skypuff: -- ZERO STEPS WARNING -- meters_to_tac_steps(%.5f) results in %d steps", (double)meters, steps); } return steps; } float tac_steps_to_meters(int steps) { float steps_per_rev = STEPS_PER_REV; float meters_per_rev = METERS_PER_REV; return (float)steps / steps_per_rev * meters_per_rev; } // Convert speed in meters per second to erpm float ms_to_erpm(float ms) { float meters_per_rev = METERS_PER_REV; float rps = ms / meters_per_rev; float rpm = rps * 60; return rpm * (mc_conf->si_motor_poles / 2); } float erpm_to_ms(float erpm) { float meters_per_rev = METERS_PER_REV; float erps = erpm / 60; float rps = erps / (mc_conf->si_motor_poles / 2); return rps * meters_per_rev; } inline static void brake(int cur_tac) { if (state != BRAKING) { prev_print = i; commands_printf("Skypuff: state %s -> braking, position: %.2f meters (%d steps), breaking: %.1fKg (%.1fA)", state_str(), (double)tac_steps_to_meters(cur_tac), cur_tac, (double)(config.brake_current / config.kg_to_amps), (double)config.brake_current); } state = BRAKING; prev_abs_tac = abs(cur_tac); mc_interface_set_brake_current(config.brake_current); timeout_reset(); } static void unwinding(int cur_tac) { // Detect direction depending on tachometer value float current = cur_tac < 0 ? config.unwinding_current : -config.unwinding_current; prev_print = i; commands_printf("Skypuff: state %s -> unwinding, position: %.2fm (%d steps), pulling: %.1fKg (%.1fA)", state_str(), (double)tac_steps_to_meters(cur_tac), cur_tac, (double)(current / config.kg_to_amps), (double)current); state = UNWINDING; prev_abs_tac = abs(cur_tac); mc_interface_set_current(current); timeout_reset(); } static void rewinding(int cur_tac) { // Detect direction depending on tachometer value float current = cur_tac < 0 ? config.rewinding_current : -config.rewinding_current; prev_print = i; commands_printf("Skypuff: state %s -> rewinding, position: %.2fm (%d steps), pulling: %.1fKg (%.1fA)", state_str(), (double)tac_steps_to_meters(cur_tac), cur_tac, (double)(current / config.kg_to_amps), (double)current); state = REWINDING; prev_abs_tac = abs(cur_tac); mc_interface_set_current(current); timeout_reset(); } static void slowing(int cur_tac, float erpm) { prev_print = i; commands_printf("Skypuff: state %s -> slowing, position: %.2fm (%d steps), speed: %.1fms (%.0f ERPM)", state_str(), (double)tac_steps_to_meters(cur_tac), cur_tac, (double)erpm_to_ms(erpm), (double)erpm); state = SLOWING; mc_interface_release_motor(); } static void slow(int cur_tac, float erpm) { // Detect direction depending on tachometer value float constant_erpm = cur_tac < 0 ? config.slow_erpm : -config.slow_erpm; prev_print = i; commands_printf("Skypuff: state %s -> slow, position: %.2fm (%d steps), speed: %.1fms (%.0f ERPM), constant speed: %.1fms (%.0f ERPM)", state_str(), (double)tac_steps_to_meters(cur_tac), cur_tac, (double)erpm_to_ms(erpm), (double)erpm, (double)erpm_to_ms(constant_erpm), (double)constant_erpm); state = SLOW; prev_erpm = erpm; mc_interface_set_pid_speed(constant_erpm); timeout_reset(); } static void set_example_config(void) { // Some example ranges config.braking_length = meters_to_tac_steps(1); config.overwinding = meters_to_tac_steps(0.1); config.rewinding_trigger_lenght = meters_to_tac_steps(0.2); config.unwinding_trigger_length = meters_to_tac_steps(0.05); config.slowing_length = meters_to_tac_steps(3); config.slow_erpm = ms_to_erpm(1); // Forces config.kg_to_amps = 7; config.brake_current = 0.2 * config.kg_to_amps; config.unwinding_current = 0.2 * config.kg_to_amps; config.rewinding_current = 0.4 * config.kg_to_amps; config.slow_max_current = 0.3 * config.kg_to_amps; } // Called when the custom application is started. Start our // threads here and set up callbacks. void app_custom_start(void) { mc_conf = mc_interface_get_configuration(); set_example_config(); state = UNINITIALIZED; stop_now = false; chThdCreateStatic(my_thread_wa, sizeof(my_thread_wa), NORMALPRIO, my_thread, NULL); // Terminal commands for the VESC Tool terminal can be registered. terminal_register_command_callback( "move_tac", "Move zero point on specified distance. Use negative value to move backward.", "[meters]", terminal_move_tac); terminal_register_command_callback( "skypuff", "Print SkyPUFF configuration here.", "", terminal_show_skypuff_conf); commands_printf("app_skypuff started"); } // Called when the custom application is stopped. Stop our threads // and release callbacks. void app_custom_stop(void) { terminal_unregister_callback(terminal_move_tac); terminal_unregister_callback(terminal_show_skypuff_conf); stop_now = true; while (is_running) { chThdSleepMilliseconds(1); } commands_printf("app_skypuff stopped"); } void app_custom_configure(app_configuration *conf) { (void)conf; } // The same code for unwinding and rewinding states // Returns true if mode changed static bool brake_or_slowing(int cur_tac, int abs_tac) { // We are in the braking range with overwinding? if (abs_tac < config.braking_length - config.overwinding) { brake(cur_tac); return true; } // We are in the slowing range? if (abs_tac < config.braking_length + config.slowing_length) { float erpm = mc_interface_get_rpm(); // Rewinding forward with more then slow speed? if (cur_tac < 0 && erpm > config.slow_erpm) { slowing(cur_tac, erpm); return true; } // Rewinding backward with more then slow speed? else if (cur_tac >= 0 && erpm < -config.slow_erpm) { slowing(cur_tac, erpm); return true; } } return false; } inline static void print_position_periodically(int cur_tac, int delay) { // prolong delay if not moving if (cur_tac == prev_printed_tac) { prev_print = i; return; } if (i - prev_print > delay) { prev_print = i; prev_printed_tac = cur_tac; commands_printf("Skypuff: %s, position: %.2f meters (%d steps)", state_str(), (double)tac_steps_to_meters(cur_tac), cur_tac); } } static THD_FUNCTION(my_thread, arg) { (void)arg; chRegSetThreadName("App SkyPUFF"); is_running = true; int cur_tac, abs_tac; float cur_erpm, abs_erpm; float cur_current, abs_current; for (i = 0;; i++) { // Check if it is time to stop. if (stop_now) { is_running = false; return; } cur_tac = mc_interface_get_tachometer_value(false); abs_tac = abs(cur_tac); switch (state) { case UNINITIALIZED: if (abs_tac < config.braking_length) brake(cur_tac); else unwinding(cur_tac); break; case BRAKING: // We are in the breaking zone? if (abs_tac <= config.braking_length) { // Timeout thread will remove breaking every second by default // Apply break current again on position changed if (abs_tac != prev_abs_tac) brake(cur_tac); } else unwinding(cur_tac); print_position_periodically(cur_tac, long_print_delay); break; case UNWINDING: // No timeouts for unwinding state if (!(i % 500)) timeout_reset(); // Go breaking or slowing? if (brake_or_slowing(cur_tac, abs_tac)) break; // Use prev_abs_tac as max tachometer if (abs_tac > prev_abs_tac) { // Print debug message if we are going out from slowing zone int eof_slowing = config.braking_length + config.slowing_length; if (prev_abs_tac < eof_slowing && abs_tac >= eof_slowing) { commands_printf("Skypuff: unwinded from slowing zone %.2f meters (%d steps)", (double)tac_steps_to_meters(cur_tac), cur_tac); } // Update maximum value of tachometer prev_abs_tac = abs_tac; } // Going back more then config.rewinding_trigger_length? if (abs_tac < prev_abs_tac - config.rewinding_trigger_lenght) { rewinding(cur_tac); break; } print_position_periodically(cur_tac, long_print_delay); break; case REWINDING: // No timeouts for rewinding state if (!(i % 500)) timeout_reset(); // Go breaking or slowing? if (brake_or_slowing(cur_tac, abs_tac)) break; // Now use prev_abs_tac as min value if (abs_tac < prev_abs_tac) prev_abs_tac = abs_tac; // Unwinding again? if (abs_tac > prev_abs_tac + config.unwinding_trigger_length) unwinding(cur_tac); print_position_periodically(cur_tac, long_print_delay); break; case SLOWING: // We are in the braking range with overwinding? if (abs_tac < config.braking_length - config.overwinding) { brake(cur_tac); break; } cur_erpm = mc_interface_get_rpm(); abs_erpm = abs(cur_erpm); // Slow enough for PID speed? if (abs_erpm < config.slow_erpm) { slow(cur_tac, cur_erpm); break; } // Print speed and tachometer to simpler tweaking slowing zone if (i - prev_print > short_print_delay) { int overwinding = config.braking_length - config.overwinding; int distance_left = abs(cur_tac) - overwinding; prev_print = i; commands_printf( "Skypuff: slowing, position %.2f meters (%d steps), speed: %.1fms (%.0f ERPM), waiting for: %.1fms, %.2f meters left until overwinding zone", (double)tac_steps_to_meters(cur_tac), cur_tac, (double)erpm_to_ms(cur_erpm), (double)cur_erpm, (double)erpm_to_ms(config.slow_erpm), (double)tac_steps_to_meters(distance_left)); } break; case SLOW: cur_current = mc_interface_get_tot_current_directional_filtered(); abs_current = abs(cur_current); cur_erpm = mc_interface_get_rpm(); // No timeouts for slow state if (!(i % 500)) timeout_reset(); // Rotating direction changed or stopped? if ((prev_erpm < 0 && cur_erpm >= 0) || (prev_erpm > 0 && cur_erpm <= 0)) { commands_printf("Skypuff: rotation direction changed"); brake(cur_tac); break; } prev_erpm = cur_erpm; // If current above the limits - brake or unwinding if (abs_current > config.slow_max_current) { commands_printf( "Skypuff: slow pulling too high %.1fKg (%.1fA) is more %.1fKg (%.1fA)", (double)(abs_current / config.kg_to_amps), (double)cur_current, (double)(config.slow_max_current / config.kg_to_amps), (double)config.slow_max_current); if (abs_tac < config.braking_length - config.overwinding) { brake(cur_tac); break; } else { unwinding(cur_tac); break; } } // Slowly rewinded more then opposite side of (breaking - overwinding) zone? if ((cur_erpm > 0 && cur_tac > config.braking_length - config.overwinding) || (cur_erpm < 0 && cur_tac < -config.braking_length + config.overwinding)) { commands_printf("Skypuff: winded to opposite braking zone"); brake(cur_tac); break; } if (i - prev_print > long_print_delay) { prev_print = i; commands_printf("Skypuff: slow, position %.2f meters (%d steps), speed: %.1fms (%.0f ERPM), pulling: %.1fKg (%.1fA)", (double)tac_steps_to_meters(cur_tac), cur_tac, (double)erpm_to_ms(cur_erpm), (double)cur_erpm, (double)(cur_current / config.kg_to_amps), (double)cur_current); } break; default: commands_printf("Skypuff: unknown control loop state, exiting!"); stop_now = true; } chThdSleepMilliseconds(1); } } // Terminal command to change tachometer value static void terminal_move_tac(int argc, const char **argv) { if (argc == 2) { float d = 0; if (sscanf(argv[1], "%f", &d) == EOF) { commands_printf("move_tac: can't parse meters: '%s' value.", argv[1]); return; }; int steps = meters_to_tac_steps(d); int cur_tac = mc_interface_get_tachometer_value(false); int new_tac = cur_tac + steps; commands_printf("move_tac: moving zero %.2fm (%d steps) %s, cur pos: %.2fm (%d steps), new pos: %.2fm (%d steps)", (double)d, steps, d < 0 ? "backward" : "forward", (double)tac_steps_to_meters(cur_tac), cur_tac, (double)tac_steps_to_meters(new_tac), new_tac); mc_interface_set_tachometer_value(new_tac); } else { commands_printf("This command requires one argument: 'move_tac -5.2' will move zero 5.2 meters backward"); } } // Terminal command to show configuration static void terminal_show_skypuff_conf(int argc, const char **argv) { (void)argc; (void)argv; int cur_tac = mc_interface_get_tachometer_value(false); commands_printf("VESC additional configuration:"); commands_printf(" wheel diameter: %.2fmm", (double)(mc_conf->si_wheel_diameter * 1000)); commands_printf(" motor poles: %dp", mc_conf->si_motor_poles); commands_printf(" gear ratio: %.5f", (double)mc_conf->si_gear_ratio); commands_printf("SkyPUFF configuration:"); commands_printf(" state %s, current position: %.2f meters (%d steps)", state_str(), (double)tac_steps_to_meters(cur_tac), cur_tac); commands_printf(" amperes per 1kg force: %.1fAKg", (double)config.kg_to_amps); commands_printf(" braking range: %.2f meters (%d steps)", (double)tac_steps_to_meters(config.braking_length), config.braking_length); commands_printf(" overwinding: %.2f meters (%d steps)", (double)tac_steps_to_meters(config.overwinding), config.overwinding); commands_printf(" slowing range: %.2f meters (%d steps)", (double)tac_steps_to_meters(config.slowing_length), config.slowing_length); commands_printf(" rewinding trigger range: %.2f meters (%d steps)", (double)tac_steps_to_meters(config.rewinding_trigger_lenght), config.rewinding_trigger_lenght); commands_printf(" unwinding trigger range: %.2f meters (%d steps)", (double)tac_steps_to_meters(config.unwinding_trigger_length), config.unwinding_trigger_length); commands_printf(" brake force: %.2fkg (%.1fA)", (double)(config.brake_current / config.kg_to_amps), (double)config.brake_current); commands_printf(" unwinding force: %.2fkg (%.1fA)", (double)(config.unwinding_current / config.kg_to_amps), (double)config.unwinding_current); commands_printf(" rewinding force: %.2fkg (%.1fA)", (double)(config.rewinding_current / config.kg_to_amps), (double)config.rewinding_current); commands_printf(" slow speed: %.1fms (%.0f ERPM)", (double)erpm_to_ms(config.slow_erpm), (double)config.slow_erpm); commands_printf(" maximum slow force: %.2fkg (%.1fA)", (double)(config.slow_max_current / config.kg_to_amps), (double)config.slow_max_current); }