mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-09-22 16:14:37 +08:00
Merge branch 'devel' into vscode-readme
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
## [0.3]
|
||||
|
||||
## [0.3] - 2017-12-18
|
||||
### Added
|
||||
* **New binary communication protocol**
|
||||
* This is a much richer and more efficient binary protocol than the old human-readable protocol.
|
||||
@@ -7,9 +6,12 @@
|
||||
* Support for C++
|
||||
* Demo scripts for getting started with commanding ODrive from python
|
||||
* Protection from user setting current_lim higher than is measurable
|
||||
* Current sense shunt values for HW v3.4
|
||||
* Check DRV chip fault line
|
||||
|
||||
### Changed
|
||||
* Shunt resistance values for v3.3 and earlier to include extra resistance of PCB
|
||||
* Default HW revision to v3.4
|
||||
* Refactoring of control code:
|
||||
* Lifted top layer of low_level.c into Axis.cpp
|
||||
|
||||
|
||||
+9
-2
@@ -53,7 +53,8 @@
|
||||
/* USER CODE BEGIN Includes */
|
||||
|
||||
#define HW_VERSION_MAJOR 3
|
||||
#define HW_VERSION_MINOR 3
|
||||
#define HW_VERSION_MINOR 4
|
||||
// #define HW_VERSION_HIGH_VOLTAGE true
|
||||
|
||||
#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 1 \
|
||||
|| HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 2
|
||||
@@ -63,7 +64,7 @@
|
||||
|
||||
/* Private define ------------------------------------------------------------*/
|
||||
#define TIM_1_8_CLOCK_HZ 168000000
|
||||
#define TIM_1_8_PERIOD_CLOCKS 10192
|
||||
#define TIM_1_8_PERIOD_CLOCKS 8192
|
||||
#define TIM_1_8_DEADTIME_CLOCKS 20
|
||||
#define TIM_APB1_CLOCK_HZ 84000000
|
||||
#define TIM_APB1_PERIOD_CLOCKS 4096
|
||||
@@ -157,6 +158,12 @@
|
||||
#define CURRENT_MEAS_PERIOD ((float)(2*TIM_1_8_PERIOD_CLOCKS)/(float)TIM_1_8_CLOCK_HZ)
|
||||
#define CURRENT_MEAS_HZ (TIM_1_8_CLOCK_HZ/(2*TIM_1_8_PERIOD_CLOCKS))
|
||||
|
||||
#ifdef HW_VERSION_HIGH_VOLTAGE
|
||||
#define VBUS_S_DIVIDER_RATIO 19.0f
|
||||
#else
|
||||
#define VBUS_S_DIVIDER_RATIO 11.0f
|
||||
#endif
|
||||
|
||||
/* USER CODE END Private defines */
|
||||
|
||||
void _Error_Handler(char *, int);
|
||||
|
||||
@@ -94,6 +94,9 @@ void motors_1_set_current_setpoint_func(void) {
|
||||
const Endpoint endpoints[] = {
|
||||
Endpoint::make_property("vbus_voltage", const_cast<const float*>(&vbus_voltage)),
|
||||
Endpoint::make_property("elec_rad_per_enc", const_cast<const float*>(&elec_rad_per_enc)),
|
||||
Endpoint::make_property("UUID_0", (const uint32_t*)(ID_UNIQUE_ADDRESS + 0*4)),
|
||||
Endpoint::make_property("UUID_1", (const uint32_t*)(ID_UNIQUE_ADDRESS + 1*4)),
|
||||
Endpoint::make_property("UUID_2", (const uint32_t*)(ID_UNIQUE_ADDRESS + 2*4)),
|
||||
Endpoint::make_object("motor0"),
|
||||
Endpoint::make_property("control_mode", reinterpret_cast<int32_t*>(&motors[0].control_mode)),
|
||||
Endpoint::make_property("error", reinterpret_cast<int32_t*>(&motors[0].error)),
|
||||
@@ -108,8 +111,8 @@ const Endpoint endpoints[] = {
|
||||
Endpoint::make_property("calibration_current", &motors[0].calibration_current),
|
||||
Endpoint::make_property("phase_inductance", const_cast<const float*>(&motors[0].phase_inductance)),
|
||||
Endpoint::make_property("phase_resistance", const_cast<const float*>(&motors[0].phase_resistance)),
|
||||
Endpoint::make_property("current_meas.phB", const_cast<const float*>(&motors[0].current_meas.phB)),
|
||||
Endpoint::make_property("current_meas.phC", const_cast<const float*>(&motors[0].current_meas.phC)),
|
||||
Endpoint::make_property("current_meas_phB", const_cast<const float*>(&motors[0].current_meas.phB)),
|
||||
Endpoint::make_property("current_meas_phC", const_cast<const float*>(&motors[0].current_meas.phC)),
|
||||
Endpoint::make_property("DC_calib.phB", &motors[0].DC_calib.phB),
|
||||
Endpoint::make_property("DC_calib.phC", &motors[0].DC_calib.phC),
|
||||
Endpoint::make_property("shunt_conductance", &motors[0].shunt_conductance),
|
||||
@@ -123,6 +126,7 @@ const Endpoint endpoints[] = {
|
||||
Endpoint::make_property("i_gain", &motors[0].current_control.i_gain),
|
||||
Endpoint::make_property("v_current_control_integral_d", &motors[0].current_control.v_current_control_integral_d),
|
||||
Endpoint::make_property("v_current_control_integral_q", &motors[0].current_control.v_current_control_integral_q),
|
||||
Endpoint::make_property("Iq_command", &motors[0].current_control.Iq),
|
||||
Endpoint::make_property("Ibus", const_cast<const float*>(&motors[0].current_control.Ibus)),
|
||||
Endpoint::close_tree(),
|
||||
Endpoint::make_object("encoder"),
|
||||
@@ -161,8 +165,8 @@ const Endpoint endpoints[] = {
|
||||
Endpoint::make_property("calibration_current", &motors[1].calibration_current),
|
||||
Endpoint::make_property("phase_inductance", const_cast<const float*>(&motors[1].phase_inductance)),
|
||||
Endpoint::make_property("phase_resistance", const_cast<const float*>(&motors[1].phase_resistance)),
|
||||
Endpoint::make_property("current_meas.phB", const_cast<const float*>(&motors[1].current_meas.phB)),
|
||||
Endpoint::make_property("current_meas.phC", const_cast<const float*>(&motors[1].current_meas.phC)),
|
||||
Endpoint::make_property("current_meas_phB", const_cast<const float*>(&motors[1].current_meas.phB)),
|
||||
Endpoint::make_property("current_meas_phC", const_cast<const float*>(&motors[1].current_meas.phC)),
|
||||
Endpoint::make_property("DC_calib.phB", &motors[1].DC_calib.phB),
|
||||
Endpoint::make_property("DC_calib.phC", &motors[1].DC_calib.phC),
|
||||
Endpoint::make_property("shunt_conductance", &motors[1].shunt_conductance),
|
||||
@@ -176,6 +180,7 @@ const Endpoint endpoints[] = {
|
||||
Endpoint::make_property("i_gain", &motors[1].current_control.i_gain),
|
||||
Endpoint::make_property("v_current_control_integral_d", &motors[1].current_control.v_current_control_integral_d),
|
||||
Endpoint::make_property("v_current_control_integral_q", &motors[1].current_control.v_current_control_integral_q),
|
||||
Endpoint::make_property("Iq_command", &motors[1].current_control.Iq),
|
||||
Endpoint::make_property("Ibus", const_cast<const float*>(&motors[1].current_control.Ibus)),
|
||||
Endpoint::close_tree(),
|
||||
Endpoint::make_object("encoder"),
|
||||
|
||||
@@ -320,7 +320,7 @@ void global_fault(int error) {
|
||||
*(motors[i].axis_legacy.enable_control) = false;
|
||||
}
|
||||
// disable brake resistor
|
||||
update_brake_current(0.0f);
|
||||
set_brake_current(0.0f);
|
||||
}
|
||||
|
||||
float phase_current_from_adcval(Motor_t* motor, uint32_t ADCValue) {
|
||||
@@ -528,7 +528,7 @@ void step_cb(uint16_t GPIO_Pin) {
|
||||
}
|
||||
|
||||
void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) {
|
||||
static const float voltage_scale = 3.3f * 11.0f / (float)(1 << 12);
|
||||
static const float voltage_scale = 3.3f * VBUS_S_DIVIDER_RATIO / (float)(1 << 12);
|
||||
// Only one conversion in sequence, so only rank1
|
||||
uint32_t ADCValue = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1);
|
||||
vbus_voltage = ADCValue * voltage_scale;
|
||||
@@ -636,8 +636,8 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) {
|
||||
|
||||
// TODO check Ibeta balance to verify good motor connection
|
||||
bool measure_phase_resistance(Motor_t* motor, float test_current, float max_voltage) {
|
||||
static const float kI = 10.0f; //[(V/s)/A]
|
||||
static const int num_test_cycles = 3.0f / CURRENT_MEAS_PERIOD; // Test runs for 3s
|
||||
static const float kI = 10.0f; // [(V/s)/A]
|
||||
static const int num_test_cycles = 3.0f / CURRENT_MEAS_PERIOD; // Test runs for 3s
|
||||
float test_voltage = 0.0f;
|
||||
for (int i = 0; i < num_test_cycles; ++i) {
|
||||
osEvent evt = osSignalWait(M_SIGNAL_PH_CURRENT_MEAS, PH_CURRENT_MEAS_TIMEOUT);
|
||||
@@ -1112,7 +1112,16 @@ bool spin_up_sensorless(Motor_t* motor) {
|
||||
// TODO: check pll vel (abs ratio, 0.8)
|
||||
}
|
||||
|
||||
void update_brake_current(float brake_current) {
|
||||
void update_brake_current() {
|
||||
float Ibus_sum = 0.0f;
|
||||
for (int i = 0; i < num_motors; ++i) {
|
||||
Ibus_sum += motors[i].current_control.Ibus;
|
||||
}
|
||||
// Note: set_brake_current will clip negative values to 0.0f
|
||||
set_brake_current(-Ibus_sum);
|
||||
}
|
||||
|
||||
void set_brake_current(float brake_current) {
|
||||
if (brake_current < 0.0f) brake_current = 0.0f;
|
||||
float brake_duty = brake_current * brake_resistance / vbus_voltage;
|
||||
|
||||
@@ -1191,19 +1200,6 @@ bool FOC_current(Motor_t* motor, float Id_des, float Iq_des) {
|
||||
// Compute estimated bus current
|
||||
ictrl->Ibus = mod_d * Id + mod_q * Iq;
|
||||
|
||||
// If this is last motor, update brake resistor duty
|
||||
// if (motor == &motors[num_motors-1]) {
|
||||
// Above check doesn't work if last motor is executing voltage control
|
||||
// TODO trigger this update in control_motor_loop instead,
|
||||
// and make voltage control a control mode in it.
|
||||
float Ibus_sum = 0.0f;
|
||||
for (int i = 0; i < num_motors; ++i) {
|
||||
Ibus_sum += motors[i].current_control.Ibus;
|
||||
}
|
||||
// Note: function will clip negative values to 0.0f
|
||||
update_brake_current(-Ibus_sum);
|
||||
// }
|
||||
|
||||
// Inverse park transform
|
||||
float mod_alpha = c * mod_d - s * mod_q;
|
||||
float mod_beta = c * mod_q + s * mod_d;
|
||||
@@ -1224,12 +1220,23 @@ bool FOC_current(Motor_t* motor, float Id_des, float Iq_des) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Returns true if the fault line is asserted
|
||||
bool check_DRV_fault(Motor_t* motor) {
|
||||
//TODO: make this pin configurable per motor ch
|
||||
GPIO_PinState nFAULT_state = HAL_GPIO_ReadPin(nFAULT_GPIO_Port, nFAULT_Pin);
|
||||
return (nFAULT_state == GPIO_PIN_RESET) ? true : false;
|
||||
}
|
||||
|
||||
void control_motor_loop(Motor_t* motor) {
|
||||
while (*(motor->axis_legacy.enable_control)) {
|
||||
if (osSignalWait(M_SIGNAL_PH_CURRENT_MEAS, PH_CURRENT_MEAS_TIMEOUT).status != osEventSignal) {
|
||||
motor->error = ERROR_FOC_MEASUREMENT_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
if (check_DRV_fault(motor)) {
|
||||
motor->error = ERROR_DRV_FAULT;
|
||||
break;
|
||||
}
|
||||
update_rotor(motor);
|
||||
anti_cogging_calibration(motor); // Only runs if anticogging.calib_anticogging is true; non-blocking
|
||||
|
||||
@@ -1304,9 +1311,11 @@ void control_motor_loop(Motor_t* motor) {
|
||||
if (!FOC_current(motor, 0.0f, Iq)) {
|
||||
break; // in case of error exit loop, motor->error has been set by FOC_current
|
||||
}
|
||||
|
||||
update_brake_current();
|
||||
}
|
||||
|
||||
//We are exiting control, reset Ibus, and update brake current
|
||||
//TODO update brake current from all motors in 1 func
|
||||
//TODO reset this motor Ibus, then call from here
|
||||
motor->current_control.Ibus = 0.0f;
|
||||
update_brake_current();
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ typedef enum {
|
||||
ERROR_UNEXPECTED_STEP_SRC,
|
||||
ERROR_POS_CTRL_DURING_SENSORLESS,
|
||||
ERROR_SPIN_UP_TIMEOUT,
|
||||
ERROR_DRV_FAULT,
|
||||
} Error_t;
|
||||
|
||||
// Note: these should be sorted from lowest level of control to
|
||||
@@ -232,7 +233,8 @@ bool using_sensorless(Motor_t* motor);
|
||||
float get_rotor_phase(Motor_t* motor);
|
||||
float get_pll_vel(Motor_t* motor);
|
||||
bool spin_up_sensorless(Motor_t* motor);
|
||||
void update_brake_current(float brake_current);
|
||||
void update_brake_current();
|
||||
void set_brake_current(float brake_current);
|
||||
void queue_modulation_timings(Motor_t* motor, float mod_alpha, float mod_beta);
|
||||
void queue_voltage_timings(Motor_t* motor, float v_alpha, float v_beta);
|
||||
bool FOC_current(Motor_t* motor, float Id_des, float Iq_des);
|
||||
|
||||
@@ -335,6 +335,10 @@ inline const char* get_default_json_modifier<int32_t>() {
|
||||
return "\"type\":\"int32\",\"access\":\"rw\"";
|
||||
}
|
||||
template<>
|
||||
inline const char* get_default_json_modifier<const uint32_t>() {
|
||||
return "\"type\":\"uint32\",\"access\":\"r\"";
|
||||
}
|
||||
template<>
|
||||
inline const char* get_default_json_modifier<const uint16_t>() {
|
||||
return "\"type\":\"uint16\",\"access\":\"r\"";
|
||||
}
|
||||
|
||||
+22
-10
@@ -69,6 +69,7 @@ You must set:
|
||||
The most important parameters are the limits:
|
||||
* The current limit: `.current_lim = 75.0f, //[A] // Note: consistent with 40v/v gain`. The default current limit, for safety reasons, is set to 10A. This is quite weak, and good for making sure the drive is stable. Once you have tuned the drive, you can increase this to 75A to get some performance. Note that above 75A, you must change the current amplifier gains.
|
||||
* The velocity limit: `.vel_limit = 20000.0f, // [counts/s]`. The motor will be limited to this speed; again the default value is quite slow.
|
||||
* 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.
|
||||
|
||||
The motion control gains are currently manually tuned:
|
||||
* `.pos_gain = 20.0f, // [(counts/s) / counts]`
|
||||
@@ -134,7 +135,7 @@ After installing all of the above, open a Git Bash shell. Continue at section [B
|
||||
|
||||
### Flashing the firmware
|
||||
* **Make sure you have [configured the parameters first](#configuring-parameters)**
|
||||
* Connect `SWD`, `SWC`, and `GND` on connector J2 to the programmer.
|
||||
* Connect `GND`, `SWD`, and `SWC` on connector J2 to the programmer. Note: Always plug in `GND` first!
|
||||
* You need to power the board by only **ONE** of the following: VCC(3.3v), 5V, or the main power connection (the DC bus). The USB port (J1) does not power the board.
|
||||
* Run `make flash` in the root of this repository.
|
||||
|
||||
@@ -145,26 +146,37 @@ If the flashing worked, you can start sending commands. If you want to do that n
|
||||
|
||||
<br><br>
|
||||
## Communicating over USB or UART
|
||||
Warning: If testing USB or UART communication for the first time it is recommend that your motors are free to spin continuously and are not connected to a drivetrain with limited travel.
|
||||
### From Linux/Windows/macOS
|
||||
There are two simple python scripts to help you get started with controlling the ODrive using python.
|
||||
There are two example python scripts to help you get started with controlling the ODrive using python. One will drop you into an interactive shell to query settings, parameters, and variables, and let you send setpoints manually ([tools/explore_odrive.py](tools/explore_odrive.py)). The other is a demo application to show you how to control the ODrive programmatically ([tools/demo.py](tools/demo.py)). Below follows a step-by-step guide on how to run these.
|
||||
|
||||
1. [Install Python 3](https://www.python.org/downloads/), then install dependencies:
|
||||
|
||||
* __Windows__: It is recommended to use a Unix style command prompt, such as Git Bash that comes with [Git for windows](https://git-scm.com/download/win).
|
||||
|
||||
1. [Install Python 3](https://www.python.org/downloads/), then install dependencies pyusb and pyserial:
|
||||
```
|
||||
pip install pyusb pyserial
|
||||
```
|
||||
3. __Linux__: set up USB permissions
|
||||
* Note: If you have python2 and python3 installed concurrently then you must specifiy that we wish to target python3. This is done as follows:
|
||||
* __Linux__: Use `pip3` instead of `pip` in the above command.
|
||||
* __Windows__: Use the full path of the Python3 pip, yeilding something like:
|
||||
`C:\Users\YOUR_USERNAME\AppData\Local\Programs\Python\Python36-32\Scripts\pip install pyusb pyserial`
|
||||
* If you have trouble with this step then refer to [this walkthrough.](https://www.youtube.com/watch?v=jnpC_Ib_lbc)
|
||||
|
||||
2. __Linux__: set up USB permissions
|
||||
```
|
||||
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d[0-9][0-9]", MODE="0666"' | sudo tee /etc/udev/rules.d/50-odrive.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger # until you reboot you may need to do this everytime you reset the ODrive
|
||||
```
|
||||
4. Power the ODrive board (as per the [Flashing the firmware](#flashing-the-firmware) step)
|
||||
5. Plug in a USB cable into the microUSB connector on ODrive, and connect it to your PC
|
||||
6. __Windows__: Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive (not STLink!) driver to libusb.
|
||||
3. Power the ODrive board (as per the [Flashing the firmware](#flashing-the-firmware) step).
|
||||
4. Plug in a USB cable into the microUSB connector on ODrive, and connect it to your PC.
|
||||
5. __Windows__: Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive (not STLink!) driver to libusb-win32.
|
||||
* If 'Odrive V3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button.
|
||||
7. Run `./tools/demo.py` or `./tools/explore_odrive.py`.
|
||||
- `demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically.
|
||||
- `explore_odrive.py` drops you into an interactive python shell where you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/explore_odrive.py --discover serial`.
|
||||
6. Open the bash prompt in the `ODrive/tools/` folder.
|
||||
7. Run `python3 demo.py` or `python3 explore_odrive.py`.
|
||||
- `demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically.
|
||||
- `explore_odrive.py` drops you into an interactive python shell where you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/explore_odrive.py --discover serial`.
|
||||
|
||||
### From Arduino
|
||||
[See ODrive Arduino Library](https://github.com/madcowswe/ODriveArduino)
|
||||
|
||||
Reference in New Issue
Block a user