mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-09-20 22:55:00 +08:00
Merge branch 'devel' into feature/CAN
This commit is contained in:
+7
-2
@@ -1,8 +1,14 @@
|
||||
# Unreleased Features
|
||||
Please add a note of your changes below this heading if you make a Pull Request.
|
||||
|
||||
### Added
|
||||
* `dump_errors()` utility function in odrivetool to dump, decode and optionally clear errors.
|
||||
|
||||
# Releases
|
||||
## [0.4.7] - 2018-11-28
|
||||
### Added
|
||||
* Overspeed fault
|
||||
* Current sense saturation fault.
|
||||
* Supress startup transients by sampling encoder estimate into position setpoint when entering closed loop control.
|
||||
* Make step dir gpio pins configurable.
|
||||
* Configuration variable `encoder.config.zero_count_on_find_idx`, true by default. Set to false to leave the initial encoder count to be where the axis was at boot.
|
||||
@@ -14,11 +20,10 @@ Please add a note of your changes below this heading if you make a Pull Request.
|
||||
* Renamed `axis.enable_step_dir` to `axis.step_dir_active`
|
||||
* New process for working with STM32CubeMX.
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
* Would get ERROR_CONTROL_DEADLINE_MISSED along with every ERROR_PHASE_RESISTANCE_OUT_OF_RANGE.
|
||||
* ODrive tool can now run interactive nested scripts with "%run -i script.py"
|
||||
|
||||
# Releases
|
||||
## [0.4.6] - 2018-10-07
|
||||
### Fixed
|
||||
* Broken printing of floats on ascii protocol
|
||||
|
||||
@@ -66,7 +66,7 @@ void MX_UART4_Init(void)
|
||||
{
|
||||
|
||||
huart4.Instance = UART4;
|
||||
huart4.Init.BaudRate = 115200;
|
||||
huart4.Init.BaudRate = 115200; // Provisionally this can be changed to 921600 for faster transfers, the low power Arduinos will not keep up.
|
||||
huart4.Init.WordLength = UART_WORDLENGTH_8B;
|
||||
huart4.Init.StopBits = UART_STOPBITS_1;
|
||||
huart4.Init.Parity = UART_PARITY_NONE;
|
||||
|
||||
@@ -271,8 +271,10 @@ bool Encoder::update() {
|
||||
if (delta_enc > 3)
|
||||
delta_enc -= 6;
|
||||
} else {
|
||||
set_error(ERROR_ILLEGAL_HALL_STATE);
|
||||
return false;
|
||||
if (!config_.ignore_illegal_hall_state) {
|
||||
set_error(ERROR_ILLEGAL_HALL_STATE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
float offset_float = 0.0f; // Sub-count phase alignment offset
|
||||
float calib_range = 0.02f;
|
||||
float bandwidth = 1000.0f;
|
||||
bool ignore_illegal_hall_state = false;
|
||||
};
|
||||
|
||||
Encoder(const EncoderHardwareConfig_t& hw_config,
|
||||
@@ -106,7 +107,8 @@ public:
|
||||
make_protocol_property("offset_float", &config_.offset_float),
|
||||
make_protocol_property("bandwidth", &config_.bandwidth,
|
||||
[](void* ctx) { static_cast<Encoder*>(ctx)->update_pll_gains(); }, this),
|
||||
make_protocol_property("calib_range", &config_.calib_range)
|
||||
make_protocol_property("calib_range", &config_.calib_range),
|
||||
make_protocol_property("ignore_illegal_hall_state", &config_.ignore_illegal_hall_state)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -713,4 +713,33 @@ void pwm_in_cb(int channel, uint32_t timestamp) {
|
||||
last_timestamp[gpio_num - 1] = timestamp;
|
||||
last_pin_state[gpio_num - 1] = current_pin_state;
|
||||
last_sample_valid[gpio_num - 1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Analog speed control input */
|
||||
|
||||
static void update_analog_endpoint(const struct PWMMapping_t *map, int gpio)
|
||||
{
|
||||
float fraction = get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio)) / 3.3f;
|
||||
float value = map->min + (fraction * (map->max - map->min));
|
||||
get_endpoint(map->endpoint)->set_from_float(value);
|
||||
}
|
||||
|
||||
static void analog_polling_thread(void *)
|
||||
{
|
||||
while (true) {
|
||||
for (int i = 0; i < GPIO_COUNT; i++) {
|
||||
struct PWMMapping_t *map = &board_config.analog_mappings[i];
|
||||
|
||||
if (is_endpoint_ref_valid(map->endpoint))
|
||||
update_analog_endpoint(map, i + 1);
|
||||
}
|
||||
osDelay(10);
|
||||
}
|
||||
}
|
||||
|
||||
void start_analog_thread()
|
||||
{
|
||||
osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 4*512);
|
||||
osThreadCreate(osThread(thread_def), NULL);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b,
|
||||
void start_general_purpose_adc();
|
||||
float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin);
|
||||
void pwm_in_init();
|
||||
void start_analog_thread();
|
||||
|
||||
void update_brake_current();
|
||||
|
||||
|
||||
@@ -223,6 +223,8 @@ int odrive_main(void) {
|
||||
axes[i]->start_thread();
|
||||
}
|
||||
|
||||
start_analog_thread();
|
||||
|
||||
system_stats_.fully_booted = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ void Motor::DRV8301_setup() {
|
||||
// Solve for exact gain, then snap down to have equal or larger range as requested
|
||||
// or largest possible range otherwise
|
||||
static const float kMargin = 0.90f;
|
||||
static const float max_output_swing = 1.6f; // [V] out of amplifier
|
||||
static const float kTripMargin = 1.0f; // Trip level is at edge of linear range of amplifer
|
||||
static const float max_output_swing = 1.35f; // [V] out of amplifier
|
||||
float max_unity_gain_current = kMargin * max_output_swing * hw_config_.shunt_conductance; // [A]
|
||||
float requested_gain = max_unity_gain_current / config_.requested_current_range; // [V/V]
|
||||
|
||||
@@ -99,6 +100,8 @@ void Motor::DRV8301_setup() {
|
||||
phase_current_rev_gain_ = 1.0f / gain_snap_down->first;
|
||||
// Clip all current control to actual usable range
|
||||
current_control_.max_allowed_current = max_unity_gain_current * phase_current_rev_gain_;
|
||||
// Set trip level
|
||||
current_control_.overcurrent_trip_level = (kTripMargin / kMargin) * current_control_.max_allowed_current;
|
||||
|
||||
// We now have the gain settings we want to use, lets set up DRV chip
|
||||
DRV_SPI_8301_Vars_t* local_regs = &gate_driver_regs_;
|
||||
@@ -296,6 +299,12 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float phase) {
|
||||
// For Reporting
|
||||
ictrl.Iq_setpoint = Iq_des;
|
||||
|
||||
// Check for current sense saturation
|
||||
if (fabsf(current_meas_.phB) > ictrl.overcurrent_trip_level
|
||||
|| fabsf(current_meas_.phC) > ictrl.overcurrent_trip_level) {
|
||||
set_error(ERROR_CURRENT_SENSE_SATURATION);
|
||||
}
|
||||
|
||||
// Clarke transform
|
||||
float Ialpha = -current_meas_.phB - current_meas_.phC;
|
||||
float Ibeta = one_by_sqrt3 * (current_meas_.phB - current_meas_.phC);
|
||||
|
||||
@@ -20,7 +20,8 @@ public:
|
||||
ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x0040,
|
||||
ERROR_MODULATION_MAGNITUDE = 0x0080,
|
||||
ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100,
|
||||
ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200
|
||||
ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200,
|
||||
ERROR_CURRENT_SENSE_SATURATION = 0x0400
|
||||
};
|
||||
|
||||
enum MotorType_t {
|
||||
@@ -43,9 +44,10 @@ public:
|
||||
// Voltage applied at end of cycle:
|
||||
float final_v_alpha; // [V]
|
||||
float final_v_beta; // [V]
|
||||
float Iq_setpoint;
|
||||
float Iq_measured;
|
||||
float max_allowed_current;
|
||||
float Iq_setpoint; // [A]
|
||||
float Iq_measured; // [A]
|
||||
float max_allowed_current; // [A]
|
||||
float overcurrent_trip_level; // [A]
|
||||
};
|
||||
|
||||
// NOTE: for gimbal motors, all units of A are instead V.
|
||||
@@ -64,7 +66,7 @@ public:
|
||||
// float current_lim = 70.0f; //[A]
|
||||
float current_lim = 10.0f; //[A]
|
||||
// Value used to compute shunt amplifier gains
|
||||
float requested_current_range = 70.0f; // [A]
|
||||
float requested_current_range = 60.0f; // [A]
|
||||
float current_control_bandwidth = 1000.0f; // [rad/s]
|
||||
};
|
||||
|
||||
@@ -153,6 +155,7 @@ public:
|
||||
.Iq_setpoint = 0.0f,
|
||||
.Iq_measured = 0.0f,
|
||||
.max_allowed_current = 0.0f,
|
||||
.overcurrent_trip_level = 0.0f,
|
||||
};
|
||||
DRV8301_FaultType_e drv_fault_ = DRV8301_FaultType_NoFault;
|
||||
DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup)
|
||||
@@ -178,7 +181,8 @@ public:
|
||||
make_protocol_property("final_v_beta", ¤t_control_.final_v_beta),
|
||||
make_protocol_property("Iq_setpoint", ¤t_control_.Iq_setpoint),
|
||||
make_protocol_property("Iq_measured", ¤t_control_.Iq_measured),
|
||||
make_protocol_property("max_allowed_current", ¤t_control_.max_allowed_current)
|
||||
make_protocol_ro_property("max_allowed_current", ¤t_control_.max_allowed_current),
|
||||
make_protocol_ro_property("overcurrent_trip_level", ¤t_control_.overcurrent_trip_level)
|
||||
),
|
||||
make_protocol_object("gate_driver",
|
||||
make_protocol_ro_property("drv_fault", &drv_fault_)
|
||||
|
||||
@@ -83,6 +83,7 @@ struct BoardConfig_t {
|
||||
//<! the brake power if the brake resistor is disabled.
|
||||
//<! The default is 26V for the 24V board version and 52V for the 48V board version.
|
||||
PWMMapping_t pwm_mappings[GPIO_COUNT];
|
||||
PWMMapping_t analog_mappings[GPIO_COUNT];
|
||||
};
|
||||
extern BoardConfig_t board_config;
|
||||
extern bool user_config_loaded_;
|
||||
|
||||
@@ -160,8 +160,11 @@ static inline auto make_obj_tree() {
|
||||
make_protocol_object("gpio2_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[1])),
|
||||
make_protocol_object("gpio3_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[2])),
|
||||
#endif
|
||||
make_protocol_object("gpio4_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[3]))
|
||||
),
|
||||
make_protocol_object("gpio4_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[3])),
|
||||
|
||||
make_protocol_object("gpio3_analog_mapping", make_protocol_definitions(board_config.analog_mappings[2])),
|
||||
make_protocol_object("gpio4_analog_mapping", make_protocol_definitions(board_config.analog_mappings[3]))
|
||||
),
|
||||
make_protocol_object("axis0", axes[0]->make_protocol_definitions()),
|
||||
make_protocol_object("axis1", axes[1]->make_protocol_definitions()),
|
||||
make_protocol_object("can", odCAN->make_protocol_definitions()),
|
||||
|
||||
+1
-1
@@ -221,7 +221,7 @@ GEM
|
||||
ruby-enum (0.7.2)
|
||||
i18n
|
||||
ruby_dep (1.5.0)
|
||||
rubyzip (1.2.1)
|
||||
rubyzip (1.2.2)
|
||||
safe_yaml (1.0.4)
|
||||
sass (3.5.6)
|
||||
sass-listen (~> 4.0.0)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
+1
-1
@@ -22,7 +22,7 @@ The current state of an axis is indicated by `<axis>.current_state`. The user ca
|
||||
|
||||
1. `AXIS_STATE_IDLE` Disable motor PWM and do nothing.
|
||||
2. `AXIS_STATE_STARTUP_SEQUENCE` Run the [startup procedure](#startup-procedure).
|
||||
3. `AXIS_STATE_FULL_CALIBRATION_SEQUENCE` Run motor calibration and then encoder offset calibration (or encoder index search if `<axis>.encoder.use_index` is `True`).
|
||||
3. `AXIS_STATE_FULL_CALIBRATION_SEQUENCE` Run motor calibration and then encoder offset calibration (or encoder index search if `<axis>.encoder.config.use_index` is `True`).
|
||||
4. `AXIS_STATE_MOTOR_CALIBRATION` Measure phase resistance and phase inductance of the motor.
|
||||
* To store the results set `<axis>.motor.config.pre_calibrated` to `True` and [save the configuration](#saving-the-configuration). After that you don't have to run the motor calibration on the next start up.
|
||||
* This modifies the variables `<axis>.motor.config.phase_resistance` and `<axis>.motor.config.phase_inductance`.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
The motor controller is a cascaded style position, velocity and current control loop, as per the diagram below. When the control mode is set to position control, the whole loop runs. When running in velocity control mode, the position control part is removed and the velocity command is fed directly in to the second stage input. In current control mode, only the current controller is used.
|
||||
|
||||

|
||||

|
||||
|
||||
### Position loop:
|
||||
The position controller is a P loop with a single proportional gain.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
+69
-11
@@ -16,6 +16,7 @@ permalink: /
|
||||
- [Start `odrivetool`](#start-odrivetool)
|
||||
- [Configure M0](#configure-m0)
|
||||
- [Position control of M0](#position-control-of-m0)
|
||||
- [Other control modes](#other-control-modes)
|
||||
- [What's next?](#whats-next)
|
||||
|
||||
<!-- /TOC -->
|
||||
@@ -129,10 +130,10 @@ Try step 5 again
|
||||
|
||||
## Firmware
|
||||
**ODrive v3.5 and later**<br>
|
||||
Your board should come preflashed with firmware. If you run into problems, follow the instructions [here](odrivetool.md#device-firmware-update) on the DFU procedure before you continue.</div>
|
||||
Your board should come preflashed with firmware. If you run into problems, follow the instructions [here](odrivetool.md#device-firmware-update) on the DFU procedure before you continue.
|
||||
|
||||
**ODrive v3.4 and earlier**<br>
|
||||
Your board does **not** come preflashed with any firmware. Follow the instructions [here](odrivetool.md#device-firmware-update) on the STP Link procedure before you continue.</div>
|
||||
Your board does **not** come preflashed with any firmware. Follow the instructions [here](odrivetool.md#device-firmware-update) on the STP Link procedure before you continue.
|
||||
|
||||
## Start `odrivetool`
|
||||
To launch the main interactive ODrive tool, type `odrivetool` <kbd>Enter</kbd>. Connect your ODrive and wait for the tool to find it. Now you can, for instance type `odrv0.vbus_voltage` <kbd>Enter</kbd> to inpect the boards main supply voltage.
|
||||
@@ -165,7 +166,7 @@ For instance, to set the current limit of M0 to 10A you would type: `odrv0.axis0
|
||||
|
||||
**Current limit**<br>
|
||||
`odrv0.axis0.motor.config.current_lim` [A].
|
||||
The default current limit, for safety reasons, is set to 10A. This is quite weak, but good for making sure the drive is stable. Once you have tuned the oDrive, you can increase this to 75A to increase performance. Note that above 75A, you must change the current amplifier gains. You do this by requesting a different current range. i.e. for 90A on M0: `odrv0.axis0.motor.config.requested_current_range = 90` [A], then save the configuration and reboot as the gains are written out to the DRV (MOSFET driver) only during startup.
|
||||
The default current limit, for safety reasons, is set to 10A. This is quite weak, but good for making sure the drive is stable. Once you have tuned the oDrive, you can increase this to 60A to increase performance. Note that above 60A, you must change the current amplifier gains. You do this by requesting a different current range. i.e. for 90A on M0: `odrv0.axis0.motor.config.requested_current_range = 90` [A], then save the configuration and reboot as the gains are written out to the DRV (MOSFET driver) only during startup.
|
||||
|
||||
*Note: The motor current and the current drawn from the power supply is not the same in general. You should not look at the power supply current to see what is going on with the motor current.*
|
||||
<details><summary markdown="span">Ok, so tell me how it actually works then...</summary><div markdown="block">
|
||||
@@ -241,16 +242,73 @@ Let's get motor 0 up and running. The procedure for motor 1 is exactly the same,
|
||||
2. Type `odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL` <kbd>Enter</kbd>. From now on the ODrive will try to hold the motor's position. If you try to turn it by hand, it will fight you gently. That is unless you bump up `odrv0.axis0.motor.config.current_lim`, in which case it will fight you more fiercely.
|
||||
3. Send the motor a new position setpoint. `odrv0.axis0.controller.pos_setpoint = 10000` <kbd>Enter</kbd>. The units are in encoder counts.
|
||||
|
||||
### Other control modes
|
||||
The ODrive also supports velocity control and current (torque) control.
|
||||
## Other control modes
|
||||
The default control mode is unfiltered position control in the absolute encoder reference frame. You may wish to use a controlled trajectory instead. Or you may wish to control position in a circular frame to allow continous rotation forever without growing the numeric value of the setpoint too large.
|
||||
You may also wish to control velocity (directly or with a ramping filter).
|
||||
You can also directly control the current of the motor, which is proportional to torque.
|
||||
|
||||
**Velocity control**<br>
|
||||
Set `odrv0.axis0.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.
|
||||
You can now control the velocity with `odrv0.axis0.controller.vel_setpoint = 5000` [count/s].
|
||||
- [Trajectory control](#trajectory-control)
|
||||
- [Circular position control](#circular-position-control)
|
||||
- [Velocity control](#velocity-control)
|
||||
- [Ramped velocity control](#ramped-velocity-control)
|
||||
- [Current control](#current-control)
|
||||
|
||||
**Current control**<br>
|
||||
Set `odrv0.axis0.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`.
|
||||
You can now control the current with `odrv0.axis0.controller.current_setpoint = 3` [A].
|
||||
|
||||
### Trajectory control
|
||||
This mode lets you smoothly accelerate, coast, and decelerate the axis from one position to another. With raw position control, the controller simply tries to go to the setpoint as quickly as possible. Using a trajectory lets you tune the feedback gains more aggressively to reject disturbance, while keeping smooth motion.
|
||||
|
||||
<br>
|
||||
In the above image blue is position and orange is velocity.
|
||||
|
||||
#### Parameters
|
||||
```
|
||||
<odrv>.<axis>.trap_traj.config.vel_limit = <Float>
|
||||
<odrv>.<axis>.trap_traj.config.accel_limit = <Float>
|
||||
<odrv>.<axis>.trap_traj.config.decel_limit = <Float>
|
||||
<odrv>.<axis>.trap_traj.config.A_per_css = <Float>
|
||||
```
|
||||
|
||||
`vel_limit` is the maximum planned trajectory speed. This sets your coasting speed.<br>
|
||||
`accel_limit` is the maximum acceleration in counts / sec^2<br>
|
||||
`decel_limit` is the maximum deceleration in counts / sec^2<br>
|
||||
`A_per_css` is a value which correlates acceleration (in counts / sec^2) and motor current. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system.
|
||||
|
||||
All values should be strictly positive (>= 0).
|
||||
|
||||
Keep in mind that you must still set your safety limits as before. I recommend you set these a little higher ( > 10%) than the planner values, to give the controller enough control authority.
|
||||
```
|
||||
<odrv>.<axis>.motor.config.current_lim = <Float>
|
||||
<odrv>.<axis>.controller.config.vel_limit = <Float>
|
||||
```
|
||||
|
||||
#### Usage
|
||||
Use the `move_to_pos` function to move to an absolute position:
|
||||
```
|
||||
<odrv>.<axis>.controller.move_to_pos(<Float>)
|
||||
```
|
||||
|
||||
### Circular position control
|
||||
This mode is useful for continuos incremental position movement. For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely.
|
||||
In the regular position mode, the `pos_setpoint` would grow to a very large value and would lose precision due to floating point rounding.
|
||||
|
||||
In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `pos_setpoint` is expected in the range `[0, cpr-1]`, where `cpr` is the number of encoder counts in one revolution. If the `pos_setpoint` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value.
|
||||
Note that in this mode `encoder.pos_cpr` is used for feedback in stead of `encoder.pos_estimate`.
|
||||
|
||||
If you try to increment the axis with a large step in one go that exceeds `cpr/2` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * your_enc_cpr`, where N is some integer. Choose N to give you an appropriate circular space for your application.
|
||||
|
||||
### Velocity control
|
||||
Set `axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.<br>
|
||||
You can now control the velocity with `axis.controller.vel_setpoint = 5000` [count/s].
|
||||
|
||||
### Ramped velocity control
|
||||
Set `axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.<br>
|
||||
Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate = 2000` [counts/s^2]<br>
|
||||
Activate the ramped velocity mode: `axis.controller.vel_ramp_enable = True`.<br>
|
||||
You can now control the velocity with `axis.controller.vel_ramp_target = 5000` [count/s].
|
||||
|
||||
### Current control
|
||||
Set `axis.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`.<br>
|
||||
You can now control the current with `axis.controller.current_setpoint = 3` [A].
|
||||
|
||||
*Note: There is no velocity limiting in current control mode. Make sure that you don't overrev the motor, or exceed the max speed for your encoder.*
|
||||
|
||||
|
||||
+2
-2
@@ -8,9 +8,9 @@ Each step is acompanied by some explanation so hopefully you can carry over some
|
||||
### Hoverboard motor wiring
|
||||
Hoverboard motors come with three motor phases (usually colored yellow, blue, green) which are thicker, and a set of 5 thinner wires for the hall sensor feedback (usually colored red, yellow, blue, green, black).
|
||||
|
||||
You may wire the motor phases in any order into a motor connector on the ODrive, as we will calibrate the phase alignment later anyway. Wire the hall feedback into the ODrive J2 conenctor (make sure that the motor channel number matches) as follows:
|
||||
You may wire the motor phases in any order into a motor connector on the ODrive, as we will calibrate the phase alignment later anyway. Wire the hall feedback into the ODrive J4 conenctor (make sure that the motor channel number matches) as follows:
|
||||
|
||||
| Hall wire | J2 signal |
|
||||
| Hall wire | J4 signal |
|
||||
|-----------|-----------|
|
||||
| Red | 5V |
|
||||
| Yellow | A |
|
||||
|
||||
+2
-14
@@ -14,21 +14,9 @@ Table of Contents:
|
||||
<!-- /TOC -->
|
||||
|
||||
## Error codes
|
||||
If your ODrive is not working as expected, run `odrivetool` and type `hex(<axis>.error)` <kbd>Enter</kbd> where `<axis>` is the axis that isn't working. This will display a [hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) representation of the error code. Each bit represents one error flag.
|
||||
|
||||
<details><summary markdown="span">Example</summary><div markdown="block">
|
||||
Say you got this error output:
|
||||
```python
|
||||
In [1]: hex(odrv0.axis0.error)
|
||||
Out[1]: '0x6'
|
||||
```
|
||||
|
||||
Written in binary, the number `0x6` corresponds to `110`, that means bits 1 and 2 are set (counting starts at 0).
|
||||
Looking at the reference below, this means that both `ERROR_DC_BUS_UNDER_VOLTAGE` and `ERROR_DC_BUS_OVER_VOLTAGE` occurred.
|
||||
</div></details>
|
||||
|
||||
The axis error may say that some other component has failed. Say it reports `ERROR_ENCODER_FAILED`, then you need to go check the encoder error: `hex(<axis>.encoder.error)`.
|
||||
If your ODrive is not working as expected, run `odrivetool` and type `dump_errors(odrv0)` <kbd>Enter</kbd>. This will dump a list of all the errors that are present. To also clear all the errors, you can run `dump_errors(odrv0, True)`.
|
||||
|
||||
The following sections will give some guidance on the most common errors. You may also check the code for the full list of errors:
|
||||
* Axis error flags defined [here](../Firmware/MotorControl/axis.hpp).
|
||||
* Motor error flags defined [here](../Firmware/MotorControl/motor.hpp).
|
||||
* Encoder error flags defined [here](../Firmware/MotorControl/encoder.hpp).
|
||||
|
||||
+41
-11
@@ -11,17 +11,47 @@ AXIS_STATE_ENCODER_INDEX_SEARCH = 6
|
||||
AXIS_STATE_ENCODER_OFFSET_CALIBRATION = 7
|
||||
AXIS_STATE_CLOSED_LOOP_CONTROL = 8
|
||||
|
||||
AXIS_ERROR_NONE = 0
|
||||
AXIS_ERROR_INVALID_STATE = 1
|
||||
#AXIS_ERROR_DC_BUS_UNDER_VOLTAGE = 2
|
||||
#AXIS_ERROR_DC_BUS_OVER_VOLTAGE = 3
|
||||
#AXIS_ERROR_CURRENT_MEASUREMENT_TIMEOUT = 4
|
||||
#AXIS_ERROR_CONTROL_LOOP_TIMEOUT = 5
|
||||
#AXIS_ERROR_MOTOR_FAILED = 6
|
||||
#AXIS_ERROR_SENSORLESS_ESTIMATOR_FAILED = 7
|
||||
#AXIS_ERROR_ENCODER_FAILED = 8
|
||||
#AXIS_ERROR_CONTROLLER_FAILED = 9
|
||||
#AXIS_ERROR_POS_CTRL_DURING_SENSORLESS = 10
|
||||
class errors:
|
||||
class axis:
|
||||
ERROR_NONE = 0x00
|
||||
ERROR_INVALID_STATE = 0x01 #<! an invalid state was requested
|
||||
ERROR_DC_BUS_UNDER_VOLTAGE = 0x02
|
||||
ERROR_DC_BUS_OVER_VOLTAGE = 0x04
|
||||
ERROR_CURRENT_MEASUREMENT_TIMEOUT = 0x08
|
||||
ERROR_BRAKE_RESISTOR_DISARMED = 0x10 #<! the brake resistor was unexpectedly disarmed
|
||||
ERROR_MOTOR_DISARMED = 0x20 #<! the motor was unexpectedly disarmed
|
||||
ERROR_MOTOR_FAILED = 0x40 # Go to motor.hpp for information, check odrvX.axisX.motor.error for error value
|
||||
ERROR_SENSORLESS_ESTIMATOR_FAILED = 0x80
|
||||
ERROR_ENCODER_FAILED = 0x100 # Go to encoder.hpp for information, check odrvX.axisX.encoder.error for error value
|
||||
ERROR_CONTROLLER_FAILED = 0x200
|
||||
ERROR_POS_CTRL_DURING_SENSORLESS = 0x400
|
||||
|
||||
class motor:
|
||||
ERROR_NONE = 0
|
||||
ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001
|
||||
ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002
|
||||
ERROR_ADC_FAILED = 0x0004
|
||||
ERROR_DRV_FAULT = 0x0008
|
||||
ERROR_CONTROL_DEADLINE_MISSED = 0x0010
|
||||
ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x0020
|
||||
ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x0040
|
||||
ERROR_MODULATION_MAGNITUDE = 0x0080
|
||||
ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100
|
||||
ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200
|
||||
ERROR_CURRENT_SENSE_SATURATION = 0x0400
|
||||
|
||||
class encoder:
|
||||
ERROR_NONE = 0
|
||||
ERROR_UNSTABLE_GAIN = 0x01
|
||||
ERROR_CPR_OUT_OF_RANGE = 0x02
|
||||
ERROR_NO_RESPONSE = 0x04
|
||||
ERROR_UNSUPPORTED_ENCODER_MODE = 0x08
|
||||
ERROR_ILLEGAL_HALL_STATE = 0x10
|
||||
ERROR_INDEX_NOT_FOUND_YET = 0x20
|
||||
|
||||
class controller:
|
||||
ERROR_NONE = 0
|
||||
ERROR_OVERSPEED = 0x01
|
||||
|
||||
MOTOR_TYPE_HIGH_CURRENT = 0
|
||||
#MOTOR_TYPE_LOW_CURRENT = 1
|
||||
|
||||
@@ -5,7 +5,7 @@ import threading
|
||||
import fibre
|
||||
import odrive
|
||||
import odrive.enums
|
||||
from odrive.utils import start_liveplotter
|
||||
from odrive.utils import start_liveplotter, dump_errors
|
||||
#from odrive.enums import * # pylint: disable=W0614
|
||||
|
||||
def print_banner():
|
||||
@@ -76,7 +76,8 @@ def launch_shell(args, logger, app_shutdown_token):
|
||||
"""
|
||||
|
||||
interactive_variables = {
|
||||
'start_liveplotter': start_liveplotter
|
||||
'start_liveplotter': start_liveplotter,
|
||||
'dump_errors': dump_errors
|
||||
}
|
||||
|
||||
# Expose all enums from odrive.enums
|
||||
|
||||
+39
-3
@@ -7,6 +7,7 @@ import platform
|
||||
import subprocess
|
||||
import os
|
||||
from fibre.utils import Event
|
||||
from odrive.enums import errors
|
||||
|
||||
try:
|
||||
if platform.system() == 'Windows':
|
||||
@@ -19,13 +20,48 @@ except ImportError:
|
||||
sys.stdout.flush()
|
||||
pass
|
||||
|
||||
data_rate = 100
|
||||
plot_rate = 10
|
||||
num_samples = 1000
|
||||
_VT100Colors = {
|
||||
'green': '\x1b[92;1m',
|
||||
'cyan': '\x1b[96;1m',
|
||||
'yellow': '\x1b[93;1m',
|
||||
'red': '\x1b[91;1m',
|
||||
'default': '\x1b[0m'
|
||||
}
|
||||
|
||||
class OperationAbortedException(Exception):
|
||||
pass
|
||||
|
||||
def dump_errors(odrv, clear=False):
|
||||
axes = [axis for name, axis in odrv._remote_attributes.items() if 'axis' in name]
|
||||
for num, axis in enumerate(axes):
|
||||
print('Axis{}:'.format(num))
|
||||
|
||||
# Flatten axis and submodules
|
||||
# (name, remote_obj, errorcode)
|
||||
module_decode_map = [
|
||||
('axis', axis, errors.axis),
|
||||
('motor', axis.motor, errors.motor),
|
||||
('encoder', axis.encoder, errors.encoder),
|
||||
('controller', axis.controller, errors.controller),
|
||||
]
|
||||
|
||||
# Module error decode
|
||||
for name, remote_obj, errorcodes in module_decode_map:
|
||||
prefix = ' '*2 + name + ": "
|
||||
if (remote_obj.error != errorcodes.ERROR_NONE):
|
||||
print(prefix + _VT100Colors['red'] + "Error(s):" + _VT100Colors['default'])
|
||||
errorcodes_tup = [(name, val) for name, val in errorcodes.__dict__.items() if 'ERROR_' in name]
|
||||
for codename, codeval in errorcodes_tup:
|
||||
if remote_obj.error & codeval != 0:
|
||||
print(" " + codename)
|
||||
if clear:
|
||||
remote_obj.error = errorcodes.ERROR_NONE
|
||||
else:
|
||||
print(prefix + _VT100Colors['green'] + "no error" + _VT100Colors['default'])
|
||||
|
||||
data_rate = 100
|
||||
plot_rate = 10
|
||||
num_samples = 1000
|
||||
def start_liveplotter(get_var_callback):
|
||||
"""
|
||||
Starts a liveplotter.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
@echo off
|
||||
ipython %~dp0\odrivetool -- %*
|
||||
ipython "%~dp0\odrivetool" -- %*
|
||||
|
||||
Reference in New Issue
Block a user