diff --git a/CHANGELOG.md b/CHANGELOG.md index f024cb65..9d510f0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ # Unreleased Features Please add a note of your changes below this heading if you make a Pull Request. +# Unreleased + # Releases +## [0.4.4] - 2018-09-18 +### Fixed +* Serious reliability issue with USB communication where packets on Native and the CDC interface would collide with each other. + ## [0.4.3] - 2018-08-30 ### Added * Encoder position count "homed" to zero when index is found. @@ -14,6 +20,7 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Fixed * Once you got an axis error `ERROR_INVALID_STATE` you could never clear it * Char to int conversion to read motornum on arduino example +* GPIO above #5 would not be used correctly in some cases ## [0.4.2] - 2018-08-04 ### Added @@ -24,6 +31,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 diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index c77d5165..570c0f29 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -4,6 +4,7 @@ "name": "Win32", "includePath": [ "${workspaceRoot}", + "${workspaceRoot}/fibre/cpp/include/**", "${workspaceRoot}/MotorControl", "${workspaceRoot}/Drivers/DRV8301", "${workspaceRoot}/Board/v3/Inc", @@ -16,12 +17,8 @@ "${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", "${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/include", "${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", - "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/arm-none-eabi/include/c++/6.3.1", - "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/arm-none-eabi/include/c++/6.3.1/arm-none-eabi", - "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/arm-none-eabi/include/c++/6.3.1/backward", - "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/lib/gcc/arm-none-eabi/6.3.1/include", - "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/lib/gcc/arm-none-eabi/6.3.1/include-fixed", - "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/arm-none-eabi/include" + "${ARM_GCC_ROOT}/arm-none-eabi/include/**", + "${ARM_GCC_ROOT}/lib/gcc/arm-none-eabi/**" ], "defines": [ "STM32F405xx", @@ -29,6 +26,7 @@ "HW_VERSION_MAJOR=3", "HW_VERSION_MINOR=5", "HW_VERSION_VOLTAGE=24", + "USB_PROTOCOL_NATIVE", "__weak=\"__attribute__((weak))\"", "__packed=\"__attribute__((__packed__))\"", "__GNUC__" @@ -37,10 +35,11 @@ "browse": { "path": [ "${workspaceRoot}", - "C:/Program Files (x86)/GNU Tools ARM Embedded" + "${ARM_GCC_ROOT}" ], "limitSymbolsToIncludedHeaders": true }, + "compilerPath": "${ARM_GCC_ROOT}/bin/arm-none-eabi-gcc.exe -mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -specs=nosys.specs -specs=nano.specs -u _printf_float -u _scanf_float", "cStandard": "c11", "cppStandard": "c++14" }, @@ -48,6 +47,7 @@ "name": "Linux", "includePath": [ "${workspaceRoot}", + "${workspaceRoot}/fibre/cpp/include/**", "${workspaceRoot}/MotorControl", "${workspaceRoot}/Drivers/DRV8301", "${workspaceRoot}/Board/v3/Inc", @@ -60,8 +60,8 @@ "${workspaceRoot}/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", "${workspaceRoot}/Board/v3/Drivers/CMSIS/Device/ST/STM32F4xx/Include", "${workspaceRoot}/Board/v3/Drivers/CMSIS/Include", - "/usr/lib/gcc/arm-none-eabi/4.9.3/include", - "/usr/lib/arm-none-eabi/include" + "${ARM_GCC_ROOT}/arm-none-eabi/include/**", + "${ARM_GCC_ROOT}/lib/gcc/arm-none-eabi/**" ], "defines": [ "STM32F405xx", @@ -77,8 +77,7 @@ "browse": { "path": [ "${workspaceRoot}", - "/usr/lib/gcc/arm-none-eabi/4.9.3/include", - "/usr/lib/arm-none-eabi/include" + "${ARM_GCC_ROOT}" ], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" @@ -91,6 +90,7 @@ "name": "Mac", "includePath": [ "${workspaceRoot}", + "${workspaceRoot}/fibre/cpp/include/**", "${workspaceRoot}/MotorControl", "${workspaceRoot}/Drivers/DRV8301", "${workspaceRoot}/Board/v3/Inc", @@ -103,10 +103,8 @@ "${workspaceRoot}/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", "${workspaceRoot}/Board/v3/Drivers/CMSIS/Device/ST/STM32F4xx/Include", "${workspaceRoot}/Board/v3/Drivers/CMSIS/Include", - "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include", - "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include/c++/6.3.1", - "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include/c++/6.3.1/arm-none-eabi", - "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/lib/gcc/arm-none-eabi/6.3.1/include" + "${ARM_GCC_ROOT}/arm-none-eabi/include/**", + "${ARM_GCC_ROOT}/lib/gcc/arm-none-eabi/**" ], "defines": [ "STM32F405xx", @@ -122,7 +120,7 @@ "browse": { "path": [ "${workspaceRoot}", - "/usr/local/Caskroom/gcc-arm-embedded/" + "${ARM_GCC_ROOT}" ], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" diff --git a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h index 3bb73c6e..c029e22a 100644 --- a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h +++ b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h @@ -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); diff --git a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c index 2bc01513..73313522 100644 --- a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c +++ b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c @@ -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 diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 031158d5..4276e697 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -285,6 +285,15 @@ GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin){ case 4: return GPIO_4_GPIO_Port; break; #ifdef GPIO_5_GPIO_Port case 5: return GPIO_5_GPIO_Port; break; +#endif +#ifdef GPIO_6_GPIO_Port + case 6: return GPIO_6_GPIO_Port; break; +#endif +#ifdef GPIO_7_GPIO_Port + case 7: return GPIO_7_GPIO_Port; break; +#endif +#ifdef GPIO_8_GPIO_Port + case 8: return GPIO_8_GPIO_Port; break; #endif default: return GPIO_1_GPIO_Port; } @@ -298,6 +307,15 @@ uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin){ case 4: return GPIO_4_Pin; break; #ifdef GPIO_5_Pin case 5: return GPIO_5_Pin; break; +#endif +#ifdef GPIO_6_Pin + case 6: return GPIO_6_Pin; break; +#endif +#ifdef GPIO_7_Pin + case 7: return GPIO_7_Pin; break; +#endif +#ifdef GPIO_8_Pin + case 8: return GPIO_8_Pin; break; #endif default: return GPIO_1_Pin; } diff --git a/Firmware/Board/v3/Src/usbd_cdc_if.c b/Firmware/Board/v3/Src/usbd_cdc_if.c index 77e70b2c..b8cb6e6c 100644 --- a/Firmware/Board/v3/Src/usbd_cdc_if.c +++ b/Firmware/Board/v3/Src/usbd_cdc_if.c @@ -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; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index f10b6211..80f5d25a 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -86,12 +86,9 @@ public: make_protocol_property("vel_limit", &config_.vel_limit) ), 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) diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index 9eee9a0d..b3141138 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -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, diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index b864981e..cd44c907 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -13,27 +13,22 @@ #include -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(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); } diff --git a/Firmware/communication/interface_usb.h b/Firmware/communication/interface_usb.h index c4dabb99..0a5b94ff 100644 --- a/Firmware/communication/interface_usb.h +++ b/Firmware/communication/interface_usb.h @@ -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 diff --git a/docs/commands.md b/docs/commands.md index 212a4899..7e7fe3da 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -2,6 +2,16 @@ We will use the `` as a placeholder for any ODrive object. Every ODrive controller is an ODrive object. In `odrivetool` this is usually `odrv0`. Furthermore we use `` 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 + + +- [Per-Axis commands](#per-axis-commands) +- [System monitoring commands](#system-monitoring-commands) +- [General system commands](#general-system-commands) +- [Setting up sensorless](#setting-up-sensorless) + + + ## Per-Axis commands For the most part, both axes on the ODrive can be controlled independently. @@ -75,7 +85,7 @@ An upcoming feature will enable automatic tuning. Until then, here is a rough tu ### Encoder position and velocity * View encoder position with `.encoder.pos_estimate` [counts] -* View rotational velocity with `.encoder.pll_vel` [counts/s] +* View rotational velocity with `.encoder.vel_estimate` [counts/s] ### Motor current and torque estimation * View the commanded motor current with `.motor.current_control.Iq_setpoint` [A] diff --git a/docs/configuring-vscode.md b/docs/configuring-vscode.md index 17f7e702..bfb3261b 100644 --- a/docs/configuring-vscode.md +++ b/docs/configuring-vscode.md @@ -16,6 +16,7 @@ Before doing the VSCode setup, make sure you've installed all of your [prerequis * Include Autocomplete * Path Autocomplete * Auto Comment Blocks +1. Create an environment variable named `ARM_GCC_ROOT` whose value is the location of the `GNU Arm Embedded Toolchain` (.e.g `C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update`) that you installed in the prerequisites section of the developer's guide. 1. Restart VSCode 1. Open the VSCode Workspace file, which is located in the root of the ODrive repository. It is called `ODrive_Workspace.code-workspace`. The first time you open it, VSCode will install some dependencies. If it fails, you may need to [change your proxy settings](https://code.visualstudio.com/docs/getstarted/settings). diff --git a/docs/control.md b/docs/control.md index 0be73866..f0b2da1d 100644 --- a/docs/control.md +++ b/docs/control.md @@ -4,6 +4,27 @@ The motor controller is a cascaded style position, velocity and current control ![Cascaded pos vel I loops](https://static1.squarespace.com/static/58aff26de4fcb53b5efd2f02/t/5b66284a0e2e72aae8818d64/1533421649405/CascadedController.png?format=2500w) -* 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). diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 440f78ac..30a20543 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -32,7 +32,7 @@ The recommended tools for ODrive development are: * **make**: Used to invoke tup * **Tup**: The build system used to invoke the compile commands - * **ARM GNU Compiler**: For cross-comiling code + * **ARM GNU Compiler**: For cross-compiling code * **ARM GDB**: For debugging the code and stepping through on the device * **OpenOCD**: For flashing the ODrive with the STLink/v2 programmer * **Python**: For running the Python tools @@ -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 ``` @@ -79,7 +80,7 @@ __Note__: make sure these programs are not only installed but also added to your Some instructions in this document may assume that you're using a bash command prompt, such as the Windows 10 built-in bash or [Git](https://git-scm.com/download/win) bash. -* [ARM compiler](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads) +* [ARM compiler](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads) __Note__: After installing, create an environment variable named `ARM_GCC_ROOT` whose value is the path you installed to. e.g. `C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update`. This variable is used to locate include files for the c/c++ Visual Studio Code extension. * [Tup](http://gittup.org/tup/index.html) * [Make for Windows](http://gnuwin32.sourceforge.net/packages/make.htm) * [OpenOCD](http://gnuarmeclipse.github.io/openocd/install/). Also follow the instructions on the ST-LINK/V2 drivers. diff --git a/docs/getting-started.md b/docs/getting-started.md index 6d442a55..f4035948 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -7,23 +7,22 @@ permalink: / # Getting Started ### Table of contents - - + - [Hardware Requirements](#hardware-requirements) - [Wiring up the ODrive](#wiring-up-the-odrive) - [Downloading and Installing Tools](#downloading-and-installing-tools) +- [Firmware](#firmware) - [Start `odrivetool`](#start-odrivetool) - [Configure M0](#configure-m0) - [Position control of M0](#position-control-of-m0) - [What's next?](#whats-next) - + ## Hardware Requirements -You will need: - +### You will need: * One or two [brushless motors](https://docs.google.com/spreadsheets/d/12vzz7XVEK6YNIOqH0jAz51F5VUpc-lJEs3mmkWP1H4Y). It is fine, even recommended, to start testing with just a single motor and encoder. * One or two [quadrature incremental encoder(s)](encoders) * A power resistor. A good starting point would be the 50W resistor included with your ODrive. @@ -50,14 +49,18 @@ You will need: ## Wiring up the ODrive
-Make sure you have a good mechanical connection between the encoder and the motor, slip can cause disasterous oscillations or runaway. +Make sure you have a good mechanical connection between the encoder and the motor, slip can cause disastrous oscillations or runaway.
All non-power I/O is 3.3V output and 5V tolerant on input, on ODrive v3.3 and newer. -1. Wire up the motor phases into the 3-phase screw terminals, and the power resistor to the AUX terminal. Wire up the power source to the DC terminal, make sure to pay attention to the polarity. Do not apply power just yet. +### Wiring up the motors +* Connect the motor phases into the 3-phase screw terminals. It is not recommended to use a clip-on connector such as an alligator clip, as this can cause issues with the phase resistance/inductance measurements. +* Connect the power source to the DC terminals. Make sure to pay attention to the polarity. +* Do not apply power just yet. -2. Wire up the encoder(s) to J4. The A,B phases are required, and the Z (index pulse) is optional. The A,B and Z lines have 3.3k pull up resistors, for use with open-drain encoder outputs. For single ended push-pull signals with weak drive current (\<4mA), you may want to desolder the pull-ups. +### Wiring up the encoders +Connect the encoder(s) to J4. The A,B phases are required, and the Z (index pulse) is optional. The A,B and Z lines have 3.3k pull up resistors, for use with open-drain encoder outputs. For single ended push-pull signals with weak drive current (\<4mA), you may want to desolder the pull-ups. ![Image of ODrive all hooked up](https://docs.google.com/drawings/d/e/2PACX-1vTCD0P40Cd-wvD7Fl8UYEaxp3_UL81oI4qUVqrrCJPi6tkJeSs2rsffIXQRpdu6rNZs6-2mRKKYtILG/pub?w=1716&h=1281) @@ -121,20 +124,22 @@ Try step 5 again 1. [Install Python 3](https://www.python.org/downloads/). 2. Install the ODrive tools by opening a terminal and typing `pip install odrive` Enter -3. __Linux__: set up USB permissions +3. 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 ``` +## Firmware +#### ODrive v3.5 and later +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 +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` - -
__ODrive v3.5 and later:__ 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:__ 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.
- -To launch the main interactive ODrive tool, type `odrivetool` Enter. Connect your ODrive and wait for the tool to find it. Now you can for instance type `odrv0.vbus_voltage` Enter to inpect the boards main supply voltage. +To launch the main interactive ODrive tool, type `odrivetool` Enter. Connect your ODrive and wait for the tool to find it. Now you can, for instance type `odrv0.vbus_voltage` Enter to inpect the boards main supply voltage. It should look something like this: ```text @@ -149,63 +154,77 @@ Out[1]: 11.97055721282959 The tool you're looking at is a fully capable Python command prompt, so you can type any valid python code. -You can read more about the odrivetool [here](odrivetool.md). +You can read more about `odrivetool` [here](odrivetool.md). ## Configure M0 -
Read this section carefully, else you risk breaking something.
There is a [separate guide](hoverboard.md) specifically for hoverboard motors.
### 1. Set the limits:
Wait, how do I set these?
+ In the previous step we started `odrivetool`. In there, you can assign variables directly by name. For instance, to set the current limit of M0 to 10A you would type: `odrv0.axis0.motor.config.current_lim = 10` Enter
-* 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. -
Ok so tell me how it actually works then...
- 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. +#### Current limit +`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 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. +*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.* + +
Ok, so tell me how it actually works then...
+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. + +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.
-* 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. 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. +#### 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. +#### Calibration current +You can change `odrv0.axis0.motor.config.calibration_current` [A] to the largest value you feel comfortable leaving running through the motor continuously 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: +### 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`. 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**. To find this, 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._ +If you can't see them, try sliding a magnet around the rotor, and counting how many times it stops. This will be the number of **pole pairs**. If you use a magnetic piece of metal instead of a magnet, you will get the number of **magnet poles**. +`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`). - * `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`). +
Which motor_type to choose?
-
Which `motor_type` to choose?
- If you're using a regular hobby brushless motor like [this](https://hobbyking.com/en_us/turnigy-aerodrive-sk3-5065-236kv-brushless-outrunner-motor.html) one, you should set `motor_mode` to `MOTOR_TYPE_HIGH_CURRENT`. For low-current gimbal motors like [this](https://hobbyking.com/en_us/turnigy-hd-5208-brushless-gimbal-motor-bldc.html) one, you should choose `MOTOR_TYPE_GIMBAL`. Do not use `MOTOR_TYPE_GIMBAL` on a motor that is not a gimbal motor, as it may overheat the motor or the ODrive. +If you're using a regular hobby brushless motor like [this](https://hobbyking.com/en_us/turnigy-aerodrive-sk3-5065-236kv-brushless-outrunner-motor.html) one, you should set `motor_mode` to `MOTOR_TYPE_HIGH_CURRENT`. For low-current gimbal motors like [this](https://hobbyking.com/en_us/turnigy-hd-5208-brushless-gimbal-motor-bldc.html) one, you should choose `MOTOR_TYPE_GIMBAL`. Do not use `MOTOR_TYPE_GIMBAL` on a motor that is not a gimbal motor, as it may overheat the motor or the ODrive. - **Further detail:** - If 100's of mA of current noise is "small" for you, you can choose `MOTOR_TYPE_HIGH_CURRENT`. - If 100's of mA of current noise is "large" for you, and you do not intend to spin the motor very fast (omega * L << R), and the motor is fairly large resistance (1 ohm or larger), you can chose `MOTOR_TYPE_GIMBAL`. - If 100's of mA current noise is "large" for you, _and_ you intend to spin the motor fast, then you need to replace the shunt resistors on the ODrive. -
+**Further detail:** +If 100's of mA of current noise is "small" for you, you can choose `MOTOR_TYPE_HIGH_CURRENT`. +If 100's of mA of current noise is "large" for you, and you do not intend to spin the motor very fast (Ω * L << R), and the motor is fairly large resistance (1 ohm or larger), you can chose `MOTOR_TYPE_GIMBAL`. +If 100's of mA current noise is "large" for you, _and_ you intend to spin the motor fast, then you need to replace the shunt resistors on the ODrive. - Note: When using gimbal motors, `current_lim` and `calibration_current` actually mean "voltage limit" and "calibration voltage", since we don't use current feedback. This means that if you set it to 10, it means 10V, despite the name of the parameter. +

- * _if using encoder_: `odrv0.axis0.encoder.config.cpr`: Encoder Count Per Revolution (CPR). This is 4x the Pulse Per Revolution (PPR) value. Usually this is indicated in the datasheet of your encoder. - * _if not using encoder_: - * If you wish to run in sensorless mode, please see [Setting up Sensorless](commands.md#setting-up-sensorless). - * If you are using hall sensor feedback, please see the [hoverboard motor example](hoverboard.md). +*Note: When using gimbal motors,* `current_lim` *and* `calibration_current` *actually mean "voltage limit" and "calibration voltage", since we don't use current feedback. This means that if you set it to 10, it means 10V, despite the name of the parameter.* + +#### If using encoder +`odrv0.axis0.encoder.config.cpr`: Encoder Count Per Revolution [CPR] +This is 4x the Pulse Per Revolution (PPR) value. Usually this is indicated in the datasheet of your encoder. +#### If not using encoder +* If you wish to run in sensorless mode, please see [Setting up sensorless](commands.md#setting-up-sensorless). +* If you are using hall sensor feedback, please see the [hoverboard motor example](hoverboard.md). -### 3. Save configuration. -You can save all `.config` parameters to persistent memory such that the ODrive remembers them between power cycles. +### 3. Save configuration +You can save all `.config` parameters to persistent memory so the ODrive remembers them between power cycles. * `odrv0.save_configuration()` Enter. 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 - -Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to replace read "axis1" wherever it says "axis0". +Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to substitute `axis0` wherever it says `axis0`. 1. Type `odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE` Enter. After about 2 seconds should hear a beep. Then the motor will turn slowly in one direction for a few seconds, then back in the other direction. @@ -215,12 +234,13 @@ Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, The startup procedure is demonstrated [here](https://www.youtube.com/watch?v=VCX1bA2xnuY). - **Note**: the rotor must be allowed to rotate without any biased load during startup. That means mass and weak friction loads are fine, but gravity or spring loads are not okay. Also note that in the video, the motors spin after initalisation, but in the current software the default behaviour is not like that. + *Note: the rotor must be allowed to rotate without any biased load during startup. That means mass and weak friction loads are fine, but gravity or spring loads are not okay. Also note that in the video, the motors spin after initialization, but in the current software the default behaviour is not like that.*
Help, something isn't working!
+ Check the encoder wiring and that the encoder is firmly connected to the motor. Check the value of `hex(odrv0.axis0.error)` and then refer to the [error code documentation](troubleshooting.md#error-codes) for details. - Once you have understood the error and fixed its cause, you may clear the error state (`odrv0.axis0.error = 0` Enter) and retry. You may also need to clear the error state of other subcomponents (e.g. `odrv0.axis0.motor.error`). + Once you understand the error and have fixed its cause, you may clear the error state with (`odrv0.axis0.error = 0` Enter) and retry. You may also need to clear the error state of other subcomponents (e.g. `odrv0.axis0.motor.error = 0`).
2. Type `odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL` Enter. 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. @@ -228,8 +248,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. +#### 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` [count/s]. +#### 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` [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? diff --git a/docs/interfaces.md b/docs/interfaces.md index be759832..ba6ccfb0 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -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 - - + - [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) - + ## Pinout diff --git a/docs/odrivetool.md b/docs/odrivetool.md index 80d926b7..6041928d 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -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 + + +- [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) + + + ## 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.
How to flash a custom firmware
-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. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 0c0d6cbc..b6d36720 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,5 +1,18 @@ # Troubleshooting +Table of Contents: + + +- [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) + + + ## Error codes If your ODrive is not working as expected, run `odrivetool` and type `hex(.error)` Enter where `` 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). diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 099579cb..0dde8387 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -116,8 +116,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 @@ -125,13 +125,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 @@ -158,7 +159,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"])