diff --git a/README.md b/README.md index a2c2acc7..78c873b6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Docs Build](https://github.com/yconst/tinymovr/workflows/Tinymovr%20Docs%20Build/badge.svg) [![Discord](https://img.shields.io/discord/742400176664084535)](https://discord.gg/CzcCaXbU) -[Tinymovr is an affordable motor controller](https://tinymovr.com) for precise control of 3-phase brushless motors. Tinymovr works with up to 38V input voltage, drives motors up to 40A continuous phase current, integrates an absolute angle encoder (MPS MA702) and features rich connectivity. +[Tinymovr is an affordable motor controller](https://tinymovr.com) with integrated encoder and CAN bus for precise control of 3-phase brushless motors (PMSMs). This repository holds the open source firmware, client library, hardware designs and documentation source. diff --git a/docs/hardware/overview.rst b/docs/hardware/overview.rst index ebab8b87..c127ca44 100644 --- a/docs/hardware/overview.rst +++ b/docs/hardware/overview.rst @@ -47,15 +47,15 @@ M5.x Tinymovr M5.x is is our specialized driver for gimbal motors and light robotic joints. It features a very compact footprint and 5A max drive. -Board Dimensions (M5.1) -####################### +Board Dimensions (M5.1, M5.2) +############################# .. image:: dimensions_m5.png :width: 800 :alt: Tinymovr M5 dimensions -Connectivity (M5.1) -################### +Connectivity (M5.1, M5.2) +######################### .. image:: connectors_m5.png :width: 800 diff --git a/docs/studio/usage.rst b/docs/studio/usage.rst index 805ed6ec..537af8a5 100644 --- a/docs/studio/usage.rst +++ b/docs/studio/usage.rst @@ -120,6 +120,8 @@ In order for multiple Tinymovr instances to coexist in the same CAN network, the :width: 400 :alt: Change CAN bus node ID using the GUI +The board will be discovered with the new ID. Studio GUI will rescan, discover the new node, and remove the old instance. + 2. |cli| Change the ID .. code-block:: python @@ -128,21 +130,21 @@ In order for multiple Tinymovr instances to coexist in the same CAN network, the where x is the desired ID. You can assign IDs in the range 1-1024. -3. Relaunch Studio. The board will be discovered with the new ID. +The board will be discovered with the new ID. Relaunch Studio CLI to remove the old board instance. -4. |gui| Save configuration. +3. |gui| Save configuration. .. image:: save_config.png :width: 400 :alt: Save configuration using the GUI -4. |cli| Save configuration. +3. |cli| Save configuration. .. code-block:: python tm1.save_config() -5. Power down or reset the board. Tinymovr is now ready to use with the new ID. +4. Power down or reset the board. Tinymovr is now ready to use with the new ID. .. _command-line-options: diff --git a/docs/upgrade/upgrade.rst b/docs/upgrade/upgrade.rst index fa1b6f73..09d867ce 100644 --- a/docs/upgrade/upgrade.rst +++ b/docs/upgrade/upgrade.rst @@ -29,8 +29,8 @@ R5.0, R5.1 :width: 800 :alt: Tinymovr R5.0 and R5.1 connectors and pinouts -M5.1 -==== +M5.1, M5.2 +========== .. image:: connectors_m5.png :width: 800 diff --git a/firmware/Makefile b/firmware/Makefile index 137a646e..ea9c2846 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -104,7 +104,7 @@ all: release debug: CFLAGS += -DDEBUG -g2 -O1 debug: CPPFLAGS += -DDEBUG -g2 -O1 debug: LDFLAGS += -O1 -debug: OBJECTS += $(BUILDDIR)/bootloader_$(REV).o +debug: OBJECTS += $(BUILDDIR)/bootloader-$(REV).o debug: binary # Upgrade target @@ -114,11 +114,11 @@ upgrade: LDFLAGS += -O3 upgrade: binary # Release target -release: OBJECTS += $(BUILDDIR)/bootloader_$(REV).o +release: OBJECTS += $(BUILDDIR)/bootloader-$(REV).o release: upgrade # Binary target -binary: print_board_rev $(HEX) $(BIN) $(ELF) +binary: print_board_info $(HEX) $(BIN) $(ELF) # Objcopy to HEX $(HEX): $(ELF) @@ -131,7 +131,7 @@ $(BIN): $(ELF) # Link $(ELF): $(OBJECTS) - - $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.bl_section,alloc,load,readonly,data,contents $(PROJECTDIR)/bootloader/bootloader_$(REV).bin $(BUILDDIR)/bootloader_$(REV).o + - $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.bl_section,alloc,load,readonly,data,contents $(PROJECTDIR)/bootloader/bootloader-$(REV).bin $(BUILDDIR)/bootloader-$(REV).o $(LD) $(LDFLAGS) $(OBJECTS) -o $(ELF) $(SIZE_EX) --format=berkeley $(ELF) @@ -151,8 +151,9 @@ clean : - $(rmdir_cmd) "$(BUILDDIR)" # Print board revision -.PHONY: print_board_rev -print_board_rev: - @echo "===Building Tinymovr===" +.PHONY: print_board_info +print_board_info: + @echo "===Building Tinymovr Bootloader===" @echo "Board revision is $(REV)" - @echo "=======================" + @echo "Fw version is $(GIT_VERSION)" + @echo "==================================" diff --git a/firmware/bootloader/bootloader-M51.bin b/firmware/bootloader/bootloader-M51.bin new file mode 100755 index 00000000..0945a943 Binary files /dev/null and b/firmware/bootloader/bootloader-M51.bin differ diff --git a/firmware/bootloader/bootloader-R32.bin b/firmware/bootloader/bootloader-R32.bin new file mode 100755 index 00000000..8409f60e Binary files /dev/null and b/firmware/bootloader/bootloader-R32.bin differ diff --git a/firmware/bootloader/bootloader-R33.bin b/firmware/bootloader/bootloader-R33.bin new file mode 100755 index 00000000..f407b4d0 Binary files /dev/null and b/firmware/bootloader/bootloader-R33.bin differ diff --git a/firmware/bootloader/bootloader-R50.bin b/firmware/bootloader/bootloader-R50.bin new file mode 100755 index 00000000..5dc7410c Binary files /dev/null and b/firmware/bootloader/bootloader-R50.bin differ diff --git a/firmware/bootloader/bootloader-R51.bin b/firmware/bootloader/bootloader-R51.bin new file mode 100755 index 00000000..936228a3 Binary files /dev/null and b/firmware/bootloader/bootloader-R51.bin differ diff --git a/firmware/bootloader/bootloader-R52.bin b/firmware/bootloader/bootloader-R52.bin new file mode 100755 index 00000000..73ecce31 Binary files /dev/null and b/firmware/bootloader/bootloader-R52.bin differ diff --git a/firmware/bootloader/bootloader_M51.bin b/firmware/bootloader/bootloader_M51.bin deleted file mode 100755 index 9ea49f0d..00000000 Binary files a/firmware/bootloader/bootloader_M51.bin and /dev/null differ diff --git a/firmware/bootloader/bootloader_R32.bin b/firmware/bootloader/bootloader_R32.bin deleted file mode 100755 index aab86464..00000000 Binary files a/firmware/bootloader/bootloader_R32.bin and /dev/null differ diff --git a/firmware/bootloader/bootloader_R33.bin b/firmware/bootloader/bootloader_R33.bin deleted file mode 100755 index 227098ab..00000000 Binary files a/firmware/bootloader/bootloader_R33.bin and /dev/null differ diff --git a/firmware/bootloader/bootloader_R50.bin b/firmware/bootloader/bootloader_R50.bin deleted file mode 100755 index ecdfd3c8..00000000 Binary files a/firmware/bootloader/bootloader_R50.bin and /dev/null differ diff --git a/firmware/bootloader/bootloader_R51.bin b/firmware/bootloader/bootloader_R51.bin deleted file mode 100755 index 74deae9b..00000000 Binary files a/firmware/bootloader/bootloader_R51.bin and /dev/null differ diff --git a/firmware/bootloader/bootloader_R52.bin b/firmware/bootloader/bootloader_R52.bin deleted file mode 100755 index b6601332..00000000 Binary files a/firmware/bootloader/bootloader_R52.bin and /dev/null differ diff --git a/firmware/src/can/can.c b/firmware/src/can/can.c index 512f5212..d48ce0f7 100644 --- a/firmware/src/can/can.c +++ b/firmware/src/can/can.c @@ -39,6 +39,9 @@ static CANState state ={0}; extern volatile uint32_t msTicks; +const uint8_t avlos_proto_hash_8 = (uint8_t)(avlos_proto_hash & 0xFF); +const size_t endpoint_count = sizeof(avlos_endpoints) / sizeof(avlos_endpoints[0]); + void CAN_init(void) { #if defined(BOARD_REV_R52) @@ -135,7 +138,8 @@ void CAN_process_interrupt(void) { can_process_extended(); - if (sizeof(avlos_endpoints) / sizeof(avlos_endpoints[0]) > can_ep_id) + if ((endpoint_count > can_ep_id) && + ((can_frame_hash == avlos_proto_hash_8) || (can_frame_hash == 0))) { uint8_t (*callback)(uint8_t buffer[], uint8_t * buffer_length, Avlos_Command cmd) = avlos_endpoints[can_ep_id]; uint8_t can_msg_buffer[8]; diff --git a/firmware/src/can/can_endpoints.c b/firmware/src/can/can_endpoints.c index 5280c9fd..f9b6ec0f 100644 --- a/firmware/src/can/can_endpoints.c +++ b/firmware/src/can/can_endpoints.c @@ -19,7 +19,6 @@ uint8_t (*avlos_endpoints[79])(uint8_t * buffer, uint8_t * buffer_len, Avlos_Command cmd) = {&avlos_protocol_hash, &avlos_uid, &avlos_fw_version, &avlos_hw_revision, &avlos_Vbus, &avlos_Ibus, &avlos_power, &avlos_temp, &avlos_calibrated, &avlos_errors, &avlos_save_config, &avlos_erase_config, &avlos_reset, &avlos_enter_dfu, &avlos_scheduler_errors, &avlos_controller_state, &avlos_controller_mode, &avlos_controller_warnings, &avlos_controller_errors, &avlos_controller_position_setpoint, &avlos_controller_position_p_gain, &avlos_controller_velocity_setpoint, &avlos_controller_velocity_limit, &avlos_controller_velocity_p_gain, &avlos_controller_velocity_i_gain, &avlos_controller_velocity_deadband, &avlos_controller_velocity_increment, &avlos_controller_current_Iq_setpoint, &avlos_controller_current_Id_setpoint, &avlos_controller_current_Iq_limit, &avlos_controller_current_Iq_estimate, &avlos_controller_current_bandwidth, &avlos_controller_current_Iq_p_gain, &avlos_controller_current_max_Ibus_regen, &avlos_controller_current_max_Ibrake, &avlos_controller_voltage_Vq_setpoint, &avlos_controller_calibrate, &avlos_controller_idle, &avlos_controller_position_mode, &avlos_controller_velocity_mode, &avlos_controller_current_mode, &avlos_controller_set_pos_vel_setpoints, &avlos_comms_can_rate, &avlos_comms_can_id, &avlos_motor_R, &avlos_motor_L, &avlos_motor_pole_pairs, &avlos_motor_type, &avlos_motor_offset, &avlos_motor_direction, &avlos_motor_calibrated, &avlos_motor_I_cal, &avlos_motor_errors, &avlos_encoder_position_estimate, &avlos_encoder_velocity_estimate, &avlos_encoder_type, &avlos_encoder_bandwidth, &avlos_encoder_calibrated, &avlos_encoder_errors, &avlos_traj_planner_max_accel, &avlos_traj_planner_max_decel, &avlos_traj_planner_max_vel, &avlos_traj_planner_t_accel, &avlos_traj_planner_t_decel, &avlos_traj_planner_t_total, &avlos_traj_planner_move_to, &avlos_traj_planner_move_to_tlimit, &avlos_traj_planner_errors, &avlos_homing_velocity, &avlos_homing_max_homing_t, &avlos_homing_retract_dist, &avlos_homing_warnings, &avlos_homing_stall_detect_velocity, &avlos_homing_stall_detect_delta_pos, &avlos_homing_stall_detect_t, &avlos_homing_home, &avlos_watchdog_enabled, &avlos_watchdog_triggered, &avlos_watchdog_timeout }; -uint32_t avlos_proto_hash = 3526126264; uint32_t _avlos_get_proto_hash(void) { diff --git a/firmware/src/can/can_endpoints.h b/firmware/src/can/can_endpoints.h index 0f773a13..11fbdf12 100644 --- a/firmware/src/can/can_endpoints.h +++ b/firmware/src/can/can_endpoints.h @@ -95,7 +95,7 @@ typedef enum ENCODER_TYPE_HALL = 1 } encoder_type_options; -extern uint32_t avlos_proto_hash; +static const uint32_t avlos_proto_hash = 3526126264; extern uint8_t (*avlos_endpoints[79])(uint8_t * buffer, uint8_t * buffer_len, Avlos_Command cmd); extern uint32_t _avlos_get_proto_hash(void); diff --git a/firmware/src/can/can_func.c b/firmware/src/can/can_func.c index 05265c80..e279722f 100644 --- a/firmware/src/can/can_func.c +++ b/firmware/src/can/can_func.c @@ -22,7 +22,7 @@ uint8_t data_length; uint32_t rx_id; bool rtr; uint32_t can_ep_id; -uint32_t can_seq_id; +uint32_t can_frame_hash; // * The function "can_io_config" is part of the Tinymovr-Firmware distribution // * (https://github.com/yconst/tinymovr-firmware). @@ -169,256 +169,5 @@ void can_baud(CAN_BAUD_TYPE baud) } } -TM_RAMFUNC void can_process_standard(void) -{ - uint32_t buffer = PAC55XX_CAN->RXBUF; // read RX buffer, RX buffer bit order same as TX buffer - - data_length = buffer & 0x0F; - rx_id = ((buffer & 0xE00000) >> 21) | ((buffer & 0xFF00) >> 5); - - can_ep_id = rx_id & 0x3F; - rtr = ((buffer >> 6) & 0x1) == 0x1; - rx_data[0] = buffer >> 24; // data0 - if (data_length > 1u) - { - buffer = PAC55XX_CAN->RXBUF; // buffer contains data1..data4 - rx_data[1] = buffer; - rx_data[2] = buffer >> 8; - rx_data[3] = buffer >> 16; - rx_data[4] = buffer >> 24; - if (data_length > 5u) - { - buffer = PAC55XX_CAN->RXBUF; // buffer contains data7..data5 - rx_data[5] = buffer; - rx_data[6] = buffer >> 8; - rx_data[7] = buffer >> 16; - } - } -} - -// * The function "can_process_extended" is part of the Tinymovr-Firmware distribution -// * (https://github.com/yconst/tinymovr-firmware). -// * Copyright (c) 2022 Ioannis Chatzikonstantinou. -// * -// * This program is free software: you can redistribute it and/or modify -// * it under the terms of the GNU General Public License as published by -// * the Free Software Foundation, version 3. -// * -// * This program is distributed in the hope that it will be useful, but -// * WITHOUT ANY WARRANTY; without even the implied warranty of -// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// * General Public License for more details. -// * -// * You should have received a copy of the GNU General Public License -// * along with this program. If not, see . - -TM_RAMFUNC void can_process_extended(void) -{ - uint32_t buffer = PAC55XX_CAN->RXBUF; // read RX buffer, RX buffer bit order same as TX buffer - - data_length = buffer & 0x0F; - rtr = ((buffer >> 6) & 0x1) == 0x1; - rx_id = ((buffer & 0xFF000000) >> 19) | ((buffer & 0x00FF0000) >> 3) | ((buffer & 0x0000FF00) << 13); - - buffer = PAC55XX_CAN->RXBUF; - - rx_id |= (buffer & 0xFF) >> 3; - - ids_from_arbitration(rx_id, &can_ep_id, &can_seq_id); - - rx_data[0] = (buffer >> 8) & 0xFF; // data0 - rx_data[1] = (buffer >> 16) & 0xFF; // data1 - rx_data[2] = (buffer >> 24) & 0xFF; // data2 - if (data_length > 3u) - { - buffer = PAC55XX_CAN->RXBUF; // buffer contains data3..data6 - rx_data[3] = buffer & 0xFF; - rx_data[4] = (buffer >> 8) & 0xFF; - rx_data[5] = (buffer >> 16) & 0xFF; - rx_data[6] = (buffer >> 24) & 0xFF; - if (data_length > 7u) - { - buffer = PAC55XX_CAN->RXBUF; // buffer contains data7 - rx_data[7] = buffer & 0xFF; - } - } -} - -TM_RAMFUNC void can_transmit_standard(uint8_t dataLen, uint16_t id, const uint8_t * data) -{ - while (PAC55XX_CAN->SR.TBS == 0) {}; // wait for TX buffer free - PAC55XX_CAN->TXBUF = (dataLen << 0) | // DLC - Data Length Code - (0u << 6) | // RTR = 0 Data Frame - (0u << 7) | // FF - Format Frame; 0=Std Frame - ((id>>3 & 0xFF) << 8) | // ID 10:3 - ((id&0x07u) << 21) | // ID 2:0 - (data[0] << 24); // Data 0 - - if (dataLen > 1u) - { - PAC55XX_CAN->TXBUF = (data[1] << 0) | // Data 1 - (data[2] << 8) | // Data 2 - (data[3] << 16) | // Data 3 - (data[4] << 24); // Data 4 - } - if (dataLen > 5u) - { - PAC55XX_CAN->TXBUF = (data[5] << 0) | // Data 5 - (data[6] << 8) | // Data 6 - (data[7] << 16); // Data 7 - - } - - PAC55XX_CAN->CMR.TR = 1; // Request transmit -} - -// * The function "can_transmit_extended" is part of the Tinymovr-Firmware distribution -// * (https://github.com/yconst/tinymovr-firmware). -// * Copyright (c) 2022 Ioannis Chatzikonstantinou. -// * -// * This program is free software: you can redistribute it and/or modify -// * it under the terms of the GNU General Public License as published by -// * the Free Software Foundation, version 3. -// * -// * This program is distributed in the hope that it will be useful, but -// * WITHOUT ANY WARRANTY; without even the implied warranty of -// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// * General Public License for more details. -// * -// * You should have received a copy of the GNU General Public License -// * along with this program. If not, see . - -TM_RAMFUNC void can_transmit_extended(uint8_t dataLen, uint32_t id, const uint8_t * data) -{ - while (PAC55XX_CAN->SR.TBS == 0) {}; // wait for TX buffer free - PAC55XX_CAN->TXBUF = (dataLen << 0) | // DLC - Data Length Code - (0u << 6) | // RTR = 0 Data Frame - (1u << 7) | // FF - Format Frame; 1=Ext Frame - ((id&0x1FE00000u) >> (21-8)) | - ((id&0x001FE000u) << (16-13)) | - ((id&0x00001FE0u) << (24-5)); - - PAC55XX_CAN->TXBUF = ((id & 0x1F) << 3) | - ((data[0] & 0xFF) << 8) | - ((data[1] & 0xFF) << 16) | - ((data[2] & 0xFF) << 24); - - if (dataLen > 3u) - { - PAC55XX_CAN->TXBUF = ((data[3] & 0xFF) << 0) | // Data 3 - ((data[4] & 0xFF) << 8) | // Data 4 - ((data[5] & 0xFF) << 16) | // Data 5 - ((data[6] & 0xFF) << 24); // Data 6 - } - if (dataLen > 7u) - { - PAC55XX_CAN->TXBUF = ((data[7] & 0xFF) << 0); // Data 7 - } - PAC55XX_CAN->CMR.TR = 1; // Request transmit -} - -uint16_t CAN_BaudTypeToInt(CAN_BAUD_TYPE type) -{ - uint16_t ret = 0u; - switch(type) - { - case CAN_BAUD_50KHz: - ret = 50u; - break; - - case CAN_BAUD_100KHz: - ret = 100u; - break; - - case CAN_BAUD_125KHz: - ret = 125u; - break; - - case CAN_BAUD_200KHz: - ret = 200u; - break; - - case CAN_BAUD_250KHz: - ret = 250u; - break; - - case CAN_BAUD_400KHz: - ret = 400u; - break; - - case CAN_BAUD_500KHz: - ret = 500u; - break; - - case CAN_BAUD_800KHz: - ret = 800u; - break; - - case CAN_BAUD_1000KHz: - ret = 1000u; - break; - - default: - // ret already set - break; - } - return ret; -} -CAN_BAUD_TYPE CAN_IntToBaudType(uint16_t baud) -{ - CAN_BAUD_TYPE ret = CAN_BAUD_250KHz; - switch(baud) - { - case 50u: - ret = CAN_BAUD_50KHz; - break; - - case 100u: - ret = CAN_BAUD_100KHz; - break; - - case 125u: - ret = CAN_BAUD_125KHz; - break; - - case 200u: - ret = CAN_BAUD_200KHz; - break; - - // 250 handled in default - - case 400u: - ret = CAN_BAUD_400KHz; - break; - - case 500u: - ret = CAN_BAUD_500KHz; - break; - - case 800u: - ret = CAN_BAUD_800KHz; - break; - - case 1000u: - ret = CAN_BAUD_1000KHz; - break; - - default: - // ret already set - break; - } - return ret; -} - -inline void ids_from_arbitration(uint32_t arb_id, uint32_t* ep_id, uint32_t* seq_id) -{ - *ep_id = arb_id & CAN_EP_MASK; - *seq_id = (arb_id & CAN_SEQ_MASK) >> CAN_EP_SIZE; -} - -inline void arbitration_from_ids(uint32_t* arb_id, uint32_t ep_id, uint32_t seq_id, uint32_t node_id) -{ - *arb_id = (ep_id & CAN_EP_MASK) | ((seq_id << CAN_EP_SIZE) & CAN_SEQ_MASK) | ((node_id << (CAN_EP_SIZE + CAN_SEQ_SIZE)) & CAN_DEV_MASK); -} diff --git a/firmware/src/can/can_func.h b/firmware/src/can/can_func.h index 597080ba..5637ee3c 100644 --- a/firmware/src/can/can_func.h +++ b/firmware/src/can/can_func.h @@ -20,10 +20,10 @@ #define CAN_EP_SIZE 12 #define CAN_EP_MASK ((1 << CAN_EP_SIZE) - 1) -#define CAN_SEQ_SIZE 9 -#define CAN_SEQ_MASK (((1 << CAN_SEQ_SIZE) - 1) << CAN_EP_SIZE) +#define CAN_HASH_SIZE 9 +#define CAN_HASH_MASK (((1 << CAN_HASH_SIZE) - 1) << CAN_EP_SIZE) #define CAN_DEV_SIZE 8 -#define CAN_DEV_MASK (((1 << CAN_DEV_SIZE) - 1) << (CAN_EP_SIZE + CAN_SEQ_SIZE)) +#define CAN_DEV_MASK (((1 << CAN_DEV_SIZE) - 1) << (CAN_EP_SIZE + CAN_HASH_SIZE)) // #define CAN_SJW_1tq ((uint8_t)0x00) /*!< 1 time quantum */ // #define CAN_SJW_2tq ((uint8_t)0x01) /*!< 2 time quantum */ @@ -77,17 +77,261 @@ extern uint8_t data_length; extern uint32_t rx_id; extern bool rtr; extern uint32_t can_ep_id; -extern uint32_t can_seq_id; +extern uint32_t can_frame_hash; void can_baud(CAN_BAUD_TYPE baud); void can_io_config(void); -void can_process_standard(void); -void can_process_extended(void); -void can_transmit_standard(uint8_t dataLen, uint16_t id, const uint8_t * data); -void can_transmit_extended(uint8_t dataLen, uint32_t id, const uint8_t * data); -uint16_t CAN_BaudTypeToInt(CAN_BAUD_TYPE type); -CAN_BAUD_TYPE CAN_IntToBaudType(uint16_t baud); +static inline void ids_from_arbitration(uint32_t arb_id, uint32_t* ep_id, uint32_t* hash) +{ + *ep_id = arb_id & CAN_EP_MASK; + *hash = (arb_id & CAN_HASH_MASK) >> CAN_EP_SIZE; +} + +static inline void arbitration_from_ids(uint32_t* arb_id, uint32_t ep_id, uint32_t hash, uint32_t node_id) +{ + *arb_id = (ep_id & CAN_EP_MASK) | ((hash << CAN_EP_SIZE) & CAN_HASH_MASK) | ((node_id << (CAN_EP_SIZE + CAN_HASH_SIZE)) & CAN_DEV_MASK); +} + +static inline void can_process_standard(void) +{ + uint32_t buffer = PAC55XX_CAN->RXBUF; // read RX buffer, RX buffer bit order same as TX buffer + + data_length = buffer & 0x0F; + rx_id = ((buffer & 0xE00000) >> 21) | ((buffer & 0xFF00) >> 5); + + can_ep_id = rx_id & 0x3F; + rtr = ((buffer >> 6) & 0x1) == 0x1; + rx_data[0] = buffer >> 24; // data0 + if (data_length > 1u) + { + buffer = PAC55XX_CAN->RXBUF; // buffer contains data1..data4 + rx_data[1] = buffer; + rx_data[2] = buffer >> 8; + rx_data[3] = buffer >> 16; + rx_data[4] = buffer >> 24; + if (data_length > 5u) + { + buffer = PAC55XX_CAN->RXBUF; // buffer contains data7..data5 + rx_data[5] = buffer; + rx_data[6] = buffer >> 8; + rx_data[7] = buffer >> 16; + } + } +} + +// * The function "can_process_extended" is part of the Tinymovr-Firmware distribution +// * (https://github.com/yconst/tinymovr-firmware). +// * Copyright (c) 2022 Ioannis Chatzikonstantinou. +// * +// * This program is free software: you can redistribute it and/or modify +// * it under the terms of the GNU General Public License as published by +// * the Free Software Foundation, version 3. +// * +// * This program is distributed in the hope that it will be useful, but +// * WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * General Public License for more details. +// * +// * You should have received a copy of the GNU General Public License +// * along with this program. If not, see . + +static inline void can_process_extended(void) +{ + uint32_t buffer = PAC55XX_CAN->RXBUF; // read RX buffer, RX buffer bit order same as TX buffer + + data_length = buffer & 0x0F; + rtr = ((buffer >> 6) & 0x1) == 0x1; + rx_id = ((buffer & 0xFF000000) >> 19) | ((buffer & 0x00FF0000) >> 3) | ((buffer & 0x0000FF00) << 13); + + buffer = PAC55XX_CAN->RXBUF; + + rx_id |= (buffer & 0xFF) >> 3; + + ids_from_arbitration(rx_id, &can_ep_id, &can_frame_hash); + + rx_data[0] = (buffer >> 8) & 0xFF; // data0 + rx_data[1] = (buffer >> 16) & 0xFF; // data1 + rx_data[2] = (buffer >> 24) & 0xFF; // data2 + if (data_length > 3u) + { + buffer = PAC55XX_CAN->RXBUF; // buffer contains data3..data6 + rx_data[3] = buffer & 0xFF; + rx_data[4] = (buffer >> 8) & 0xFF; + rx_data[5] = (buffer >> 16) & 0xFF; + rx_data[6] = (buffer >> 24) & 0xFF; + if (data_length > 7u) + { + buffer = PAC55XX_CAN->RXBUF; // buffer contains data7 + rx_data[7] = buffer & 0xFF; + } + } +} + +static inline void can_transmit_standard(uint8_t dataLen, uint16_t id, const uint8_t * data) +{ + while (PAC55XX_CAN->SR.TBS == 0) {}; // wait for TX buffer free + PAC55XX_CAN->TXBUF = (dataLen << 0) | // DLC - Data Length Code + (0u << 6) | // RTR = 0 Data Frame + (0u << 7) | // FF - Format Frame; 0=Std Frame + ((id>>3 & 0xFF) << 8) | // ID 10:3 + ((id&0x07u) << 21) | // ID 2:0 + (data[0] << 24); // Data 0 + + if (dataLen > 1u) + { + PAC55XX_CAN->TXBUF = (data[1] << 0) | // Data 1 + (data[2] << 8) | // Data 2 + (data[3] << 16) | // Data 3 + (data[4] << 24); // Data 4 + } + if (dataLen > 5u) + { + PAC55XX_CAN->TXBUF = (data[5] << 0) | // Data 5 + (data[6] << 8) | // Data 6 + (data[7] << 16); // Data 7 + + } + + PAC55XX_CAN->CMR.TR = 1; // Request transmit +} + +// * The function "can_transmit_extended" is part of the Tinymovr-Firmware distribution +// * (https://github.com/yconst/tinymovr-firmware). +// * Copyright (c) 2022 Ioannis Chatzikonstantinou. +// * +// * This program is free software: you can redistribute it and/or modify +// * it under the terms of the GNU General Public License as published by +// * the Free Software Foundation, version 3. +// * +// * This program is distributed in the hope that it will be useful, but +// * WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * General Public License for more details. +// * +// * You should have received a copy of the GNU General Public License +// * along with this program. If not, see . + +static inline void can_transmit_extended(uint8_t dataLen, uint32_t id, const uint8_t * data) +{ + while (PAC55XX_CAN->SR.TBS == 0) {}; // wait for TX buffer free + PAC55XX_CAN->TXBUF = (dataLen << 0) | // DLC - Data Length Code + (0u << 6) | // RTR = 0 Data Frame + (1u << 7) | // FF - Format Frame; 1=Ext Frame + ((id&0x1FE00000u) >> (21-8)) | + ((id&0x001FE000u) << (16-13)) | + ((id&0x00001FE0u) << (24-5)); + + PAC55XX_CAN->TXBUF = ((id & 0x1F) << 3) | + ((data[0] & 0xFF) << 8) | + ((data[1] & 0xFF) << 16) | + ((data[2] & 0xFF) << 24); + + if (dataLen > 3u) + { + PAC55XX_CAN->TXBUF = ((data[3] & 0xFF) << 0) | // Data 3 + ((data[4] & 0xFF) << 8) | // Data 4 + ((data[5] & 0xFF) << 16) | // Data 5 + ((data[6] & 0xFF) << 24); // Data 6 + } + if (dataLen > 7u) + { + PAC55XX_CAN->TXBUF = ((data[7] & 0xFF) << 0); // Data 7 + } + + PAC55XX_CAN->CMR.TR = 1; // Request transmit +} + +static inline uint16_t CAN_BaudTypeToInt(CAN_BAUD_TYPE type) +{ + uint16_t ret = 0u; + switch(type) + { + case CAN_BAUD_50KHz: + ret = 50u; + break; + + case CAN_BAUD_100KHz: + ret = 100u; + break; + + case CAN_BAUD_125KHz: + ret = 125u; + break; + + case CAN_BAUD_200KHz: + ret = 200u; + break; + + case CAN_BAUD_250KHz: + ret = 250u; + break; + + case CAN_BAUD_400KHz: + ret = 400u; + break; + + case CAN_BAUD_500KHz: + ret = 500u; + break; + + case CAN_BAUD_800KHz: + ret = 800u; + break; + + case CAN_BAUD_1000KHz: + ret = 1000u; + break; + + default: + // ret already set + break; + } + return ret; +} + +static inline CAN_BAUD_TYPE CAN_IntToBaudType(uint16_t baud) +{ + CAN_BAUD_TYPE ret = CAN_BAUD_250KHz; + switch(baud) + { + case 50u: + ret = CAN_BAUD_50KHz; + break; + + case 100u: + ret = CAN_BAUD_100KHz; + break; + + case 125u: + ret = CAN_BAUD_125KHz; + break; + + case 200u: + ret = CAN_BAUD_200KHz; + break; + + // 250 handled in default + + case 400u: + ret = CAN_BAUD_400KHz; + break; + + case 500u: + ret = CAN_BAUD_500KHz; + break; + + case 800u: + ret = CAN_BAUD_800KHz; + break; + + case 1000u: + ret = CAN_BAUD_1000KHz; + break; -void ids_from_arbitration(uint32_t arb_id, uint32_t* can_ep_id, uint32_t* can_seq_id); -void arbitration_from_ids(uint32_t* arb_id, uint32_t ep_id, uint32_t seq_id, uint32_t node_id); \ No newline at end of file + default: + // ret already set + break; + } + return ret; +} diff --git a/firmware/src/system/system.h b/firmware/src/system/system.h index 91bf0497..5ddf404b 100644 --- a/firmware/src/system/system.h +++ b/firmware/src/system/system.h @@ -38,10 +38,11 @@ void system_update(void); void system_reset(void); void system_enter_dfu(void); -inline uint8_t system_get_fw_version_string(char *buffer) +static inline uint8_t system_get_fw_version_string(char *buffer) { - memcpy(buffer, GIT_VERSION, 4); - return 4; + const uint8_t size = fminf(sizeof(GIT_VERSION), 8); + memcpy(buffer, GIT_VERSION, size); + return size; } inline uint32_t system_get_uid(void) diff --git a/studio/Python/tests/test_dfu.py b/studio/Python/tests/test_dfu.py index c560b576..031626b7 100644 --- a/studio/Python/tests/test_dfu.py +++ b/studio/Python/tests/test_dfu.py @@ -39,24 +39,26 @@ def setUp(cls): def test_dfu(self, node_id=1): init_tee(self.can_bus) time.sleep(0.5) - tm = create_device(node_id=node_id) - tm_hash = tm.protocol_hash - tm.enter_dfu() - time.sleep(0.5) - bl = create_device(node_id=node_id) - bl_hash = bl.protocol_hash - bl.reset() - time.sleep(0.2) - tm = create_device(node_id=node_id) - tm_hash2 = tm.protocol_hash - tm.reset() - time.sleep(0.2) - tm_hash3 = tm.protocol_hash - self.assertNotEqual(tm_hash, 0) - self.assertEqual(tm_hash, tm_hash2) - self.assertEqual(tm_hash, tm_hash3) - self.assertNotEqual(tm_hash, bl_hash) - time.sleep(0.2) + for i in range(10): + print("Testing DFU iteration ", i+1) + tm = create_device(node_id=node_id) + tm_hash = tm.protocol_hash + tm.enter_dfu() + time.sleep(0.5) + bl = create_device(node_id=node_id) + bl_hash = bl.protocol_hash + bl.reset() + time.sleep(0.2) + tm = create_device(node_id=node_id) + tm_hash2 = tm.protocol_hash + tm.reset() + time.sleep(0.2) + tm_hash3 = tm.protocol_hash + self.assertNotEqual(tm_hash, 0) + self.assertEqual(tm_hash, tm_hash2) + self.assertEqual(tm_hash, tm_hash3) + self.assertNotEqual(tm_hash, bl_hash) + time.sleep(0.2) @classmethod def tearDownClass(cls): diff --git a/studio/Python/tinymovr/channel.py b/studio/Python/tinymovr/channel.py index aa088194..42cb07fc 100644 --- a/studio/Python/tinymovr/channel.py +++ b/studio/Python/tinymovr/channel.py @@ -24,30 +24,29 @@ CAN_DEV_MASK, CAN_EP_SIZE, CAN_EP_MASK, - CAN_SEQ_SIZE, - CAN_SEQ_MASK, + CAN_HASH_SIZE, + CAN_HASH_MASK, ) from tinymovr.codec import MultibyteCodec class ResponseError(Exception): - def __init__(self, kw, *args, **kwargs): - msg = "Node {} did not respond".format(kw) - super().__init__(msg, *args, **kwargs) - self.kw = kw + def __init__(self, node_id): + super().__init__(f"Node {node_id} did not respond") + self.node_id = node_id class CANChannel(BaseChannel): - def __init__(self, node_id): + def __init__(self, node_id, compare_hash = 0): self.node_id = node_id - get_tee().add( - lambda frame: frame.is_remote_frame == False - and ids_from_arbitration(frame.arbitration_id)[2] == node_id, - self._recv_cb, - ) + self.compare_hash = compare_hash self.queue = [] self.lock = Lock() self.evt = Event() + get_tee().add(self._filter_frame, self._recv_cb) + + def _filter_frame(self, frame): + return not frame.is_remote_frame and ids_from_arbitration(frame.arbitration_id)[2] == self.node_id def _recv_cb(self, frame): """ @@ -76,12 +75,11 @@ def recv(self, ep_id, timeout=1.0): with self.lock: self.evt.wait(timeout=timeout) self.evt.clear() - frame_id = arbitration_from_ids(ep_id, 0, self.node_id) - index = 0 - while index < len(self.queue): - if self.queue[index].arbitration_id == frame_id: - return self.queue.pop(index).data - index += 1 + for frame in self.queue: + inc_ep_id, inc_hash, _ = ids_from_arbitration(frame.arbitration_id) + if inc_ep_id == ep_id and (inc_hash == self.compare_hash or inc_hash == 0): + self.queue.remove(frame) + return frame.data raise ResponseError(self.node_id) def create_frame(self, endpoint_id, rtr=False, payload=None): @@ -89,7 +87,7 @@ def create_frame(self, endpoint_id, rtr=False, payload=None): Generate a CAN frame using python-can Message class """ return can.Message( - arbitration_id=arbitration_from_ids(endpoint_id, 0, self.node_id), + arbitration_id=arbitration_from_ids(endpoint_id, self.compare_hash, self.node_id), is_extended_id=True, is_remote_frame=rtr, data=payload, @@ -103,22 +101,22 @@ def serializer(self): # TODO: Implement unit test for these functions def ids_from_arbitration(arbitration_id): """ - Generate endpoint, message sequence and node ids + Generate endpoint, hash value and node ids from a CAN arbitration id """ - node_id = (arbitration_id & CAN_DEV_MASK) >> (CAN_EP_SIZE + CAN_SEQ_SIZE) - seq_id = (arbitration_id & CAN_SEQ_MASK) >> CAN_EP_SIZE + node_id = (arbitration_id & CAN_DEV_MASK) >> (CAN_EP_SIZE + CAN_HASH_SIZE) + hash = (arbitration_id & CAN_HASH_MASK) >> CAN_EP_SIZE ep_id = arbitration_id & CAN_EP_MASK - return ep_id, seq_id, node_id + return ep_id, hash, node_id -def arbitration_from_ids(ep_id, seq_id, node_id): +def arbitration_from_ids(ep_id, hash, node_id): """ Generate a CAN arbitration id from endpoint, - message sequence and node ids + hash value and node ids """ return ( ep_id & CAN_EP_MASK - | ((seq_id << CAN_EP_SIZE) & CAN_SEQ_MASK) - | ((node_id << (CAN_EP_SIZE + CAN_SEQ_SIZE)) & CAN_DEV_MASK) + | ((hash << CAN_EP_SIZE) & CAN_HASH_MASK) + | ((node_id << (CAN_EP_SIZE + CAN_HASH_SIZE)) & CAN_DEV_MASK) ) diff --git a/studio/Python/tinymovr/config/config.py b/studio/Python/tinymovr/config/config.py index 28ab4698..6f72b1a9 100644 --- a/studio/Python/tinymovr/config/config.py +++ b/studio/Python/tinymovr/config/config.py @@ -69,18 +69,20 @@ def create_device(node_id): Create a device with the defined ID. The hash value will be retrieved from the remote. """ - chan = CANChannel(node_id) # Temporarily using a default spec to get the protocol_hash # This assumes that `protocol_hash` is standard across different specs # Get the first spec as a temp - tmp_spec = list(specs["hash_uint32"].values())[0] + tmp_hash = list(specs["hash_uint32"].keys())[0] + tmp_spec = specs["hash_uint32"][tmp_hash] node = deserialize(tmp_spec) + chan = CANChannel(node_id, 0) node._channel = chan # Check for the correct spec using the remote hash protocol_hash = node.protocol_hash device_spec = specs["hash_uint32"].get(protocol_hash) + chan.compare_hash = protocol_hash & 0xFF if not device_spec: raise ValueError(f"No device spec found for hash {protocol_hash}.") @@ -101,6 +103,7 @@ def create_device_with_hash_msg(heartbeat_msg): hash, *_ = chan.serializer.deserialize(heartbeat_msg.data[:4], DataType.UINT32) device_spec = specs["hash_uint32"].get(hash) + chan.compare_hash = hash & 0xFF if not device_spec: raise ValueError(f"No device spec found for hash {hash}.") diff --git a/studio/Python/tinymovr/constants.py b/studio/Python/tinymovr/constants.py index de5d58fb..f2f07674 100644 --- a/studio/Python/tinymovr/constants.py +++ b/studio/Python/tinymovr/constants.py @@ -25,8 +25,8 @@ CAN_EP_SIZE = 12 CAN_EP_MASK = (1 << CAN_EP_SIZE) - 1 -CAN_SEQ_SIZE = 9 -CAN_SEQ_MASK = ((1 << CAN_SEQ_SIZE) - 1) << CAN_EP_SIZE +CAN_HASH_SIZE = 9 +CAN_HASH_MASK = ((1 << CAN_HASH_SIZE) - 1) << CAN_EP_SIZE CAN_DEV_SIZE = 8 -CAN_DEV_MASK = ((1 << CAN_DEV_SIZE) - 1) << (CAN_EP_SIZE + CAN_SEQ_SIZE) +CAN_DEV_MASK = ((1 << CAN_DEV_SIZE) - 1) << (CAN_EP_SIZE + CAN_HASH_SIZE) diff --git a/studio/Python/tinymovr/dfu.py b/studio/Python/tinymovr/dfu.py index 4b1c8d3e..299bbaf6 100644 --- a/studio/Python/tinymovr/dfu.py +++ b/studio/Python/tinymovr/dfu.py @@ -15,6 +15,7 @@ import sys import os import time +import inspect from pathlib import Path import can import yaml @@ -89,7 +90,12 @@ def upload_bin(device, bin_path): total_size = os.path.getsize(bin_path) # Get the total size of .bin file uploaded_size = 0 print("\nErasing flash...") - result = device.erase_all() + try: + # Assume device.erase_all can take hash_validation and attempt to call it + result = device.erase_all(device.hash_uint32) + except TypeError: + # Fallback to calling erase_all without hash_validation if TypeError is raised + result = device.erase_all() if result != 0: print("\nError while erasing!") return @@ -110,7 +116,12 @@ def upload_bin(device, bin_path): time.sleep(1e-5) # Commit the data in scratchpad to flash memory and get checksum - device_checksum = device.commit(flash_addr) + try: + # Assume device.commit can take hash_validation and attempt to call it + device_checksum = device.commit(flash_addr, device.hash_uint32) + except TypeError: + # Fallback to calling commit without hash_validation if TypeError is raised + device_checksum = device.commit(flash_addr) local_checksum = calculate_local_checksum(chunk) diff --git a/studio/Python/tinymovr/gui/widgets.py b/studio/Python/tinymovr/gui/widgets.py index 6914b827..21f5d225 100644 --- a/studio/Python/tinymovr/gui/widgets.py +++ b/studio/Python/tinymovr/gui/widgets.py @@ -131,6 +131,7 @@ def __init__(self, name, node, *args, **kwargs): self.text_editor.editingFinished.connect(self._on_editor_text_changed) else: self.text_editor = QLineEdit(format_value(node.get_value())) + self.text_editor.editingFinished.connect(self._on_editor_text_changed) if not editable: self.text_editor.setReadOnly(True) self._checked = False @@ -153,7 +154,7 @@ def _on_editor_text_changed(self): except UndefinedUnitError: attr.set_value(text) if "reload_data" in attr.meta and attr.meta["reload_data"]: - self.worker.reset() + self.treeWidget().window().worker.reset() return else: self.text_editor.setText(format_value(attr.get_value())) diff --git a/studio/Python/tinymovr/specs/dfu.yaml b/studio/Python/tinymovr/specs/dfu_1_0_x.yaml similarity index 100% rename from studio/Python/tinymovr/specs/dfu.yaml rename to studio/Python/tinymovr/specs/dfu_1_0_x.yaml diff --git a/studio/Python/tinymovr/specs/dfu_1_1_x.yaml b/studio/Python/tinymovr/specs/dfu_1_1_x.yaml new file mode 100644 index 00000000..1cef75e1 --- /dev/null +++ b/studio/Python/tinymovr/specs/dfu_1_1_x.yaml @@ -0,0 +1,72 @@ + +name: tm_dfu +remote_attributes: + - name: protocol_hash + dtype: uint32 + getter_name: _system_get_proto_hash_and_trigger_dfu + summary: The Avlos protocol hash. + - name: uid + dtype: uint32 + getter_name: system_get_uid + summary: The unique device ID, unique to each PAC55xx chip produced. + - name: fw_version + dtype: string + getter_name: system_get_fw_version_string + summary: The bootloader firmware version. + - name: hw_revision + dtype: uint32 + getter_name: system_get_hw_revision + summary: The hardware revision. + - name: reset + summary: Reset the device. + caller_name: system_reset + dtype: void + arguments: [] + meta: {reload_data: True} + - name: node_id + summary: The node id + getter_name: CAN_get_ID + dtype: uint8 + - name: error + options: [NONE, START_EXECUTION_STACK_POINTER_OUT_OF_RANGE, START_EXECUTION_ADDRESS_OUT_OF_RANGE, START_EXECUTION_ADDRESS_LSB_NOT_SET] + getter_name: system_get_error + summary: The last error encountered in DFU + - name: read_flash_32 + summary: Read a 32 bit value from the flash + caller_name: nvm_read_flash_32 + dtype: uint32 + arguments: + - name: address + dtype: uint32 + - name: write_scratch_32 + summary: Write a 32 bit value to the scratchpad + caller_name: nvm_write_scratch_32 + dtype: void + arguments: + - name: offset_word32 + dtype: uint8 + - name: value + dtype: uint32 + - name: read_scratch_32 + summary: Read a 32 bit value from the scratchpad + caller_name: nvm_read_scratch_32 + dtype: uint32 + arguments: + - name: offset_word32 + dtype: uint8 + - name: commit + summary: Commit the 128 bit scratchpad to the specified flash address. Pass the current protocol hash for validation. + caller_name: nvm_commit + dtype: uint32 + arguments: + - name: address + dtype: uint32 + - name: hash_validation + dtype: uint32 + - name: erase_all + summary: Erase all flash. Pass the current protocol hash for validation. + caller_name: nvm_erase_all + dtype: uint32 + arguments: + - name: hash_validation + dtype: uint32