Merge branch 'devel' into fw4

This commit is contained in:
Oskar Weigl
2018-06-05 23:13:58 -07:00
32 changed files with 1040 additions and 475 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ env:
# Various protocol combinations
- CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=native-stream CONFIG_UART_PROTOCOL=native
- CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=stdout CONFIG_UART_PROTOCOL=ascii
- CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=stdout CONFIG_UART_PROTOCOL=stdout
- CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=none CONFIG_UART_PROTOCOL=none
script:
+1 -1
View File
@@ -132,7 +132,7 @@ extern USBD_CDC_ItfTypeDef USBD_Interface_fops_FS;
* @{
*/
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len, uint8_t endpoint_pair);
/* USER CODE BEGIN EXPORTED_FUNCTIONS */
/* USER CODE END EXPORTED_FUNCTIONS */
+2 -1
View File
@@ -89,6 +89,7 @@
* @brief Defines for configuration of the Usb device.
* @{
*/
#define MS_VendorCode 'P'
/*---------- -----------*/
#define USBD_MAX_NUM_INTERFACES 1
@@ -97,7 +98,7 @@
/*---------- -----------*/
#define USBD_MAX_STR_DESC_SIZ 512
/*---------- -----------*/
#define USBD_SUPPORT_USER_STRING 0
#define USBD_SUPPORT_USER_STRING 1
/*---------- -----------*/
#define USBD_DEBUG_LEVEL 0
/*---------- -----------*/
+2
View File
@@ -133,6 +133,8 @@ extern USBD_DescriptorsTypeDef FS_Desc;
/* USER CODE BEGIN EXPORTED_FUNCTIONS */
uint8_t * USBD_UsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length);
/* USER CODE END EXPORTED_FUNCTIONS */
/**
@@ -52,13 +52,15 @@
#define CDC_IN_EP 0x81 /* EP1 for data IN */
#define CDC_OUT_EP 0x01 /* EP1 for data OUT */
#define CDC_CMD_EP 0x82 /* EP2 for CDC commands */
#define ODRIVE_IN_EP 0x83 /* EP3 IN: ODrive device TX endpoint */
#define ODRIVE_OUT_EP 0x03 /* EP3 OUT: ODrive device RX endpoint */
/* CDC Endpoints parameters: you can fine tune these values depending on the needed baudrates and performance. */
#define CDC_DATA_HS_MAX_PACKET_SIZE 512 /* Endpoint IN & OUT Packet size */
#define CDC_DATA_HS_MAX_PACKET_SIZE 64 /* Endpoint IN & OUT Packet size */
#define CDC_DATA_FS_MAX_PACKET_SIZE 64 /* Endpoint IN & OUT Packet size */
#define CDC_CMD_PACKET_SIZE 8 /* Control Endpoint Packet size */
#define USB_CDC_CONFIG_DESC_SIZ 67
#define USB_CDC_CONFIG_DESC_SIZ (67 + 39)
#define CDC_DATA_HS_IN_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE
#define CDC_DATA_HS_OUT_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE
@@ -103,7 +105,7 @@ typedef struct _USBD_CDC_Itf
int8_t (* Init) (void);
int8_t (* DeInit) (void);
int8_t (* Control) (uint8_t, uint8_t * , uint16_t);
int8_t (* Receive) (uint8_t *, uint32_t *);
int8_t (* Receive) (uint8_t *, uint32_t *, uint8_t);
}USBD_CDC_ItfTypeDef;
@@ -156,9 +158,9 @@ uint8_t USBD_CDC_SetTxBuffer (USBD_HandleTypeDef *pdev,
uint8_t USBD_CDC_SetRxBuffer (USBD_HandleTypeDef *pdev,
uint8_t *pbuff);
uint8_t USBD_CDC_ReceivePacket (USBD_HandleTypeDef *pdev);
uint8_t USBD_CDC_ReceivePacket (USBD_HandleTypeDef *pdev, uint8_t endpoint_pair);
uint8_t USBD_CDC_TransmitPacket (USBD_HandleTypeDef *pdev);
uint8_t USBD_CDC_TransmitPacket (USBD_HandleTypeDef *pdev, uint8_t endpoint_pair);
/**
* @}
*/
File diff suppressed because it is too large Load Diff
@@ -68,7 +68,9 @@
#define USBD_IDX_PRODUCT_STR 0x02
#define USBD_IDX_SERIAL_STR 0x03
#define USBD_IDX_CONFIG_STR 0x04
#define USBD_IDX_INTERFACE_STR 0x05
#define USBD_IDX_INTERFACE_STR 0x05
#define USBD_IDX_ODRIVE_INTF_STR 0x06
#define USBD_IDX_MICROSOFT_DESC_STR 0xEE
#define USB_REQ_TYPE_STANDARD 0x00
#define USB_REQ_TYPE_CLASS 0x20
+5 -5
View File
@@ -151,7 +151,7 @@ extern USBD_HandleTypeDef hUsbDeviceFS;
static int8_t CDC_Init_FS(void);
static int8_t CDC_DeInit_FS(void);
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length);
static int8_t CDC_Receive_FS(uint8_t* pbuf, uint32_t *Len);
static int8_t CDC_Receive_FS(uint8_t* pbuf, uint32_t *Len, uint8_t endpoint_pair);
/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */
@@ -287,10 +287,10 @@ static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length)
* @param Len: Number of data received (in bytes)
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
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);
usb_process_packet(Buf, *Len, endpoint_pair);
return (USBD_OK);
/* USER CODE END 6 */
@@ -307,7 +307,7 @@ static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
* @param Len: Number of data to be sent (in bytes)
* @retval USBD_OK if all operations are OK else USBD_FAIL or USBD_BUSY
*/
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len, uint8_t endpoint_pair)
{
uint8_t result = USBD_OK;
/* USER CODE BEGIN 7 */
@@ -323,7 +323,7 @@ uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
memcpy(UserTxBufferFS, Buf, Len);
// Update Len
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, Len);
result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
result = USBD_CDC_TransmitPacket(&hUsbDeviceFS, endpoint_pair);
/* USER CODE END 7 */
return result;
}
+20 -2
View File
@@ -154,6 +154,23 @@ void HAL_PCD_MspDeInit(PCD_HandleTypeDef* pcdHandle)
*/
void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *hpcd)
{
USBD_StatusTypeDef ret = USBD_OK;
USBD_HandleTypeDef *pdev = hpcd->pData;
USBD_SetupReqTypedef *req = &pdev->request;
USBD_ParseSetupRequest(req, (uint8_t *)hpcd->Setup);
if ( ( USB_REQ_TYPE_VENDOR == (req->bmRequest & USB_REQ_TYPE_MASK) ) && ( MS_VendorCode == req->bRequest ) )
{
pdev->ep0_state = USBD_EP0_SETUP;
pdev->ep0_data_len = pdev->request.wLength;
ret = pdev->pClass->Setup(pdev, req);
if( (req->wLength == 0) && (ret == USBD_OK) )
{
USBD_CtlSendStatus(pdev);
}
return;
}
USBD_LL_SetupStage((USBD_HandleTypeDef*)hpcd->pData, (uint8_t *)hpcd->Setup);
}
@@ -312,7 +329,7 @@ USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
pdev->pData = &hpcd_USB_OTG_FS;
hpcd_USB_OTG_FS.Instance = USB_OTG_FS;
hpcd_USB_OTG_FS.Init.dev_endpoints = 4;
hpcd_USB_OTG_FS.Init.dev_endpoints = 6;
hpcd_USB_OTG_FS.Init.speed = PCD_SPEED_FULL;
hpcd_USB_OTG_FS.Init.dma_enable = DISABLE;
hpcd_USB_OTG_FS.Init.ep0_mps = DEP0CTL_MPS_64;
@@ -329,7 +346,8 @@ USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x80);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x40);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x80);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x40); // CDC IN endpoint
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 3, 0x40); // ODrive IN endpoint
}
return USBD_OK;
}
+50 -4
View File
@@ -97,7 +97,8 @@
#define USBD_PID_FS 0x0D32
#define USBD_PRODUCT_XSTR(s) USBD_PRODUCT_STR(s)
#define USBD_PRODUCT_STR(s) #s
#define USBD_PRODUCT_STRING_FS ODrive version HW_VERSION_MAJOR.HW_VERSION_MINOR
#define USBD_PRODUCT_STRING_FS ODrive HW_VERSION_MAJOR.HW_VERSION_MINOR CDC Interface
#define NATIVE_STRING ODrive HW_VERSION_MAJOR.HW_VERSION_MINOR Native Interface
#define USBD_SERIALNUMBER_STRING_FS "000000000001"
#define USBD_CONFIGURATION_STRING_FS "CDC Config"
#define USBD_INTERFACE_STRING_FS "CDC Interface"
@@ -114,6 +115,50 @@
/* USER CODE BEGIN 0 */
// MS OS String descriptor to tell Windows that it may query for other descriptors
// It's a standard string descriptor.
// Windows will only query for OS descriptors once!
// Delete the information about already queried devices in registry by deleting:
// HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags\VVVVPPPPRRRR
__ALIGN_BEGIN uint8_t USBD_MS_OS_StringDescriptor[] __ALIGN_END =
{
0x12, // bLength 1 0x12 Length of the descriptor
0x03, // bDescriptorType 1 0x03 Descriptor type
// qwSignature 14 MSFT100 Signature field
0x4D, 0x00, // 'M'
0x53, 0x00, // 'S'
0x46, 0x00, // 'F'
0x54, 0x00, // 'T'
0x31, 0x00, // '1'
0x30, 0x00, // '0'
0x30, 0x00, // '0'
MS_VendorCode, // bMS_VendorCode 1 Vendor-specific Vendor code
0x00 // bPad 1 0x00 Pad field
};
// redefined further down
__ALIGN_BEGIN uint8_t USBD_StrDesc[USBD_MAX_STR_DESC_SIZ] __ALIGN_END;
/**
* @brief UsrStrDescriptor
* return non standard string descriptor
* @param pdev: device instance
* @param index : descriptor index (0xEE for MS OS String Descriptor)
* @param length : pointer data length
* @retval pointer to descriptor buffer
*/
uint8_t * USBD_UsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length)
{
*length = 0;
if (USBD_IDX_MICROSOFT_DESC_STR == index) {
*length = sizeof (USBD_MS_OS_StringDescriptor);
return USBD_MS_OS_StringDescriptor;
} else if (USBD_IDX_ODRIVE_INTF_STR == index) {
USBD_GetString((uint8_t *)USBD_PRODUCT_XSTR(NATIVE_STRING), USBD_StrDesc, length);
return USBD_StrDesc;
}
return NULL;
}
/* USER CODE END 0 */
/** @defgroup USBD_DESC_Private_Macros USBD_DESC_Private_Macros
@@ -189,16 +234,17 @@ __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
0x00, /*bcdUSB */
#endif /* (USBD_LPM_ENABLED == 1) */
0x02,
0x02, /*bDeviceClass*/
// Notify OS that this is a composite device
0xEF, /*bDeviceClass*/
0x02, /*bDeviceSubClass*/
0x00, /*bDeviceProtocol*/
0x01, /*bDeviceProtocol*/
USB_MAX_EP0_SIZE, /*bMaxPacketSize*/
LOBYTE(USBD_VID), /*idVendor*/
HIBYTE(USBD_VID), /*idVendor*/
LOBYTE(USBD_PID_FS), /*idProduct*/
HIBYTE(USBD_PID_FS), /*idProduct*/
0x00, /*bcdDevice rel. 2.00*/
0x02,
0x03, /* bNumInterfaces */
USBD_IDX_MFC_STR, /*Index of manufacturer string*/
USBD_IDX_PRODUCT_STR, /*Index of product string*/
USBD_IDX_SERIAL_STR, /*Index of serial number string*/
+8 -1
View File
@@ -11,7 +11,10 @@ Please add a note of your changes below this heading if you make a Pull Request.
* System stats (e.g. stack usage) are exposed under `<odrv>.system_stats`
### Changed
* The DFU script now verifies the flash after writing
* DFU script updates
* Verify the flash after writing
* Automatically download firmware from GitHub releases if no file is provided
* Retain configuration during firmware updates
* Refactor python tools
* The scripts `explore_odrive.py`, `liveplotter.py`, `drv_status.py` and `rate_test.py` have been merged into one single `odrivetool` script. Running this script without any arguments provides the shell that `explore_odrive.py` used to provide.
* The command line options of `odrivetool` have changed compared to the original `explore_odrive.py`. See `odrivetool --help` for more details.
@@ -19,11 +22,15 @@ Please add a note of your changes below this heading if you make a Pull Request.
* No need to restart the `odrivetool` shell when devices get disconnected and reconnected
* ODrive accesses from within python tools are now thread-safe. That means you can read from the same remote property from multiple threads concurrently.
* The liveplotter (`odrivetool liveplotter`, formerly `liveplotter.py`) does no longer steal focus and closes as expected
* Add commands `odrivetool backup-config` and `odrivetool restore-config`
* (experimental: start liveplotter from `odrivetool` shell by typing `start_liveplotter(lambda: odrv0.motor0.encoder.encoder_state)`)
* `make write_otp` command to burn the board version onto the ODrive's one-time programmable memory. If you have an ODrive v3.4 or older, you can run this once for a better firmware update user experience in the future. Run the command without any options for more details. Once set, the board version is exposed through the `hw_version_[...]` properties.
* bake Git-derived firmware version into firmware binary. The firmware version is exposed through the `fw_version_[...]` properties.
* Set thread priority of USB pump thread above protocol thread
* GPIO3 not sensitive to edges by default
* The device now appears as a composite device on USB. One subdevice is still a CDC device (virtual COM port), the other subdevice is a vendor specific class. This should resolve several issues that were caused by conflicting kernel drivers or OS services.
* Add WinUSB descriptors. This will tell Windows >= 8 to automatically load winusb.sys for the ODrive (only for the vendor specific subdevice). This makes it possible to use the ODrive from userspace via WinUSB with zero configuration. The Python tool currently still uses libusb so Zadig is still required.
* Add a configuration to enable the ASCII protocol on USB at runtime. This will only enable the ASCII protocol on the USB CDC subdevice, not the vendor specific subdevice so the python tools will still be able to talk to the ODrive.
### Fixed
* Enums now transported with correct underlying type on native protocol
+2
View File
@@ -34,6 +34,8 @@ void save_configuration(void) {
&motor_configs,
&axis_configs)) {
//printf("saving configuration failed\r\n"); osDelay(5);
} else {
user_config_loaded_ = true;
}
}
+2 -1
View File
@@ -57,8 +57,9 @@ extern SystemStats_t system_stats_;
// @brief general user configurable board configuration
struct BoardConfig_t {
bool enable_uart = false;
bool enable_uart = true;
bool enable_i2c_instead_of_can = false;
bool enable_ascii_protocol_on_usb = true;
float brake_resistance = 0.47f; // [ohm]
float dc_bus_undervoltage_trip_level = 8.0f; //<! [V] minimum voltage below which the motor stops operating
float dc_bus_overvoltage_trip_level = 1.08f * HW_VERSION_VOLTAGE; //<! [V] maximum voltage above which the motor stops operating.
-2
View File
@@ -43,8 +43,6 @@ if tup.getconfig("USB_PROTOCOL") == "native" or tup.getconfig("USB_PROTOCOL") ==
FLAGS += "-DUSB_PROTOCOL_NATIVE"
elseif tup.getconfig("USB_PROTOCOL") == "native-stream" then
FLAGS += "-DUSB_PROTOCOL_NATIVE_STREAM_BASED"
elseif tup.getconfig("USB_PROTOCOL") == "ascii" then
FLAGS += "-DUSB_PROTOCOL_ASCII"
elseif tup.getconfig("USB_PROTOCOL") == "stdout" then
FLAGS += "-DUSB_PROTOCOL_STDOUT"
elseif tup.getconfig("USB_PROTOCOL") == "none" then
+4 -3
View File
@@ -139,6 +139,7 @@ static inline auto make_obj_tree() {
// TODO: changing this currently requires a reboot - fix this
make_protocol_property("enable_uart", &board_config.enable_uart),
make_protocol_property("enable_i2c_instead_of_can" , &board_config.enable_i2c_instead_of_can), // requires a reboot
make_protocol_property("enable_ascii_protocol_on_usb", &board_config.enable_ascii_protocol_on_usb),
make_protocol_property("dc_bus_undervoltage_trip_level", &board_config.dc_bus_undervoltage_trip_level),
make_protocol_property("dc_bus_overvoltage_trip_level", &board_config.dc_bus_overvoltage_trip_level)
),
@@ -179,10 +180,10 @@ void communication_task(void * ctx) {
auto endpoint_provider = EndpointProvider_from_MemberList<tree_type>(*tree_ptr);
set_application_endpoints(&endpoint_provider);
serve_on_uart();
serve_on_usb();
start_uart_server();
start_usb_server();
if (board_config.enable_i2c_instead_of_can) {
serve_on_i2c();
start_i2c_server();
}
for (;;) {
+1 -1
View File
@@ -24,7 +24,7 @@ public:
} i2c1_packet_output;
BidirectionalPacketBasedChannel i2c1_channel(i2c1_packet_output);
void serve_on_i2c() {
void start_i2c_server() {
// CAN H = SDA
// CAN L = SCL
HAL_I2C_EnableListen_IT(&hi2c1);
+1 -1
View File
@@ -16,7 +16,7 @@ struct I2CStats_t {
extern I2CStats_t i2c_stats_;
void serve_on_i2c(void);
void start_i2c_server(void);
#ifdef __cplusplus
}
+1 -1
View File
@@ -87,7 +87,7 @@ static void uart_server_thread(void * ctx) {
};
}
void serve_on_uart() {
void start_uart_server() {
// DMA is set up to recieve in a circular buffer forever.
// We dont use interrupts to fetch the data, instead we periodically read
// data out of the circular buffer into a parse buffer, controlled by a state machine
+1 -1
View File
@@ -12,7 +12,7 @@ extern "C" {
extern osThreadId uart_thread;
void serve_on_uart(void);
void start_uart_server(void);
#ifdef __cplusplus
}
+16 -9
View File
@@ -12,8 +12,12 @@
#include <cmsis_os.h>
#include <freertos_vars.h>
#include <odrive_main.h>
#include "ascii_protocol.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;
@@ -39,12 +43,12 @@ 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);
well... it's not actually. Stupid STM. */, length, active_endpoint_pair);
if (status != USBD_OK) {
osSemaphoreRelease(sem_usb_tx);
return -1;
}
usb_stats_.tx_cnt = 0;
usb_stats_.tx_cnt++;
return 0;
}
} usb_packet_output;
@@ -90,27 +94,30 @@ static void usb_server_thread(void * ctx) {
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 {
#if defined(USB_PROTOCOL_NATIVE)
usb_channel.process_packet(usb_buf, usb_len);
usb_channel.process_packet(usb_buf, usb_len);
#elif defined(USB_PROTOCOL_NATIVE_STREAM_BASED)
usb_native_stream_input.process_bytes(usb_buf, usb_len);
#elif defined(USB_PROTOCOL_ASCII)
ASCII_protocol_parse_stream(usb_buf, usb_len, usb_stream_output);
usb_native_stream_input.process_bytes(usb_buf, usb_len);
#endif
USBD_CDC_ReceivePacket(&hUsbDeviceFS); // 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) {
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;
osSemaphoreRelease(sem_usb_rx);
}
void serve_on_usb() {
void start_usb_server() {
// Start USB communication thread
osThreadDef(usb_server_thread_def, usb_server_thread, osPriorityNormal, 0, 512);
usb_thread = osThreadCreate(osThread(usb_server_thread_def), NULL);
+2 -2
View File
@@ -21,8 +21,8 @@ typedef struct {
extern USBStats_t usb_stats_;
void usb_process_packet(uint8_t *buf, uint32_t len);
void serve_on_usb(void);
void usb_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair);
void start_usb_server(void);
#ifdef __cplusplus
}
+82
View File
@@ -0,0 +1,82 @@
import json
import os
import tempfile
import odrive.remote_object
from odrive.utils import OperationAbortedException
def get_dict(obj, is_config_object):
result = {}
for (k,v) in obj._remote_attributes.items():
if isinstance(v, odrive.remote_object.RemoteProperty) and is_config_object:
result[k] = v.get_value()
elif isinstance(v, odrive.remote_object.RemoteObject):
sub_dict = get_dict(v, k == 'config')
if sub_dict != {}:
result[k] = sub_dict
return result
def set_dict(obj, path, config_dict):
errors = []
for (k,v) in config_dict.items():
name = path + ("." if path != "" else "") + k
if not k in obj._remote_attributes:
errors.append("Could not restore {}: property not found on device".format(name))
continue
remote_attribute = obj._remote_attributes[k]
if isinstance(remote_attribute, odrive.remote_object.RemoteObject):
errors += set_dict(remote_attribute, name, v)
else:
try:
remote_attribute.set_value(v)
except Exception as ex:
errors.append("Could not restore {}: {}".format(name, str(ex)))
return errors
def get_temp_config_filename(device):
serial_number = odrive.utils.get_serial_number_str(device)
safe_serial_number = ''.join(filter(str.isalnum, serial_number))
return os.path.join(tempfile.gettempdir(), 'odrive-config-{}.json'.format(safe_serial_number))
def backup_config(device, filename, logger):
"""
Exports the configuration of an ODrive to a JSON file.
If no file name is provided, the file is placed into a
temporary directory.
"""
if filename is None:
filename = get_temp_config_filename(device)
logger.info("Saving configuration to {}...".format(filename))
if os.path.exists(filename):
if not odrive.utils.yes_no_prompt("The file {} already exists. Do you want to override it?".format(filename), True):
raise OperationAbortedException()
data = get_dict(device, False)
with open(filename, 'w') as file:
json.dump(data, file)
logger.info("Configuration saved.")
def restore_config(device, filename, logger):
"""
Restores the configuration stored in a file
"""
if filename is None:
filename = get_temp_config_filename(device)
with open(filename) as file:
data = json.load(file)
logger.info("Restoring configuration from {}...".format(filename))
errors = odrive.configuration.set_dict(device, "", data)
for error in errors:
logger.info(error)
if errors:
logger.warn("Some of the configuration could not be restored.")
device.save_configuration()
logger.info("Configuration restored.")
+292 -196
View File
File diff suppressed because it is too large Load Diff
+119
View File
@@ -1,5 +1,8 @@
import usb.util
import time
import fractions
import array
from odrive.dfuse.DfuState import DfuState
DFU_REQUEST_SEND = 0x21
DFU_REQUEST_RECEIVE = 0xa1
@@ -12,6 +15,9 @@ DFU_CLRSTATUS = 0x04
DFU_GETSTATE = 0x05
DFU_ABORT = 0x06
SIZE_MULTIPLIERS = {' ': 1, 'K': 1024, 'M' : 1024*1024}
MAX_TRANSFER_SIZE = 2048
# Order is LSB first
def address_to_4bytes(a):
return [ a % 256, (a >> 8)%256, (a >> 16)%256, (a >> 24)%256 ]
@@ -24,6 +30,7 @@ class DfuDevice:
self.intf = None
#self.dev.reset()
self.cfg.set()
self.sectors = list(self.get_device_sectors())
def alternates(self):
return [(usb.util.get_string(self.dev, intf.iInterface), intf) for intf in self.cfg]
@@ -98,3 +105,115 @@ class DfuDevice:
return status
## High level functions ##
# by ODrive Robotics
def get_device_sectors(self):
"""
Returns a list of all sectors on the device.
Each sector is represented as a dictionary with the following keys:
- name: name of the associated memory region (e.g. "Internal Flash")
- alt: USB alternate setting associated with this memory region
- addr: Start address of the sector (e.g. 0x08004000 for the second flash sectors)
- baseaddr: Start address of the memory region associated with the sector
(e.g. 0x08000000 for all flash sectors)
- len: Number of bytes in the sector
"""
for name, alt in self.alternates():
# example for name:
# '@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg'
label, baseaddr, layout = name.split('/')
baseaddr = int(baseaddr, 0) # convert hex to decimal
addr = baseaddr
for sector in layout.split(','):
repeat, size = map(int, sector[:-2].split('*'))
size *= SIZE_MULTIPLIERS[sector[-2].upper()]
mode = sector[-1]
while repeat > 0:
# TODO: verify if the section is writable
yield {
'name': label.strip().strip('@'),
'alt': alt,
'baseaddr': baseaddr,
'addr': addr,
'len': size,
'mode': mode
}
addr += size
repeat -= 1
def set_alternate_safe(self, alt):
self.set_alternate(alt)
if self.get_state() == DfuState.DFU_ERROR:
self.clear_status()
self.wait_while_state(DfuState.DFU_ERROR)
#def clear_error(self)
def set_address_safe(self, addr):
self.set_address(addr)
status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY)
if status[1] != DfuState.DFU_DOWNLOAD_IDLE:
raise RuntimeError("An error occured. Device Status: %r" % status)
# take device out of DFU_DOWNLOAD_SYNC and into DFU_IDLE
self.abort()
status = self.wait_while_state(DfuState.DFU_DOWNLOAD_SYNC)
if status[1] != DfuState.DFU_IDLE:
raise RuntimeError("An error occured. Device Status: %r" % status)
def erase_sector(self, sector):
self.set_alternate_safe(sector['alt'])
self.erase(sector['addr'])
status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY, timeout=sector['len']/32)
if status[1] != DfuState.DFU_DOWNLOAD_IDLE:
raise RuntimeError("An error occured. Device Status: %r" % status)
def write_sector(self, sector, data):
self.set_alternate_safe(sector['alt'])
self.set_address_safe(sector['addr'])
transfer_size = fractions.gcd(sector['len'], MAX_TRANSFER_SIZE)
blocks = [data[i:i + transfer_size] for i in range(0, len(data), transfer_size)]
for blocknum, block in enumerate(blocks):
#print('write to {:08X} ({} bytes)'.format(
# sector['addr'] + blocknum * TRANSFER_SIZE, len(block)))
self.write(blocknum, block)
status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY)
if status[1] != DfuState.DFU_DOWNLOAD_IDLE:
raise RuntimeError("An error occured. Device Status: %r" % status)
def read_sector(self, sector):
"""
Reads data from the specified sector
Returns: a byte array containing the data
"""
self.set_alternate_safe(sector['alt'])
self.set_address_safe(sector['addr'])
transfer_size = fractions.gcd(sector['len'], MAX_TRANSFER_SIZE)
#blocknum_offset = int((sector['addr'] - sector['baseaddr']) / transfer_size)
data = array.array(u'B')
for blocknum in range(int(sector['len'] / transfer_size)):
#print('read at {:08X}'.format(sector['addr'] + blocknum * TRANSFER_SIZE))
deviceBlock = self.read(blocknum, transfer_size)
data.extend(deviceBlock)
self.abort() # take device into DFU_IDLE
return data
def jump_to_application(self, address):
self.set_address_safe(address)
#self.set_address(address)
#status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY)
#if status[1] != DfuState.DFU_DOWNLOAD_IDLE:
# raise RuntimeError("An error occured. Device Status: {}".format(status[1]))
self.leave()
status = self.wait_while_state(DfuState.DFU_MANIFEST_SYNC)
if status[1] != DfuState.DFU_MANIFEST:
raise RuntimeError("An error occured. Device Status: {}".format(status[1]))
+1 -1
View File
@@ -66,7 +66,7 @@ def find_all(path, serial_number,
obj.__dict__['_json_data'] = json_data['members']
obj.__dict__['_json_crc'] = json_crc16
device_serial_number = format(obj.serial_number, 'x').upper() if hasattr(obj, 'serial_number') else "[unknown serial number]"
device_serial_number = odrive.utils.get_serial_number_str(obj)
if serial_number != None and device_serial_number != serial_number:
printer("Ignoring device with serial number {}".format(device_serial_number))
return
+3 -3
View File
@@ -27,7 +27,7 @@ MOTOR_TYPE_HIGH_CURRENT = 0
#MOTOR_TYPE_LOW_CURRENT = 1
MOTOR_TYPE_GIMBAL = 2
CTRL_MODE_VOLTAGE_CONTROL = 0,
CTRL_MODE_CURRENT_CONTROL = 1,
CTRL_MODE_VELOCITY_CONTROL = 2,
CTRL_MODE_VOLTAGE_CONTROL = 0
CTRL_MODE_CURRENT_CONTROL = 1
CTRL_MODE_VELOCITY_CONTROL = 2
CTRL_MODE_POSITION_CONTROL = 3
+1 -1
View File
@@ -52,7 +52,7 @@ def did_discover_device(odrive, logger, app_shutdown_token):
# Publish new ODrive to interactive console
interactive_variables[interactive_name] = odrive
globals()[interactive_name] = odrive # Add to globals so tab complete works
logger.info("{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name))
logger.notify("{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name))
# Subscribe to disappearance of the device
odrive.__channel__._channel_broken.subscribe(lambda: did_lose_device(interactive_name, logger, app_shutdown_token))
+31 -19
View File
@@ -46,20 +46,30 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink)
if platform.system() != 'Windows':
self.dev.reset()
interface_number = 1
#self.dev.set_configuration() # no args: set first configuration
# Find the best interface
self.cfg = self.dev.get_active_configuration()
custom_interfaces = [i for i in self.cfg.interfaces() if i.bInterfaceClass == 0x00 and i.bInterfaceSubClass == 0x01]
cdc_interfaces = [i for i in self.cfg.interfaces() if i.bInterfaceClass == 0x0a and i.bInterfaceSubClass == 0x00]
all_compatible_interfaces = custom_interfaces + cdc_interfaces
if len(all_compatible_interfaces) == 0:
raise Exception("the device has no compatible interfaces")
self.intf = all_compatible_interfaces[0]
# Try to detach kernel driver from interface
#interface_number = 1
try:
if self.dev.is_kernel_driver_active(interface_number):
self.dev.detach_kernel_driver(interface_number)
if self.dev.is_kernel_driver_active(self.intf.bInterfaceNumber):
self.dev.detach_kernel_driver(self.intf.bInterfaceNumber)
self._printer("Detached Kernel Driver")
else:
self._printer("Kernel Driver was not attached")
except NotImplementedError:
pass #is_kernel_driver_active not implemented on Windows
self.dev.set_configuration() # no args: set first configuration
self.cfg = self.dev.get_active_configuration()
self.intf = self.cfg[(1,0)] # this implicitly claims the interface
# write endpoint
# find write endpoint (first OUT endpoint)
self.epw = usb.util.find_descriptor(self.intf,
# match the first OUT endpoint
custom_match = \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
@@ -67,9 +77,8 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink)
)
assert self.epw is not None
self._printer("EndpointAddress for writing {}".format(self.epw.bEndpointAddress))
# read endpoint
# find read endpoint (first IN endpoint)
self.epr = usb.util.find_descriptor(self.intf,
# match the first IN endpoint
custom_match = \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
@@ -154,15 +163,18 @@ def discover_channels(path, serial_number, callback, cancellation_token, channel
known_devices = []
def device_matcher(device):
#print(" test {:04X}:{:04X}".format(device.idVendor, device.idProduct))
if (device.bus, device.address) in known_devices:
return False
if bus != None and device.bus != bus:
return False
if address != None and device.address != address:
return False
if serial_number != None and device.serial_number != serial_number:
return False
if (device.idVendor, device.idProduct) not in ODRIVE_VID_PID_PAIRS:
try:
if (device.bus, device.address) in known_devices:
return False
if bus != None and device.bus != bus:
return False
if address != None and device.address != address:
return False
if serial_number != None and device.serial_number != serial_number:
return False
if (device.idVendor, device.idProduct) not in ODRIVE_VID_PID_PAIRS:
return False
except:
return False
return True
+29
View File
@@ -25,6 +25,9 @@ data_rate = 100
plot_rate = 10
num_samples = 1000
class OperationAbortedException(Exception):
pass
def start_liveplotter(get_var_callback):
"""
Starts a liveplotter.
@@ -157,6 +160,11 @@ def setup_udev_rules(logger):
subprocess.run(["udevadm", "trigger"], check=True)
logger.info('udev rules configured successfully')
def get_serial_number_str(device):
if hasattr(device, 'serial_number'):
return format(device.serial_number, 'x').upper()
else:
return "[unknown serial number]"
## Exceptions ##
@@ -357,9 +365,30 @@ class Logger():
def success(self, text):
self.print_colored(self._prefix + text, Logger.COLOR_GREEN)
def info(self, text):
self.print_colored(self._prefix + text, Logger.COLOR_DEFAULT)
def notify(self, text):
self.print_colored(self._prefix + text, Logger.COLOR_CYAN)
def warn(self, text):
self.print_colored(self._prefix + text, Logger.COLOR_YELLOW)
def error(self, text):
# TODO: write to stderr
self.print_colored(self._prefix + text, Logger.COLOR_RED)
def yes_no_prompt(question, default=None):
if default is None:
question += " [y/n] "
elif default == True:
question += " [Y/n] "
elif default == False:
question += " [y/N] "
while True:
print(question, end='')
choice = input().lower()
if choice in {'yes', 'y'}:
return True
elif choice in {'no', 'n'}:
return False
elif choice == '' and default is not None:
return default
+18 -8
View File
@@ -4,6 +4,20 @@ import subprocess
import os
import sys
def version_str_to_tuple(version_string):
"""
Converts a version string to a tuple of the form
(major, minor, revision, prerelease)
Example: "fw-v0.3.6-23" => (0, 3, 6, True)
"""
regex=r'.*v([0-9a-zA-Z]+).([0-9a-zA-Z]+).([0-9a-zA-Z]+)(.*)'
return (int(re.sub(regex, r"\1", version_string)),
int(re.sub(regex, r"\2", version_string)),
int(re.sub(regex, r"\3", version_string)),
(re.sub(regex, r"\4", version_string) != ""))
def get_version_from_git():
script_dir = os.path.dirname(os.path.realpath(__file__))
try:
@@ -12,19 +26,15 @@ def get_version_from_git():
cwd=script_dir)
git_tag = git_tag.decode(sys.stdout.encoding).rstrip('\n')
regex=r'.*v([0-9a-zA-Z]).([0-9a-zA-Z]).([0-9a-zA-Z])(.*)'
package_version_major = int(re.sub(regex, r"\1", git_tag))
package_version_minor = int(re.sub(regex, r"\2", git_tag))
package_version_revision = int(re.sub(regex, r"\3", git_tag))
package_version_unreleased = (re.sub(regex, r"\4", git_tag) != "")
(major, minor, revision, is_prerelease) = version_str_to_tuple(git_tag)
if package_version_unreleased:
package_version_revision += 1
if is_prerelease:
revision += 1
return git_tag, major, minor, revision, is_prerelease
except Exception as ex:
print(ex)
return "[unknown version]", 0, 0, 0, 1
return git_tag, package_version_major, package_version_minor, package_version_revision, package_version_unreleased
def get_version_str(git_only=False):
"""
+50 -7
View File
@@ -8,7 +8,8 @@ import sys
import argparse
import os
import odrive.discovery
from odrive.utils import Logger, Event
from odrive.utils import Logger, Event, OperationAbortedException
from odrive.configuration import *
# Flush stdout by default
# Source:
@@ -35,8 +36,26 @@ shell_parser.add_argument("--no-ipython", action="store_true",
"instead of the IPython shell, "
"even if IPython is installed.")
dfu_parser = subparsers.add_parser('dfu', help="Upgrade the ODrive device firmware")
dfu_parser.add_argument('file', metavar='HEX', help='The .hex file to be flashed. Make sure your firmware board version matches the actual board version.')
dfu_parser = subparsers.add_parser('dfu', help="Upgrade the ODrive device firmware."
"If no serial number is specified, the first ODrive that is found is updated")
dfu_parser.add_argument('file', metavar='HEX', nargs='?',
help='The .hex file to be flashed. Make sure target board version '
'of the firmware file matches the actual board version. '
'You can download the latest release manually from '
'https://github.com/madcowswe/ODrive/releases. '
'If no file is provided, the script automatically downloads '
'the latest firmware.')
dfu_parser = subparsers.add_parser('backup-config', help="Saves the configuration of the ODrive to a JSON file")
dfu_parser.add_argument('file', nargs='?',
help="Path to the file where to store the data. "
"If no path is provided, the configuration is stored in {}.".format(tempfile.gettempdir()))
dfu_parser = subparsers.add_parser('restore-config', help="Restores the configuration of the ODrive from a JSON file")
dfu_parser.add_argument('file', nargs='?',
help="Path to the file that contains the configuration data. "
"If no path is provided, the configuration is loaded from {}.".format(tempfile.gettempdir()))
code_generator_parser = subparsers.add_parser('generate-code', help="Process a jinja2 template, passing the ODrive's JSON data as data input")
code_generator_parser.add_argument("-t", "--template", type=argparse.FileType('r'),
@@ -119,12 +138,14 @@ try:
elif args.command == 'dfu':
print_version()
import odrive.dfu
odrive.dfu.launch_dfu(args, app_shutdown_token)
odrive.dfu.launch_dfu(args, logger, app_shutdown_token)
elif args.command == 'liveplotter':
from odrive.utils import start_liveplotter
print("Waiting for ODrive...")
my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number)
my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number,
search_cancellation_token=app_shutdown_token,
channel_termination_token=app_shutdown_token)
# If you want to plot different values, change them here.
# You can plot any number of values concurrently.
@@ -134,14 +155,18 @@ try:
elif args.command == 'drv-status':
from odrive.utils import print_drv_regs
print("Waiting for ODrive...")
my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number)
my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number,
search_cancellation_token=app_shutdown_token,
channel_termination_token=app_shutdown_token)
print_drv_regs("Motor 0", my_odrive.axis0.motor)
print_drv_regs("Motor 1", my_odrive.axis1.motor)
elif args.command == 'rate-test':
from odrive.utils import rate_test
print("Waiting for ODrive...")
my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number)
my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number,
search_cancellation_token=app_shutdown_token,
channel_termination_token=app_shutdown_token)
rate_test(my_odrive)
elif args.command == 'udev-setup':
@@ -154,8 +179,26 @@ try:
channel_termination_token=app_shutdown_token)
generate_code(my_odrive, args.template, args.output)
elif args.command == 'backup-config':
from odrive.configuration import backup_config
print("Waiting for ODrive...")
my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number,
search_cancellation_token=app_shutdown_token,
channel_termination_token=app_shutdown_token)
backup_config(my_odrive, args.file, logger)
elif args.command == 'restore-config':
from odrive.configuration import restore_config
print("Waiting for ODrive...")
my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number,
search_cancellation_token=app_shutdown_token,
channel_termination_token=app_shutdown_token)
restore_config(my_odrive, args.file, logger)
else:
raise Exception("unknown command: " + args.command)
except OperationAbortedException:
logger.info("Operation aborted.")
finally:
app_shutdown_token.set()
+3 -3
View File
@@ -142,7 +142,7 @@ try:
if isinstance(test, ODriveTest):
def odrv_test_thread(odrv_name):
odrv_ctx = odrives_by_name[odrv_name]
logger.info('* running {} on {}...'.format(type(test).__name__, odrv_name))
logger.notify('* running {} on {}...'.format(type(test).__name__, odrv_name))
try:
test.check_preconditions(odrv_ctx,
logger.indent(' {}: '.format(odrv_name)))
@@ -171,7 +171,7 @@ try:
try:
if not app_shutdown_token.is_set():
# Run test on this axis
logger.info('* running {} on {}...'.format(type(test).__name__, axis_name))
logger.notify('* running {} on {}...'.format(type(test).__name__, axis_name))
try:
test.check_preconditions(axis_ctx,
logger.indent(' {}: '.format(axis_name)))
@@ -203,7 +203,7 @@ try:
try:
if not app_shutdown_token.is_set():
# Run test on this axis
logger.info('* running {} on {}...'.format(type(test).__name__, coupling_name))
logger.notify('* running {} on {}...'.format(type(test).__name__, coupling_name))
try:
test.check_preconditions(coupled_axes[0], coupled_axes[1],
logger.indent(' {}: '.format(coupling_name)))