Merge branch 'devel' into devel_fast

This commit is contained in:
Paul Guenette
2020-10-30 20:48:37 -04:00
34 changed files with 471 additions and 355 deletions
+4 -2
View File
@@ -5,14 +5,16 @@ Please add a note of your changes below this heading if you make a Pull Request.
* Added periodic sending of encoder position on CAN
### Changed
* Modified encoder offset calibration to work correctly when calib_scan_distance is not a multiple of 4pi
* Moved thermistors from being a top level object to belonging to Motor objects. Also changed errors: thermistor errors rolled into motor errors
* Use DMA for DRV8301 setup
* Make NVM configuration code more dynamic so that the layout doesn't have to be known at compile time.
* GPIO initialization logic was changed. GPIOs now need to be explicitly set to the mode corresponding to the feature that they are used by. See `<odrv>.config.gpioX_mode`.
* Previously, if two components used the same interrupt pin (e.g. step input for axis0 and axis1) then the one that was configured later would override the other one. Now this is no longer the case (the old component remains the owner of the pin).
### API Miration Notes
### API Migration Notes
* `odrive.axis.fet_thermistor`, `odrive.axis.motor_thermistor` moved to `odrive.axis.motor` object
* `enable_uart` and `uart_baudrate` were renamed to `enable_uart0` and `uart0_baudrate`.
* `enable_i2c_instead_of_can` was replaced by the separate settings `enable_i2c0` and `enable_can0`.
* `<axis>.motor.gate_driver` was moved to `<axis>.gate_driver`.
+8 -7
View File
@@ -58,20 +58,26 @@ OnboardThermistorCurrentLimiter fet_thermistors[AXIS_COUNT] = {
}
};
OffboardThermistorCurrentLimiter motor_thermistors[AXIS_COUNT];
Motor motors[AXIS_COUNT] = {
{
&htim1, // timer
TIM_1_8_PERIOD_CLOCKS, // control_deadline
1.0f / SHUNT_RESISTANCE, // shunt_conductance [S]
m0_gate_driver, // gate_driver
m0_gate_driver // opamp
m0_gate_driver, // opamp
fet_thermistors[0],
motor_thermistors[0]
},
{
&htim8, // timer
(3 * TIM_1_8_PERIOD_CLOCKS) / 2, // control_deadline
1.0f / SHUNT_RESISTANCE, // shunt_conductance [S]
m1_gate_driver, // gate_driver
m1_gate_driver // opamp
m1_gate_driver, // opamp
fet_thermistors[1],
motor_thermistors[1]
}
};
@@ -101,7 +107,6 @@ MechanicalBrake mechanical_brakes[AXIS_COUNT];
SensorlessEstimator sensorless_estimators[AXIS_COUNT];
Controller controllers[AXIS_COUNT];
TrapezoidalTrajectory trap[AXIS_COUNT];
OffboardThermistorCurrentLimiter motor_thermistors[AXIS_COUNT];
std::array<Axis, AXIS_COUNT> axes{{
{
@@ -112,8 +117,6 @@ std::array<Axis, AXIS_COUNT> axes{{
encoders[0], // encoder
sensorless_estimators[0], // sensorless_estimator
controllers[0], // controller
fet_thermistors[0], // fet_thermistor
motor_thermistors[0], // motor_thermistor
motors[0], // motor
trap[0], // trap
endstops[0], endstops[1], // min_endstop, max_endstop
@@ -132,8 +135,6 @@ std::array<Axis, AXIS_COUNT> axes{{
encoders[1], // encoder
sensorless_estimators[1], // sensorless_estimator
controllers[1], // controller
fet_thermistors[1], // fet_thermistor
motor_thermistors[1], // motor_thermistor
motors[1], // motor
trap[1], // trap
endstops[2], endstops[3], // min_endstop, max_endstop
+3 -19
View File
@@ -14,8 +14,6 @@ Axis::Axis(int axis_num,
Encoder& encoder,
SensorlessEstimator& sensorless_estimator,
Controller& controller,
OnboardThermistorCurrentLimiter& fet_thermistor,
OffboardThermistorCurrentLimiter& motor_thermistor,
Motor& motor,
TrapezoidalTrajectory& trap,
Endstop& min_endstop,
@@ -28,25 +26,15 @@ Axis::Axis(int axis_num,
encoder_(encoder),
sensorless_estimator_(sensorless_estimator),
controller_(controller),
fet_thermistor_(fet_thermistor),
motor_thermistor_(motor_thermistor),
motor_(motor),
trap_traj_(trap),
min_endstop_(min_endstop),
max_endstop_(max_endstop),
mechanical_brake_(mechanical_brake),
current_limiters_(make_array(
static_cast<CurrentLimiter*>(&fet_thermistor),
static_cast<CurrentLimiter*>(&motor_thermistor))),
thermistors_(make_array(
static_cast<ThermistorCurrentLimiter*>(&fet_thermistor),
static_cast<ThermistorCurrentLimiter*>(&motor_thermistor)))
mechanical_brake_(mechanical_brake)
{
encoder_.axis_ = this;
sensorless_estimator_.axis_ = this;
controller_.axis_ = this;
fet_thermistor_.axis_ = this;
motor_thermistor.axis_ = this;
motor_.axis_ = this;
trap_traj_.axis_ = this;
min_endstop_.axis_ = this;
@@ -180,9 +168,6 @@ bool Axis::do_checks() {
// Sub-components should use set_error which will propegate to this error_
motor_.effective_current_lim();
for (ThermistorCurrentLimiter* thermistor : thermistors_) {
thermistor->do_checks();
}
motor_.do_checks();
// encoder_.do_checks();
// sensorless_estimator_.do_checks();
@@ -201,11 +186,10 @@ bool Axis::do_checks() {
// @brief Update all esitmators
bool Axis::do_updates() {
// Sub-components should use set_error which will propegate to this error_
for (ThermistorCurrentLimiter* thermistor : thermistors_) {
thermistor->update();
}
encoder_.update();
sensorless_estimator_.update();
motor_.fet_thermistor_.update();
motor_.motor_thermistor_.update();
min_endstop_.update();
max_endstop_.update();
bool ret = check_for_errors();
-9
View File
@@ -97,8 +97,6 @@ public:
Encoder& encoder,
SensorlessEstimator& sensorless_estimator,
Controller& controller,
OnboardThermistorCurrentLimiter& fet_thermistor,
OffboardThermistorCurrentLimiter& motor_thermistor,
Motor& motor,
TrapezoidalTrajectory& trap,
Endstop& min_endstop,
@@ -227,19 +225,12 @@ public:
Encoder& encoder_;
SensorlessEstimator& sensorless_estimator_;
Controller& controller_;
OnboardThermistorCurrentLimiter& fet_thermistor_;
OffboardThermistorCurrentLimiter& motor_thermistor_;
Motor& motor_;
TrapezoidalTrajectory& trap_traj_;
Endstop& min_endstop_;
Endstop& max_endstop_;
MechanicalBrake& mechanical_brake_;
// List of current_limiters and thermistors to
// provide easy iteration.
std::array<CurrentLimiter*, 2> current_limiters_;
std::array<ThermistorCurrentLimiter*, 2> thermistors_;
osThreadId thread_id_;
const uint32_t stack_size_ = 2048; // Bytes
volatile bool thread_id_valid_ = false;
+5 -2
View File
@@ -228,10 +228,13 @@ bool Encoder::run_offset_calibration() {
else
return false;
// go to motor zero phase for start_lock_duration to get ready to scan
// go to start position of forward scan for start_lock_duration to get ready to scan
int i = 0;
axis_->run_control_loop([&](){
if (!axis_->motor_.enqueue_voltage_timings(voltage_magnitude, 0.0f))
float phase = wrap_pm_pi(0 - config_.calib_scan_distance / 2.0f);
float v_alpha = voltage_magnitude * our_arm_cos_f32(phase);
float v_beta = voltage_magnitude * our_arm_sin_f32(phase);
if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta))
return false; // error set inside enqueue_voltage_timings
axis_->motor_.log_timing(TIMING_LOG_ENC_CALIB);
return ++i < start_lock_duration * current_meas_hz;
+7 -6
View File
@@ -50,8 +50,8 @@ static bool config_read_all() {
config_manager.read(&axes[i].max_endstop_.config_) &&
config_manager.read(&axes[i].mechanical_brake_.config_) &&
config_manager.read(&motors[i].config_) &&
config_manager.read(&fet_thermistors[i].config_) &&
config_manager.read(&axes[i].motor_thermistor_.config_) &&
config_manager.read(&motors[i].fet_thermistor_.config_) &&
config_manager.read(&motors[i].motor_thermistor_.config_) &&
config_manager.read(&axes[i].config_);
}
return success;
@@ -70,8 +70,8 @@ static bool config_write_all() {
config_manager.write(&axes[i].max_endstop_.config_) &&
config_manager.write(&axes[i].mechanical_brake_.config_) &&
config_manager.write(&motors[i].config_) &&
config_manager.write(&fet_thermistors[i].config_) &&
config_manager.write(&axes[i].motor_thermistor_.config_) &&
config_manager.write(&motors[i].fet_thermistor_.config_) &&
config_manager.write(&motors[i].motor_thermistor_.config_) &&
config_manager.write(&axes[i].config_);
}
return success;
@@ -90,8 +90,8 @@ static void config_clear_all() {
axes[i].max_endstop_.config_ = {};
axes[i].mechanical_brake_.config_ = {};
motors[i].config_ = {};
fet_thermistors[i].config_ = {};
axes[i].motor_thermistor_.config_ = {};
motors[i].fet_thermistor_.config_ = {};
motors[i].motor_thermistor_.config_ = {};
axes[i].clear_config();
}
}
@@ -104,6 +104,7 @@ static bool config_apply_all() {
&& axes[i].min_endstop_.apply_config()
&& axes[i].max_endstop_.apply_config()
&& motors[i].apply_config()
&& motors[i].motor_thermistor_.apply_config()
&& axes[i].apply_config();
}
return success;
+21 -8
View File
@@ -10,13 +10,19 @@ Motor::Motor(TIM_HandleTypeDef* timer,
uint16_t control_deadline,
float shunt_conductance,
TGateDriver& gate_driver,
TOpAmp& opamp) :
TOpAmp& opamp,
OnboardThermistorCurrentLimiter& fet_thermistor,
OffboardThermistorCurrentLimiter& motor_thermistor) :
timer_(timer),
control_deadline_(control_deadline),
shunt_conductance_(shunt_conductance),
gate_driver_(gate_driver),
opamp_(opamp) {
opamp_(opamp),
fet_thermistor_(fet_thermistor),
motor_thermistor_(motor_thermistor) {
apply_config();
fet_thermistor_.motor_ = this;
motor_thermistor_.motor_ = this;
}
// @brief Arms the PWM outputs that belong to this motor.
@@ -110,7 +116,16 @@ bool Motor::do_checks() {
set_error(ERROR_DRV_FAULT);
return false;
}
if (!motor_thermistor_.do_checks()) {
axis_->error_ |= Axis::ERROR_MOTOR_FAILED;
set_error(ERROR_MOTOR_THERMISTOR_OVER_TEMP);
return false;
}
if (!fet_thermistor_.do_checks()) {
axis_->error_ |= Axis::ERROR_MOTOR_FAILED;
set_error(ERROR_FET_THERMISTOR_OVER_TEMP);
return false;
}
return true;
}
@@ -124,11 +139,9 @@ float Motor::effective_current_lim() {
current_lim = std::min(current_lim, axis_->motor_.current_control_.max_allowed_current);
}
// Apply axis current limiters
for (const CurrentLimiter* const limiter : axis_->current_limiters_) {
current_lim = std::min(current_lim, limiter->get_current_limit(config_.current_lim));
}
// Apply thermistor current limiters
current_lim = std::min(current_lim, motor_thermistor_.get_current_limit(config_.current_lim));
current_lim = std::min(current_lim, fet_thermistor_.get_current_limit(config_.current_lim));
effective_current_lim_ = current_lim;
return effective_current_lim_;
+5 -1
View File
@@ -99,7 +99,9 @@ public:
uint16_t control_deadline,
float shunt_conductance,
TGateDriver& gate_driver,
TOpAmp& opamp);
TOpAmp& opamp,
OnboardThermistorCurrentLimiter& fet_thermistor,
OffboardThermistorCurrentLimiter& motor_thermistor);
bool arm();
void disarm();
@@ -130,6 +132,8 @@ public:
const float shunt_conductance_;
TGateDriver& gate_driver_;
TOpAmp& opamp_;
OnboardThermistorCurrentLimiter& fet_thermistor_;
OffboardThermistorCurrentLimiter& motor_thermistor_;
Config_t config_;
Axis* axis_ = nullptr; // set by Axis constructor
+7 -4
View File
@@ -14,8 +14,7 @@ ThermistorCurrentLimiter::ThermistorCurrentLimiter(uint16_t adc_channel,
temperature_(NAN),
temp_limit_lower_(temp_limit_lower),
temp_limit_upper_(temp_limit_upper),
enabled_(enabled),
error_(ERROR_NONE)
enabled_(enabled)
{
}
@@ -26,8 +25,6 @@ void ThermistorCurrentLimiter::update() {
bool ThermistorCurrentLimiter::do_checks() {
if (enabled_ && temperature_ >= temp_limit_upper_ + 5) {
error_ = ERROR_OVER_TEMP;
axis_->error_ |= Axis::ERROR_OVER_TEMP;
return false;
}
return true;
@@ -69,6 +66,12 @@ OffboardThermistorCurrentLimiter::OffboardThermistorCurrentLimiter() :
decode_pin();
}
bool OffboardThermistorCurrentLimiter::apply_config() {
config_.parent = this;
decode_pin();
return true;
}
void OffboardThermistorCurrentLimiter::decode_pin() {
adc_channel_ = channel_from_gpio(get_gpio(config_.gpio_pin));
}
+4 -3
View File
@@ -1,7 +1,7 @@
#ifndef __THERMISTOR_HPP
#define __THERMISTOR_HPP
class Axis; // declared in axis.hpp
class Motor; // declared in motor.hpp
#include "current_limiter.hpp"
#include <autogen/interfaces.hpp>
@@ -28,8 +28,7 @@ public:
const float& temp_limit_lower_;
const float& temp_limit_upper_;
const bool& enabled_;
Error error_;
Axis* axis_ = nullptr; // set by Axis constructor
Motor* motor_ = nullptr; // set by Motor::apply_config()
};
class OnboardThermistorCurrentLimiter : public ThermistorCurrentLimiter, public ODriveIntf::OnboardThermistorCurrentLimiterIntf {
@@ -68,6 +67,8 @@ public:
Config_t config_;
bool apply_config();
private:
void decode_pin();
};
+7 -4
View File
@@ -109,10 +109,13 @@ def find_all(path, serial_number,
# Save JSON to cache
if not cache_path is None:
logger.debug("Creating new JSON cache file {}".format(cache_path))
os.makedirs(cache_dir, exist_ok=True)
with open(cache_path, 'w+') as json_cache:
json_cache.write(json_string)
logger.debug("Saved JSON to cache file {}".format(cache_path))
try:
os.makedirs(cache_dir, exist_ok=True)
with open(cache_path, 'w+') as json_cache:
json_cache.write(json_string)
logger.debug("Saved JSON to cache file {}".format(cache_path))
except Exception as ex:
logger.warn("Failed to cache JSON: {}".format(ex))
channel._interface_definition_crc = json_crc16
+6 -11
View File
@@ -355,7 +355,8 @@ interfaces:
bit: 17
doc: the min endstop was not enabled during homing
OverTemp:
doc: Check `fet_thermistor.error` and `motor_thermistor.error` for more information.
# unused
doc: Check `motor.error` for more details.
step_dir_active: readonly bool
current_state: readonly AxisState
requested_state: AxisState
@@ -440,8 +441,6 @@ interfaces:
# status_reg_2: readonly uint32
# ctrl_reg_1: readonly uint32
# ctrl_reg_2: readonly uint32
fet_thermistor: OnboardThermistorCurrentLimiter
motor_thermistor: OffboardThermistorCurrentLimiter
motor: Motor
controller: Controller
encoder: Encoder
@@ -495,7 +494,6 @@ interfaces:
ODrive.OnboardThermistorCurrentLimiter:
c_is_class: True
attributes:
error: ThermistorCurrentLimiter.Error
temperature: readonly float32
config:
c_is_class: False
@@ -511,7 +509,6 @@ interfaces:
ODrive.OffboardThermistorCurrentLimiter:
c_is_class: True
attributes:
error: ThermistorCurrentLimiter.Error
temperature: readonly float32
config:
c_is_class: False
@@ -609,6 +606,8 @@ interfaces:
DcBusOverRegenCurrent: {doc: too much current pushed into the power supply}
DcBusOverCurrent: {doc: too much current pulled out of the power supply}
ModulationIsNan:
MotorThermistorOverTemp: {doc: The motor thermistor measured a temperature above motor.motor_thermistor.config.temp_limit_upper}
FetThermistorOverTemp: {doc: The inverter thermistor measured a temperature above motor.fet_thermistor.config.temp_limit_upper}
armed_state:
typeargs: {fibre.Property.mode: readonly}
values:
@@ -623,6 +622,8 @@ interfaces:
DC_calib_phC: {type: float32, c_name: DC_calib_.phC}
phase_current_rev_gain: float32
effective_current_lim: readonly float32
fet_thermistor: OnboardThermistorCurrentLimiter
motor_thermistor: OffboardThermistorCurrentLimiter
current_control:
c_is_class: False
attributes:
@@ -1024,12 +1025,6 @@ valuetypes:
doc:
Endstops must be enabled to use this feature.
ODrive.ThermistorCurrentLimiter.Error:
nullflag: None
flags:
OverTemp:
doc: The thermistor temperature upper limit was exceeded.
ODrive.Encoder.Mode:
values:
Incremental:
+24 -11
View File
@@ -58,8 +58,12 @@ def discovered_device(device):
while globals()['inUse']:
time.sleep(0.1)
globals()['odrives'][odrive_name] = device
globals()['odrives_status'][odrive_name] = True
print("Found " + str(serial_number))
print("odrive list: " + str([key for key in globals()['odrives'].keys()]))
# tell GUI the status of known ODrives (previously connected and then disconnected ODrives will be "False")
socketio.emit('odrives-status', json.dumps(globals()['odrives_status']))
# triggers a getODrives socketio message
socketio.emit('odrive-found')
def start_discovery():
@@ -68,9 +72,12 @@ def start_discovery():
shutdown = fibre.Event()
fibre.find_all("usb", None, discovered_device, shutdown, shutdown, log)
def handle_disconnect():
def handle_disconnect(odrive_name):
print("lost odrive")
#socketio.emit('odrive-disconnected')
globals()['odrives_status'][odrive_name] = False
# emit the whole list of odrive statuses
# in the GUI, mark and use status as ODrive state.
socketio.emit('odrives-status', json.dumps(globals()['odrives_status']))
@socketio.on('findODrives')
def getODrives(message):
@@ -115,8 +122,9 @@ def get_odrives(data):
odriveDict = {}
#for (index, odrv) in enumerate(globals()['odrives']):
# odriveDict["odrive" + str(index)] = dictFromRO(odrv)
for key in globals()['odrives'].keys():
odriveDict[key] = dictFromRO(globals()['odrives'][key])
for key in globals()['odrives_status'].keys():
if globals()['odrives_status'][key] == True:
odriveDict[key] = dictFromRO(globals()['odrives'][key])
globals()['inUse'] = False
emit('odrives', json.dumps(odriveDict))
@@ -126,10 +134,11 @@ def get_property(message):
# will be {"path": "odriveX.axisY.blah.blah"}
while globals()['inUse']:
time.sleep(0.1)
globals()['inUse'] = True
val = getVal(globals()['odrives'], message["path"].split('.'))
globals()['inUse'] = False
emit('ODriveProperty', json.dumps({"path": message["path"], "val": val}))
if globals()['odrives_status'][message["path"].split('.')[0]]:
globals()['inUse'] = True
val = getVal(globals()['odrives'], message["path"].split('.'))
globals()['inUse'] = False
emit('ODriveProperty', json.dumps({"path": message["path"], "val": val}))
@socketio.on('setProperty')
def set_property(message):
@@ -194,7 +203,7 @@ def postVal(odrives, keyList, value, argType):
else:
pass # dont support that type yet
except fibre.protocol.ChannelBrokenException:
handle_disconnect()
handle_disconnect(odrv)
except:
print("exception in postVal")
@@ -210,7 +219,7 @@ def getVal(odrives, keyList):
else:
return RO.get_value()
except fibre.protocol.ChannelBrokenException:
handle_disconnect()
handle_disconnect(odrv)
except:
print("exception in getVal")
return 0
@@ -235,7 +244,7 @@ def callFunc(odrives, keyList):
if isinstance(RO, fibre.remote_object.RemoteFunction):
RO.__call__()
except fibre.protocol.ChannelBrokenException:
handle_disconnect()
handle_disconnect(odrv)
except:
print("fcn call failed")
@@ -252,7 +261,11 @@ if __name__ == "__main__":
import odrive.utils # for dump_errors()
import fibre
# global for holding references to all connected odrives
globals()['odrives'] = {}
# global dict {'odriveX': True/False} where True/False reflects status of connection
# on handle_disconnect, set it to False. On connection, set it to True
globals()['odrives_status'] = {}
globals()['discovered_devices'] = []
# spinlock
globals()['inUse'] = False
-19
View File
@@ -56,9 +56,6 @@
:axis="axis.name"
:odrives="odrives"
></Axis>
<!--<div class="odrive-status">
ODrive:{{ODriveConnected}}
</div>-->
</div>
</div>
</template>
@@ -129,22 +126,6 @@ export default {
currentDash: function () {
return this.$store.state.currentDash;
},
ODriveConnected: function () {
// if server and odrive disconnected, disconnected
// if server connected and odrive disco, connecting
// if server and odrive connected, connected
let ret;
if (this.$store.state.serverConnected && this.$store.state.ODriveConnected) {
ret = "connected";
}
else if (this.$store.state.serverConnected && !this.$store.state.ODriveConnected) {
ret = "connecting...";
}
else {
ret = "disconnected";
}
return ret;
},
samplingText: function () {
let ret;
if (this.$store.state.sampling) {
+2 -3
View File
@@ -29,9 +29,6 @@
"AXIS_STATE_ENCODER_DIR_FIND" : 10,
"AXIS_STATE_HOMING" : 11,
"THERMISTOR_CURRENT_LIMITER_ERROR_NONE" : 0,
"THERMISTOR_CURRENT_LIMITER_ERROR_OVER_TEMP" : 1,
"ENCODER_MODE_INCREMENTAL" : 0,
"ENCODER_MODE_HALL" : 1,
"ENCODER_MODE_SINCOS" : 2,
@@ -102,6 +99,8 @@
"MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT" : 16384,
"MOTOR_ERROR_DC_BUS_OVER_CURRENT" : 32768,
"MOTOR_ERROR_MODULATION_IS_NAN" : 65536,
"MOTOR_ERROR_MOTOR_THERMISTOR_OVER_TEMP" : 131072,
"MOTOR_ERROR_FET_THERMISTOR_OVER_TEMP" : 262144,
"ARMED_STATE_DISARMED" : 0,
"ARMED_STATE_WAITING_FOR_TIMINGS" : 1,
+4 -2
View File
@@ -17,7 +17,8 @@
"config": {
"mode": null,
"use_index": null,
"cpr": null
"cpr": null,
"calib_scan_distance": null
}
},
"controller": {
@@ -51,7 +52,8 @@
"config": {
"mode": null,
"use_index": null,
"cpr": null
"cpr": null,
"calib_scan_distance": null
}
},
"controller": {
+38 -17
View File
@@ -23,25 +23,42 @@ protocol.registerSchemesAsPrivileged([
// function to get determine correct command for python
function getPyCmd() {
let spawnRet = spawnSync('python',['-V']);
let vString;
if (spawnRet.stdout.toString().length > 1){
vString = spawnRet.stdout.toString();
// call both 'python' and 'python3' to figure out the correct command
let spawnRet = spawnSync('python', ['-V']);
let success = spawnRet.status != null;
let outputString;
let cmd = '';
if (success) {
if (spawnRet.stdout.toString().length > 1) {
outputString = spawnRet.stdout.toString();
}
else {
outputString = spawnRet.stderr.toString();
}
if (outputString.includes("Python 3")) {
cmd = 'python';
}
}
else {
vString = spawnRet.stderr.toString();
}
if (vString.split(' ')[1].split('.')[0] == '2') {
return 'python3';
}
else {
return 'python';
if (cmd == '') {
spawnRet = spawnSync('python3', ['-V']);
success = spawnRet.status != null;
if (success) {
if (spawnRet.stdout.toString().length > 1) {
outputString = spawnRet.stdout.toString();
}
else {
outputString = spawnRet.stderr.toString();
}
if (outputString.includes("Python 3")) {
cmd = 'python3';
}
}
}
return cmd;
}
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
@@ -125,15 +142,19 @@ app.on('ready', async () => {
// launch python server on event from renderer process (gui) and pipe stdout/stderr to it
ipcMain.on('start-server', () => {
server = spawn(getPyCmd(), effectiveCommand);
server.stdout.on('data',function(data) {
console.log(data.toString('utf8'));
server.stdout.on('data', function (data) {
try {
console.log(data.toString('utf8'));
} catch (error) {
console.log(error);
}
try {
win.webContents.send('server-stdout', String(data.toString('utf8')));
} catch (error) {
console.log(error);
}
});
server.stderr.on('data',function(data) {
server.stderr.on('data', function (data) {
console.log(data.toString('utf8'));
try {
win.webContents.send('server-stderr', String(data.toString('utf8')));
+27 -25
View File
@@ -2,7 +2,7 @@
<div
class="axis"
@click.self="showError = !showError;"
:class="{ noError: !error, error: error}"
:class="{inactive: !connected, noError: !error, error: error}"
>
{{ axis }}
<div v-show="showError" class="error-popup card" @click.self="showError = !showError">
@@ -66,6 +66,9 @@ const motorErrors = {
0x00002000: "MOTOR_ERROR_BRAKE_DUTY_CYCLE_NAN",
0x00004000: "MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT",
0x00008000: "MOTOR_ERROR_DC_BUS_OVER_CURRENT",
0x00010000: "MOTOR_ERROR_MODULATION_IS_NAN",
0x00020000: "MOTOR_ERROR_MOTOR_THERMISTOR_OVER_TEMP",
0x00040000: "MOTOR_ERROR_FET_THERMISTOR_OVER_TEMP",
};
let encoderErrors = {
@@ -107,6 +110,9 @@ export default {
};
},
computed: {
connected() {
return this.$store.state.ODrivesConnected[this.axis.split('.')[0]];
},
axisErrorMsg() {
let retMsg = "none";
let errCode = this.axisErr;
@@ -125,10 +131,7 @@ export default {
errs.push(axisErrors[errKey]);
}
}
retMsg = "";
for (const err of errs) {
retMsg = retMsg + " " + err;
}
retMsg = errs.join(', ');
}
return retMsg;
@@ -150,10 +153,7 @@ export default {
errs.push(motorErrors[errKey]);
}
}
retMsg = "";
for (const err of errs) {
retMsg = retMsg + " " + err;
}
retMsg = errs.join(', ');
}
return retMsg;
@@ -175,10 +175,7 @@ export default {
errs.push(encoderErrors[errKey]);
}
}
retMsg = "";
for (const err of errs) {
retMsg = retMsg + " " + err;
}
retMsg = errs.join(', ');
}
return retMsg;
@@ -195,10 +192,7 @@ export default {
errs.push(controllerErrors[errKey]);
}
}
retMsg = "";
for (const err of errs) {
retMsg = retMsg + " " + err;
}
retMsg = errs.join(', ');
}
return retMsg;
@@ -238,14 +232,18 @@ export default {
created() {
// set up timeout loop for grabbing axis error values
let update = () => {
fetchParam(this.axis + ".error");
fetchParam(this.axis + '.motor.error');
fetchParam(this.axis + '.controller.error');
fetchParam(this.axis + '.encoder.error');
this.axisErr = getVal(this.axis + '.error');
this.motorErr = getVal(this.axis + '.motor.error');
this.controllerErr = getVal(this.axis + '.controller.error');
this.encoderErr = getVal(this.axis + '.encoder.error');
// Do we have an active connection to the ODrive that contains this axis?
if (this.$store.state.ODrivesConnected[this.axis.split('.')[0]]) {
fetchParam(this.axis + ".error");
fetchParam(this.axis + '.motor.error');
fetchParam(this.axis + '.controller.error');
fetchParam(this.axis + '.encoder.error');
this.axisErr = getVal(this.axis + '.error');
this.motorErr = getVal(this.axis + '.motor.error');
this.controllerErr = getVal(this.axis + '.controller.error');
this.encoderErr = getVal(this.axis + '.encoder.error');
}
// ODrive not connected
setTimeout(update, 1000);
}
update();
@@ -279,4 +277,8 @@ export default {
color: black;
margin-left: 0px;
}
.inactive {
color: grey;
}
</style>
+1 -1
View File
@@ -40,7 +40,7 @@ export default {
methods: {
newVal: function (e) {
let val = parseMath(e.target.value);
if (val != false) {
if (val !== false) {
this.value = val;
console.log("input = " + e.target.value + ", val = " + this.value);
this.$store.commit("setActionVal", {dashID: this.dashID, actionID: this.id, val: this.value});
+2 -1
View File
@@ -47,8 +47,9 @@ export default {
putVal: function (e) {
let keys = this.path.split('.');
keys.shift();
console.log("input recieved: " + e.target.value);
let val = parseMath(e.target.value);
if (val != false) {
if (val !== false) {
putVal(keys.join('.'), val);
}
},
@@ -37,7 +37,7 @@ export default {
setBR(e) {
console.log("from setBR " + e.target.value);
let val = parseMath(e.target.value);
if (val != false) {
if (val !== false) {
this.brake_resistance = val;
let configStub = undefined;
configStub = {
@@ -26,7 +26,7 @@ export default {
methods: {
setCPR(e) {
let val = parseMath(e.target.value);
if (val != false) {
if (val !== false) {
this.cpr = val;
let configStub = undefined;
if (this.data.axis == "axis0") {
@@ -27,7 +27,7 @@ export default {
methods: {
setCPR(e) {
let val = parseMath(e.target.value);
if (val != false) {
if (val !== false) {
this.cpr = val;
let configStub = undefined;
if (this.data.axis == "axis0") {
@@ -100,7 +100,7 @@ export default {
},
setVelocityLimit(e) {
let val = parseMath(e.target.value);
if (val != false) {
if (val !== false) {
this.vel_limit = parseFloat(e.target.value);
this.vel_set = true;
this.sendConfig();
@@ -108,7 +108,7 @@ export default {
},
setCurrentLimit(e) {
let val = parseMath(e.target.value);
if (val != false) {
if (val !== false) {
this.current_lim = parseFloat(e.target.value);
this.current_set = true;
this.sendConfig();
@@ -5,8 +5,8 @@
</template>
<script>
import odriveEnums from "../../../assets/odriveEnums.json";
import { putVal } from "../../../lib/odrive_utils.js"
//import odriveEnums from "../../../assets/odriveEnums.json";
//import { putVal } from "../../../lib/odrive_utils.js"
export default {
name: "wizardMotorCal",
@@ -21,9 +21,6 @@ export default {
},
methods: {
calibrate() {
// ask ODrive to measure resistance and inductance
let path = "odrive0." + this.data.axis + ".requested_state";
putVal(path, odriveEnums.AXIS_STATE_MOTOR_CALIBRATION);
this.$emit('page-comp-event', {data: "motor calibration", axis: this.data.axis});
},
},
+129 -8
View File
@@ -1,16 +1,24 @@
import store from "../store.js";
import * as socketio from "../comms/socketio.js";
import {wait, waitFor} from "./utils.js";
import odriveEnums from "../assets/odriveEnums.json"
// helper functions and utilities for getting ODrive values
// given a path like "odrive0.axis0.config.blah", return the value
export function getParam(path) {
let keys = path.split('.');
let odriveObj = store.state.odrives;
for (const key of keys) {
odriveObj = odriveObj[key];
if (store.state.ODrivesConnected[keys[0]]) {
let odriveObj = store.state.odrives;
for (const key of keys) {
odriveObj = odriveObj[key];
}
return odriveObj;
}
else {
console.log("getParam for " + path + " is for disconnected ODrive");
return undefined;
}
return odriveObj;
}
// wrapper for val field
@@ -51,10 +59,15 @@ export function parseMath(inString) {
export function putVal(path, value) {
console.log("path: " + path + ", val: " + value + ", type: " + typeof value);
socketio.sendEvent({
type: "setProperty",
data: {path: path, val: value, type: typeof value}
})
if (store.state.ODrivesConnected[path.split('.')[0]]) {
socketio.sendEvent({
type: "setProperty",
data: {path: path, val: value, type: typeof value}
});
}
else {
console.log("requesting " + path + " from disconnected odrive")
}
}
// path is path to function, args is list of parameters
@@ -81,6 +94,114 @@ export function getUnit(odrive, path) {
return unit;
}
export function clearErrors(odrive, axis) {
// odrive is odrive path like 'odrive0'
// axis is 'axis0', etc
let paths = [];
[".error", ".motor.error", ".encoder.error", ".controller.error"].forEach((path) => {
paths.push(odrive + axis + path);
});
paths.forEach((path) => {
putVal(path, 0);
})
}
export async function motorCalibration(odrive, axis) {
// set up our continuous fetch
let state;
let motorError;
let updateVals = () => {
fetchParam(odrive + axis + ".current_state");
fetchParam(odrive + axis + ".motor.config.phase_resistance");
fetchParam(odrive + axis + ".motor.config.phase_inductance");
fetchParam(odrive + axis + ".motor.is_calibrated");
fetchParam(odrive + axis + ".motor.error")
state = getVal(odrive + axis + ".current_state");
motorError = getVal(odrive + axis + ".motor.error");
if (run) {
setTimeout(updateVals, 100);
}
}
// set up our state watch function
let end = () => {
// return {done: boolean, data <whatever>}
if (state != odriveEnums.AXIS_STATE_MOTOR_CALIBRATION) {
return {done: true, data: motorError}
}
else {
return {done: false}
}
}
// start getting live updates
let run = true;
updateVals();
// send motor calibration command to correct odrive and axis
putVal(odrive + axis + ".requested_state", odriveEnums.AXIS_STATE_MOTOR_CALIBRATION);
// give it some time to start
await wait(500);
// only two things can happen now, either we successfully calibrate or we error out for some reason
const result = await waitFor(end);
// stop our parameter updates
run = false;
return result;
}
export async function encoderCalibration(odrive,axis) {
// set up our continuous fetch
let state;
let encoderError;
let updateVals = () => {
fetchParam(odrive + axis + ".current_state");
fetchParam(odrive + axis + ".encoder.is_ready");
fetchParam(odrive + axis + ".encoder.error")
state = getVal(odrive + axis + ".current_state");
encoderError = getVal(odrive + axis + ".encoder.error");
if (run) {
setTimeout(updateVals, 100);
}
}
// set up our state watch function
let end = () => {
// return {done: boolean, data <whatever>}
if (state != odriveEnums.AXIS_STATE_ENCODER_OFFSET_CALIBRATION) {
return {done: true, data: encoderError}
}
else {
return {done: false}
}
}
// start getting live updates
let run = true;
updateVals();
// start encoder offset cal
putVal(odrive + axis + ".requested_state", odriveEnums.AXIS_STATE_ENCODER_OFFSET_CALIBRATION);
// give it some time to start
await wait(500);
// only two things can happen now, either we successfully calibrate or we error out for some reason
const result = await waitFor(end);
// stop our parameter updates
run = false;
return result;
}
// standins for wizard and gui unit displays
// TODO - convert between odrive api units and user-defined units like degrees and rpm
export let odriveUnits = {
+25
View File
@@ -30,4 +30,29 @@ export let deleteBy = (obj, test) => {
delete obj[key];
}
})
}
// sets up a timeout to wait for fun() to evaluate to true;
// fun must return {done: boolean, data: <whatever>}
export let waitFor = (fun) => {
return new Promise(resolve => {
let check = () => {
let res = fun();
if (res.done) {
resolve(res.data);
}
else {
setTimeout(check,100);
}
}
check();
});
}
// utility function to allow time for things to happen
// async wait
export let wait = (time) => {
return new Promise(resolve => {
setTimeout(() => resolve(), time);
});
}
+22 -13
View File
@@ -21,7 +21,7 @@ export default new Vuex.Store({
axes: Array,
odriveServerAddress: String,
serverConnected: Boolean,
ODriveConnected: false,
ODrivesConnected: Object,
serverOutput: [],
dashboards: [
{
@@ -95,6 +95,13 @@ export default new Vuex.Store({
state.odriveConfigs['writeAble'] = payload.writeAble;
state.odriveConfigs['writeAbleNumeric'] = payload.writeAbleNumeric;
},
setODrivesStatus(state, obj) {
// obj is {"odriveX": true/false}
for (const odrive of Object.keys(obj)){
state.ODrivesConnected[odrive] = obj[odrive];
console.log(state.ODrivesConnected);
}
},
setAxes(state, axes) {
state.axes = axes;
},
@@ -163,9 +170,6 @@ export default new Vuex.Store({
setServerStatus(state, val) {
state.serverConnected = val;
},
setODriveConnected(state, val) {
state.ODriveConnected = val;
},
removeCtrlFromDash(state, obj) {
// obj is {dash: dashID, path: control path}
for (const dash of state.dashboards) {
@@ -325,7 +329,6 @@ export default new Vuex.Store({
type: "odrive-found",
callback: () => {
console.log("odrive-found recieved from server");
context.commit("setODriveConnected", true);
context.dispatch("getOdrives");
}
})
@@ -370,15 +373,21 @@ export default new Vuex.Store({
});
socketio.addEventListener({
type: "odrive-disconnected",
callback: () => {
console.log("odrive disconnected");
context.commit("setODriveConnected", false);
console.log("restarting server...");
window.ipcRenderer.send('kill-server');
window.ipcRenderer.send('start-server');
context.dispatch('setServerAddress', context.state.odriveServerAddress);
callback: (odrive_name) => {
console.log(odrive_name + " disconnected");
//console.log("restarting server...");
//window.ipcRenderer.send('kill-server');
//window.ipcRenderer.send('start-server');
//context.dispatch('setServerAddress', context.state.odriveServerAddress);
}
})
});
socketio.addEventListener({
type: "odrives-status",
callback: (odrives_status) => {
console.log("From odrives-status msg " + odrives_status);
context.commit('setODrivesStatus', JSON.parse(odrives_status));
}
});
}
}
})
+88 -154
View File
@@ -55,7 +55,15 @@ import configTemplate from "../assets/wizard/configTemplate.json";
import wizardPage from "../components/wizard/wizardPage.vue";
import odriveEnums from "../assets/odriveEnums.json";
import { pages } from "../assets/wizard/wizard.js";
import { getVal, putVal, fetchParam } from "../lib/odrive_utils.js";
import {
getVal,
putVal,
fetchParam,
clearErrors,
motorCalibration,
encoderCalibration
} from "../lib/odrive_utils.js";
import {wait} from "../lib/utils.js"
export default {
name: "Wizard",
@@ -70,6 +78,7 @@ export default {
choiceMade: false,
calibrating: false, // for indicating that calibration is in progress
calStatus: undefined, // for indicating the status of a calibration attempt
odrive: "odrive0.",
};
},
computed: {
@@ -80,69 +89,31 @@ export default {
methods: {
// certain actions, like starting motor or encoder calibration,
// require special handling. This function is used for those events
pageEventHandler(e) {
async pageEventHandler(e) {
this.choiceMade = false;
let clear = () => {
let paths = [
"odrive0." + e.axis + ".error",
"odrive0." + e.axis + ".motor.error",
"odrive0." + e.axis + ".encoder.error",
"odrive0." + e.axis + ".controller.error",
];
for (const path of paths) {
putVal(path, 0);
}
console.log("clearing error for " + e.axis);
};
if (e.data == "motor calibration") {
// set a timeout to grab axis resistance and inductance values
fetchParam("odrive0." + e.axis + ".error");
let apply = () => {
if (getVal("odrive0." + e.axis + ".error") == 0) {
let configStub = undefined;
let inductance = getVal(
"odrive0." + e.axis + ".motor.config.phase_inductance"
);
let resistance = getVal(
"odrive0." + e.axis + ".motor.config.phase_resistance"
);
if (e.axis == "axis0") {
configStub = {
axis0: {
motor: {
config: {
phase_resistance: resistance,
phase_inductance: inductance,
},
},
},
};
} else if (e.axis == "axis1") {
configStub = {
axis1: {
motor: {
config: {
phase_resistance: resistance,
phase_inductance: inductance,
},
},
},
};
let configStub = {};
configStub[e.axis] = {
motor: {
config: {
phase_resistance: getVal(this.odrive + e.axis + ".motor.config.phase_resistance"),
phase_inductance: getVal(this.odrive + e.axis + ".motor.config.phase_inductance"),
}
}
this.choiceHandler({
choice: "Motor Calibration",
configStub: configStub,
hooks: [],
});
this.choiceMade = true;
}
this.choiceHandler({
choice: "Motor Calibration",
configStub: configStub,
hooks: [],
});
this.choiceMade = true;
};
let updateCalInfo = () => {
// parse error code, update calibration information
let motorError = getVal("odrive0." + e.axis + ".motor.error");
let motorError = getVal(this.odrive + e.axis + ".motor.error");
if (
motorError ==
odriveEnums.MOTOR_ERROR_PHASE_RESISTANCE_OUT_OF_RANGE ||
motorError == odriveEnums.MOTOR_ERROR_PHASE_RESISTANCE_OUT_OF_RANGE ||
motorError == odriveEnums.MOTOR_ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE
) {
//we got the expected calibration error
@@ -150,104 +121,66 @@ export default {
this.calStatus = false;
}
};
this.wait = function () {
fetchParam("odrive0." + e.axis + ".current_state");
fetchParam("odrive0." + e.axis + ".motor.config.phase_resistance");
fetchParam("odrive0." + e.axis + ".motor.config.phase_inductance");
fetchParam("odrive0." + e.axis + ".motor.is_calibrated");
console.log();
if (
getVal("odrive0." + e.axis + ".current_state") ==
odriveEnums.AXIS_STATE_MOTOR_CALIBRATION
) {
// still calibrating
console.log("waiting for motor cal to finish");
setTimeout(() => this.wait(), 100);
this.calibrating = true;
} else if (getVal("odrive0." + e.axis + ".error") != 0) {
console.log("motor cal error");
this.calibrating = false;
this.choiceMade = false;
// clear errors, display info.
updateCalInfo();
clear();
} else if (
getVal("odrive0." + e.axis + ".motor.is_calibrated") == true &&
getVal("odrive0." + e.axis + ".motor.config.phase_resistance") !=
0 &&
getVal("odrive0." + e.axis + ".motor.config.phase_inductance") != 0
) {
// calibration is over
apply();
this.calibrating = false;
this.calStatus = true;
} else {
setTimeout(() => this.wait(), 100);
console.log("waiting for motor cal to finish");
}
};
fetchParam("odrive0." + e.axis + ".current_state");
fetchParam("odrive0." + e.axis + ".motor.config.phase_resistance");
fetchParam("odrive0." + e.axis + ".motor.config.phase_inductance");
fetchParam("odrive0." + e.axis + ".motor.is_calibrated");
// wait for at least a second for comms to update state of ODrive
setTimeout(() => this.wait(), 1000);
// result is motor.error
this.calibrating = true;
let result = await motorCalibration(this.odrive, e.axis);
this.calibrating = false;
fetchParam(this.odrive + e.axis + '.motor.is_calibrated');
// no error, we're good!
if (result == 0) {
wait(250);
apply();
this.calStatus = true;
} else {
console.log("motor cal error" + result.data);
this.calibrating = false;
this.choiceMade = false;
// clear errors, display info.
updateCalInfo();
clearErrors(this.odrive, e.axis);
}
} else if (e.data == "encoder calibration") {
// get old CPR
// apply CPR from this.wizardConfig
// start calibration
// wait for cal to finish
// set odrive cpr back to oldVal
let oldCPR = getVal("odrive0." + e.axis + ".encoder.config.cpr");
let oldCPR = getVal(this.odrive + e.axis + ".encoder.config.cpr");
console.log("oldCPR = " + oldCPR);
console.log("axis = " + e.axis);
// set up the calib_scan_distance to a value that will work
let pp = this.wizardConfig[e.axis].motor.config.pole_pairs;
// smallest multiple of 4pi that is bigger than pole_pairs * 2pi
let scan_distance = pp % 0 ? pp * 2 * Math.PI : (pp + 1) * 2 * Math.PI;
putVal(this.odrive + e.axis + ".encoder.config.calib_scan_distance", scan_distance);
putVal(
"odrive0." + e.axis + ".encoder.config.cpr",
this.odrive + e.axis + ".encoder.config.cpr",
this.wizardConfig[e.axis].encoder.config.cpr
);
putVal(
"odrive0." + e.axis + ".requested_state",
odriveEnums.AXIS_STATE_ENCODER_OFFSET_CALIBRATION
);
this.wait = function () {
fetchParam("odrive0." + e.axis + ".current_state");
fetchParam("odrive0." + e.axis + ".encoder.error");
fetchParam("odrive0." + e.axis + ".encoder.is_ready");
if (
getVal("odrive0." + e.axis + ".current_state") ==
odriveEnums.AXIS_STATE_ENCODER_OFFSET_CALIBRATION
) {
console.log("waiting for encoder cal to finish...");
setTimeout(() => this.wait(), 100);
this.calibrating = true;
} else if (getVal("odrive0." + e.axis + ".encoder.error") != 0) {
putVal("odrive0." + e.axis + ".encoder.config.cpr", oldCPR);
console.log("applying old CPR, error detected");
clear();
this.calibrating = false;
this.calStatus = false;
} else if (
getVal("odrive0." + e.axis + ".encoder.is_ready") == true
) {
putVal("odrive0." + e.axis + ".encoder.config.cpr", oldCPR);
console.log("applying old CPR");
this.choiceMade = true;
this.currentStep.choiceMade = true;
this.calibrating = false;
this.calStatus = true;
} else {
console.log("waiting for encoder cal to finish...");
setTimeout(() => this.wait(), 100);
}
};
fetchParam("odrive0." + e.axis + ".current_state");
fetchParam("odrive0." + e.axis + ".encoder.error");
fetchParam("odrive0." + e.axis + ".encoder.is_ready");
setTimeout(() => this.wait(), 1000);
await wait(250);
this.calibrating = true;
let result = await encoderCalibration(this.odrive, e.axis);
this.calibrating = false;
putVal(this.odrive + e.axis + ".encoder.config.cpr", oldCPR);
fetchParam(this.odrive + e.axis + '.encoder.is_ready');
if (result == 0){
// no encoder error, we're good
this.choiceMade = true;
this.currentStep.choiceMade = true;
this.calStatus = true;
}
else {
clearErrors(this.odrive, e.axis);
this.calStatus = false;
}
}
},
choiceHandler(e) {
async choiceHandler(e) {
// apply static configStub
this.updateConfig(this.wizardConfig, e.configStub);
@@ -259,7 +192,6 @@ export default {
// ugly, but a special case.
// for motors, wait for calibration to finish before giving the green light unless motor.is_calibrated == true
// for encoders, wait for calibration to finish unless encoder.is_ready == true
this.choiceMade = true;
if (
this.currentStep == pages.Motor_0 ||
this.currentStep == pages.Motor_1
@@ -267,20 +199,33 @@ export default {
let axis;
if (this.currentStep == pages.Motor_0) axis = "axis0";
if (this.currentStep == pages.Motor_1) axis = "axis1";
if (getVal("odrive0." + axis + ".motor.is_calibrated") == false) {
fetchParam(this.odrive + axis + ".motor.is_calibrated");
await wait(100);
if (getVal(this.odrive + axis + ".motor.is_calibrated") == false) {
this.choiceMade = false;
}
else {
this.choiceMade = true;
}
}
if (
else if (
this.currentStep == pages.Encoder_0 ||
this.currentStep == pages.Encoder_1
) {
let axis;
if (this.currentStep == pages.Encoder_0) axis = "axis0";
if (this.currentStep == pages.Encoder_1) axis = "axis1";
if (getVal("odrive0." + axis + ".encoder.is_ready") == false) {
fetchParam(this.odrive + axis + ".encoder.is_ready");
await wait(100);
if (getVal(this.odrive + axis + ".encoder.is_ready") == false) {
this.choiceMade = false;
}
else {
this.choiceMade = true;
}
}
else {
this.choiceMade = true;
}
this.currentStep.choiceMade = this.choiceMade;
console.log(JSON.parse(JSON.stringify(this.wizardConfig)));
@@ -335,17 +280,6 @@ export default {
this.choiceMade = false;
},
},
created() {
// when wizard is active, we want to poll for certain values
let update = () => {
fetchParam("odrive0.axis0.motor.is_calibrated");
fetchParam("odrive0.axis1.motor.is_calibrated");
fetchParam("odrive0.axis0.encoder.is_ready");
fetchParam("odrive0.axis1.encoder.is_ready");
setTimeout(() => update(), 1000);
};
update();
},
beforeDestroy() {
for (const page of Object.keys(pages)) {
pages[page].choiceMade = false;
+1 -1
View File
@@ -69,7 +69,7 @@ Connect the encoder(s) to J4. The A,B phases are required, and the Z (index puls
Always think safety before powering up the ODrive if motors are attached. Consider what might happen if the motor spins as soon as power is applied.
</div>
* Unlike some devices, the ODrive does not recieve power over the USB port so the 24/56 volt power input is required even just to communicate with it using USB. It is ok to power up the ODrive before or after connecting the USB cable.
* To power up the ODrive, connect the power source to the DC terminals. Make sure to pay attention to the polarity. A small spark is normal. This is caused by the capacitors charging up.
* To power up the ODrive, connect the power source to the DC terminals. Make sure to pay attention to the polarity. Try to connect the power source first and then turn it on to avoid inrush current. If this can't be avoided then a small spark is normal. This is caused by the capacitors charging up.
## Downloading and Installing Tools
Most instructions in this guide refer to a utility called `odrivetool`, so you should install that first.
+3 -3
View File
@@ -4,11 +4,11 @@
Thermistors are elements that change their resistance based on the temperature. They can be used to electrically measure temperature. The ODrive itself has thermistors on board near the FETs to ensure that they don't burn themselves out. In addition to this it's possible to connect your own thermistor to measure the temperature of the connected motors. There are two types of thermistors, Negative Temperature Coefficient (NTC) and Positive Temperature Coefficient (PTC). This indicates whether the resistance goes up or down when the temperature goes up or down. The ODrive only supports the NTC type thermistor.
## FET thermistor
The temperature of the onboard FET thermistors can be read out by using the `odrivetool` under `<axis>.fet_thermistor.temp`. The odrive will automatically start current limiting the motor when the `<axis>.fet_thermistor.config.temp_limit_lower` threshold is exceeded and once `<axis>.fet_thermistor.config.temp_limit_upper` is exceeded the ODrive will stop controlling the motor and set an error. The lower and upper threshold can be changed, but this is not recommended.
The temperature of the onboard FET thermistors can be read out by using the `odrivetool` under `<axis>.motor.fet_thermistor.temperature`. The odrive will automatically start current limiting the motor when the `<axis>.motor.fet_thermistor.config.temp_limit_lower` threshold is exceeded and once `<axis>.motor.fet_thermistor.config.temp_limit_upper` is exceeded the ODrive will stop controlling the motor and set an error. The lower and upper threshold can be changed, but this is not recommended.
## Connecting motor thermistors
To use your own thermistors with the ODrive a few things have to be clarified first. The use of your own thermistor requires one analog input pin. Under `<axis>.motor_thermistor.config` the configuration of your own thermistor is available with the following fields:
To use your own thermistors with the ODrive a few things have to be clarified first. The use of your own thermistor requires one analog input pin. Under `<axis>.motor.motor_thermistor.config` the configuration of your own thermistor is available with the following fields:
* `gpio_pin`: The GPIO input in used for this thermistor.
* `poly_coefficient_0` to `poly_coefficient_3`: Coefficient that needs to be set for your specific setup more on that in [Thermistor coefficients](#thermistor-coefficients).
@@ -25,7 +25,7 @@ The way this works is that the thermistor is connected in series with a known re
To use a thermistor with the ODrive a voltage divider circuit has to be made that uses `VCCA` as the power source with `GNDA` as the ground. The voltage divider output can be connected to a GPIO pin that supports analog input.
## Thermistor coefficients
Every thermistor and voltage divider circuit is different and thus it's necessary to let the ODrive know how to relate a voltage it measures at the GPIO pin to a temperature. The `poly_coefficient_0` to `poly_coefficient_3` under `<axis>.motor_thermistor.config` are used for this. The `odrivetool` has a convenience function `set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, Tmax)` which can be used to calculate and set these coefficients.
Every thermistor and voltage divider circuit is different and thus it's necessary to let the ODrive know how to relate a voltage it measures at the GPIO pin to a temperature. The `poly_coefficient_0` to `poly_coefficient_3` under `<axis>.motor.motor_thermistor.config` are used for this. The `odrivetool` has a convenience function `set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, Tmax)` which can be used to calculate and set these coefficients.
* `axis`: Which axis do set the motor thermistor coefficients for (`odrv0.axis0` or `odrv0.axis1`).
* `Rload`: The Ohm value of the resistor used in the voltage divider circuit.
+15 -1
View File
@@ -2,6 +2,7 @@ import usb.util
import time
import fractions
import array
import time
from odrive.dfuse.DfuState import DfuState
DFU_REQUEST_SEND = 0x21
@@ -64,7 +65,20 @@ class DfuDevice:
self.control_msg(DFU_REQUEST_SEND, DFU_CLRSTATUS, 0, None)
def get_state(self):
return self.control_msg(DFU_REQUEST_RECEIVE, DFU_GETSTATE, 0, 1)[0]
msg = self.control_msg(DFU_REQUEST_RECEIVE, DFU_GETSTATE, 0, 1)
# Second chance after giving the device some time to breathe.
if len(msg) == 0:
time.sleep(0.5)
msg = self.control_msg(DFU_REQUEST_RECEIVE, DFU_GETSTATE, 0, 1)
if len(msg) == 0:
raise Exception("Could not get device state. Firmware upgrade will abort. "
"Please try again. If odrivetool can't find the device "
"anymore after this, follow the instructions in "
"https://docs.odriverobotics.com/odrivetool#device-firmware-update "
"(\"How to force DFU mode\").")
return msg[0]
def abort(self):
self.control_msg(DFU_REQUEST_RECEIVE, DFU_ABORT, 0, 0)
+2 -4
View File
@@ -37,10 +37,6 @@ AXIS_STATE_LOCKIN_SPIN = 9
AXIS_STATE_ENCODER_DIR_FIND = 10
AXIS_STATE_HOMING = 11
# ODrive.ThermistorCurrentLimiter.Error
THERMISTOR_CURRENT_LIMITER_ERROR_NONE = 0x00000000
THERMISTOR_CURRENT_LIMITER_ERROR_OVER_TEMP = 0x00000001
# ODrive.Encoder.Mode
ENCODER_MODE_INCREMENTAL = 0
ENCODER_MODE_HALL = 1
@@ -119,6 +115,8 @@ MOTOR_ERROR_BRAKE_DUTY_CYCLE_NAN = 0x00002000
MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x00004000
MOTOR_ERROR_DC_BUS_OVER_CURRENT = 0x00008000
MOTOR_ERROR_MODULATION_IS_NAN = 0x00010000
MOTOR_ERROR_MOTOR_THERMISTOR_OVER_TEMP = 0x00020000
MOTOR_ERROR_FET_THERMISTOR_OVER_TEMP = 0x00040000
# ODrive.Motor.ArmedState
ARMED_STATE_DISARMED = 0
+4 -6
View File
@@ -64,10 +64,10 @@ class OperationAbortedException(Exception):
def set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, TMax):
coeffs = calculate_thermistor_coeffs(3, Rload, R_25, Beta, Tmin, TMax)
axis.motor_thermistor.config.poly_coefficient_0 = float(coeffs[3])
axis.motor_thermistor.config.poly_coefficient_1 = float(coeffs[2])
axis.motor_thermistor.config.poly_coefficient_2 = float(coeffs[1])
axis.motor_thermistor.config.poly_coefficient_3 = float(coeffs[0])
axis.motor.motor_thermistor.config.poly_coefficient_0 = float(coeffs[3])
axis.motor.motor_thermistor.config.poly_coefficient_1 = float(coeffs[2])
axis.motor.motor_thermistor.config.poly_coefficient_2 = float(coeffs[1])
axis.motor.motor_thermistor.config.poly_coefficient_3 = float(coeffs[0])
def dump_errors(odrv, clear=False):
axes = [(name, axis) for name, axis in odrv._remote_attributes.items() if 'axis' in name]
@@ -80,8 +80,6 @@ def dump_errors(odrv, clear=False):
module_decode_map = [
(name, odrv, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("AXIS_ERROR_")}),
('motor', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("MOTOR_ERROR_")}),
('fet_thermistor', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("THERMISTOR_CURRENT_LIMITER_ERROR")}),
('motor_thermistor', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("THERMISTOR_CURRENT_LIMITER_ERROR")}),
('encoder', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("ENCODER_ERROR_")}),
('controller', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("CONTROLLER_ERROR_")}),
]