mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-09-21 15:34:33 +08:00
Merge remote-tracking branch 'madcowswe/devel' into Endstops
This commit is contained in:
+7
-2
@@ -1,7 +1,12 @@
|
||||
# Unreleased Features
|
||||
Please add a note of your changes below this heading if you make a Pull Request.
|
||||
|
||||
## Unreleased
|
||||
# Unreleased
|
||||
## Fixed
|
||||
* Serious reliability issue with USB communication where packets on Native and the CDC interface would collide with each other.
|
||||
|
||||
# Releases
|
||||
## [0.4.3] - 2018-08-30
|
||||
### Added
|
||||
* `min_endstop` and `max_endstop` objects can be configured on GPIO
|
||||
* Axes can be homed if `min_endstop` is enabled
|
||||
@@ -16,7 +21,6 @@ Please add a note of your changes below this heading if you make a Pull Request.
|
||||
* Once you got an axis error `ERROR_INVALID_STATE` you could never clear it
|
||||
* Char to int conversion to read motornum on arduino example
|
||||
|
||||
# Releases
|
||||
## [0.4.2] - 2018-08-04
|
||||
### Added
|
||||
* Hall sensor feedback
|
||||
@@ -26,6 +30,7 @@ Please add a note of your changes below this heading if you make a Pull Request.
|
||||
* `motor.config.requested_current_range`
|
||||
* `motor.config.current_control_bandwidth` and `motor.set_current_control_bandwidth`. Latter required to invoke gain recalculation.
|
||||
* `encoder.config.bandwidth`
|
||||
* `sensorless_estimator.config.pm_flux_linkage`
|
||||
|
||||
## [0.4.1] - 2018-07-01
|
||||
### Fixed
|
||||
|
||||
+1
@@ -29,6 +29,7 @@
|
||||
"HW_VERSION_MAJOR=3",
|
||||
"HW_VERSION_MINOR=5",
|
||||
"HW_VERSION_VOLTAGE=24",
|
||||
"USB_PROTOCOL_NATIVE",
|
||||
"__weak=\"__attribute__((weak))\"",
|
||||
"__packed=\"__attribute__((__packed__))\"",
|
||||
"__GNUC__"
|
||||
|
||||
+17
-10
@@ -109,19 +109,25 @@ typedef struct _USBD_CDC_Itf
|
||||
|
||||
}USBD_CDC_ItfTypeDef;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t* Buffer;
|
||||
uint32_t Length;
|
||||
volatile uint8_t State;
|
||||
}
|
||||
USBD_CDC_EP_HandleTypeDef;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t data[CDC_DATA_HS_MAX_PACKET_SIZE/4]; /* Force 32bits alignment */
|
||||
uint8_t CmdOpCode;
|
||||
uint8_t CmdLength;
|
||||
uint8_t *RxBuffer;
|
||||
uint8_t *TxBuffer;
|
||||
uint32_t RxLength;
|
||||
uint32_t TxLength;
|
||||
|
||||
__IO uint32_t TxState;
|
||||
__IO uint32_t RxState;
|
||||
uint8_t CmdLength;
|
||||
|
||||
USBD_CDC_EP_HandleTypeDef CDC_Tx;
|
||||
USBD_CDC_EP_HandleTypeDef CDC_Rx;
|
||||
|
||||
USBD_CDC_EP_HandleTypeDef ODRIVE_Tx;
|
||||
USBD_CDC_EP_HandleTypeDef ODRIVE_Rx;
|
||||
}
|
||||
USBD_CDC_HandleTypeDef;
|
||||
|
||||
@@ -153,10 +159,11 @@ uint8_t USBD_CDC_RegisterInterface (USBD_HandleTypeDef *pdev,
|
||||
|
||||
uint8_t USBD_CDC_SetTxBuffer (USBD_HandleTypeDef *pdev,
|
||||
uint8_t *pbuff,
|
||||
uint16_t length);
|
||||
uint16_t length,
|
||||
uint8_t endpoint_pair);
|
||||
|
||||
uint8_t USBD_CDC_SetRxBuffer (USBD_HandleTypeDef *pdev,
|
||||
uint8_t *pbuff);
|
||||
uint8_t *pbuff, uint8_t endpoint_pair);
|
||||
|
||||
uint8_t USBD_CDC_ReceivePacket (USBD_HandleTypeDef *pdev, uint8_t endpoint_pair);
|
||||
|
||||
|
||||
+89
-48
@@ -292,9 +292,9 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
|
||||
0x0B, /* bDescriptorType: Interface Association Descriptor */
|
||||
0x02, /* bFirstInterface */
|
||||
0x01, /* bInterfaceCount */
|
||||
0x00, /* bFunctionClass: Communication Interface Class */
|
||||
0x00, /* bFunctionSubClass: Abstract Control Model */
|
||||
0x00, /* bFunctionProtocol: Common AT commands */
|
||||
0x00, /* bFunctionClass: */
|
||||
0x00, /* bFunctionSubClass: */
|
||||
0x00, /* bFunctionProtocol: */
|
||||
0x06, /* iFunction */
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
@@ -414,15 +414,17 @@ static uint8_t USBD_CDC_Init (USBD_HandleTypeDef *pdev,
|
||||
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Init();
|
||||
|
||||
/* Init Xfer states */
|
||||
hcdc->TxState =0;
|
||||
hcdc->RxState =0;
|
||||
hcdc->CDC_Tx.State =0;
|
||||
hcdc->CDC_Rx.State =0;
|
||||
hcdc->ODRIVE_Tx.State =0;
|
||||
hcdc->ODRIVE_Rx.State =0;
|
||||
|
||||
if(pdev->dev_speed == USBD_SPEED_HIGH )
|
||||
{
|
||||
/* Prepare Out endpoint to receive next packet */
|
||||
USBD_LL_PrepareReceive(pdev,
|
||||
CDC_OUT_EP,
|
||||
hcdc->RxBuffer,
|
||||
hcdc->CDC_Rx.Buffer,
|
||||
CDC_DATA_HS_OUT_PACKET_SIZE);
|
||||
}
|
||||
else
|
||||
@@ -430,14 +432,14 @@ static uint8_t USBD_CDC_Init (USBD_HandleTypeDef *pdev,
|
||||
/* Prepare Out endpoint to receive next packet */
|
||||
USBD_LL_PrepareReceive(pdev,
|
||||
CDC_OUT_EP,
|
||||
hcdc->RxBuffer,
|
||||
hcdc->CDC_Rx.Buffer,
|
||||
CDC_DATA_FS_OUT_PACKET_SIZE);
|
||||
}
|
||||
|
||||
/* Prepare ODrive Out endpoint to receive next packet */
|
||||
USBD_LL_PrepareReceive(pdev,
|
||||
ODRIVE_OUT_EP,
|
||||
hcdc->RxBuffer,
|
||||
hcdc->ODRIVE_Rx.Buffer,
|
||||
CDC_DATA_FS_OUT_PACKET_SIZE);
|
||||
}
|
||||
return ret;
|
||||
@@ -568,8 +570,12 @@ static uint8_t USBD_CDC_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum)
|
||||
|
||||
if(pdev->pClassData != NULL)
|
||||
{
|
||||
|
||||
hcdc->TxState = 0;
|
||||
// NOTE: We would logically expect xx_IN_EP here, but we actually get the xx_OUT_EP
|
||||
if (epnum == CDC_OUT_EP)
|
||||
hcdc->CDC_Tx.State = 0;
|
||||
if (epnum == ODRIVE_OUT_EP)
|
||||
hcdc->ODRIVE_Tx.State = 0;
|
||||
//Note: We could use independent semaphores for simoultainous USB transmission.
|
||||
osSemaphoreRelease(sem_usb_tx);
|
||||
return USBD_OK;
|
||||
}
|
||||
@@ -589,15 +595,24 @@ static uint8_t USBD_CDC_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum)
|
||||
static uint8_t USBD_CDC_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum)
|
||||
{
|
||||
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;
|
||||
|
||||
USBD_CDC_EP_HandleTypeDef* hEP_Rx;
|
||||
if (epnum == CDC_OUT_EP) {
|
||||
hEP_Rx = &hcdc->CDC_Rx;
|
||||
} else if (epnum == ODRIVE_OUT_EP) {
|
||||
hEP_Rx = &hcdc->ODRIVE_Rx;
|
||||
} else {
|
||||
return USBD_FAIL;
|
||||
}
|
||||
|
||||
/* Get the received data length */
|
||||
hcdc->RxLength = USBD_LL_GetRxDataSize (pdev, epnum);
|
||||
hEP_Rx->Length = USBD_LL_GetRxDataSize (pdev, epnum);
|
||||
|
||||
/* USB data will be immediately processed, this allow next USB traffic being
|
||||
NAKed till the end of the application Xfer */
|
||||
if(pdev->pClassData != NULL)
|
||||
{
|
||||
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength, epnum);
|
||||
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hEP_Rx->Buffer, &hEP_Rx->Length, epnum);
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
@@ -710,12 +725,22 @@ uint8_t USBD_CDC_RegisterInterface (USBD_HandleTypeDef *pdev,
|
||||
*/
|
||||
uint8_t USBD_CDC_SetTxBuffer (USBD_HandleTypeDef *pdev,
|
||||
uint8_t *pbuff,
|
||||
uint16_t length)
|
||||
uint16_t length,
|
||||
uint8_t endpoint_pair)
|
||||
{
|
||||
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;
|
||||
|
||||
hcdc->TxBuffer = pbuff;
|
||||
hcdc->TxLength = length;
|
||||
USBD_CDC_EP_HandleTypeDef* hEP_Tx;
|
||||
if (endpoint_pair == CDC_OUT_EP) {
|
||||
hEP_Tx = &hcdc->CDC_Tx;
|
||||
} else if (endpoint_pair == ODRIVE_OUT_EP) {
|
||||
hEP_Tx = &hcdc->ODRIVE_Tx;
|
||||
} else {
|
||||
return USBD_FAIL;
|
||||
}
|
||||
|
||||
hEP_Tx->Buffer = pbuff;
|
||||
hEP_Tx->Length = length;
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
@@ -728,11 +753,20 @@ uint8_t USBD_CDC_SetTxBuffer (USBD_HandleTypeDef *pdev,
|
||||
* @retval status
|
||||
*/
|
||||
uint8_t USBD_CDC_SetRxBuffer (USBD_HandleTypeDef *pdev,
|
||||
uint8_t *pbuff)
|
||||
uint8_t *pbuff, uint8_t endpoint_pair)
|
||||
{
|
||||
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;
|
||||
|
||||
USBD_CDC_EP_HandleTypeDef* hEP_Rx;
|
||||
if (endpoint_pair == CDC_OUT_EP) {
|
||||
hEP_Rx = &hcdc->CDC_Rx;
|
||||
} else if (endpoint_pair == ODRIVE_OUT_EP) {
|
||||
hEP_Rx = &hcdc->ODRIVE_Rx;
|
||||
} else {
|
||||
return USBD_FAIL;
|
||||
}
|
||||
|
||||
hcdc->RxBuffer = pbuff;
|
||||
hEP_Rx->Buffer = pbuff;
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
@@ -750,25 +784,30 @@ uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev, uint8_t endpoint_pair
|
||||
|
||||
if(pdev->pClassData != NULL)
|
||||
{
|
||||
if(hcdc->TxState == 0)
|
||||
// Select Endpoint
|
||||
USBD_CDC_EP_HandleTypeDef* hEP_Tx;
|
||||
uint8_t in_ep;
|
||||
if (endpoint_pair == CDC_OUT_EP) {
|
||||
hEP_Tx = &hcdc->CDC_Tx;
|
||||
in_ep = CDC_IN_EP;
|
||||
} else if (endpoint_pair == ODRIVE_OUT_EP) {
|
||||
hEP_Tx = &hcdc->ODRIVE_Tx;
|
||||
in_ep = ODRIVE_IN_EP;
|
||||
} else {
|
||||
return USBD_FAIL;
|
||||
}
|
||||
|
||||
if(hEP_Tx->State == 0)
|
||||
{
|
||||
/* Tx Transfer in progress */
|
||||
hcdc->TxState = 1;
|
||||
|
||||
//endpoint_pair = 1;
|
||||
if (endpoint_pair == 1) {
|
||||
/* Transmit next packet */
|
||||
USBD_LL_Transmit(pdev,
|
||||
CDC_IN_EP,
|
||||
hcdc->TxBuffer,
|
||||
hcdc->TxLength);
|
||||
} else if (endpoint_pair == 3) {
|
||||
USBD_LL_Transmit(pdev,
|
||||
ODRIVE_IN_EP,
|
||||
hcdc->TxBuffer,
|
||||
hcdc->TxLength);
|
||||
}
|
||||
hEP_Tx->State = 1;
|
||||
|
||||
/* Transmit next packet */
|
||||
USBD_LL_Transmit(pdev,
|
||||
in_ep,
|
||||
hEP_Tx->Buffer,
|
||||
hEP_Tx->Length);
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
else
|
||||
@@ -796,23 +835,25 @@ uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev, uint8_t endpoint_pair)
|
||||
/* Suspend or Resume USB Out process */
|
||||
if(pdev->pClassData != NULL)
|
||||
{
|
||||
if (endpoint_pair == CDC_OUT_EP)
|
||||
{
|
||||
/* Prepare Out endpoint to receive next packet */
|
||||
USBD_LL_PrepareReceive(pdev,
|
||||
CDC_OUT_EP,
|
||||
hcdc->RxBuffer,
|
||||
pdev->dev_speed == USBD_SPEED_HIGH ? CDC_DATA_HS_OUT_PACKET_SIZE : CDC_DATA_FS_OUT_PACKET_SIZE);
|
||||
}
|
||||
else if (endpoint_pair == ODRIVE_OUT_EP)
|
||||
{
|
||||
/* Prepare ODrive Out endpoint to receive next packet */
|
||||
USBD_LL_PrepareReceive(pdev,
|
||||
ODRIVE_OUT_EP,
|
||||
hcdc->RxBuffer,
|
||||
pdev->dev_speed == USBD_SPEED_HIGH ? CDC_DATA_HS_OUT_PACKET_SIZE : CDC_DATA_FS_OUT_PACKET_SIZE);
|
||||
// Select Endpoint
|
||||
USBD_CDC_EP_HandleTypeDef* hEP_Rx;
|
||||
uint8_t out_ep;
|
||||
if (endpoint_pair == CDC_OUT_EP) {
|
||||
hEP_Rx = &hcdc->CDC_Rx;
|
||||
out_ep = CDC_OUT_EP;
|
||||
} else if (endpoint_pair == ODRIVE_OUT_EP) {
|
||||
hEP_Rx = &hcdc->ODRIVE_Rx;
|
||||
out_ep = ODRIVE_OUT_EP;
|
||||
} else {
|
||||
return USBD_FAIL;
|
||||
}
|
||||
|
||||
/* Prepare Out endpoint to receive next packet */
|
||||
USBD_LL_PrepareReceive(pdev,
|
||||
out_ep,
|
||||
hEP_Rx->Buffer,
|
||||
pdev->dev_speed == USBD_SPEED_HIGH ? CDC_DATA_HS_OUT_PACKET_SIZE : CDC_DATA_FS_OUT_PACKET_SIZE);
|
||||
|
||||
return USBD_OK;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -117,10 +117,12 @@
|
||||
/* Create buffer for reception and transmission */
|
||||
/* It's up to user to redefine and/or remove those define */
|
||||
/** Received data over USB are stored in this buffer */
|
||||
uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
|
||||
uint8_t CDCRxBufferFS[APP_RX_DATA_SIZE];
|
||||
uint8_t ODRIVERxBufferFS[APP_RX_DATA_SIZE];
|
||||
|
||||
/** Data to send over USB CDC are stored in this buffer */
|
||||
uint8_t UserTxBufferFS[APP_TX_DATA_SIZE];
|
||||
uint8_t CDCTxBufferFS[APP_TX_DATA_SIZE];
|
||||
uint8_t ODRIVETxBufferFS[APP_TX_DATA_SIZE];
|
||||
|
||||
/* USER CODE BEGIN PRIVATE_VARIABLES */
|
||||
/* USER CODE END PRIVATE_VARIABLES */
|
||||
@@ -177,8 +179,10 @@ static int8_t CDC_Init_FS(void)
|
||||
{
|
||||
/* USER CODE BEGIN 3 */
|
||||
/* Set Application Buffers */
|
||||
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, 0);
|
||||
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
|
||||
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, CDCTxBufferFS, 0, CDC_OUT_EP);
|
||||
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, CDCRxBufferFS, CDC_OUT_EP);
|
||||
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, ODRIVETxBufferFS, 0, ODRIVE_OUT_EP);
|
||||
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, ODRIVERxBufferFS, ODRIVE_OUT_EP);
|
||||
return (USBD_OK);
|
||||
/* USER CODE END 3 */
|
||||
}
|
||||
@@ -290,7 +294,7 @@ static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length)
|
||||
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len, uint8_t endpoint_pair)
|
||||
{
|
||||
/* USER CODE BEGIN 6 */
|
||||
usb_process_packet(Buf, *Len, endpoint_pair);
|
||||
usb_rx_process_packet(Buf, *Len, endpoint_pair);
|
||||
|
||||
return (USBD_OK);
|
||||
/* USER CODE END 6 */
|
||||
@@ -315,14 +319,29 @@ uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len, uint8_t endpoint_pair)
|
||||
//Check length
|
||||
if (Len > USB_TX_DATA_SIZE)
|
||||
return USBD_FAIL;
|
||||
// Check for ongoing transmission
|
||||
|
||||
USBD_CDC_HandleTypeDef* hcdc = (USBD_CDC_HandleTypeDef*) hUsbDeviceFS.pClassData;
|
||||
if (hcdc->TxState != 0)
|
||||
return USBD_BUSY;
|
||||
|
||||
// Select EP
|
||||
USBD_CDC_EP_HandleTypeDef* hEP_Tx;
|
||||
uint8_t* TxBuff;
|
||||
if (endpoint_pair == CDC_OUT_EP) {
|
||||
hEP_Tx = &hcdc->CDC_Tx;
|
||||
TxBuff = CDCTxBufferFS;
|
||||
} else if (endpoint_pair == ODRIVE_OUT_EP) {
|
||||
hEP_Tx = &hcdc->ODRIVE_Tx;
|
||||
TxBuff = ODRIVETxBufferFS;
|
||||
} else {
|
||||
return USBD_FAIL;
|
||||
}
|
||||
|
||||
// Check for ongoing transmission
|
||||
if (hEP_Tx->State != 0)
|
||||
return USBD_BUSY;
|
||||
// memcpy Buf into UserTxBufferFS
|
||||
memcpy(UserTxBufferFS, Buf, Len);
|
||||
memcpy(TxBuff, Buf, Len);
|
||||
// Update Len
|
||||
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, Len);
|
||||
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, TxBuff, Len, endpoint_pair);
|
||||
result = USBD_CDC_TransmitPacket(&hUsbDeviceFS, endpoint_pair);
|
||||
/* USER CODE END 7 */
|
||||
return result;
|
||||
|
||||
@@ -90,12 +90,9 @@ public:
|
||||
make_protocol_property("homing_speed", &config_.homing_speed)
|
||||
),
|
||||
make_protocol_function("set_pos_setpoint", *this, &Controller::set_pos_setpoint,
|
||||
"pos_setpoint",
|
||||
"vel_feed_forward",
|
||||
"current_feed_forward"),
|
||||
"pos_setpoint", "vel_feed_forward", "current_feed_forward"),
|
||||
make_protocol_function("set_vel_setpoint", *this, &Controller::set_vel_setpoint,
|
||||
"vel_setpoint",
|
||||
"current_feed_forward"),
|
||||
"vel_setpoint", "current_feed_forward"),
|
||||
make_protocol_function("set_current_setpoint", *this, &Controller::set_current_setpoint,
|
||||
"current_setpoint"),
|
||||
make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration),
|
||||
|
||||
@@ -19,7 +19,7 @@ static uint8_t dma_rx_buffer[UART_RX_BUFFER_SIZE];
|
||||
static uint32_t dma_last_rcv_idx;
|
||||
|
||||
// FIXME: the stdlib doesn't know about CMSIS threads, so this is just a global variable
|
||||
static thread_local uint32_t deadline_ms = 0;
|
||||
// static thread_local uint32_t deadline_ms = 0;
|
||||
|
||||
osThreadId uart_thread;
|
||||
|
||||
@@ -32,7 +32,8 @@ public:
|
||||
size_t chunk = length < UART_TX_BUFFER_SIZE ? length : UART_TX_BUFFER_SIZE;
|
||||
// wait for USB interface to become ready
|
||||
// TODO: implement ring buffer to get a more continuous stream of data
|
||||
if (osSemaphoreWait(sem_uart_dma, deadline_to_timeout(deadline_ms)) != osOK)
|
||||
// if (osSemaphoreWait(sem_uart_dma, deadline_to_timeout(deadline_ms)) != osOK)
|
||||
if (osSemaphoreWait(sem_uart_dma, PROTOCOL_SERVER_TIMEOUT_MS) != osOK)
|
||||
return -1;
|
||||
// transmit chunk
|
||||
memcpy(tx_buf_, buffer, chunk);
|
||||
@@ -68,7 +69,7 @@ static void uart_server_thread(void * ctx) {
|
||||
// Fetch the circular buffer "write pointer", where it would write next
|
||||
uint32_t new_rcv_idx = UART_RX_BUFFER_SIZE - huart4.hdmarx->Instance->NDTR;
|
||||
|
||||
deadline_ms = timeout_to_deadline(PROTOCOL_SERVER_TIMEOUT_MS);
|
||||
// deadline_ms = timeout_to_deadline(PROTOCOL_SERVER_TIMEOUT_MS);
|
||||
// Process bytes in one or two chunks (two in case there was a wrap)
|
||||
if (new_rcv_idx < dma_last_rcv_idx) {
|
||||
uart4_stream_input.process_bytes(dma_rx_buffer + dma_last_rcv_idx,
|
||||
|
||||
@@ -13,27 +13,22 @@
|
||||
|
||||
#include <odrive_main.h>
|
||||
|
||||
static uint8_t* usb_buf;
|
||||
static uint32_t usb_len;
|
||||
static uint8_t active_endpoint_pair;
|
||||
|
||||
// FIXME: the stdlib doesn't know about CMSIS threads, so this is just a global variable
|
||||
static thread_local uint32_t deadline_ms = 0;
|
||||
|
||||
osThreadId usb_thread;
|
||||
|
||||
USBStats_t usb_stats_ = {0};
|
||||
|
||||
class USBSender : public PacketSink {
|
||||
public:
|
||||
USBSender(uint8_t endpoint_pair, const osSemaphoreId& sem_usb_tx)
|
||||
: endpoint_pair_(endpoint_pair), sem_usb_tx_(sem_usb_tx) {}
|
||||
|
||||
int process_packet(const uint8_t* buffer, size_t length) {
|
||||
// cannot send partial packets
|
||||
if (length > USB_TX_DATA_SIZE)
|
||||
return -1;
|
||||
// wait for USB interface to become ready
|
||||
if (osSemaphoreWait(sem_usb_tx, deadline_to_timeout(deadline_ms)) != osOK) {
|
||||
if (osSemaphoreWait(sem_usb_tx_, PROTOCOL_SERVER_TIMEOUT_MS) != osOK) {
|
||||
// If the host resets the device it might be that the TX-complete handler is never called
|
||||
// and the sem_usb_tx semaphore is never released. To handle this we just override the
|
||||
// and the sem_usb_tx_ semaphore is never released. To handle this we just override the
|
||||
// TX buffer if this wait times out. The implication is that the channel is no longer lossless.
|
||||
// TODO: handle endpoint reset properly
|
||||
usb_stats_.tx_overrun_cnt++;
|
||||
@@ -41,15 +36,22 @@ public:
|
||||
// transmit packet
|
||||
uint8_t status = CDC_Transmit_FS(
|
||||
const_cast<uint8_t*>(buffer) /* casting this const away is safe because...
|
||||
well... it's not actually. Stupid STM. */, length, active_endpoint_pair);
|
||||
well... it's not actually. Stupid STM. */, length, endpoint_pair_);
|
||||
if (status != USBD_OK) {
|
||||
osSemaphoreRelease(sem_usb_tx);
|
||||
osSemaphoreRelease(sem_usb_tx_);
|
||||
return -1;
|
||||
}
|
||||
usb_stats_.tx_cnt++;
|
||||
return 0;
|
||||
}
|
||||
} usb_packet_output;
|
||||
private:
|
||||
uint8_t endpoint_pair_;
|
||||
const osSemaphoreId& sem_usb_tx_;
|
||||
};
|
||||
|
||||
// Note we could have independent semaphores here to allow concurrent transmission
|
||||
USBSender usb_packet_output_cdc(CDC_OUT_EP, sem_usb_tx);
|
||||
USBSender usb_packet_output_native(ODRIVE_OUT_EP, sem_usb_tx);
|
||||
|
||||
class TreatPacketSinkAsStreamSink : public StreamSink {
|
||||
public:
|
||||
@@ -70,46 +72,106 @@ public:
|
||||
size_t get_free_space() { return SIZE_MAX; }
|
||||
private:
|
||||
PacketSink& output_;
|
||||
} usb_stream_output(usb_packet_output);
|
||||
} usb_stream_output(usb_packet_output_cdc);
|
||||
|
||||
// This is used by the printf feature. Hence the above statics, and below seemingly random ptr (it's externed)
|
||||
// TODO: less spaghetti code
|
||||
StreamSink* usb_stream_output_ptr = &usb_stream_output;
|
||||
|
||||
#if defined(USB_PROTOCOL_NATIVE)
|
||||
BidirectionalPacketBasedChannel usb_channel(usb_packet_output);
|
||||
BidirectionalPacketBasedChannel usb_channel(usb_packet_output_native);
|
||||
#elif defined(USB_PROTOCOL_NATIVE_STREAM_BASED)
|
||||
StreamBasedPacketSink usb_packetized_output(usb_stream_output);
|
||||
BidirectionalPacketBasedChannel usb_channel(usb_packetized_output);
|
||||
StreamToPacketSegmenter usb_native_stream_input(usb_channel);
|
||||
#endif
|
||||
|
||||
struct USBInterface {
|
||||
uint8_t* rx_buf = nullptr;
|
||||
uint32_t rx_len = 0;
|
||||
bool data_pending = false;
|
||||
uint8_t out_ep;
|
||||
uint8_t in_ep;
|
||||
USBSender& usb_sender;
|
||||
};
|
||||
|
||||
// Note: statics make this less modular.
|
||||
// Note: we use a single rx semaphore and loop over data_pending to allow a single pump loop thread
|
||||
static USBInterface CDC_interface = {
|
||||
.rx_buf = nullptr,
|
||||
.rx_len = 0,
|
||||
.data_pending = false,
|
||||
.out_ep = CDC_OUT_EP,
|
||||
.in_ep = CDC_IN_EP,
|
||||
.usb_sender = usb_packet_output_cdc,
|
||||
};
|
||||
static USBInterface ODrive_interface = {
|
||||
.rx_buf = nullptr,
|
||||
.rx_len = 0,
|
||||
.data_pending = false,
|
||||
.out_ep = ODRIVE_OUT_EP,
|
||||
.in_ep = ODRIVE_IN_EP,
|
||||
.usb_sender = usb_packet_output_native,
|
||||
};
|
||||
|
||||
static void usb_server_thread(void * ctx) {
|
||||
(void) ctx;
|
||||
|
||||
for (;;) {
|
||||
const uint32_t usb_check_timeout = 1; // ms
|
||||
osStatus sem_stat = osSemaphoreWait(sem_usb_rx, usb_check_timeout);
|
||||
// const uint32_t usb_check_timeout = 1; // ms
|
||||
osStatus sem_stat = osSemaphoreWait(sem_usb_rx, osWaitForever);
|
||||
if (sem_stat == osOK) {
|
||||
usb_stats_.rx_cnt++;
|
||||
deadline_ms = timeout_to_deadline(PROTOCOL_SERVER_TIMEOUT_MS);
|
||||
if (active_endpoint_pair == CDC_OUT_EP && board_config.enable_ascii_protocol_on_usb) {
|
||||
ASCII_protocol_parse_stream(usb_buf, usb_len, usb_stream_output);
|
||||
} else {
|
||||
|
||||
// CDC Interface
|
||||
if (CDC_interface.data_pending) {
|
||||
CDC_interface.data_pending = false;
|
||||
if (board_config.enable_ascii_protocol_on_usb) {
|
||||
ASCII_protocol_parse_stream(CDC_interface.rx_buf,
|
||||
CDC_interface.rx_len, usb_stream_output);
|
||||
} else {
|
||||
#if defined(USB_PROTOCOL_NATIVE)
|
||||
usb_channel.process_packet(usb_buf, usb_len);
|
||||
usb_channel.process_packet(CDC_interface.rx_buf, CDC_interface.rx_len);
|
||||
#elif defined(USB_PROTOCOL_NATIVE_STREAM_BASED)
|
||||
usb_native_stream_input.process_bytes(usb_buf, usb_len, nullptr);
|
||||
usb_native_stream_input.process_bytes(
|
||||
CDC_interface.rx_buf, CDC_interface.rx_len, nullptr);
|
||||
#endif
|
||||
}
|
||||
USBD_CDC_ReceivePacket(&hUsbDeviceFS, CDC_interface.out_ep); // Allow next packet
|
||||
}
|
||||
|
||||
// Native Interface
|
||||
if (ODrive_interface.data_pending) {
|
||||
ODrive_interface.data_pending = false;
|
||||
#if defined(USB_PROTOCOL_NATIVE)
|
||||
usb_channel.process_packet(ODrive_interface.rx_buf, ODrive_interface.rx_len);
|
||||
#elif defined(USB_PROTOCOL_NATIVE_STREAM_BASED)
|
||||
usb_native_stream_input.process_bytes(
|
||||
ODrive_interface.rx_buf, ODrive_interface.rx_len, nullptr);
|
||||
#endif
|
||||
USBD_CDC_ReceivePacket(&hUsbDeviceFS, ODrive_interface.out_ep); // Allow next packet
|
||||
}
|
||||
USBD_CDC_ReceivePacket(&hUsbDeviceFS, active_endpoint_pair); // Allow next packet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called from CDC_Receive_FS callback function, this allows the communication
|
||||
// thread to handle the incoming data
|
||||
void usb_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair) {
|
||||
usb_buf = buf;
|
||||
usb_len = len;
|
||||
active_endpoint_pair = endpoint_pair;
|
||||
void usb_rx_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair) {
|
||||
USBInterface* usb_iface;
|
||||
if (endpoint_pair == CDC_interface.out_ep) {
|
||||
usb_iface = &CDC_interface;
|
||||
} else if (endpoint_pair == ODrive_interface.out_ep) {
|
||||
usb_iface = &ODrive_interface;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't allow the next USB packet until the previous one has been processed completely.
|
||||
// Therefore it's safe to write to these vars directly since we know previous processing is complete.
|
||||
usb_iface->rx_buf = buf;
|
||||
usb_iface->rx_len = len;
|
||||
usb_iface->data_pending = true;
|
||||
osSemaphoreRelease(sem_usb_rx);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ typedef struct {
|
||||
|
||||
extern USBStats_t usb_stats_;
|
||||
|
||||
void usb_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair);
|
||||
void usb_rx_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair);
|
||||
void start_usb_server(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
+30
-3
@@ -2,6 +2,16 @@
|
||||
|
||||
We will use the `<odrv>` as a placeholder for any ODrive object. Every ODrive controller is an ODrive object. In `odrivetool` this is usually `odrv0`. Furthermore we use `<axis>` as a placeholder for any axis, which is an attribute of an ODrive object (for example `odrv0.axis0`). An axis represents where the motors are connected. (axis0 for M0 or axis1 for M1)
|
||||
|
||||
### Table of contents
|
||||
<!-- TOC depthFrom:2 depthTo:2 -->
|
||||
|
||||
- [Per-Axis commands](#per-axis-commands)
|
||||
- [System monitoring commands](#system-monitoring-commands)
|
||||
- [General system commands](#general-system-commands)
|
||||
- [Setting up sensorless](#setting-up-sensorless)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## Per-Axis commands
|
||||
|
||||
For the most part, both axes on the ODrive can be controlled independently.
|
||||
@@ -71,6 +81,18 @@ An upcoming feature will enable automatic tuning. Until then, here is a rough tu
|
||||
* Back down `pos_gain` until you do not have overshoot anymore.
|
||||
* The integrator is not easily tuned, nor is it strictly required. Tune at your own discretion.
|
||||
|
||||
## System monitoring commands
|
||||
|
||||
### Encoder position and velocity
|
||||
* View encoder position with `<axis>.encoder.pos_estimate` [counts]
|
||||
* View rotational velocity with `<axis>.encoder.vel_estimate` [counts/s]
|
||||
|
||||
### Motor current and torque estimation
|
||||
* View the commanded motor current with `<axis>.motor.current_control.Iq_setpoint` [A]
|
||||
* View the measured motor current with `<axis>.motor.current_control.Iq_measured` [A]. If you find that this returns noisy data then use the command motor current instead. The two values should be close so long as you are not approching the maximim achieveable rotational velocity of your motor for a given supply votlage, in which case the commanded current may become larger than the measured current.
|
||||
|
||||
Using the motor current and the known KV of your motor you can estimate the motors torque using the following relationship: Torque [N.m] = 8.27 * Current [A] / KV.
|
||||
|
||||
## General system commands
|
||||
|
||||
### Saving the configuration
|
||||
@@ -88,10 +110,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.
|
||||
However the
|
||||
However the units of this mode is different from when using an encoder. Velocities are not measured in counts/s, instead it is electrical rad/s. This also applies to the gains. For example, `vel_gain` is in units of `A / (rad/s)` instead of `A / (count/s)`.
|
||||
|
||||
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 `vel_setpoint` 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.
|
||||
|
||||
```
|
||||
odrv0.axis0.controller.config.vel_gain = 0.1
|
||||
odrv0.axis0.controller.config.vel_integrator_gain = 0
|
||||
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.vel_setpoint = 400
|
||||
odrv0.axis0.sensorless_estimator.config.pm_flux_linkage = 5.51328895422 / (<pole pairs> * <motor kv>)
|
||||
|
||||
@@ -11,10 +11,8 @@ Before doing the VSCode setup, make sure you've installed all of your [prerequis
|
||||
1. Install extensions. This can be done directly from VSCode (Ctrl+Shift+X)
|
||||
* Required extensions:
|
||||
* C/C++
|
||||
* Recommended Extensions:
|
||||
* Cortex-Debug
|
||||
* vscode-icons
|
||||
* Code Outline
|
||||
* Recommended Extensions:
|
||||
* Include Autocomplete
|
||||
* Path Autocomplete
|
||||
* Auto Comment Blocks
|
||||
@@ -51,4 +49,4 @@ Note: If developing on Windows, you should have `arm-none-eabi-gdb` and `openOCD
|
||||
|
||||
## Cleaning the Build
|
||||
This sometimes needs to be done if you change branches.
|
||||
* Open a terminal (View -> Integrated Terminal) and enter `make clean`
|
||||
* Open a terminal (View -> Integrated Terminal) and enter `make clean`
|
||||
|
||||
+24
-3
@@ -4,6 +4,27 @@ The motor controller is a cascaded style position, velocity and current control
|
||||
|
||||

|
||||
|
||||
* The position controller is a P loop with a single proportional gain.
|
||||
* The velocity controller is a PI loop.
|
||||
* The current controller is a PI loop.
|
||||
### Position loop:
|
||||
The position controller is a P loop with a single proportional gain.
|
||||
```text
|
||||
pos_error = pos_setpoint - pos_feedback
|
||||
vel_cmd = pos_error * pos_gain + vel_feedforward
|
||||
```
|
||||
|
||||
### Velocity loop:
|
||||
The velocity controller is a PI loop.
|
||||
```text
|
||||
vel_error = vel_cmd - vel_feedback
|
||||
current_integral += vel_error * vel_integrator_gain
|
||||
current_cmd = vel_error * vel_gain + current_integral + current_feedforward
|
||||
```
|
||||
|
||||
### Current loop:
|
||||
The current controller is a PI loop.
|
||||
```text
|
||||
current_error = current_cmd - current_fb
|
||||
voltage_integral += current_error * current_integrator_gain
|
||||
voltage_cmd = current_error * current_gain + voltage_integral (+ voltage_feedforward when we have motor model)
|
||||
```
|
||||
|
||||
For more detail refer to [controller.cpp](https://github.com/madcowswe/ODrive/blob/master/Firmware/MotorControl/controller.cpp#L86).
|
||||
|
||||
@@ -52,8 +52,9 @@ $ python --version # should be 3.7 or later
|
||||
|
||||
#### Linux (Ubuntu)
|
||||
```bash
|
||||
sudo apt-get install gcc-arm-none-eabi
|
||||
sudo apt-get install gdb-arm-none-eabi
|
||||
sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-arm-embedded
|
||||
sudo apt-get install openocd
|
||||
sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get install tup
|
||||
```
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
+14
-12
@@ -7,8 +7,7 @@ permalink: /
|
||||
# Getting Started
|
||||
|
||||
### Table of contents
|
||||
|
||||
<!-- MarkdownTOC depth=2 autolink=true bracket=round -->
|
||||
<!-- TOC depthFrom:2 depthTo:2 -->
|
||||
|
||||
- [Hardware Requirements](#hardware-requirements)
|
||||
- [Wiring up the ODrive](#wiring-up-the-odrive)
|
||||
@@ -18,7 +17,7 @@ permalink: /
|
||||
- [Position control of M0](#position-control-of-m0)
|
||||
- [What's next?](#whats-next)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
<!-- /TOC -->
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
@@ -123,9 +122,9 @@ Try step 5 again
|
||||
2. Install the ODrive tools by opening a terminal and typing `pip install odrive` <kbd>Enter</kbd>
|
||||
3. __Linux__: set up USB permissions
|
||||
```bash
|
||||
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d[0-9][0-9]", MODE="0666"' | sudo tee /etc/udev/rules.d/50-odrive.rules
|
||||
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d[0-9][0-9]", MODE="0666"' | sudo tee /etc/udev/rules.d/91-odrive.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger # until you reboot you may need to do this everytime you reset the ODrive
|
||||
sudo udevadm trigger
|
||||
```
|
||||
|
||||
## Start `odrivetool`
|
||||
@@ -163,7 +162,7 @@ In the previous step we started `odrivetool`. In there, you can assign variables
|
||||
For instance, to set the current limit of M0 to 10A you would type: `odrv0.axis0.motor.config.current_lim = 10` <kbd>Enter</kbd>
|
||||
</div></details>
|
||||
|
||||
* The current limit: `odrv0.axis0.motor.config.current_lim` [A]. 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 current limit: `odrv0.axis0.motor.config.current_lim` [A]. 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. 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 configeration 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">
|
||||
The current in the motor is only connected to the current in the power supply _sometimes_ and other times it just cycles out of one phase and back in the other. This is what the modulation magnitude is (sometimes people call this duty cycle, but that's a bit confusing because we use SVM not straight PWM). When the modulation magnitude is 0, the average voltage seen across the motor phases is 0, and the motor current is never connected to the power supply. When the magnitude is 100%, it is always connected, and at 50% it's connected half the time, and cycled in just the motor half the time.
|
||||
@@ -171,11 +170,11 @@ For instance, to set the current limit of M0 to 10A you would type: `odrv0.axis0
|
||||
The largest effect on modulation magnitude is speed. There are other smaller factors, but in general: if the motor is still it's not unreasonable to have 50A in the motor from 5A on the power supply. When the motor is spinning close to top speed, the power supply current and the motor current will be somewhat close to each other.
|
||||
</div></details>
|
||||
* The velocity limit: `odrv0.axis0.controller.config.vel_limit` [counts/s]. The motor will be limited to this speed; again the default value is quite slow.
|
||||
* You can change `odrv0.axis0.motor.config.calibration_current` [A] to the largest value you feel comfortable leaving running through the motor continously when the motor is stationary.
|
||||
* You can change `odrv0.axis0.motor.config.calibration_current` [A] to the largest value you feel comfortable leaving running through the motor continously when the motor is stationary. If you are using a small motor (i.e. 15A current rated) you may need to reduce `calibration_current` to a value smaller than the default.
|
||||
|
||||
### 2. Set other hardware parameters:
|
||||
|
||||
* `odrv0.config.brake_resistance` [Ohm]: This is the resistance of the brake resistor. If you are not using it, you may set it to `0`.
|
||||
* `odrv0.config.brake_resistance` [Ohm]: This is the resistance of the brake resistor. If you are not using it, you may set it to `0`. Note that there may be some extra resistance in your wiring and in the screw terminals, so if you are getting issues while braking you may want to increase this parameter by around 0.05 ohm.
|
||||
* `odrv0.axis0.motor.config.pole_pairs`: This is the number of **magnet poles** in the rotor, **divided by two**. You can simply count the number of permanent magnets in the rotor, if you can see them. _Note: this is not the same as the number of coils in the stator._
|
||||
* `odrv0.axis0.motor.config.motor_type`: This is the type of motor being used. Currently two types of motors are supported: High-current motors (`MOTOR_TYPE_HIGH_CURRENT`) and Gimbal motors (`MOTOR_TYPE_GIMBAL`).
|
||||
|
||||
@@ -197,8 +196,11 @@ For instance, to set the current limit of M0 to 10A you would type: `odrv0.axis0
|
||||
|
||||
|
||||
### 3. Save configuration.
|
||||
You can save all `.config` parameters to persistent memory such that the ODrive remembers them between power cycles.
|
||||
* `odrv0.save_configuration()` <kbd>Enter</kbd>
|
||||
You can save all `.config` parameters to persistent memory such that the ODrive remembers them between power cycles.
|
||||
* `odrv0.save_configuration()` <kbd>Enter</kbd>.
|
||||
|
||||
Due to a [known issue](https://github.com/madcowswe/ODrive/issues/183) it is strongly recommended that you reboot following every save of your configuration using `odrv0.reboot()`.
|
||||
|
||||
|
||||
## Position control of M0
|
||||
|
||||
@@ -226,14 +228,14 @@ Let's get motor 0 up and running. The procedure for motor 1 is exactly the same,
|
||||
### Other control modes
|
||||
The ODrive also supports velocity control and current (torque) control.
|
||||
* **Velocity control**: Set `odrv0.axis0.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`. You can now control the velocity with `odrv0.axis0.controller.vel_setpoint = 5000`. Units are counts/s.
|
||||
* **Current control**: Set `odrv0.axis0.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`. You can now control the current with `odrv0.axis0.controller.vel_setpoint = 3`. Units are 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.
|
||||
* **Current control**: Set `odrv0.axis0.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`. You can now control the current with `odrv0.axis0.controller.current_setpoint = 3`. Units are 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.
|
||||
|
||||
## What's next?
|
||||
|
||||
You can now:
|
||||
|
||||
* See what other [commands and parameters](commands.md) are available, including setting tuning parameters for better performance.
|
||||
* Control the ODrive from your own program or hook it up to an existing system through one of it's [interfaces](interfaces).
|
||||
* Control the ODrive from your own program or hook it up to an existing system through one of it's [interfaces](interfaces.md).
|
||||
* 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.
|
||||
|
||||
+4
-7
@@ -5,19 +5,16 @@
|
||||
The ODrive can be controlled over various ports and protocols. If you're comfortable with embedded systems development, you can also run custom code directly on the ODrive. For that refer to the [developer documentation](developer-guide.md).
|
||||
|
||||
### Table of contents
|
||||
|
||||
<!-- MarkdownTOC depth=2 autolink=true bracket=round -->
|
||||
<!-- TOC depthFrom:2 depthTo:2 -->
|
||||
|
||||
- [Pinout](#pinout)
|
||||
- [Native Protocol](#native-protocol)
|
||||
- [ASCII Protocol](#ascii-protocol) (and Arduino)
|
||||
- [ASCII protocol](#ascii-protocol)
|
||||
- [Step/direction](#stepdirection)
|
||||
- [RC PWM input](#rc-pwm-input) (coming soon)
|
||||
- [RC PWM input](#rc-pwm-input)
|
||||
- [Ports](#ports)
|
||||
- [USB](#usb)
|
||||
- [UART](#uart)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
<!-- /TOC -->
|
||||
|
||||
## Pinout
|
||||
|
||||
|
||||
+91
-2
@@ -2,6 +2,18 @@
|
||||
|
||||
The ODrive Tool is the accompanying PC program for the ODrive. It's main purpose is to provide an interactive shell to control the device manually, as well as some supporting functions like firmware update.
|
||||
|
||||
### Table of contents
|
||||
<!-- TOC depthFrom:2 depthTo:2 -->
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Multiple ODrives](#multiple-odrives)
|
||||
- [Configuration Backup](#configuration-backup)
|
||||
- [Device Firmware Update](#device-firmware-update)
|
||||
- [Flashing with an STLink](#flashing-with-an-stlink)
|
||||
- [Liveplotter](#liveplotter)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## Installation
|
||||
|
||||
Refer to the [Getting Started guide](getting-started#downloading-and-installing-tools).
|
||||
@@ -62,7 +74,7 @@ Note that this command will connect to GitHub servers to retrieve the latest fir
|
||||
If you have a non-default configuration saved on the device, ODrive Tool will try to carry over the configuration across the firmware update. If any of the settings are removed or renamed, you will get warning messages.
|
||||
|
||||
<details><summary markdown="span">How to flash a custom firmware</summary><div markdown="block">
|
||||
If you want to flash a specific firmware file instead of automatically downloading one, you can run `odrivetool dfu [path/to/firmware/file.hex]`.
|
||||
If you want to flash a specific firmware file instead of automatically downloading one, you can run `odrivetool dfu path/to/firmware/file.hex`
|
||||
|
||||
You can download one of the officially released firmware files from [here](https://github.com/madcowswe/ODrive/releases). You will need one of the __.hex__ files (not the __.elf__ file). Make sure you select the file that matches your board version.
|
||||
|
||||
@@ -77,13 +89,45 @@ To compile firmware from source, refer to the [developer guide](developer-guide)
|
||||
* 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)</summary><div markdown="block">
|
||||
Flick the DIP switch that "DFU, RUN" to "DFU" and power cycle the board. If that alone doesn't work, also connect the After you're done, put the switch back into the "RUN" position and power cycle the board again.
|
||||
Flick the DIP switch that "DFU, RUN" to "DFU" and power cycle the board. After you're done, 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">
|
||||
Connect the pin "BOOT0" to "3.3V" and power cycle the board. If that alone doesn't work, also connect the pin "GPIO1" to "GND". After you're done, remove the wires and power cycle the board again.
|
||||
</div></details>
|
||||
|
||||
### Upgrading firmware with a different DFU tool
|
||||
Some people have had issues using the python dfu tool, so below is a guide on how to manually use a different tool.
|
||||
|
||||
Before starting the below steps, you need to get firmware binary. You can download one of the officially released firmware files from [here](https://github.com/madcowswe/ODrive/releases). Make sure you select the file that matches your board version, and that you get the __.hex__ file (not the __.elf__ file).
|
||||
|
||||
To compile firmware from source, refer to the [developer guide](developer-guide).
|
||||
|
||||
#### Windows
|
||||
You can use the DfuSe app from ST.
|
||||
|
||||
1. Download the tool [here](https://www.st.com/en/development-tools/stsw-stm32080.html). Unfortunately they make you create a login to download. Sorry about that.
|
||||
1. After installing the tool, launch `DfuFileMgr.exe` which probably got added to the start menu as "Dfu file manager".
|
||||
1. Select "I want to GENERATE a DFU file from S19, HEX or BIN files", press OK.
|
||||
1. Click the button that says "S19 or Hex...", find the `ODriveFirmware.hex` file you built or downloaded.
|
||||
1. Leave all the other settings as default and click the "Generate..." button.
|
||||
1. Save the output file as `ODriveFirmware.dfu`. Note that the success message has a warning sign for some reason...
|
||||
1. Launch `DfuSeDemo.exe` which probably got added to the start menu as "DfuSeDemo".
|
||||
1. Force the ODrive into DFU mode, as per the instructions above "How to force DFU mode".
|
||||
1. In the top left it should now be connected to "STM Device in DFU Mode".
|
||||
1. If it doesn't appear, it may be because the driver is set to libusb by Zadig. We need to set it back to the original driver. Follow [these instructions](https://github.com/pbatard/libwdi/wiki/FAQ#Help_Zadig_replaced_the_driver_for_the_wrong_device_How_do_I_restore_it).
|
||||
1. In the bottom right section called "Upgrade or Verify Action" click the button "Choose...".
|
||||
1. Locate the `ODriveFirmware.dfu` we made before.
|
||||
1. Click button "Upgrade".
|
||||
1. If you get a warning that it's not possible to check that it's the correct device type: click yes to continue.
|
||||
1. Congratulations your ODrive should now be flashed; you can now quit DfuSeDemo.
|
||||
1. Turn off the power to the ODrive and set the DIP switch back to RUN mode.
|
||||
|
||||
#### MacOS or Linux
|
||||
**This section needs more detail. Please consider adding detail if you got it to work.**
|
||||
You may be able to use [dfu-util](http://dfu-util.sourceforge.net/) to upgrade the firmware. You will need to convert the .hex file to a .dfu file. You may be able to do it with the python script [dfu-convert](https://github.com/plietar/dfuse-tool/blob/master/dfu-convert) or the c program [hex2dfu](https://github.com/encedo/hex2dfu).
|
||||
|
||||
You probably need to force DFU mode, as per the instructions above.
|
||||
|
||||
## Flashing with an STLink
|
||||
|
||||
@@ -131,3 +175,48 @@ adapter speed: 2000 kHz
|
||||
```
|
||||
|
||||
If something doesn't work, make sure `openocd` is in your `PATH` variable, check that the wires are connected properly and try with elevated privileges.
|
||||
|
||||
## Liveplotter
|
||||
|
||||
Liveplotter is used for the graphical plotting of odrive parameters (i.e. position) in real time. To start liveplotter, close any other instances of liveplotter and run `odrivetool liveplotter` from a new anaconda prompt window. By defult two parameters are plotted on startup; the encoder positon of axis 1 and axis 2. In the below example the motors are running in `closed_loop_control` while they are being forced off position by hand.
|
||||
|
||||

|
||||
|
||||
To change what parameters are plotted open odrivetool (located in Anaconda3\Scripts or ODrive-master\tools) with a text editor and modify the liveplotter function:
|
||||
```
|
||||
# If you want to plot different values, change them here.
|
||||
# You can plot any number of values concurrently.
|
||||
cancellation_token = start_liveplotter(lambda: [
|
||||
my_odrive.axis0.encoder.pos_estimate,
|
||||
my_odrive.axis1.encoder.pos_estimate,
|
||||
])
|
||||
```
|
||||
For example, to plot the approximate motor torque [N.cm] and the velocity [RPM] of axis1 with a 150KV motor and an 8192 count per rotation econder you would modify the function to read:
|
||||
```
|
||||
# If you want to plot different values, change them here.
|
||||
# You can plot any number of values concurrently.
|
||||
cancellation_token = start_liveplotter(lambda: [
|
||||
(((my_odrive.axis0.encoder.pll_vel)/8192)*60), # 8192 CPR encoder
|
||||
((8.27*my_odrive.axis0.motor.current_control.Iq_setpoint/150) * 100), # Torque [N.cm] = (8.27 * Current [A] / KV) * 100
|
||||
])
|
||||
```
|
||||
In the example below the motor is forced off axis by hand and held there. In response the motor controller increases the torque (orange line) to counteract this disturbance up to a peak of 500 N.cm at which point the motor current limit is reached. When the motor is released it returns back to its commanded position very quickly as can be seen by the spike in the motor velocity (blue line).
|
||||
|
||||

|
||||
|
||||
To change the scale and sample rate of the plot modify the following parameters located at the beginning of utils.py (located in Anaconda3\Lib\site-packages\odrive):
|
||||
|
||||
```
|
||||
data_rate = 100
|
||||
plot_rate = 10
|
||||
num_samples = 1000
|
||||
```
|
||||
|
||||
For more examples on how to interact with the plotting functinality refer to the [Matplotlib examples.](https://matplotlib.org/examples)
|
||||
|
||||
### Liveplotter from interactive odrivetool instance
|
||||
You can also run `start_liveplotter(...)` directly from the interactive odrivetool prompt. This is useful if you want to issue commands or otherwise keep interacting with the odrive while plotting.
|
||||
|
||||
For example you can type the following directly into the interactive prompt: `start_liveplotter(lambda: [odrv0.axis0.encoder.pos_estimate])`. Just like the examples above, you can list several parameters to plot separated by comma in the square brackets.
|
||||
In general, you can plot any variable that you are able to read like normal in odrivetool.
|
||||
|
||||
|
||||
+103
-5
@@ -1,5 +1,18 @@
|
||||
# Troubleshooting
|
||||
|
||||
Table of Contents:
|
||||
<!-- TOC depthFrom:2 depthTo:2 -->
|
||||
|
||||
- [Error codes](#error-codes)
|
||||
- [Common Axis Errors](#common-axis-errors)
|
||||
- [Common Motor Errors](#common-motor-errors)
|
||||
- [Common Encoder Errors](#common-encoder-errors)
|
||||
- [USB Connectivity Issues](#usb-connectivity-issues)
|
||||
- [Firmware Issues](#firmware-issues)
|
||||
- [Other issues that may not produce an error code](#other-issues-that-may-not-produce-an-error-code)
|
||||
|
||||
<!-- /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.
|
||||
|
||||
@@ -21,18 +34,64 @@ The axis error may say that some other component has failed. Say it reports `ERR
|
||||
* Encoder error flags defined [here](../Firmware/MotorControl/encoder.hpp).
|
||||
* Sensorless estimator error flags defined [here](../Firmware/MotorControl/sensorless_estimator.hpp).
|
||||
|
||||
## DRV fault
|
||||
## Common Axis Errors
|
||||
|
||||
* `ERROR_INVALID_STATE = 0x01`
|
||||
|
||||
Typically returned along with another error. Resolve that error and then reboot using `odrv0.reboot()` or remoivng power, waiting 5 seconds and restoring power to return to normal operating.
|
||||
|
||||
* `ERROR_DC_BUS_UNDER_VOLTAGE = 0x02`
|
||||
|
||||
Confirm that your power leads are connected securely. For initial testing a 12V PSU which can supply a couple of amps should be sufficient while the use of low current 'wall wart' plug packs may lead to inconsistent behaviour and is not recommended.
|
||||
|
||||
You can monitor your PUS voltage using liveplotter in odrive tool by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If you see your votlage drop below ~ 8V then you will trip this error. Even a relatively small motor can draw multiple kW momentary and so unless you have a very large PSU or are running of a battery you may encounter this error when executing high speed movements with a high current limit. To limit your PSU power draw you can limit your motor current and/or velocity limit `odrv0.axis0.controller.config.vel_limit` and `odrv0.axis0.motor.config.current_lim`.
|
||||
|
||||
* `ERROR_DC_BUS_OVER_VOLTAGE = 0x04`
|
||||
|
||||
Confirm that you have a break resistor of the correct value connected securly and that `odrv0.config.brake_resistance` is set to the value of your break resistor.
|
||||
|
||||
You can monitor your PUS voltage using liveplotter in odrive tool by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If during a move you see the voltage rise above your PSU's nominal set voltage then you have your break resistance set too low. This may happen if you are using long wires or small gauge wires to connect your break resistor to your odrive which will added extra resistance. This extra resistance needs to be accounted for to prevent this voltage spike. If you have checked all your connections you can also try increasing your break resistance by ~ 0.01 Ohm at a time to a maximum of 0.05 greater than your break resistor value.
|
||||
|
||||
## Common Motor Errors
|
||||
|
||||
* `ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001` and `ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002`
|
||||
|
||||
During calibration the motor resistance and [inductance](https://en.wikipedia.org/wiki/Inductance) is measured. If the measured motor resistance or inductance falls outside a set range this error will be returned. Check that all motor leads are connected securely.
|
||||
|
||||
The measured values can be viewed using odrivetool as is shown below:
|
||||
```
|
||||
In [2]: odrv0.axis0.motor.config.phase_inductance
|
||||
Out[2]: 1.408751450071577e-05
|
||||
|
||||
In [3]: odrv0.axis0.motor.config.phase_resistance
|
||||
Out[3]: 0.029788672924041748
|
||||
```
|
||||
Some motors will have a considerably different phase resistance and inductance than this. For example, gimbal motors, some small motors (e.g. < 10A peak current). If you think this applies to you try increasing `odrv0.axis0.motor.config.resistance_calib_max_voltage` from its default value of 1 using odrive tool and repeat the motor calibration process. If your motor has a small peak current draw (e.g. < 20A) you can also try decreasing `odrv0.axis0.motor.config.calibration_current` from its default value of 10A.
|
||||
|
||||
* `ERROR_DRV_FAULT = 0x0008`
|
||||
|
||||
The ODrive v3.4 is known to have a hardware issue whereby the motors would stop operating
|
||||
when applying high currents to M0. The reported error of both motors in this case
|
||||
is `ERROR_DRV_FAULT`.
|
||||
|
||||
The conjecture is that the high switching current creates large ripples in the
|
||||
power supply of the DRV8301 gate driver chips, thus tripping its undervoltage
|
||||
fault detection.
|
||||
power supply of the DRV8301 gate driver chips, thus tripping its under-voltage fault detection.
|
||||
|
||||
* Limit the M0 current to 40A. The lowest current at which the DRV fault was observed is 45A on one test motor and 50A on another test motor.
|
||||
* Refer to [this post](https://discourse.odriverobotics.com/t/drv-fault-on-odrive-v3-4/558) for instructions for a hardware fix
|
||||
To resolve this issue you can limit the M0 current to 40A. The lowest current at which the DRV fault was observed is 45A on one test motor and 50A on another test motor. Refer to [this post](https://discourse.odriverobotics.com/t/drv-fault-on-odrive-v3-4/558) for instructions for a hardware fix.
|
||||
|
||||
## Common Encoder Errors
|
||||
|
||||
* `ERROR_CPR_OUT_OF_RANGE = 0x02`
|
||||
|
||||
Confirm you have entered the correct count per rotation (CPR) for [your encoder](https://docs.odriverobotics.com/encoders). Note that the AMT encoders are configurable using the micro-switches on the encoder PCB and so you may need to check that these are in the right positions. If your encoder lists its pulse per rotation (PPR) multiply that number by four to get CPR.
|
||||
|
||||
* `ERROR_NO_RESPONSE = 0x04`
|
||||
|
||||
Confirm that your encoder is plugged into the right pins on the odrive board.
|
||||
|
||||
* `ERROR_INDEX_NOT_FOUND_YET = 0x20`
|
||||
|
||||
Check that your encoder is a model that has an index pulse. If your encoder does not have a wire connected to pin Z on your odrive then it does not output an index pulse.
|
||||
|
||||
|
||||
## USB Connectivity Issues
|
||||
@@ -47,3 +106,42 @@ fault detection.
|
||||
* Run `odrivetools` with the `--verbose` option.
|
||||
* Run `PYUSB_DEBUG=debug odrivetools` to get even more log output.
|
||||
* If you're a developer you can use Wireshark to capture USB traffic.
|
||||
* Try a different USB cable
|
||||
* Try routing your USB cable so that it is far away from the motor and PSU cables to reduce EMI
|
||||
|
||||
## Firmware Issues
|
||||
|
||||
### Failure to build the firmware when running `make`
|
||||
- Clear out temporary files from previous compiles by first running `make clean` to prevent conflicts.
|
||||
- **Windows users**: Confirm that tup has been correctly added to path by running `env|grep PATH` in Git Bash. If you see no mention of tup then you must [add its location to your PATH environment variable.](https://docs.alfresco.com/4.2/tasks/fot-addpath.html). Note that you may need to restart for the added path to take effect.
|
||||
|
||||
### Failure to flash the firmware when running `make flash`
|
||||
- If using an ST-link, confirm that the ST-link is connected the correct pins and that you have power supplied to the board. This can be by the 5V pin on the ST link or the main DC power jack. No power is supplied over the USB connection.
|
||||
|
||||
## Other issues that may not produce an error code
|
||||
|
||||
### Motor cuts off or spins uncontrollably at high rotational speeds (ie: > 5000 RPM)
|
||||
- You may be approaching the limit of your encoder. The 2400 count/rotation encoders that were initially included with odrive are realistically limited to around 5000 RPM. Exceeding this speed causes the odrive to lose track of position. This can only be fixed by using an alternative encoder or gearing down the output of your motor onto your encoder so that it still sees < 5000RPM at full speed. If using the gearing options be sure to change your counts/rotation accordingly.
|
||||
|
||||
### Motor vibrates when stationary or makes constant noise
|
||||
|
||||
- Likely due to incorrect gains, specifically `vel_gain` may be set too high. Try following the [tuning procedure](https://docs.odriverobotics.com/commands).
|
||||
- Check encoder shaft connection. Grub screws may vibrate lose with time. If using a CUI shaft encoder try remounting the plastic retaining ring and confirm that it is not coming into contact with the encoder housing. Also confirm that the encoder is securely mounted.
|
||||
- If you are using a high resolution encoder (>4000 counts/rotation) then increasing encoder_pll_bandwidth may help reduce vibration.
|
||||
- If you connect your motor to an object with a large moment of inertia (such as a flywheel) this will help reduce vibrations at high gians. However, make sure that all connections are ridged. Cheap shaft couplers or belts under low tension can introduce enough flex into a system that the motor may still vibrate independently.
|
||||
|
||||
### Motor overshoots target position or oscillates back and forth
|
||||
- Likely due to incorrect gains for a given motor current limit. Specifically `pos_gain` is set too high. Try following the [tuning procedure](https://docs.odriverobotics.com/commands).
|
||||
- Increase the current limit of your motor for more torque.
|
||||
|
||||
### Motor slowly starts to increase in speed
|
||||
- Encoder has likely slipped. This may occur when your motor makes a hard stop or violently vibrates causing something to come lose. Power the board off and on again so that it undertakes a new calibration. If you are using an index search on startup then you will need to repeat the index calibration process.
|
||||
|
||||
### Motor feels like it has less torque than it should and/or gets hot sitting still while under no load.
|
||||
- Encoder has likely slipped causing the motor controller to commutate the wrong windings slightly which reduces output torque and produces excess heat as the motor 'fights itself'.
|
||||
|
||||
### False steps or direction changes when using step/dir
|
||||
- Prior to Odrive board V3.5 no filtering is present on the GPIO pins used for step/dir interface and so inductively coupled noise may causes false steps to be detected. Odrive V3.5 and has onboard filtering to resolve this issue.
|
||||
- If you experience this issue use a twisted pair cable between your microcontroller that’s generating the step/dir signals and your odrive board. A section cut from cat-5 cable works well as does just twisting some normal insulated wire together.
|
||||
- Ensure that the step/dir signal cables are not draped over the odrive board, are not running in parallel to the motor or power supply cables.
|
||||
- If the above does not resolve your issue on V3.4 boards and lower try adding a ~22 Ohm resistor in series with the step and direction pins along with a ~ 4.7 nF capacitor between the ground pin and the step and dir pins such as shown [here](https://cdn.discordapp.com/attachments/369667319280173069/420811057431445504/IMG_20180306_211224.jpg).
|
||||
|
||||
@@ -109,8 +109,8 @@ def rate_test(device):
|
||||
Tests how many integers per second can be transmitted
|
||||
"""
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
plt.ion()
|
||||
# import matplotlib.pyplot as plt
|
||||
# plt.ion()
|
||||
|
||||
print("reading 10000 values...")
|
||||
numFrames = 10000
|
||||
@@ -118,13 +118,14 @@ def rate_test(device):
|
||||
for _ in range(numFrames):
|
||||
vals.append(device.axis0.loop_counter)
|
||||
|
||||
plt.plot(vals)
|
||||
|
||||
loopsPerFrame = (vals[-1] - vals[0])/numFrames
|
||||
loopsPerSec = (168000000/(2*10192))
|
||||
FramePerSec = loopsPerSec/loopsPerFrame
|
||||
print("Frames per second: " + str(FramePerSec))
|
||||
|
||||
# plt.plot(vals)
|
||||
# plt.show(block=True)
|
||||
|
||||
def usb_burn_in_test(get_var_callback, cancellation_token):
|
||||
"""
|
||||
Starts background threads that read a values form the USB device in a spin-loop
|
||||
@@ -151,7 +152,7 @@ def setup_udev_rules(logger):
|
||||
logger.error("This command only makes sense on Linux")
|
||||
if os.getuid() != 0:
|
||||
logger.warn("you should run this as root, otherwise it will probably not work")
|
||||
with open('/etc/udev/rules.d/50-odrive.rules', 'w') as file:
|
||||
with open('/etc/udev/rules.d/91-odrive.rules', 'w') as file:
|
||||
file.write('SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d3[0-9]", MODE="0666"\n')
|
||||
subprocess.check_call(["udevadm", "control", "--reload-rules"])
|
||||
subprocess.check_call(["udevadm", "trigger"])
|
||||
|
||||
Reference in New Issue
Block a user