mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-09-22 08:04:07 +08:00
Merge branch 'devel' of https://github.com/madcowswe/ODrive into gui
Merge devel into gui
This commit is contained in:
@@ -2,7 +2,7 @@ name: Build and publish HTML documentation website
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ feature/doc_autogen ]
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
jekyll:
|
||||
@@ -52,15 +52,22 @@ jobs:
|
||||
cd /srv/jekyll/docs && \
|
||||
bundle config path vendor/bundle && \
|
||||
bundle install && \
|
||||
JEKYLL_ENV=production bundle exec jekyll build
|
||||
bundle exec jekyll build --baseurl \"\"
|
||||
cd ..
|
||||
mv docs/_site _site
|
||||
rm -rdf docs
|
||||
mv _site docs
|
||||
touch docs/.nojekyll
|
||||
"
|
||||
touch .nojekyll
|
||||
# Extra checks to reduce likelihood of defect build
|
||||
test -f docs/CNAME
|
||||
test -f docs/index.html
|
||||
|
||||
- name: Push to documentation branch
|
||||
run: |
|
||||
git config user.name "${GITHUB_ACTOR}"
|
||||
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
|
||||
git add -f docs/_site
|
||||
git add -f docs
|
||||
git commit -m "jekyll build from Action ${GITHUB_SHA}"
|
||||
git push --force origin HEAD:${REMOTE_BRANCH}
|
||||
env:
|
||||
|
||||
+2
-3
@@ -62,8 +62,7 @@ ODrive\.includes
|
||||
|
||||
Firmware/Tests/bin/
|
||||
|
||||
# Electron-builder output
|
||||
# GUI
|
||||
GUI/dist_electron
|
||||
|
||||
# Node Modules
|
||||
GUI/node_modules
|
||||
GUI/build
|
||||
|
||||
+10
-3
@@ -1,5 +1,7 @@
|
||||
# Unreleased Features
|
||||
Please add a note of your changes below this heading if you make a Pull Request.
|
||||
### Added
|
||||
* [Mechanical brake support](docs/mechanical-brakes.md)
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -15,8 +17,8 @@ Please add a note of your changes below this heading if you make a Pull Request.
|
||||
* `<axis>.motor.gate_driver` was moved to `<axis>.gate_driver`.
|
||||
* `<axis>.min_endstop.pullup` and `<axis>.max_endstop.pullup` were removed. Use `<odrv>.config.gpioX_mode = GPIO_MODE_DIGITAL / GPIO_MODE_DIGITAL_PULL_UP / GPIO_MODE_DIGITAL_PULL_DOWN` instead.
|
||||
|
||||
# Release Candidate
|
||||
## [0.5.1] - Date TBD
|
||||
# Releases
|
||||
## [0.5.1] - 2020-09-27
|
||||
### Added
|
||||
* Added motor `torque_constant`: units of torque are now [Nm] instead of just motor current.
|
||||
* [Motor thermistors support](docs/thermistors.md)
|
||||
@@ -29,9 +31,14 @@ Please add a note of your changes below this heading if you make a Pull Request.
|
||||
* `axis.motor.thermal_current_lim` has been removed. Instead a new property is available `axis.motor.effective_current_lim` which contains the effective current limit including any thermal limits.
|
||||
* `axis.motor.get_inverter_temp()`, `axis.motor.inverter_temp_limit_lower` and `axis.motor.inverter_temp_limit_upper` have been moved to seperate fet thermistor object under `axis.fet_thermistor`. `get_inverter_temp()` function has been renamed to `temp` and is now a read-only property.
|
||||
* `axis.config.counts_per_step` is now `axis.config.turns_per_step`
|
||||
* Outputs of `axis.sensorless_estimator` are now in turns/s instead of electrical rad/s
|
||||
* Fixed bug of high current during lockin-ramp caused by `motor::update()` expecting a torque command instead of current
|
||||
* Fixed bug where commanded velocity was extremely high just after sensorless ramp when using `input_mode` INPUT_MODE_VEL_RAMP caused by `vel_setpoint` and `axis.config.sensorless_ramp.vel` being in different units
|
||||
|
||||
### Fixed
|
||||
* Fixed bug of high current during lockin-ramp caused by `motor::update()` expecting a torque command instead of current
|
||||
* Fixed bug where commanded velocity was extremely high just after sensorless ramp when using `input_mode` INPUT_MODE_VEL_RAMP caused by `vel_setpoint` and `axis.config.sensorless_ramp.vel` being in different units
|
||||
|
||||
# Releases
|
||||
## [0.5.0] - 2020-08-03
|
||||
### Added
|
||||
* AC Induction Motor support.
|
||||
|
||||
+13
-7
@@ -7,16 +7,22 @@ RUN add-apt-repository ppa:team-gcc-arm-embedded/ppa
|
||||
RUN add-apt-repository ppa:jonathonf/tup
|
||||
RUN apt-get update
|
||||
RUN apt-get -y upgrade
|
||||
RUN apt-get -y install gcc-arm-embedded openocd tup python3.7 build-essential git
|
||||
RUN apt-get -y install gcc-arm-embedded openocd tup python3.7 python3-yaml python3-jinja2 python3-jsonschema build-essential git
|
||||
|
||||
# Build step below does not know about debian's python naming schemme
|
||||
RUN ln -s /usr/bin/python3.7 /usr/bin/python
|
||||
|
||||
# Copy the firmware tree into the container
|
||||
RUN mkdir ODrive
|
||||
COPY . ODrive
|
||||
RUN mkdir -p ODrive
|
||||
WORKDIR ODrive/Firmware
|
||||
|
||||
# Hack around Tup's dependency on FUSE
|
||||
RUN tup generate build.sh
|
||||
RUN ./build.sh
|
||||
# Must attach the firmware tree into the container
|
||||
CMD \
|
||||
# Regenerate python interface
|
||||
python interface_generator_stub.py \
|
||||
--definitions odrive-interface.yaml \
|
||||
--template ../tools/enums_template.j2 \
|
||||
--output ../tools/odrive/enums.py && \
|
||||
# Hack around Tup's dependency on FUSE
|
||||
tup init && \
|
||||
tup generate build.sh && \
|
||||
./build.sh
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "stm32f4xx.h"
|
||||
#include "stm32f4xx_it.h"
|
||||
#include "cmsis_os.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
/* USER CODE BEGIN 0 */
|
||||
#include <Drivers/STM32/stm32_system.h>
|
||||
@@ -85,6 +86,13 @@ void get_regs(void** stack_ptr) {
|
||||
void* volatile pc __attribute__((unused)) = stack_ptr[6]; // Program counter
|
||||
void* volatile psr __attribute__((unused)) = stack_ptr[7]; // Program status register
|
||||
|
||||
void* volatile cfsr __attribute__((unused)) = (void*)SCB->CFSR; // Configurable fault status register
|
||||
void* volatile cpacr __attribute__((unused)) = (void*)SCB->CPACR;
|
||||
void* volatile fpccr __attribute__((unused)) = (void*)FPU->FPCCR;
|
||||
|
||||
volatile bool preciserr __attribute__((unused)) = (uint32_t)cfsr & 0x200;
|
||||
volatile bool ibuserr __attribute__((unused)) = (uint32_t)cfsr & 0x100;
|
||||
|
||||
volatile int stay_looping = 1;
|
||||
while(stay_looping);
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ Encoder encoders[AXIS_COUNT] = {
|
||||
|
||||
// TODO: this has no hardware dependency and should be allocated depending on config
|
||||
Endstop endstops[2 * AXIS_COUNT];
|
||||
MechanicalBrake mechanical_brakes[AXIS_COUNT];
|
||||
|
||||
SensorlessEstimator sensorless_estimators[AXIS_COUNT];
|
||||
Controller controllers[AXIS_COUNT];
|
||||
@@ -116,6 +117,7 @@ std::array<Axis, AXIS_COUNT> axes{{
|
||||
motors[0], // motor
|
||||
trap[0], // trap
|
||||
endstops[0], endstops[1], // min_endstop, max_endstop
|
||||
mechanical_brakes[0], // mechanical brake
|
||||
},
|
||||
{
|
||||
1, // axis_num
|
||||
@@ -135,6 +137,7 @@ std::array<Axis, AXIS_COUNT> axes{{
|
||||
motors[1], // motor
|
||||
trap[1], // trap
|
||||
endstops[2], endstops[3], // min_endstop, max_endstop
|
||||
mechanical_brakes[1], // mechanical brake
|
||||
},
|
||||
}};
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ Axis::Axis(int axis_num,
|
||||
Motor& motor,
|
||||
TrapezoidalTrajectory& trap,
|
||||
Endstop& min_endstop,
|
||||
Endstop& max_endstop)
|
||||
Endstop& max_endstop,
|
||||
MechanicalBrake& mechanical_brake)
|
||||
: axis_num_(axis_num),
|
||||
default_step_gpio_pin_(default_step_gpio_pin),
|
||||
default_dir_gpio_pin_(default_dir_gpio_pin),
|
||||
@@ -33,6 +34,7 @@ Axis::Axis(int axis_num,
|
||||
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))),
|
||||
@@ -49,6 +51,7 @@ Axis::Axis(int axis_num,
|
||||
trap_traj_.axis_ = this;
|
||||
min_endstop_.axis_ = this;
|
||||
max_endstop_.axis_ = this;
|
||||
mechanical_brake_.axis_ = this;
|
||||
}
|
||||
|
||||
Axis::LockinConfig_t Axis::default_calibration() {
|
||||
@@ -235,9 +238,9 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) {
|
||||
float x = 0.0f;
|
||||
run_control_loop([&]() {
|
||||
float phase = wrap_pm_pi(lockin_config.ramp_distance * x);
|
||||
float I_mag = lockin_config.current * x;
|
||||
float torque = lockin_config.current * motor_.config_.torque_constant * x;
|
||||
x += current_meas_period / lockin_config.ramp_time;
|
||||
if (!motor_.update(I_mag, phase, 0.0f))
|
||||
if (!motor_.update(torque, phase, 0.0f))
|
||||
return false;
|
||||
return x < 1.0f;
|
||||
});
|
||||
@@ -266,7 +269,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) {
|
||||
distance += vel * current_meas_period;
|
||||
phase = wrap_pm_pi(phase + vel * current_meas_period);
|
||||
|
||||
if (!motor_.update(lockin_config.current, phase, vel))
|
||||
if (!motor_.update(lockin_config.current * motor_.config_.torque_constant, phase, vel))
|
||||
return false;
|
||||
return !spin_done(true); //vel_override to go to next phase
|
||||
});
|
||||
@@ -282,7 +285,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) {
|
||||
distance += vel * current_meas_period;
|
||||
phase = wrap_pm_pi(phase + vel * current_meas_period);
|
||||
|
||||
if (!motor_.update(lockin_config.current, phase, vel))
|
||||
if (!motor_.update(lockin_config.current * motor_.config_.torque_constant, phase, vel))
|
||||
return false;
|
||||
return !spin_done();
|
||||
});
|
||||
@@ -460,6 +463,7 @@ bool Axis::run_idle_loop() {
|
||||
// run_control_loop ignores missed modulation timing updates
|
||||
// if and only if we're in AXIS_STATE_IDLE
|
||||
safety_critical_disarm_motor_pwm(motor_);
|
||||
mechanical_brake_.engage();
|
||||
set_step_dir_active(config_.enable_step_dir && config_.step_dir_always_on);
|
||||
run_control_loop([this]() {
|
||||
return true;
|
||||
@@ -472,6 +476,7 @@ void Axis::run_state_machine_loop() {
|
||||
|
||||
// arm!
|
||||
motor_.arm();
|
||||
mechanical_brake_.release();
|
||||
|
||||
for (;;) {
|
||||
// Load the task chain if a specific request is pending
|
||||
@@ -556,7 +561,7 @@ void Axis::run_state_machine_loop() {
|
||||
if (status) {
|
||||
// call to controller.reset() that happend when arming means that vel_setpoint
|
||||
// is zeroed. So we make the setpoint the spinup target for smooth transition.
|
||||
controller_.vel_setpoint_ = config_.sensorless_ramp.vel;
|
||||
controller_.vel_setpoint_ = config_.sensorless_ramp.vel / (2.0f * M_PI * motor_.config_.pole_pairs);
|
||||
status = run_sensorless_control_loop();
|
||||
}
|
||||
} break;
|
||||
@@ -573,6 +578,7 @@ void Axis::run_state_machine_loop() {
|
||||
case AXIS_STATE_IDLE: {
|
||||
run_idle_loop();
|
||||
status = motor_.arm(); // done with idling - try to arm the motor
|
||||
mechanical_brake_.release();
|
||||
} break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -8,6 +8,7 @@ class Axis;
|
||||
#include "controller.hpp"
|
||||
#include "trapTraj.hpp"
|
||||
#include "endstop.hpp"
|
||||
#include "mechanical_brake.hpp"
|
||||
#include "low_level.h"
|
||||
#include "utils.hpp"
|
||||
#include "communication/interface_uart.h" // TODO: remove once uart_poll() is gone
|
||||
@@ -90,7 +91,8 @@ public:
|
||||
Motor& motor,
|
||||
TrapezoidalTrajectory& trap,
|
||||
Endstop& min_endstop,
|
||||
Endstop& max_endstop);
|
||||
Endstop& max_endstop,
|
||||
MechanicalBrake& mechanical_brake);
|
||||
|
||||
bool apply_config();
|
||||
void clear_config();
|
||||
@@ -220,6 +222,7 @@ public:
|
||||
TrapezoidalTrajectory& trap_traj_;
|
||||
Endstop& min_endstop_;
|
||||
Endstop& max_endstop_;
|
||||
MechanicalBrake& mechanical_brake_;
|
||||
|
||||
// List of current_limiters and thermistors to
|
||||
// provide easy iteration.
|
||||
|
||||
@@ -380,7 +380,7 @@ void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) {
|
||||
}
|
||||
|
||||
// This is the callback from the ADC that we expect after the PWM has triggered an ADC conversion.
|
||||
// TODO: Document how the phasing is done, link to timing diagram
|
||||
// Timing diagram: Firmware/timing_diagram_v3.png
|
||||
void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) {
|
||||
#define calib_tau 0.2f //@TOTO make more easily configurable
|
||||
constexpr float calib_filter_k = CURRENT_MEAS_PERIOD / calib_tau;
|
||||
|
||||
@@ -48,6 +48,7 @@ static bool config_read_all() {
|
||||
config_manager.read(&axes[i].trap_traj_.config_) &&
|
||||
config_manager.read(&axes[i].min_endstop_.config_) &&
|
||||
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_) &&
|
||||
@@ -67,6 +68,7 @@ static bool config_write_all() {
|
||||
config_manager.write(&axes[i].trap_traj_.config_) &&
|
||||
config_manager.write(&axes[i].min_endstop_.config_) &&
|
||||
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_) &&
|
||||
@@ -86,6 +88,7 @@ static void config_clear_all() {
|
||||
axes[i].trap_traj_.config_ = {};
|
||||
axes[i].min_endstop_.config_ = {};
|
||||
axes[i].max_endstop_.config_ = {};
|
||||
axes[i].mechanical_brake_.config_ = {};
|
||||
motors[i].config_ = {};
|
||||
fet_thermistors[i].config_ = {};
|
||||
axes[i].motor_thermistor_.config_ = {};
|
||||
@@ -152,7 +155,7 @@ void ODrive::enter_dfu_mode() {
|
||||
|
||||
static void usb_deferred_interrupt_thread(void * ctx) {
|
||||
(void) ctx; // unused parameter
|
||||
|
||||
|
||||
for (;;) {
|
||||
// Wait for signalling from USB interrupt (OTG_FS_IRQHandler)
|
||||
osStatus semaphore_status = osSemaphoreWait(sem_usb_irq, osWaitForever);
|
||||
@@ -419,6 +422,7 @@ extern "C" int main(void) {
|
||||
if (mode == ODriveIntf::GPIO_MODE_DIGITAL ||
|
||||
mode == ODriveIntf::GPIO_MODE_DIGITAL_PULL_UP ||
|
||||
mode == ODriveIntf::GPIO_MODE_DIGITAL_PULL_DOWN ||
|
||||
mode == ODriveIntf::GPIO_MODE_MECH_BRAKE ||
|
||||
mode == ODriveIntf::GPIO_MODE_ANALOG_IN) {
|
||||
GPIO_InitStruct.Alternate = 0;
|
||||
} else {
|
||||
@@ -515,6 +519,11 @@ extern "C" int main(void) {
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
||||
} break;
|
||||
case ODriveIntf::GPIO_MODE_MECH_BRAKE: {
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
||||
} break;
|
||||
default: {
|
||||
odrv.misconfigured_ = true;
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <odrive_main.h>
|
||||
|
||||
void MechanicalBrake::engage() {
|
||||
if (odrv.config_.gpio_modes[config_.gpio_num] == ODriveIntf::GPIO_MODE_MECH_BRAKE){
|
||||
get_gpio(config_.gpio_num).write(config_.is_active_low ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
void MechanicalBrake::release() {
|
||||
if (odrv.config_.gpio_modes[config_.gpio_num] == ODriveIntf::GPIO_MODE_MECH_BRAKE){
|
||||
get_gpio(config_.gpio_num).write(config_.is_active_low ? 1 : 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef __MECHANICAL_BRAKE_HPP
|
||||
#define __MECHANICAL_BRAKE_HPP
|
||||
|
||||
#include <autogen/interfaces.hpp>
|
||||
|
||||
class MechanicalBrake : public ODriveIntf::MechanicalBrakeIntf {
|
||||
public:
|
||||
struct Config_t {
|
||||
uint16_t gpio_num = 0;
|
||||
bool is_active_low = true;
|
||||
|
||||
// custom setters
|
||||
MechanicalBrake* parent = nullptr;
|
||||
void set_gpio_num(uint16_t value) { gpio_num = value; }
|
||||
};
|
||||
|
||||
MechanicalBrake() {}
|
||||
|
||||
MechanicalBrake::Config_t config_;
|
||||
Axis* axis_ = nullptr;
|
||||
|
||||
void release();
|
||||
void engage();
|
||||
};
|
||||
#endif // __MECHANICAL_BRAKE_HPP
|
||||
@@ -49,6 +49,7 @@ void Motor::reset_current_control() {
|
||||
current_control_.v_current_control_integral_d = 0.0f;
|
||||
current_control_.v_current_control_integral_q = 0.0f;
|
||||
current_control_.acim_rotor_flux = 0.0f;
|
||||
current_control_.Ibus = 0.0f;
|
||||
}
|
||||
|
||||
// @brief Tune the current controller based on phase resistance and inductance
|
||||
|
||||
@@ -142,6 +142,7 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast<ENUMTYPE>(~static_c
|
||||
#include <thermistor.hpp>
|
||||
#include <trapTraj.hpp>
|
||||
#include <endstop.hpp>
|
||||
#include <mechanical_brake.hpp>
|
||||
#include <axis.hpp>
|
||||
#include <communication/communication.h>
|
||||
|
||||
|
||||
@@ -66,13 +66,15 @@ bool SensorlessEstimator::update() {
|
||||
}
|
||||
|
||||
// predict PLL phase with velocity
|
||||
pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * vel_estimate_);
|
||||
pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * vel_estimate_erad_);
|
||||
// update PLL phase with observer permanent magnet phase
|
||||
phase_ = fast_atan2(eta[1], eta[0]);
|
||||
float delta_phase = wrap_pm_pi(phase_ - pll_pos_);
|
||||
pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * pll_kp * delta_phase);
|
||||
// update PLL velocity
|
||||
vel_estimate_ += current_meas_period * pll_ki * delta_phase;
|
||||
vel_estimate_erad_ += current_meas_period * pll_ki * delta_phase;
|
||||
// convert to mechanical turns/s for controller usage.
|
||||
vel_estimate_ = vel_estimate_erad_ / (std::max((float)axis_->motor_.config_.pole_pairs, 1.0f) * 2.0f * M_PI);
|
||||
|
||||
vel_estimate_valid_ = true;
|
||||
return true;
|
||||
|
||||
@@ -18,7 +18,8 @@ public:
|
||||
Error error_ = ERROR_NONE;
|
||||
float phase_ = 0.0f; // [rad]
|
||||
float pll_pos_ = 0.0f; // [rad]
|
||||
float vel_estimate_ = 0.0f; // [rad/s]
|
||||
float vel_estimate_ = 0.0f; // [turn/s]
|
||||
float vel_estimate_erad_ = 0.0f; // [rad/s]
|
||||
bool vel_estimate_valid_ = false;
|
||||
// float pll_kp_ = 0.0f; // [rad/s / rad]
|
||||
// float pll_ki_ = 0.0f; // [(rad/s^2) / rad]
|
||||
|
||||
@@ -133,6 +133,7 @@ FLAGS += '-DUSE_HAL_DRIVER'
|
||||
FLAGS += '-mthumb'
|
||||
FLAGS += '-mfloat-abi=hard'
|
||||
FLAGS += { '-Wall', '-Wdouble-promotion', '-Wfloat-conversion', '-fdata-sections', '-ffunction-sections'}
|
||||
FLAGS += '-g'
|
||||
|
||||
-- linker flags
|
||||
LDFLAGS += board.ldflags
|
||||
@@ -142,7 +143,7 @@ LDFLAGS += '-Wl,--undefined=uxTopUsedPriority'
|
||||
|
||||
-- debug build
|
||||
if tup.getconfig("DEBUG") == "true" then
|
||||
FLAGS += '-g -gdwarf-2'
|
||||
FLAGS += '-gdwarf-2'
|
||||
OPT += '-Og'
|
||||
else
|
||||
OPT += '-O2'
|
||||
@@ -189,6 +190,7 @@ sources = {
|
||||
'MotorControl/thermistor.cpp',
|
||||
'MotorControl/encoder.cpp',
|
||||
'MotorControl/endstop.cpp',
|
||||
'MotorControl/mechanical_brake.cpp',
|
||||
'MotorControl/controller.cpp',
|
||||
'MotorControl/sensorless_estimator.cpp',
|
||||
'MotorControl/trapTraj.cpp',
|
||||
|
||||
@@ -328,7 +328,7 @@ def regularize_valuetype(path, name, elem):
|
||||
elem['flags'][k]['value'] = 0 if current_bit is None else (1 << current_bit)
|
||||
bit = bit if current_bit is None else current_bit + 1
|
||||
if 'nullflag' in elem:
|
||||
elem['flags'] = OrderedDict([(elem['nullflag'], {'value': 0, 'bit': None}), *elem['flags'].items()])
|
||||
elem['flags'] = OrderedDict([(elem['nullflag'], OrderedDict({'name': elem['nullflag'], 'value': 0, 'bit': None})), *elem['flags'].items()])
|
||||
elem['values'] = elem['flags']
|
||||
elem['is_flags'] = True
|
||||
elem['is_enum'] = True
|
||||
@@ -653,6 +653,7 @@ env.filters['skip_first'] = lambda x: list(x)[1:]
|
||||
env.filters['to_c_string'] = lambda x: '\n'.join(('"' + line.replace('"', '\\"') + '"') for line in json.dumps(x, separators=(',', ':')).replace('{"name"', '\n{"name"').split('\n'))
|
||||
env.filters['tokenize'] = tokenize
|
||||
env.filters['diagonalize'] = lambda lst: [lst[:i + 1] for i in range(len(lst))]
|
||||
env.filters['debug'] = lambda x: print(x)
|
||||
|
||||
template = env.from_string(template_file.read())
|
||||
|
||||
|
||||
@@ -454,6 +454,7 @@ interfaces:
|
||||
trap_traj: TrapezoidalTrajectory
|
||||
min_endstop: Endstop
|
||||
max_endstop: Endstop
|
||||
mechanical_brake: MechanicalBrake
|
||||
functions:
|
||||
watchdog_feed:
|
||||
doc: Feed the watchdog to prevent watchdog timeouts.
|
||||
@@ -902,7 +903,6 @@ interfaces:
|
||||
accel_limit: float32
|
||||
decel_limit: float32
|
||||
|
||||
|
||||
ODrive.Endstop:
|
||||
c_is_class: True
|
||||
attributes:
|
||||
@@ -916,6 +916,21 @@ interfaces:
|
||||
is_active_high: bool
|
||||
debounce_ms: {type: uint32, c_setter: set_debounce_ms}
|
||||
|
||||
ODrive.MechanicalBrake:
|
||||
c_is_class: True
|
||||
attributes:
|
||||
config:
|
||||
c_is_class: False
|
||||
attributes:
|
||||
gpio_num: {type: uint16, c_setter: set_gpio_num}
|
||||
is_active_low: bool
|
||||
functions:
|
||||
engage:
|
||||
doc: |
|
||||
This function engages the mechanical brake if one is present and enabled.
|
||||
release:
|
||||
doc: |
|
||||
This function releases the mecahncal brake if one is present and enabled.
|
||||
|
||||
valuetypes:
|
||||
ODrive.GpioMode:
|
||||
@@ -944,6 +959,7 @@ valuetypes:
|
||||
Enc0: {doc: The pin is used by quadrature encoder 0.}
|
||||
Enc1: {doc: The pin is used by quadrature encoder 1.}
|
||||
Enc2: {doc: This mode is not supported on ODrive v3.x.}
|
||||
MechBrake: {doc: This is to support external mechanical brakes.}
|
||||
|
||||
ODrive.Can.Protocol:
|
||||
values: {Simple: }
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 217 KiB |
+4
-7
@@ -1,6 +1,6 @@
|
||||
function cleanup {
|
||||
echo "Removing previous build artifacts"
|
||||
rm -rf build
|
||||
rm -rf build/ Firmware/autogen Firmware/build Firmware/.tup
|
||||
docker rm odrive-build-cont
|
||||
}
|
||||
|
||||
@@ -13,14 +13,11 @@ function gc {
|
||||
function build {
|
||||
cleanup
|
||||
|
||||
echo "Building the firmware"
|
||||
echo "Building the build-environment image"
|
||||
docker build -t odrive-build-img .
|
||||
|
||||
echo "Create container"
|
||||
docker create --name odrive-build-cont odrive-build-img:latest
|
||||
|
||||
echo "Extract build artifacts"
|
||||
docker cp odrive-build-cont:ODrive/Firmware/build .
|
||||
echo "Build in container"
|
||||
docker run -v $(pwd):/ODrive --name odrive-build-cont odrive-build-img:latest
|
||||
}
|
||||
|
||||
function usage {
|
||||
|
||||
@@ -17,6 +17,8 @@ sections:
|
||||
url: /encoders
|
||||
- title: Homing & Endstops
|
||||
url: /endstops
|
||||
- title: Mechanical Brakes
|
||||
url: /mechanical-brakes
|
||||
- title: Thermistors
|
||||
url: /thermistors
|
||||
- title: Control & Tuning
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div style="position: absolute;top: 0;left: 0;width: 100%;background-color: #5bc0de;height: 20px;text-align: center;font-weight: 600">The docs reflect firmware version 0.5.1. There are many breaking <a href="https://github.com/madcowswe/ODrive/blob/master/CHANGELOG.md">changes</a>. Please find docs for v0.4.12 <a href="https://github.com/madcowswe/ODrive/blob/fw-v0.4.12/docs/getting-started.md">here</a>.</div>
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<div>
|
||||
@@ -78,7 +79,7 @@
|
||||
<path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"></path>
|
||||
</svg>
|
||||
{% if page.edit_url %}
|
||||
{% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/" | append: edit_url %}
|
||||
{% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/" | append: page.edit_url %}
|
||||
{% else %}
|
||||
{% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/docs/" | append: pagename | append: ".md" %}
|
||||
{% endif %}
|
||||
@@ -90,7 +91,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="10" height="10" style="fill: #656565;">
|
||||
<path d="M216 0h80c13.3 0 24 10.7 24 24v168h87.7c17.8 0 26.7 21.5 14.1 34.1L269.7 378.3c-7.5 7.5-19.8 7.5-27.3 0L90.1 226.1c-12.6-12.6-3.7-34.1 14.1-34.1H192V24c0-13.3 10.7-24 24-24zm296 376v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V376c0-13.3 10.7-24 24-24h146.7l49 49c20.1 20.1 52.5 20.1 72.6 0l49-49H488c13.3 0 24 10.7 24 24zm-124 88c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20zm64 0c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20z"/>
|
||||
</svg>
|
||||
<a href="https://www.github.com/madcowswe/ODrive/edit/master/{{page.download.url}}">{{page.download.text}}</a>
|
||||
<a href="https://www.github.com/madcowswe/ODrive/blob/master/{{page.download.url}}">{{page.download.text}}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -73,7 +73,7 @@ Configuration of the CAN parameters should be done via USB before putting the de
|
||||
|
||||
To set the desired baud rate, use `<odrv>.can.set_baud_rate(<value>)`. The baud rate can be done without rebooting the device. If you'd like to keep the baud rate, simply call `<odrv>.save_configuration()` before rebooting.
|
||||
|
||||
Each axis looks like a separate node on the bus. Thus, they both have the two properties `can_node_id` and `can_node_id_extended`. The node ID can be from 0 to 63 (0x3F) inclusive, or, if extended CAN IDs are used, from 0 to 16777215 (0xFFFFFF).
|
||||
Each axis looks like a separate node on the bus. Thus, they both have the two properties `can_node_id` and `can_node_id_extended`. The node ID can be from 0 to 63 (0x3F) inclusive, or, if extended CAN IDs are used, from 0 to 16777215 (0xFFFFFF). If you want to connect more than one ODrive on a CAN bus, you must set different node IDs for the second ODrive or they will conflict and crash the bus.
|
||||
|
||||
### Example Configuration
|
||||
|
||||
|
||||
+5
-4
@@ -44,6 +44,7 @@ Possible values are listed [here](api/odrive.axis.controller.controlmode).
|
||||
|
||||
As of version v0.5.0, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are:
|
||||
|
||||
### Control Commands
|
||||
* `<axis>.controller.input_pos = <turn>`
|
||||
* `<axis>.controller.input_vel = <turn/s>`
|
||||
* `<axis>.controller.input_torque = <torque in Nm>`
|
||||
@@ -82,15 +83,15 @@ All variables that are part of a `[...].config` object can be saved to non-volat
|
||||
## Setting up sensorless
|
||||
The ODrive can run without encoder/hall feedback, but there is a minimum speed, usually around a few hunderd RPM.
|
||||
|
||||
To give an example, suppose you have a motor with 7 pole pairs, and you want to spin it at 3000 RPM. Then you would set the `input_vel` to `3000 * 2*pi/60 * 7 = 2199 rad/s electrical`.
|
||||
|
||||
Below are some suggested starting parameters that you can use. Note that you _must_ set the `pm_flux_linkage` correctly for sensorless mode to work.
|
||||
Below are some suggested starting parameters that you can use. Note that you _must_ set the `pm_flux_linkage` correctly for sensorless mode to work. Motor calibration and setup must also be completed before sensorless mode will work.
|
||||
|
||||
```
|
||||
odrv0.axis0.controller.config.vel_gain = 0.01
|
||||
odrv0.axis0.controller.config.vel_integrator_gain = 0.05
|
||||
odrv0.axis0.controller.config.control_mode = 2
|
||||
odrv0.axis0.controller.input_vel = 400
|
||||
odrv0.axis0.controller.input_vel = 10
|
||||
odrv0.axis0.controller.config.vel_limit = <a value greater than input_vel>
|
||||
odrv0.axis0.motor.config.current_lim = 2 * odrv0.axis0.config.sensorless_ramp.current
|
||||
odrv0.axis0.motor.config.direction = 1
|
||||
odrv0.axis0.sensorless_estimator.config.pm_flux_linkage = 5.51328895422 / (<pole pairs> * <motor kv>)
|
||||
```
|
||||
|
||||
@@ -29,6 +29,8 @@ voltage_integral += current_error * current_integrator_gain
|
||||
voltage_cmd = current_error * current_gain + voltage_integral (+ voltage_feedforward when we have motor model)
|
||||
```
|
||||
|
||||
Note: `current_gain` and `current_integrator_gain` are automatically set according to `motor.config.current_control_bandwidth`
|
||||
|
||||
For more detail refer to [controller.cpp](https://github.com/madcowswe/ODrive/blob/master/Firmware/MotorControl/controller.cpp#L86).
|
||||
|
||||
### Controller Details:
|
||||
|
||||
+10
-5
@@ -43,7 +43,7 @@ That's it, now on every reboot the motor will turn in one direction until it fin
|
||||
* If your motor has problems reaching the index location due to the mechanical load, you can increase `<axis>.motor.config.calibration_current`.
|
||||
|
||||
### Reversing index search
|
||||
Sometimes you would like the index search to only happen in a particular direction (the reverse of the default), instead of swapping the motor leads, you can ensure the following three values are negative:
|
||||
Sometimes you would like the index search to only happen in a particular direction (the reverse of the default). Instead of swapping the motor leads, you can ensure that the following three values are negative:
|
||||
* `<axis0>.config.calibration_lockin.vel`
|
||||
* `<axis0>.config.calibration_lockin.accel`
|
||||
* `<axis0>.config.calibration_lockin.ramp_distance`
|
||||
@@ -78,7 +78,7 @@ Do you still have no errors? Awesome. Now, setup the motor and encoder to use kn
|
||||
* `<axis>.encoder.config.pre_calibrated = True`
|
||||
* `<axis>.motor.config.pre_calibrated = True `
|
||||
|
||||
And see if ODrive agrees that calibration worked by just running
|
||||
And see if ODrive agrees that the calibration worked by just running
|
||||
* `<axis>.encoder.config.pre_calibrated`
|
||||
|
||||
(using no "= True" ). Make sure that 'pre_calibrated' is in fact True.
|
||||
@@ -121,6 +121,7 @@ Connect to the I pin, see if you get a pulse on a complete rotation. Sometimes t
|
||||
|
||||
If you are using SPI, use a logic analyzer and connect to the CLK, MISO, and CS pins. Set a trigger for the CS pin and ensure that the encoder position is being sent and is increasing/decreasing as you spin the motor. There is extremely cheap hardware that is supported by [Sigrok](https://sigrok.org/) for protocol analysis.
|
||||
|
||||
|
||||
## Encoder Noise
|
||||
Noise is found in all circuits, life is just about figuring out if it is preventing your system from working. Lots of users have no problems with noise interfering with their ODrive operation, others will tell you "_I've been using the same encoder as you with no problems_". Power to 'em, that may be true, but it doesn't mean it will work for you. If you are concerned about noise, there are several possible sources:
|
||||
|
||||
@@ -128,13 +129,13 @@ Noise is found in all circuits, life is just about figuring out if it is prevent
|
||||
* Long wires between encoder and ODrive
|
||||
* Use of ribbon cable
|
||||
|
||||
The following _might_ mitigate noise problems. Use shielded cable, or use twisted pairs, where one side of each twisted pair is tied to ground, the other side is tied to your signal. If you are using SPI, use a 20-50 ohm resistor in series on CLK, which is more susceptible noise.
|
||||
The following _might_ mitigate noise problems. Use shielded cable, or use twisted pairs, where one side of each twisted pair is tied to ground and the other side is tied to your signal. If you are using SPI, use a 20-50 ohm resistor in series on CLK, which is more susceptible noise.
|
||||
|
||||
If you are using an encoder with an index signal, another problem that has been encountered is with noise on the Z input of ODrive. Symptoms for this problem include:
|
||||
If you are using an encoder with an index signal, another problem that has been encountered is noise on the Z input of ODrive. Symptoms for this problem include:
|
||||
* difficulty with requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE, where your calibration sequence may not complete
|
||||
* strange behavior after performing odrv0.save_configuration() and odrv0.reboot()
|
||||
* when performing an index_search, the motor does not return to the same position each time.
|
||||
One easy step that _might_ fix the noise on the Z input has been to solder a 22nF-47nF capacitor to the Z pin and the GND pin on the underside of the ODrive board.
|
||||
One easy step that _might_ fix the noise on the Z input is to solder a 22nF-47nF capacitor to the Z pin and the GND pin on the underside of the ODrive board.
|
||||
|
||||
## Hall feedback pinout
|
||||
If position accuracy is not a concern, you can use A/B/C hall effect encoders for position feedback.
|
||||
@@ -170,12 +171,16 @@ Apart from (incremental) quadrature encoders, ODrive also supports absolute SPI
|
||||
|
||||
Some of these chips come with evaluation boards that can simplify mounting the chips to your motor. For our purposes if you are using an evaluation board you should select the settings for 3.3v.
|
||||
|
||||
**Note:** The AMT23x family has a hardware bug that causes them to not properly tristate the MISO line. To use them with ODrive, there are two workarounds. One is to sequence power to the encoder a second or two after the ODrive recieves power. This allows 1 encoder to be used without issue. Another solution is to add a tristate buffer, such as the 74AHC1G125SE, on the MISO line between the ODrive and each AMT23x encoder. Tie the enable pin on the buffer to the CS line for the respective encoder. This allows for more than one AMT23x encoder, or one AMT23x and another SPI encoder, to be used at the same time.
|
||||
|
||||
1. Connect the encoder to the ODrive's SPI interface:
|
||||
|
||||
- The encoder's SCK, MISO (aka "DATA" on CUI encoders), GND and 3.3V should connect to the ODrive pins with the same label.
|
||||
- The encoder's MOSI should be tied to 3.3V (AMS encoders only. CUI encoders don't have this pin.)
|
||||
- The encoder's Chip Select (aka nCS/CSn) can be connected to any of the ODrive's GPIOs (caution: GPIOs 1 and 2 are usually used by UART).
|
||||
|
||||
If you are having calibration problems, make sure that your magnet is centered on the axis of rotation on the motor. Some users report that this has a significant impact on calibration. Also make sure that your magnet height is within range of the spec sheet.
|
||||
|
||||
2. In `odrivetool`, run:
|
||||
|
||||
<axis>.encoder.config.abs_spi_cs_gpio_pin = 4 # or which ever GPIO pin you choose
|
||||
|
||||
+13
-8
@@ -19,6 +19,7 @@ permalink: /
|
||||
- [Other control modes](#other-control-modes)
|
||||
- [Watchdog Timer](#watchdog-timer)
|
||||
- [What's next?](#whats-next)
|
||||
- [Upgrading from 0.4.12](#upgrading-from-0412)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
@@ -41,7 +42,7 @@ permalink: /
|
||||
|
||||
</div></details>
|
||||
|
||||
* A power supply (12V-24V for the 24V board variant, 12V-48V for the 48V board variant). A battery is also fine. Some advice on choosing a power supply can be found [here](https://things-in-motion.blogspot.com/2018/12/how-to-select-right-power-source-for.html).
|
||||
* A power supply (12V-24V for the 24V board variant, 12V-56V for the 56V board variant). A battery is also fine. Some advice on choosing a power supply can be found [here](https://things-in-motion.blogspot.com/2018/12/how-to-select-right-power-source-for.html).
|
||||
<details><summary markdown="span">What voltage variant do I have?</summary><div markdown="block">
|
||||
On all ODrives shipped July 2018 or after have a silkscreen label clearly indicating the voltage variant.
|
||||
|
||||
@@ -61,13 +62,13 @@ All non-power I/O is 3.3V output and 5V tolerant on input, on ODrive v3.3 and ne
|
||||
### Wiring up the encoders
|
||||
Connect the encoder(s) to J4. The A,B phases are required, and the Z (index pulse) is optional. The A,B and Z lines have 3.3k pull up resistors, for use with open-drain encoder outputs. For single ended push-pull signals with weak drive current (\<4mA), you may want to desolder the pull-ups.
|
||||
|
||||

|
||||

|
||||
|
||||
### Safety & Power UP
|
||||
<div class="alert">
|
||||
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/48 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.
|
||||
* 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.
|
||||
|
||||
## Downloading and Installing Tools
|
||||
@@ -81,7 +82,7 @@ Most instructions in this guide refer to a utility called `odrivetool`, so you s
|
||||
2. Launch the command prompt.
|
||||
* __Anaconda__: In the start menu, type `Anaconda Prompt` <kbd>Enter</kbd>
|
||||
* __Standalone Python__: In the start menu, type `cmd` <kbd>Enter</kbd>
|
||||
3. Install the ODrive tools by typing `pip install odrive` <kbd>Enter</kbd>
|
||||
3. Install the ODrive tools by typing `pip install --upgrade odrive` <kbd>Enter</kbd>
|
||||
4. Plug in a USB cable into the microUSB connector on ODrive, and connect it to your PC.
|
||||
5. Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive driver to libusb-win32.
|
||||
* Check 'List All Devices' from the options menu, and select 'ODrive 3.x Native Interface (Interface 2)'. With that selected in the device list choose 'libusb-win32' from the target driver list and then press the large 'install driver' button.
|
||||
@@ -107,13 +108,13 @@ brew install libusb
|
||||
```
|
||||
5. Now that you have Python 3 and all the package managers, run:
|
||||
```bash
|
||||
pip3 install odrive
|
||||
pip3 install --upgrade odrive
|
||||
```
|
||||
|
||||
__Troubleshooting__
|
||||
1. Permission Errors: Just run the previous command in sudo
|
||||
```bash
|
||||
sudo pip3 install odrive
|
||||
sudo pip3 install --upgrade odrive
|
||||
```
|
||||
|
||||
2. Dependency Errors: If the installer doesn't complete and you get a dependency
|
||||
@@ -126,7 +127,7 @@ Try step 5 again
|
||||
|
||||
### Linux
|
||||
1. [Install Python 3](https://www.python.org/downloads/). (for example, on Ubuntu, `sudo apt install python3 python3-pip`)
|
||||
2. Install the ODrive tools by opening a terminal and typing `sudo pip3 install odrive` <kbd>Enter</kbd>
|
||||
2. Install the ODrive tools by opening a terminal and typing `sudo pip3 install --upgrade odrive` <kbd>Enter</kbd>
|
||||
* This should automatically add the udev rules. If this fails for some reason you can add them manually:
|
||||
```bash
|
||||
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d[0-9][0-9]", MODE="0666"' | sudo tee /etc/udev/rules.d/91-odrive.rules
|
||||
@@ -230,7 +231,7 @@ You can save all `.config` parameters to persistent memory so the ODrive remembe
|
||||
|
||||
|
||||
## Position control of M0
|
||||
Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to substitute `axis0` wherever it says `axis0`.
|
||||
Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to substitute `axis1` wherever it says `axis0`.
|
||||
|
||||
1. Type `odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE` <kbd>Enter</kbd>. After about 2 seconds should hear a beep. Then the motor will turn slowly in one direction for a few seconds, then back in the other direction.
|
||||
|
||||
@@ -373,3 +374,7 @@ You can now:
|
||||
* See how you can improve the behavior during the startup procedure, like [bypassing encoder calibration](encoders.md#encoder-with-index-signal).
|
||||
|
||||
If you have any issues or any questions please get in touch. The [ODrive Community](https://discourse.odriverobotics.com/) warmly welcomes you.
|
||||
|
||||
|
||||
## Upgrading from 0.4.12
|
||||
A new version (0.5.1) of ODrive firmware has released, complete with a new odrivetool. Follow the installation instructions, making sure to add the `--upgrade` flag to pip commands, and check out the [Changelog](../CHANGELOG.md) for changes!
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ Check the status of the encoder object:
|
||||
odrv0.axis0.encoder
|
||||
```
|
||||
|
||||
Check that there are no errors. If your hall sensors has a standard timing angle then `offset_float` should be close to 0.5.
|
||||
Check that there are no errors. If your hall sensors has a standard timing angle then `offset_float` should be close to 0.5 or 1.5.
|
||||
```txt
|
||||
error = 0x0000 (int)
|
||||
offset_float = 0.5126956701278687 (float)
|
||||
|
||||
+19
-19
@@ -18,25 +18,25 @@ The ODrive can be controlled over various ports and protocols. If you're comfort
|
||||
|
||||
## Pinout
|
||||
|
||||
| # | Label | `GPIO_MODE_DIGITAL` | `GPIO_MODE_ANALOG_IN` | `GPIO_MODE_UART0` | `GPIO_MODE_PWM0` | `GPIO_MODE_CAN0` | `GPIO_MODE_I2C0` | `GPIO_MODE_ENC0` | `GPIO_MODE_ENC1` |
|
||||
|----|---------------|------------------------|-----------------------|-------------------|------------------|------------------|------------------|------------------|------------------|
|
||||
| 0 | _not a pin_ | | | | | | | | |
|
||||
| 1 | GPIO1 (+) | general purpose | analog input | **UART0.TX** | PWM0.0 | | | | |
|
||||
| 2 | GPIO2 (+) | general purpose | analog input | **UART0.RX** | PWM0.1 | | | | |
|
||||
| 3 | GPIO3 | general purpose | **analog input** | | PWM0.2 | | | | |
|
||||
| 4 | GPIO4 | general purpose | **analog input** | | PWM0.3 | | | | |
|
||||
| 5 | GPIO5 | general purpose | **analog input** (*) | | | | | | |
|
||||
| 6 | GPIO6 (*) (+) | **general purpose** | | | | | | | |
|
||||
| 7 | GPIO7 (*) (+) | **general purpose** | | | | | | | |
|
||||
| 8 | GPIO8 (*) (+) | **general purpose** | | | | | | | |
|
||||
| 9 | M0.A | general purpose | | | | | | **ENC0.A** | |
|
||||
| 10 | M0.B | general purpose | | | | | | **ENC0.B** | |
|
||||
| 11 | M0.Z | **general purpose** | | | | | | | |
|
||||
| 12 | M1.A | general purpose | | | | | I2C.SCL | | **ENC1.A** |
|
||||
| 13 | M1.B | general purpose | | | | | I2C.SDA | | **ENC1.B** |
|
||||
| 14 | M1.Z | **general purpose** | | | | | | | |
|
||||
| 15 | _not exposed_ | general purpose | | | | **CAN0.RX** | I2C.SCL | | |
|
||||
| 16 | _not exposed_ | general purpose | | | | **CAN0.TX** | I2C.SDA | | |
|
||||
| # | Label | `GPIO_MODE_DIGITAL` | `GPIO_MODE_ANALOG_IN` | `GPIO_MODE_UART0` | `GPIO_MODE_PWM0` | `GPIO_MODE_CAN0` | `GPIO_MODE_I2C0` | `GPIO_MODE_ENC0` | `GPIO_MODE_ENC1` | `GPIO_MODE_MECH_BRAKE` |
|
||||
|----|---------------|------------------------|-----------------------|-------------------|------------------|------------------|------------------|------------------|------------------|------------------------|
|
||||
| 0 | _not a pin_ | | | | | | | | | |
|
||||
| 1 | GPIO1 (+) | general purpose | analog input | **UART0.TX** | PWM0.0 | | | | | mechanical brake |
|
||||
| 2 | GPIO2 (+) | general purpose | analog input | **UART0.RX** | PWM0.1 | | | | | mechanical brake |
|
||||
| 3 | GPIO3 | general purpose | **analog input** | | PWM0.2 | | | | | mechanical brake |
|
||||
| 4 | GPIO4 | general purpose | **analog input** | | PWM0.3 | | | | | mechanical brake |
|
||||
| 5 | GPIO5 | general purpose | **analog input** (*) | | | | | | | mechanical brake |
|
||||
| 6 | GPIO6 (*) (+) | **general purpose** | | | | | | | | mechanical brake |
|
||||
| 7 | GPIO7 (*) (+) | **general purpose** | | | | | | | | mechanical brake |
|
||||
| 8 | GPIO8 (*) (+) | **general purpose** | | | | | | | | mechanical brake |
|
||||
| 9 | M0.A | general purpose | | | | | | **ENC0.A** | | |
|
||||
| 10 | M0.B | general purpose | | | | | | **ENC0.B** | | |
|
||||
| 11 | M0.Z | **general purpose** | | | | | | | | |
|
||||
| 12 | M1.A | general purpose | | | | | I2C.SCL | | **ENC1.A** | |
|
||||
| 13 | M1.B | general purpose | | | | | I2C.SDA | | **ENC1.B** | |
|
||||
| 14 | M1.Z | **general purpose** | | | | | | | | |
|
||||
| 15 | _not exposed_ | general purpose | | | | **CAN0.RX** | I2C.SCL | | | |
|
||||
| 16 | _not exposed_ | general purpose | | | | **CAN0.TX** | I2C.SDA | | | |
|
||||
|
||||
|
||||
(*) ODrive v3.5 and later <br>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Mechanical Brake
|
||||
|
||||
Some systems employ mechanical brakes on motors as a safety feature. These brakes can also be engaged as a power-saving function if the motor is not moving, but still under load.
|
||||
|
||||
ODrive supports the use of its GPIO pins to connect to external brake drive electronics.
|
||||
|
||||
When the ODrive engages the drive electronics, the brake will be disabled. When the drive enters a fault or idle state, the brake will be re-engaged.
|
||||
|
||||
---
|
||||
|
||||
## Mechanical Brake Configuration
|
||||
Each axis supports one mechanical brake. The following properties are accessible through `odrivetool`:
|
||||
|
||||
Name | Type | Default
|
||||
--- | -- | --
|
||||
gpio_num | int | 0
|
||||
is_active_low | boolean | true
|
||||
|
||||
### gpio_num
|
||||
The GPIO pin number, according to the silkscreen labels on ODrive. Set with these commands:
|
||||
```
|
||||
<odrv>.<axis>.mechanical_brake.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8>
|
||||
```
|
||||
After GPIO pin number is changed, you'll need to run `<odrv>.save_configuration()` and `<odrv>.reboot()` for changes to take effect.
|
||||
|
||||
### is_active_low
|
||||
Most safety braking systems are active low, e.g. when the power is off, the brake is on. If the system uses brake drive electronics which use active high logic, flip this bit then reconsider the safety implications of your design...
|
||||
|
||||
### Enabling
|
||||
The configuration of the mechanical brake will enable the brake functionality. There's no need to specifically 'enable' this feature.
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
Let's say we're hacking away on an old ABB robotic arm. We've wired a 24V brake drive circuit triggered by GPIO5. When GPIO5 is driven, it will release the brakes on the axis we're moving.
|
||||
|
||||
We need to notify the axis of the GPIO number we've attached our brake to, and configure the ODrive pin mode to `GPIO_MODE_MECH_BRAKE`:
|
||||
```
|
||||
<odrv>.<axis>.mechanical_brake.config.gpio_num = 5
|
||||
<odrv>.config.gpio5_mode = GPIO_MODE_MECH_BRAKE
|
||||
```
|
||||
|
||||
Pin configurations only take effect after a save/reboot so don't forget to run:
|
||||
```
|
||||
<odrv>.save_configuration()
|
||||
<odrv>.reboot()
|
||||
```
|
||||
|
||||
### Testing The Mechanical Brakes
|
||||
Depending on your system this could be a dangerous experiment. Ensure that you have taken all necessary precautions to confirm if the wrong brake were inadvertently released it would not lead to injury or damage to equipment.
|
||||
|
||||
```
|
||||
<odrv>.<axis>.mechanical_brake.release()
|
||||
```
|
||||
Note: If a brake is configured, it will be automatically engaged/disengaged during the next state machine step.
|
||||
|
||||
After you're satisfied with the testing, you can re-enable the brake using the command
|
||||
|
||||
```
|
||||
<odrv>.<axis>.mechanical_brake.engage()
|
||||
```
|
||||
+2
-1
@@ -85,11 +85,12 @@ To compile firmware from source, refer to the [developer guide](developer-guide)
|
||||
### Troubleshooting
|
||||
|
||||
* __Windows__: During the update, a new device called "STM32 BOOTLOADER" will appear. Open the [Zadig utility](http://zadig.akeo.ie/) and set the driver for "STM32 BOOTLOADER" to libusb-win32. After that the firmware update will continue.
|
||||
* __Linux__: Try running `sudo odrivetool dfu` instead of `odrivetool dfu`.
|
||||
* On some machines you will need to unplug and plug back in the USB cable to make the PC understand that we switched from regular mode to bootloader mode.
|
||||
* If the DFU script can't find the device, try forcing it into DFU mode.
|
||||
|
||||
<details><summary markdown="span">How to force DFU mode (ODrive v3.5 and newer)</summary><div markdown="block">
|
||||
Flick the DIP switch that "DFU, RUN" to "DFU" and power cycle the board. After you're done upgrading firmware, don't forget to put the switch back into the "RUN" position and power cycle the board again.
|
||||
Flick the DIP switch that says "DFU, RUN" to "DFU" and power cycle the board. If that alone doesn't work, also connect the pin "GPIO6" to "GND". After you're done upgrading firmware, don't forget to put the switch back into the "RUN" position and power cycle the board again.
|
||||
</div></details>
|
||||
|
||||
<details><summary markdown="span">How to force DFU mode (ODrive v3.1, v3.2)</summary><div markdown="block">
|
||||
|
||||
+6
-3
@@ -223,9 +223,12 @@ def put_into_dfu_mode(device, cancellation_token):
|
||||
Puts the specified device into DFU mode
|
||||
"""
|
||||
if not hasattr(device, "enter_dfu_mode"):
|
||||
print("The firmware on device {} does not support DFU. You need to \n"
|
||||
"flash the firmware once using STLink (`make flash`), after that \n"
|
||||
"DFU with this script should work fine."
|
||||
print("The firmware on device {} cannot soft enter DFU mode.\n"
|
||||
"Please remove power, put the DFU switch into DFU mode,\n"
|
||||
"then apply power again. Then try again.\n"
|
||||
"If it still doesn't work, you can try to use the DeFuse app or \n"
|
||||
"dfu-util, see the odrive documentation.\n"
|
||||
"You can also flash the firmware using STLink (`make flash`)"
|
||||
.format(device.__channel__.usb_device.serial_number))
|
||||
return
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ GPIO_MODE_PWM0 = 10
|
||||
GPIO_MODE_ENC0 = 11
|
||||
GPIO_MODE_ENC1 = 12
|
||||
GPIO_MODE_ENC2 = 13
|
||||
GPIO_MODE_MECH_BRAKE = 14
|
||||
|
||||
# ODrive.Can.Protocol
|
||||
PROTOCOL_SIMPLE = 0
|
||||
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
declare -a tests=('analog_input_test.py'
|
||||
'calibration_test.py'
|
||||
'can_test.py'
|
||||
'closed_loop_test.py'
|
||||
'encoder_test.py'
|
||||
'fibre_test.py'
|
||||
'integration_test.py'
|
||||
'nvm_test.py'
|
||||
'pwm_input_test.py'
|
||||
'step_dir_test.py'
|
||||
'uart_ascii_test.py'
|
||||
)
|
||||
summary=""
|
||||
|
||||
for test in "${tests[@]}"; do
|
||||
(ipython3 "$test" -- --test-rig-yaml ../../test-rig-rpi.yaml || true) | tee /tmp/odrivetest.log
|
||||
if grep "All tests passed!" /tmp/odrivetest.log; then
|
||||
summary="$summary - $test: passed"$'\n'
|
||||
else
|
||||
summary="$summary - $test: failed"$'\n'
|
||||
fi
|
||||
|
||||
echo "########################"
|
||||
echo "Current status:"
|
||||
echo -n "$summary"
|
||||
echo "########################"
|
||||
done
|
||||
Reference in New Issue
Block a user