I'm trying to control a servo based on ADC1 and ADC2 inputs, so far I've modified app_adc.c with servo_simple_set_output and set servo enable 1 in conf_general
Compiled firmware and uploaded but it seems there is no servo output,
everything else is working normally
What are the steps i need to do to output a servo signal through the servo cable?
Is there any examples where someone else has done this?
Bit of context I'm running 4.12 hardware with ADC1 throttle and ADC2 brake on an eBike, the servo is intended to physically disengage the motor to reduce drag
Below is the modified app_adc I'm trying to use
/*
Copyright 2016 - 2017 Benjamin Vedder benjamin@vedder.se
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 <http://www.gnu.org/licenses/>.
*/
//CUSTOM CODE: Adds Output Servo Signal Proportional to ADC1 and ADC2 inputs
//CUSTOM CODE: Removes button inputs when TX and RX are used elsewhere
//CUSTOM CODE: Customize servo travel with conf_general.h
//CUSTOM CODE: Must Set #define SERVO_OUT_ENABLE 1 in conf_general.h
#include "app.h"
#include "general.h" //CUSTOM CODE: Required for MAX function
#include "ch.h"
#include "hal.h"
#include "stm32f4xx_conf.h"
#include "mc_interface.h"
#include "timeout.h"
#include "utils.h"
#include "comm_can.h"
#include "hw.h"
#include <math.h>
// Settings
#define MAX_CAN_AGE 0.1
#define MIN_MS_WITHOUT_POWER 500
#define FILTER_SAMPLES 5
#define RPM_FILTER_SAMPLES 8
// Threads
static THD_FUNCTION(adc_thread, arg);
static THD_WORKING_AREA(adc_thread_wa, 1024);
// Private variables
static volatile adc_config config;
static volatile float ms_without_power = 0.0;
static volatile float decoded_level = 0.0;
static volatile float read_voltage = 0.0;
static volatile float decoded_level2 = 0.0;
static volatile float read_voltage2 = 0.0;
static volatile bool use_rx_tx_as_buttons = false;
static volatile bool stop_now = true;
static volatile bool is_running = false;
void app_adc_configure(adc_config *conf) {
config = *conf;
ms_without_power = 0.0;
}
void app_adc_start(bool use_rx_tx) {
use_rx_tx_as_buttons = use_rx_tx;
stop_now = false;
chThdCreateStatic(adc_thread_wa, sizeof(adc_thread_wa), NORMALPRIO, adc_thread, NULL);
}
void app_adc_stop(void) {
stop_now = true;
while (is_running) {
chThdSleepMilliseconds(1);
}
}
float app_adc_get_decoded_level(void) {
return decoded_level;
}
float app_adc_get_voltage(void) {
return read_voltage;
}
float app_adc_get_decoded_level2(void) {
return decoded_level2;
}
float app_adc_get_voltage2(void) {
return read_voltage2;
}
static THD_FUNCTION(adc_thread, arg) {
(void)arg;
chRegSetThreadName("APP_ADC");
// Set servo pin as an input with pullup
if (use_rx_tx_as_buttons) {
palSetPadMode(HW_UART_TX_PORT, HW_UART_TX_PIN, PAL_MODE_INPUT_PULLUP);
palSetPadMode(HW_UART_RX_PORT, HW_UART_RX_PIN, PAL_MODE_INPUT_PULLUP);
} else {
palSetPadMode(HW_ICU_GPIO, HW_ICU_PIN, PAL_MODE_INPUT_PULLUP);
}
is_running = true;
for(;;) {
// Sleep for a time according to the specified rate
systime_t sleep_time = CH_CFG_ST_FREQUENCY / config.update_rate_hz;
// At least one tick should be slept to not block the other threads
if (sleep_time == 0) {
sleep_time = 1;
}
chThdSleep(sleep_time);
if (stop_now) {
is_running = false;
return;
}
// For safe start when fault codes occur
if (mc_interface_get_fault() != FAULT_CODE_NONE) {
ms_without_power = 0;
}
// Read the external ADC pin and convert the value to a voltage.
float pwr = (float)ADC_Value[ADC_IND_EXT];
pwr /= 4095;
pwr *= V_REG;
read_voltage = pwr;
// Optionally apply a mean value filter
if (config.use_filter) {
static float filter_buffer[FILTER_SAMPLES];
static int filter_ptr = 0;
filter_buffer[filter_ptr++] = pwr;
if (filter_ptr >= FILTER_SAMPLES) {
filter_ptr = 0;
}
pwr = 0.0;
for (int i = 0;i < FILTER_SAMPLES;i++) {
pwr += filter_buffer[i];
}
pwr /= FILTER_SAMPLES;
}
// Map the read voltage
switch (config.ctrl_type) {
case ADC_CTRL_TYPE_CURRENT_REV_CENTER:
case ADC_CTRL_TYPE_CURRENT_REV_BUTTON_BRAKE_CENTER:
case ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_CENTER:
case ADC_CTRL_TYPE_DUTY_REV_CENTER:
case ADC_CTRL_TYPE_PID_REV_CENTER:
// Mapping with respect to center voltage
if (pwr < config.voltage_center) {
pwr = utils_map(pwr, config.voltage_start,
config.voltage_center, 0.0, 0.5);
} else {
pwr = utils_map(pwr, config.voltage_center,
config.voltage_end, 0.5, 1.0);
}
break;
default:
// Linear mapping between the start and end voltage
pwr = utils_map(pwr, config.voltage_start, config.voltage_end, 0.0, 1.0);
break;
}
// Truncate the read voltage
utils_truncate_number(&pwr, 0.0, 1.0);
// Optionally invert the read voltage
if (config.voltage_inverted) {
pwr = 1.0 - pwr;
}
decoded_level = pwr;
// Read the external ADC pin and convert the value to a voltage.
#ifdef ADC_IND_EXT2
float brake = (float)ADC_Value[ADC_IND_EXT2];
brake /= 4095;
brake *= V_REG;
#else
float brake = 0.0;
#endif
read_voltage2 = brake;
// Optionally apply a mean value filter
if (config.use_filter) {
static float filter_buffer2[FILTER_SAMPLES];
static int filter_ptr2 = 0;
filter_buffer2[filter_ptr2++] = brake;
if (filter_ptr2 >= FILTER_SAMPLES) {
filter_ptr2 = 0;
}
brake = 0.0;
for (int i = 0;i < FILTER_SAMPLES;i++) {
brake += filter_buffer2[i];
}
brake /= FILTER_SAMPLES;
}
// Map and truncate the read voltage
brake = utils_map(brake, config.voltage2_start, config.voltage2_end, 0.0, 1.0);
utils_truncate_number(&brake, 0.0, 1.0);
// Optionally invert the read voltage
if (config.voltage2_inverted) {
brake = 1.0 - brake;
}
decoded_level2 = brake;
// Read the button pins
bool cc_button = false;
bool rev_button = false;
if (use_rx_tx_as_buttons) {
cc_button = !palReadPad(HW_UART_TX_PORT, HW_UART_TX_PIN);
if (config.cc_button_inverted) {
cc_button = !cc_button;
}
rev_button = !palReadPad(HW_UART_RX_PORT, HW_UART_RX_PIN);
if (config.rev_button_inverted) {
rev_button = !rev_button;
}
}//CUSTOM CODE: Removed ELSE statement
servo_simple_set_output(sqrt(MAX(decoded_level, decoded_level2))); //CUSTOM CODE: Outputs Servo Signal Proportional to ADC1 and ADC2 inputs
// All pins and buttons are still decoded for debugging, even
// when output is disabled.
if (app_is_output_disabled()) {
continue;
}
switch (config.ctrl_type) {
case ADC_CTRL_TYPE_CURRENT_REV_CENTER:
case ADC_CTRL_TYPE_CURRENT_REV_BUTTON_BRAKE_CENTER:
case ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_CENTER:
case ADC_CTRL_TYPE_DUTY_REV_CENTER:
case ADC_CTRL_TYPE_PID_REV_CENTER:
// Scale the voltage and set 0 at the center
pwr *= 2.0;
pwr -= 1.0;
break;
case ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_ADC:
case ADC_CTRL_TYPE_CURRENT_REV_BUTTON_BRAKE_ADC:
pwr -= brake;
break;
case ADC_CTRL_TYPE_CURRENT_REV_BUTTON:
case ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_BUTTON:
case ADC_CTRL_TYPE_DUTY_REV_BUTTON:
case ADC_CTRL_TYPE_PID_REV_BUTTON:
// Invert the voltage if the button is pressed
if (rev_button) {
pwr = -pwr;
}
break;
default:
break;
}
// Apply deadband
utils_deadband(&pwr, config.hyst, 1.0);
// Apply throttle curve
pwr = utils_throttle_curve(pwr, config.throttle_exp, config.throttle_exp_brake, config.throttle_exp_mode);
// Apply ramping
static systime_t last_time = 0;
static float pwr_ramp = 0.0;
float ramp_time = fabsf(pwr) > fabsf(pwr_ramp) ? config.ramp_time_pos : config.ramp_time_neg;
// TODO: Remember what this was about?
// if (fabsf(pwr) > 0.001) {
// ramp_time = fminf(config.ramp_time_pos, config.ramp_time_neg);
// }
if (ramp_time > 0.01) {
const float ramp_step = (float)ST2MS(chVTTimeElapsedSinceX(last_time)) / (ramp_time * 1000.0);
utils_step_towards(&pwr_ramp, pwr, ramp_step);
last_time = chVTGetSystemTimeX();
pwr = pwr_ramp;
}
float current_rel = 0.0;
bool current_mode = false;
bool current_mode_brake = false;
const volatile mc_configuration *mcconf = mc_interface_get_configuration();
const float rpm_now = mc_interface_get_rpm();
bool send_duty = false;
// Use the filtered and mapped voltage for control according to the configuration.
switch (config.ctrl_type) {
case ADC_CTRL_TYPE_CURRENT:
case ADC_CTRL_TYPE_CURRENT_REV_CENTER:
case ADC_CTRL_TYPE_CURRENT_REV_BUTTON:
current_mode = true;
if ((pwr >= 0.0 && rpm_now > 0.0) || (pwr < 0.0 && rpm_now < 0.0)) {
current_rel = pwr;
} else {
current_rel = pwr;
}
if (fabsf(pwr) < 0.001) {
ms_without_power += (1000.0 * (float)sleep_time) / (float)CH_CFG_ST_FREQUENCY;
}
break;
case ADC_CTRL_TYPE_CURRENT_REV_BUTTON_BRAKE_CENTER:
case ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_CENTER:
case ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_BUTTON:
case ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_ADC:
case ADC_CTRL_TYPE_CURRENT_REV_BUTTON_BRAKE_ADC:
current_mode = true;
if (pwr >= 0.0) {
// if pedal assist (PAS) thread is running, use the highest current command
if (app_pas_is_running()) {
pwr = utils_max_abs(pwr, app_pas_get_current_target_rel());
}
current_rel = pwr;
} else {
current_rel = fabsf(pwr);
current_mode_brake = true;
}
if (pwr < 0.001) {
ms_without_power += (1000.0 * (float)sleep_time) / (float)CH_CFG_ST_FREQUENCY;
}
if ((config.ctrl_type == ADC_CTRL_TYPE_CURRENT_REV_BUTTON_BRAKE_ADC ||
config.ctrl_type == ADC_CTRL_TYPE_CURRENT_REV_BUTTON_BRAKE_CENTER) && rev_button) {
current_rel = -current_rel;
}
break;
case ADC_CTRL_TYPE_DUTY:
case ADC_CTRL_TYPE_DUTY_REV_CENTER:
case ADC_CTRL_TYPE_DUTY_REV_BUTTON:
if (fabsf(pwr) < 0.001) {
ms_without_power += (1000.0 * (float)sleep_time) / (float)CH_CFG_ST_FREQUENCY;
}
if (!(ms_without_power < MIN_MS_WITHOUT_POWER && config.safe_start)) {
mc_interface_set_duty(utils_map(pwr, -1.0, 1.0, -mcconf->l_max_duty, mcconf->l_max_duty));
send_duty = true;
}
break;
case ADC_CTRL_TYPE_PID:
case ADC_CTRL_TYPE_PID_REV_CENTER:
case ADC_CTRL_TYPE_PID_REV_BUTTON:
if ((pwr >= 0.0 && rpm_now > 0.0) || (pwr < 0.0 && rpm_now < 0.0)) {
current_rel = pwr;
} else {
current_rel = pwr;
}
if (!(ms_without_power < MIN_MS_WITHOUT_POWER && config.safe_start)) {
float speed = 0.0;
if (pwr >= 0.0) {
speed = pwr * mcconf->l_max_erpm;
} else {
speed = pwr * fabsf(mcconf->l_min_erpm);
}
mc_interface_set_pid_speed(speed);
send_duty = true;
}
if (fabsf(pwr) < 0.001) {
ms_without_power += (1000.0 * (float)sleep_time) / (float)CH_CFG_ST_FREQUENCY;
}
break;
default:
continue;
}
// If safe start is enabled and the output has not been zero for long enough
if (ms_without_power < MIN_MS_WITHOUT_POWER && config.safe_start) {
static int pulses_without_power_before = 0;
if (ms_without_power == pulses_without_power_before) {
ms_without_power = 0;
}
pulses_without_power_before = ms_without_power;
mc_interface_set_brake_current(timeout_get_brake_current());
if (config.multi_esc) {
for (int i = 0;i < CAN_STATUS_MSGS_TO_STORE;i++) {
can_status_msg *msg = comm_can_get_status_msg_index(i);
if (msg->id >= 0 && UTILS_AGE_S(msg->rx_time) < MAX_CAN_AGE) {
comm_can_set_current_brake(msg->id, timeout_get_brake_current());
}
}
}
continue;
}
// Reset timeout
timeout_reset();
// If c is pressed and no throttle is used, maintain the current speed with PID control
static bool was_pid = false;
// Filter RPM to avoid glitches
static float filter_buffer[RPM_FILTER_SAMPLES];
static int filter_ptr = 0;
filter_buffer[filter_ptr++] = mc_interface_get_rpm();
if (filter_ptr >= RPM_FILTER_SAMPLES) {
filter_ptr = 0;
}
float rpm_filtered = 0.0;
for (int i = 0;i < RPM_FILTER_SAMPLES;i++) {
rpm_filtered += filter_buffer[i];
}
rpm_filtered /= RPM_FILTER_SAMPLES;
if (current_mode && cc_button && fabsf(pwr) < 0.001) {
static float pid_rpm = 0.0;
if (!was_pid) {
was_pid = true;
pid_rpm = rpm_filtered;
}
mc_interface_set_pid_speed(pid_rpm);
// Send the same duty cycle to the other controllers
if (config.multi_esc) {
float current = mc_interface_get_tot_current_directional_filtered();
for (int i = 0;i < CAN_STATUS_MSGS_TO_STORE;i++) {
can_status_msg *msg = comm_can_get_status_msg_index(i);
if (msg->id >= 0 && UTILS_AGE_S(msg->rx_time) < MAX_CAN_AGE) {
comm_can_set_current(msg->id, current);
}
}
}
continue;
}
was_pid = false;
// Find lowest RPM (for traction control)
float rpm_local = mc_interface_get_rpm();
float rpm_lowest = rpm_local;
if (config.multi_esc) {
for (int i = 0;i < CAN_STATUS_MSGS_TO_STORE;i++) {
can_status_msg *msg = comm_can_get_status_msg_index(i);
if (msg->id >= 0 && UTILS_AGE_S(msg->rx_time) < MAX_CAN_AGE) {
float rpm_tmp = msg->rpm;
if (fabsf(rpm_tmp) < fabsf(rpm_lowest)) {
rpm_lowest = rpm_tmp;
}
}
}
}
// Optionally send the duty cycles to the other ESCs seen on the CAN-bus
if (send_duty && config.multi_esc) {
float duty = mc_interface_get_duty_cycle_now();
for (int i = 0;i < CAN_STATUS_MSGS_TO_STORE;i++) {
can_status_msg *msg = comm_can_get_status_msg_index(i);
if (msg->id >= 0 && UTILS_AGE_S(msg->rx_time) < MAX_CAN_AGE) {
comm_can_set_duty(msg->id, duty);
}
}
}
if (current_mode) {
if (current_mode_brake) {
mc_interface_set_brake_current_rel(current_rel);
// Send brake command to all ESCs seen recently on the CAN bus
if (config.multi_esc) {
for (int i = 0;i < CAN_STATUS_MSGS_TO_STORE;i++) {
can_status_msg *msg = comm_can_get_status_msg_index(i);
if (msg->id >= 0 && UTILS_AGE_S(msg->rx_time) < MAX_CAN_AGE) {
comm_can_set_current_brake_rel(msg->id, current_rel);
}
}
}
} else {
float current_out = current_rel;
bool is_reverse = false;
if (current_out < 0.0) {
is_reverse = true;
current_out = -current_out;
current_rel = -current_rel;
rpm_local = -rpm_local;
rpm_lowest = -rpm_lowest;
}
// Traction control
if (config.multi_esc) {
for (int i = 0;i < CAN_STATUS_MSGS_TO_STORE;i++) {
can_status_msg *msg = comm_can_get_status_msg_index(i);
if (msg->id >= 0 && UTILS_AGE_S(msg->rx_time) < MAX_CAN_AGE) {
if (config.tc) {
float rpm_tmp = msg->rpm;
if (is_reverse) {
rpm_tmp = -rpm_tmp;
}
float diff = rpm_tmp - rpm_lowest;
current_out = utils_map(diff, 0.0, config.tc_max_diff, current_rel, 0.0);
}
if (is_reverse) {
comm_can_set_current_rel(msg->id, -current_out);
} else {
comm_can_set_current_rel(msg->id, current_out);
}
}
}
if (config.tc) {
float diff = rpm_local - rpm_lowest;
current_out = utils_map(diff, 0.0, config.tc_max_diff, current_rel, 0.0);
}
}
if (is_reverse) {
mc_interface_set_current_rel(-current_out);
} else {
mc_interface_set_current_rel(current_out);
}
}
}
}
}
Found my mistake, i was setting the pin an as input, removing this line fixed it
palSetPadMode(HW_ICU_GPIO, HW_ICU_PIN, PAL_MODE_INPUT_PULLUP);
Now i've just gotta sort out mapping the ADC to the servo output
It will sit at 50% on startup then move to 0% and sit there happily but it won't move proportionally with the throttle
I've tested it with servo_simple_set_output(decoded_level2) but the servo just moves randomly
Problem solved (mostly)
For some reason whenever i called the function servo_simple_set_output() it would output either a 1000us pulse or 2000us pulse and just flick back and forth between the two
Instead of calling the function i copied the following in its place
Which is basically the half of the function servo_simple_set_output() that i need
Can't explain exactly why the function wasn't working for me, but I can live with this workaround
Hey, any links to your project? As far as I understand, this is friction drive? I'm considering something like this myself and would be great to use a few pointers.