diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e019485..2e40d076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Unreleased Features Please add a note of your changes below this heading if you make a Pull Request. +### API Miration Notes + +* `enable_uart` and `uart_baudrate` were renamed to `enable_uart0` and `uart0_baudrate`. +* `enable_i2c_instead_of_can` was replaced by the separate settings `enable_i2c0` and `enable_can0`. + ### Added * AC Induction Motor support. * Tracking of rotor flux through rotor time constant @@ -30,6 +35,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Added scripts for building via docker. * Added ability to change uart baudrate via fibre * Introduced `odrive-interface.yaml` as a root source for the ODrive's API. `odrivetool` connects much faster as a side effect. +* Introduced GPIO modes. GPIOs now need to be explicitly set to the mode corresponding to the feature that they are used by. See `.config.gpioX_mode`. ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` diff --git a/Firmware/Board/v3/Inc/board.h b/Firmware/Board/v3/Inc/board.h index 0294b34b..4183b757 100644 --- a/Firmware/Board/v3/Inc/board.h +++ b/Firmware/Board/v3/Inc/board.h @@ -18,23 +18,26 @@ #include -#if HW_VERSION_MAJOR == 3 #if HW_VERSION_MINOR <= 3 #define SHUNT_RESISTANCE (675e-6f) #else #define SHUNT_RESISTANCE (500e-6f) #endif -#endif - #define AXIS_COUNT (2) -#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR <= 4 -#define GPIO_COUNT (5) -#elif HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 5 -#define GPIO_COUNT (8) +// Total count of GPIOs, including encoder pins, CAN pins and a dummy GPIO0. +// ODrive v3.4 and earlier don't have GPIOs 6, 7 and 8 but to keep the numbering +// consistent we just leave a gap in the counting scheme. +#define GPIO_COUNT (17) + +#if HW_VERSION_MINOR >= 5 && HW_VERSION_VOLTAGE >= 48 +#define DEFAULT_BRAKE_RESISTANCE (2.0f) // [ohm] +#else +#define DEFAULT_BRAKE_RESISTANCE (0.47f) // [ohm] #endif +#define GPIO_AF_NONE ((uint8_t)0xff) #ifdef __cplusplus #include @@ -51,6 +54,7 @@ extern Motor motors[AXIS_COUNT]; extern OnboardThermistorCurrentLimiter fet_thermistors[AXIS_COUNT]; extern Encoder encoders[AXIS_COUNT]; extern Stm32Gpio gpios[GPIO_COUNT]; +extern uint8_t alternate_functions[GPIO_COUNT][6]; extern uint32_t pwm_in_gpios[4]; #include diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index a64ff519..cadeae8c 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -106,12 +106,6 @@ void MX_GPIO_Init(void) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - /*Configure GPIO pins : PBPin PBPin */ - GPIO_InitStruct.Pin = GPIO_6_Pin|GPIO_8_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_NOPULL; - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); - /*Configure GPIO pin : PtPin */ GPIO_InitStruct.Pin = EN_GATE_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; @@ -119,12 +113,6 @@ void MX_GPIO_Init(void) GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(EN_GATE_GPIO_Port, &GPIO_InitStruct); - /*Configure GPIO pin : PtPin */ - GPIO_InitStruct.Pin = GPIO_7_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_NOPULL; - HAL_GPIO_Init(GPIO_7_GPIO_Port, &GPIO_InitStruct); - /*Configure GPIO pin : PtPin */ GPIO_InitStruct.Pin = nFAULT_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; diff --git a/Firmware/Board/v3/Src/i2c.c b/Firmware/Board/v3/Src/i2c.c index bae77f12..ad0324a1 100644 --- a/Firmware/Board/v3/Src/i2c.c +++ b/Firmware/Board/v3/Src/i2c.c @@ -84,23 +84,11 @@ void MX_I2C1_Init(uint8_t addr) void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle) { - GPIO_InitTypeDef GPIO_InitStruct; if(i2cHandle->Instance==I2C1) { /* USER CODE BEGIN I2C1_MspInit 0 */ /* USER CODE END I2C1_MspInit 0 */ - - /**I2C1 GPIO Configuration - PB8 ------> I2C1_SCL - PB9 ------> I2C1_SDA - */ - GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9; - GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; - GPIO_InitStruct.Pull = GPIO_PULLUP; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* I2C1 clock enable */ __HAL_RCC_I2C1_CLK_ENABLE(); diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index 1429e7f7..e073fb87 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -451,19 +451,19 @@ void ADC_IRQ_Dispatch(ADC_HandleTypeDef* hadc, ADC_handler_t callback) { void decode_tim_capture(TIM_HandleTypeDef *htim, TIM_capture_callback_t callback) { if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1)) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1); - callback(1, htim->Instance->CCR1); + callback(0, htim->Instance->CCR1); } if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC2)) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC2); - callback(2, htim->Instance->CCR2); + callback(1, htim->Instance->CCR2); } if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC3)) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC3); - callback(3, htim->Instance->CCR3); + callback(2, htim->Instance->CCR3); } if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC4)) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC4); - callback(4, htim->Instance->CCR4); + callback(3, htim->Instance->CCR4); } } diff --git a/Firmware/Board/v3/Src/tim.c b/Firmware/Board/v3/Src/tim.c index 97205c15..f68ad61e 100644 --- a/Firmware/Board/v3/Src/tim.c +++ b/Firmware/Board/v3/Src/tim.c @@ -462,7 +462,6 @@ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* tim_pwmHandle) void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle) { - GPIO_InitTypeDef GPIO_InitStruct; if(tim_encoderHandle->Instance==TIM3) { /* USER CODE BEGIN TIM3_MspInit 0 */ @@ -470,17 +469,6 @@ void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle) /* USER CODE END TIM3_MspInit 0 */ /* TIM3 clock enable */ __HAL_RCC_TIM3_CLK_ENABLE(); - - /**TIM3 GPIO Configuration - PB4 ------> TIM3_CH1 - PB5 ------> TIM3_CH2 - */ - GPIO_InitStruct.Pin = M0_ENC_A_Pin|M0_ENC_B_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.Alternate = GPIO_AF2_TIM3; - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* USER CODE BEGIN TIM3_MspInit 1 */ @@ -493,17 +481,6 @@ void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle) /* USER CODE END TIM4_MspInit 0 */ /* TIM4 clock enable */ __HAL_RCC_TIM4_CLK_ENABLE(); - - /**TIM4 GPIO Configuration - PB6 ------> TIM4_CH1 - PB7 ------> TIM4_CH2 - */ - GPIO_InitStruct.Pin = M1_ENC_A_Pin|M1_ENC_B_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.Alternate = GPIO_AF2_TIM4; - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* USER CODE BEGIN TIM4_MspInit 1 */ @@ -514,7 +491,6 @@ void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle) void HAL_TIM_IC_MspInit(TIM_HandleTypeDef* tim_icHandle) { - GPIO_InitTypeDef GPIO_InitStruct; if(tim_icHandle->Instance==TIM5) { /* USER CODE BEGIN TIM5_MspInit 0 */ @@ -522,17 +498,6 @@ void HAL_TIM_IC_MspInit(TIM_HandleTypeDef* tim_icHandle) /* USER CODE END TIM5_MspInit 0 */ /* TIM5 clock enable */ __HAL_RCC_TIM5_CLK_ENABLE(); - - /**TIM5 GPIO Configuration - PA2 ------> TIM5_CH3 - PA3 ------> TIM5_CH4 - */ - GPIO_InitStruct.Pin = GPIO_3_Pin|GPIO_4_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.Alternate = GPIO_AF2_TIM5; - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* TIM5 interrupt Init */ HAL_NVIC_SetPriority(TIM5_IRQn, 5, 0); diff --git a/Firmware/Board/v3/Src/usart.c b/Firmware/Board/v3/Src/usart.c index 0c4bed6f..93bac536 100644 --- a/Firmware/Board/v3/Src/usart.c +++ b/Firmware/Board/v3/Src/usart.c @@ -83,7 +83,6 @@ void MX_UART4_Init(void) void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) { - GPIO_InitTypeDef GPIO_InitStruct; if(uartHandle->Instance==UART4) { /* USER CODE BEGIN UART4_MspInit 0 */ @@ -91,24 +90,6 @@ void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) /* USER CODE END UART4_MspInit 0 */ /* UART4 clock enable */ __HAL_RCC_UART4_CLK_ENABLE(); - - /**UART4 GPIO Configuration - PA0-WKUP ------> UART4_TX - PA1 ------> UART4_RX - */ - GPIO_InitStruct.Pin = GPIO_1_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_PULLDOWN; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF8_UART4; - HAL_GPIO_Init(GPIO_1_GPIO_Port, &GPIO_InitStruct); - - GPIO_InitStruct.Pin = GPIO_2_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF8_UART4; - HAL_GPIO_Init(GPIO_2_GPIO_Port, &GPIO_InitStruct); /* UART4 DMA Init */ /* UART4_RX Init */ diff --git a/Firmware/Board/v3/board.cpp b/Firmware/Board/v3/board.cpp index 8ca73270..42e35e50 100644 --- a/Firmware/Board/v3/board.cpp +++ b/Firmware/Board/v3/board.cpp @@ -80,24 +80,54 @@ Encoder encoders[AXIS_COUNT] = { }; -// Note that GPIO1 as labeled on the board corresponds to gpios[0] in code. #if (HW_VERSION_MINOR == 1) || (HW_VERSION_MINOR == 2) Stm32Gpio gpios[] = { + {nullptr, 0}, // dummy GPIO0 so that PCB labels and software numbers match + {GPIOB, GPIO_PIN_2}, {GPIOA, GPIO_PIN_5}, {GPIOA, GPIO_PIN_4}, - {GPIOA, GPIO_PIN_3} + {GPIOA, GPIO_PIN_3}, + {nullptr, 0}, + {nullptr, 0}, + {nullptr, 0}, + {nullptr, 0}, + + {GPIOB, GPIO_PIN_4}, // ENC0_A + {GPIOB, GPIO_PIN_5}, // ENC0_B + {GPIOA, GPIO_PIN_15}, // ENC0_Z + {GPIOB, GPIO_PIN_6}, // ENC1_A + {GPIOB, GPIO_PIN_7}, // ENC1_B + {GPIOB, GPIO_PIN_3}, // ENC1_Z + {GPIOB, GPIO_PIN_8}, // CAN_R + {GPIOB, GPIO_PIN_9}, // CAN_D }; #elif (HW_VERSION_MINOR == 3) || (HW_VERSION_MINOR == 4) Stm32Gpio gpios[] = { + {nullptr, 0}, // dummy GPIO0 so that PCB labels and software numbers match + {GPIOA, GPIO_PIN_0}, {GPIOA, GPIO_PIN_1}, {GPIOA, GPIO_PIN_2}, {GPIOA, GPIO_PIN_3}, - {GPIOB, GPIO_PIN_2} + {GPIOB, GPIO_PIN_2}, + {nullptr, 0}, + {nullptr, 0}, + {nullptr, 0}, + + {GPIOB, GPIO_PIN_4}, // ENC0_A + {GPIOB, GPIO_PIN_5}, // ENC0_B + {GPIOA, GPIO_PIN_15}, // ENC0_Z + {GPIOB, GPIO_PIN_6}, // ENC1_A + {GPIOB, GPIO_PIN_7}, // ENC1_B + {GPIOB, GPIO_PIN_3}, // ENC1_Z + {GPIOB, GPIO_PIN_8}, // CAN_R + {GPIOB, GPIO_PIN_9}, // CAN_D }; #elif (HW_VERSION_MINOR == 5) || (HW_VERSION_MINOR == 6) -Stm32Gpio gpios[] = { +Stm32Gpio gpios[GPIO_COUNT] = { + {nullptr, 0}, // dummy GPIO0 so that PCB labels and software numbers match + {GPIOA, GPIO_PIN_0}, {GPIOA, GPIO_PIN_1}, {GPIOA, GPIO_PIN_2}, @@ -105,12 +135,50 @@ Stm32Gpio gpios[] = { {GPIOC, GPIO_PIN_4}, {GPIOB, GPIO_PIN_2}, {GPIOA, GPIO_PIN_15}, - {GPIOB, GPIO_PIN_3} + {GPIOB, GPIO_PIN_3}, + + {GPIOB, GPIO_PIN_4}, // ENC0_A + {GPIOB, GPIO_PIN_5}, // ENC0_B + {GPIOC, GPIO_PIN_9}, // ENC0_Z + {GPIOB, GPIO_PIN_6}, // ENC1_A + {GPIOB, GPIO_PIN_7}, // ENC1_B + {GPIOC, GPIO_PIN_15}, // ENC1_Z + {GPIOB, GPIO_PIN_8}, // CAN_R + {GPIOB, GPIO_PIN_9}, // CAN_D }; #else #error "unknown GPIOs" #endif +#define GPIO_AF_NONE ((uint8_t)0xff) +uint8_t alternate_functions[GPIO_COUNT][6] = { + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + +#if HW_VERSION_MINOR >= 3 + {GPIO_AF8_UART4, GPIO_AF2_TIM5, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF8_UART4, GPIO_AF2_TIM5, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF2_TIM5, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, +#else + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, +#endif + {GPIO_AF_NONE, GPIO_AF2_TIM5, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF2_TIM3, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF2_TIM3, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF4_I2C1, GPIO_AF_NONE, GPIO_AF2_TIM4}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF4_I2C1, GPIO_AF_NONE, GPIO_AF2_TIM4}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF9_CAN1, GPIO_AF4_I2C1, GPIO_AF_NONE, GPIO_AF_NONE}, + {GPIO_AF_NONE, GPIO_AF_NONE, GPIO_AF9_CAN1, GPIO_AF4_I2C1, GPIO_AF_NONE, GPIO_AF_NONE}, +}; + #if HW_VERSION_MINOR <= 2 uint32_t pwm_in_gpios[4] = { 0, 0, 0, 4 }; // 0 means not in use #else diff --git a/Firmware/Drivers/STM32/stm32_gpio.cpp b/Firmware/Drivers/STM32/stm32_gpio.cpp index cb4e738c..0d09b174 100644 --- a/Firmware/Drivers/STM32/stm32_gpio.cpp +++ b/Firmware/Drivers/STM32/stm32_gpio.cpp @@ -38,6 +38,52 @@ IRQn_Type get_irq_number(uint16_t pin_number) { } } +#define GPIO_MODE 0x00000003U +#define GPIO_OUTPUT_TYPE 0x00000010U + + +bool Stm32Gpio::config(uint32_t mode, uint32_t pull, uint32_t speed) { + if (!port_) + return false; + + size_t position = get_pin_number(); + + // The following code is mostly taken from HAL_GPIO_Init + + /* Configure IO Direction mode (Input, Output, Alternate or Analog) */ + uint32_t temp = port_->MODER; + temp &= ~(GPIO_MODER_MODER0 << (position * 2U)); + temp |= ((mode & GPIO_MODE) << (position * 2U)); + port_->MODER = temp; + + /* In case of Output or Alternate function mode selection */ + if((mode == GPIO_MODE_OUTPUT_PP) || (mode == GPIO_MODE_AF_PP) || + (mode == GPIO_MODE_OUTPUT_OD) || (mode == GPIO_MODE_AF_OD)) + { + /* Check the Speed parameter */ + assert_param(IS_GPIO_SPEED(speed)); + /* Configure the IO Speed */ + temp = port_->OSPEEDR; + temp &= ~(GPIO_OSPEEDER_OSPEEDR0 << (position * 2U)); + temp |= (speed << (position * 2U)); + port_->OSPEEDR = temp; + + /* Configure the IO Output Type */ + temp = port_->OTYPER; + temp &= ~(GPIO_OTYPER_OT_0 << position) ; + temp |= (((mode & GPIO_OUTPUT_TYPE) >> 4U) << position); + port_->OTYPER = temp; + } + + /* Activate the Pull-up or Pull down resistor for the current IO */ + temp = port_->PUPDR; + temp &= ~(GPIO_PUPDR_PUPDR0 << (position * 2U)); + temp |= ((pull) << (position * 2U)); + port_->PUPDR = temp; + + return true; +} + bool Stm32Gpio::subscribe(bool rising_edge, bool falling_edge, void (*callback)(void*), void* ctx) { uint32_t pin_number = get_pin_number(); if (pin_number >= N_EXTI) { diff --git a/Firmware/Drivers/STM32/stm32_gpio.hpp b/Firmware/Drivers/STM32/stm32_gpio.hpp index 405f6fb2..a5641a96 100644 --- a/Firmware/Drivers/STM32/stm32_gpio.hpp +++ b/Firmware/Drivers/STM32/stm32_gpio.hpp @@ -17,21 +17,9 @@ public: * * This can be done regardless of the current state of the GPIO. * - * TODO: make glitch-free if the configuration is the same as before - * TODO: avoid disabling interrupt if it is enabled + * If any subscription is in place, it is not disabled by this function. */ - bool config(uint32_t mode, uint32_t pull) { - if (!port_) - return false; - HAL_GPIO_DeInit(port_, pin_mask_); - GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = pin_mask_; - GPIO_InitStruct.Mode = mode; - GPIO_InitStruct.Pull = pull; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - HAL_GPIO_Init(port_, &GPIO_InitStruct); - return true; - } + bool config(uint32_t mode, uint32_t pull, uint32_t speed = GPIO_SPEED_FREQ_LOW); void write(bool state) { if (port_) { diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index ec2efea5..aca94dd9 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -148,7 +148,9 @@ void Axis::set_step_dir_active(bool active) { step_gpio_.config(GPIO_MODE_INPUT, GPIO_PULLDOWN); // Subscribe to rising edges of the step GPIO - step_gpio_.subscribe(true, false, step_cb_wrapper, this); + if (!step_gpio_.subscribe(true, false, step_cb_wrapper, this)) { + odrv.misconfigured_ = true; + } step_dir_active_ = true; } else { diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 7a982197..9e6709e8 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -104,7 +104,9 @@ void Encoder::enc_index_cb() { void Encoder::set_idx_subscribe(bool override_enable) { if (config_.use_index && (override_enable || !config_.find_idx_on_lockin_only)) { index_gpio_.config(GPIO_MODE_INPUT, GPIO_PULLDOWN); - index_gpio_.subscribe(true, false, enc_index_cb_wrapper, this); + if (!index_gpio_.subscribe(true, false, enc_index_cb_wrapper, this)) { + odrv.misconfigured_ = true; + } } else if (!config_.use_index || config_.find_idx_on_lockin_only) { index_gpio_.unsubscribe(); } @@ -444,18 +446,9 @@ void Encoder::abs_spi_cb() { } void Encoder::abs_spi_cs_pin_init(){ - // Decode cs pin + // Decode and init cs pin abs_spi_cs_gpio_ = get_gpio(config_.abs_spi_cs_gpio_pin); - - // Init cs pin - // TODO: absorb into Stm32Gpio class - HAL_GPIO_DeInit(abs_spi_cs_gpio_.port_, abs_spi_cs_gpio_.pin_mask_); - GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = abs_spi_cs_gpio_.pin_mask_; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_PULLUP; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - HAL_GPIO_Init(abs_spi_cs_gpio_.port_, &GPIO_InitStruct); + abs_spi_cs_gpio_.config(GPIO_MODE_OUTPUT_PP, GPIO_PULLUP); // Write pin high abs_spi_cs_gpio_.write(true); diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 891f59b4..386f8914 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -537,12 +537,6 @@ void update_brake_current() { /* RC PWM input --------------------------------------------------------------*/ void pwm_in_init() { - GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_PULLDOWN; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - GPIO_InitStruct.Alternate = GPIO_AF2_TIM5; - TIM_IC_InitTypeDef sConfigIC; sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; @@ -552,16 +546,8 @@ void pwm_in_init() { uint32_t channels[] = {TIM_CHANNEL_1, TIM_CHANNEL_2, TIM_CHANNEL_3, TIM_CHANNEL_4}; for (size_t i = 0; i < 4; ++i) { - uint32_t gpio_num = pwm_in_gpios[i]; - if (gpio_num < 1 || gpio_num > GPIO_COUNT) + if (!fibre::is_endpoint_ref_valid(odrv.config_.pwm_mappings[i].endpoint)) continue; - if (!fibre::is_endpoint_ref_valid(odrv.config_.pwm_mappings[gpio_num - 1].endpoint)) - continue; - - Stm32Gpio gpio = gpios[gpio_num]; - GPIO_InitStruct.Pin = gpio.pin_mask_; - HAL_GPIO_DeInit(gpio.port_, gpio.pin_mask_); - HAL_GPIO_Init(gpio.port_, &GPIO_InitStruct); HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, channels[i]); HAL_TIM_IC_Start_IT(&htim5, channels[i]); } @@ -575,7 +561,10 @@ void pwm_in_init() { #define PWM_MAX_LEGAL_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 2500UL) // ignore high periods longer than 2.5ms #define PWM_INVERT_INPUT false -void handle_pulse(int gpio_num, uint32_t high_time) { +/** + * @param channel: A channel number in [0, 3] + */ +void handle_pulse(int channel, uint32_t high_time) { if (high_time < PWM_MIN_LEGAL_HIGH_TIME || high_time > PWM_MAX_LEGAL_HIGH_TIME) return; @@ -584,33 +573,36 @@ void handle_pulse(int gpio_num, uint32_t high_time) { if (high_time > PWM_MAX_HIGH_TIME) high_time = PWM_MAX_HIGH_TIME; float fraction = (float)(high_time - PWM_MIN_HIGH_TIME) / (float)(PWM_MAX_HIGH_TIME - PWM_MIN_HIGH_TIME); - float value = odrv.config_.pwm_mappings[gpio_num - 1].min + - (fraction * (odrv.config_.pwm_mappings[gpio_num - 1].max - odrv.config_.pwm_mappings[gpio_num - 1].min)); + float value = odrv.config_.pwm_mappings[channel].min + + (fraction * (odrv.config_.pwm_mappings[channel].max - odrv.config_.pwm_mappings[channel].min)); - fibre::set_endpoint_from_float(odrv.config_.pwm_mappings[gpio_num - 1].endpoint, value); + fibre::set_endpoint_from_float(odrv.config_.pwm_mappings[channel].endpoint, value); } +/** + * @param channel: A channel number in [0, 3] + */ void pwm_in_cb(int channel, uint32_t timestamp) { - static uint32_t last_timestamp[GPIO_COUNT] = { 0 }; - static bool last_pin_state[GPIO_COUNT] = { false }; - static bool last_sample_valid[GPIO_COUNT] = { false }; + static uint32_t last_timestamp[4] = { 0 }; + static bool last_pin_state[4] = { false }; + static bool last_sample_valid[4] = { false }; if (channel >= 4) return; - int gpio_num = pwm_in_gpios[channel]; - if (gpio_num < 1 || gpio_num > GPIO_COUNT) + Stm32Gpio gpio = get_gpio(pwm_in_gpios[channel]); + if (!gpio) return; - bool current_pin_state = get_gpio(gpio_num).read(); + bool current_pin_state = gpio.read(); - if (last_sample_valid[gpio_num - 1] - && (last_pin_state[gpio_num - 1] != PWM_INVERT_INPUT) + if (last_sample_valid[channel] + && (last_pin_state[channel] != PWM_INVERT_INPUT) && (current_pin_state == PWM_INVERT_INPUT)) { - handle_pulse(gpio_num, timestamp - last_timestamp[gpio_num - 1]); + handle_pulse(channel, timestamp - last_timestamp[channel]); } - last_timestamp[gpio_num - 1] = timestamp; - last_pin_state[gpio_num - 1] = current_pin_state; - last_sample_valid[gpio_num - 1] = true; + last_timestamp[channel] = timestamp; + last_pin_state[channel] = current_pin_state; + last_sample_valid[channel] = true; } @@ -630,7 +622,7 @@ static void analog_polling_thread(void *) struct PWMMapping_t *map = &odrv.config_.analog_mappings[i]; if (fibre::is_endpoint_ref_valid(map->endpoint)) - update_analog_endpoint(map, i + 1); + update_analog_endpoint(map, i); } osDelay(10); } diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 19eee8a4..f3c680ad 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -154,42 +154,28 @@ void ODrive::enter_dfu_mode() { extern "C" int construct_objects(){ #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 - if (odrv.config_.enable_i2c_instead_of_can) { + if (odrv.config_.enable_i2c0) { // Set up the direction GPIO as input - GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_PULLUP; + get_gpio(3).config(GPIO_MODE_INPUT, GPIO_PULLUP); + get_gpio(4).config(GPIO_MODE_INPUT, GPIO_PULLUP); + get_gpio(5).config(GPIO_MODE_INPUT, GPIO_PULLUP); - GPIO_InitStruct.Pin = I2C_A0_PIN; - HAL_GPIO_Init(I2C_A0_PORT, &GPIO_InitStruct); - GPIO_InitStruct.Pin = I2C_A1_PIN; - HAL_GPIO_Init(I2C_A1_PORT, &GPIO_InitStruct); - GPIO_InitStruct.Pin = I2C_A2_PIN; - HAL_GPIO_Init(I2C_A2_PORT, &GPIO_InitStruct); - - osDelay(1); + osDelay(1); // This has no effect but was here before. i2c_stats_.addr = (0xD << 3); - i2c_stats_.addr |= HAL_GPIO_ReadPin(I2C_A0_PORT, I2C_A0_PIN) != GPIO_PIN_RESET ? 0x1 : 0; - i2c_stats_.addr |= HAL_GPIO_ReadPin(I2C_A1_PORT, I2C_A1_PIN) != GPIO_PIN_RESET ? 0x2 : 0; - i2c_stats_.addr |= HAL_GPIO_ReadPin(I2C_A2_PORT, I2C_A2_PIN) != GPIO_PIN_RESET ? 0x4 : 0; + i2c_stats_.addr |= get_gpio(3).read() ? 0x1 : 0; + i2c_stats_.addr |= get_gpio(4).read() ? 0x2 : 0; + i2c_stats_.addr |= get_gpio(5).read() ? 0x4 : 0; MX_I2C1_Init(i2c_stats_.addr); - } else + } +#elif HW_VERSION_MAJOR != 3 + #error "unsupported hardware" #endif - MX_CAN1_Init(); + HAL_UART_DeInit(&huart4); - huart4.Init.BaudRate = odrv.config_.uart_baudrate; + huart4.Init.BaudRate = odrv.config_.uart0_baudrate; HAL_UART_Init(&huart4); - // Init general user ADC on some GPIOs. - GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; - GPIO_InitStruct.Pull = GPIO_NOPULL; - for (Stm32Gpio& gpio: gpios) { - GPIO_InitStruct.Pin = gpio.pin_mask_; - HAL_GPIO_Init(gpio.port_, &GPIO_InitStruct); - } - // Construct all objects. odCAN = new ODriveCAN(can_config, &hcan1); for (size_t i = 0; i < AXIS_COUNT; ++i) { @@ -250,33 +236,96 @@ int odrive_main(void) { // Init timers board_init(); + // Init GPIOs according to their configured mode + for (size_t i = 0; i < GPIO_COUNT; ++i) { + // Skip unavailable GPIOs + if (!get_gpio(i)) { + continue; + } + + ODriveIntf::GpioMode mode = odrv.config_.gpio_modes[i]; + + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitStruct.Pin = get_gpio(i).pin_mask_; + GPIO_InitStruct.Alternate = 0; + + switch (mode) { + case ODriveIntf::GPIO_MODE_DIGITAL: { + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + } break; + case ODriveIntf::GPIO_MODE_ANALOG_IN: { + GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; + GPIO_InitStruct.Pull = GPIO_NOPULL; + } break; + case ODriveIntf::GPIO_MODE_UART0: { + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = (i == 0) ? GPIO_PULLDOWN : GPIO_PULLUP; // this is probably swapped but imitates old behavior + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = alternate_functions[i][0]; + if (!odrv.config_.enable_uart0) { + odrv.misconfigured_ = true; + } + } break; + case ODriveIntf::GPIO_MODE_PWM0: { + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_PULLDOWN; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.Alternate = alternate_functions[i][1]; + } break; + case ODriveIntf::GPIO_MODE_CAN0: { + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = alternate_functions[i][2]; + if (!odrv.config_.enable_can0) { + odrv.misconfigured_ = true; + } + } break; + case ODriveIntf::GPIO_MODE_I2C0: { + GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = alternate_functions[i][3]; + if (!odrv.config_.enable_i2c0) { + odrv.misconfigured_ = true; + } + } break; + case ODriveIntf::GPIO_MODE_ENC0: { + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.Alternate = alternate_functions[i][4]; + } break; + case ODriveIntf::GPIO_MODE_ENC1: { + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.Alternate = alternate_functions[i][5]; + } break; + default: { + GPIO_InitStruct.Alternate = GPIO_AF_NONE; + } + } + + // The selected mode is invalid for this GPIO. Leave GPIO uninitialized. + if (GPIO_InitStruct.Alternate == GPIO_AF_NONE) { + odrv.misconfigured_ = true; + continue; + } + + HAL_GPIO_Init(get_gpio(i).port_, &GPIO_InitStruct); + } + + // Some peripherals must be initialized after the GPIOs are set up. + if (odrv.config_.enable_can0) { + MX_CAN1_Init(); + } + // Start ADC for temperature measurements and user measurements start_general_purpose_adc(); - // TODO: make dynamically reconfigurable -#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 - if (odrv.config_.enable_uart) { - GPIO_InitTypeDef GPIO_InitStruct; - - // make sure nothing is hogging the GPIO's - Stm32Gpio{GPIO_1_GPIO_Port, GPIO_1_Pin}.unsubscribe(); - Stm32Gpio{GPIO_2_GPIO_Port, GPIO_2_Pin}.unsubscribe(); - - GPIO_InitStruct.Pin = GPIO_1_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_PULLDOWN; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF8_UART4; - HAL_GPIO_Init(GPIO_1_GPIO_Port, &GPIO_InitStruct); - - GPIO_InitStruct.Pin = GPIO_2_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - GPIO_InitStruct.Alternate = GPIO_AF8_UART4; - HAL_GPIO_Init(GPIO_2_GPIO_Port, &GPIO_InitStruct); - } -#endif //osDelay(100); // Init communications (this requires the axis objects to be constructed) init_communication(); diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index dfc2f4ea..184140fc 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -58,15 +58,33 @@ struct PWMMapping_t { // @brief general user configurable board configuration struct BoardConfig_t { - bool enable_uart = true; - bool enable_i2c_instead_of_can = false; + ODriveIntf::GpioMode gpio_modes[GPIO_COUNT] = { + ODriveIntf::GPIO_MODE_DIGITAL, + ODriveIntf::GPIO_MODE_UART0, + ODriveIntf::GPIO_MODE_UART0, + ODriveIntf::GPIO_MODE_ANALOG_IN, + ODriveIntf::GPIO_MODE_ANALOG_IN, + ODriveIntf::GPIO_MODE_ANALOG_IN, + ODriveIntf::GPIO_MODE_DIGITAL, + ODriveIntf::GPIO_MODE_DIGITAL, + ODriveIntf::GPIO_MODE_DIGITAL, + ODriveIntf::GPIO_MODE_ENC0, + ODriveIntf::GPIO_MODE_ENC0, + ODriveIntf::GPIO_MODE_DIGITAL, + ODriveIntf::GPIO_MODE_ENC1, + ODriveIntf::GPIO_MODE_ENC1, + ODriveIntf::GPIO_MODE_DIGITAL, + ODriveIntf::GPIO_MODE_CAN0, + ODriveIntf::GPIO_MODE_CAN0, + }; + + bool enable_uart0 = true; + uint32_t uart0_baudrate = 115200; + bool enable_can0 = true; + bool enable_i2c0 = false; bool enable_ascii_protocol_on_usb = true; float max_regen_current = 0.0f; -#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 5 && HW_VERSION_VOLTAGE >= 48 - float brake_resistance = 2.0f; // [ohm] -#else - float brake_resistance = 0.47f; // [ohm] -#endif + float brake_resistance = DEFAULT_BRAKE_RESISTANCE; float dc_bus_undervoltage_trip_level = 8.0f; //= 1 && gpio_num <= GPIO_COUNT) ? gpios[gpio_num - 1] : GPIO_COUNT ? gpios[0] : Stm32Gpio::none; + return (gpio_num < GPIO_COUNT) ? gpios[gpio_num] : GPIO_COUNT ? gpios[0] : Stm32Gpio::none; } // general system functions defined in main.cpp @@ -244,6 +236,7 @@ public: BoardConfig_t config_; bool user_config_loaded_; + bool misconfigured_ = false; uint32_t test_property_ = 0; }; diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 153d750c..dfd45eb6 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -64,9 +64,10 @@ void communication_task(void * ctx) { start_uart_server(); start_usb_server(); - if (odrv.config_.enable_i2c_instead_of_can) { + if (odrv.config_.enable_i2c0) { start_i2c_server(); - } else { + } + if (odrv.config_.enable_can0) { odCAN->start_can_server(); } diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 5e789e2a..6447f44e 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -81,10 +81,28 @@ interfaces: config: c_is_class: False attributes: - enable_uart: + # TODO: add support for arrays + gpio1_mode: {type: GpioMode, doc: Mode of GPIO1 (changes take effect after reboot), c_name: 'gpio_modes[1]'} + gpio2_mode: {type: GpioMode, doc: Mode of GPIO2 (changes take effect after reboot), c_name: 'gpio_modes[2]'} + gpio3_mode: {type: GpioMode, doc: Mode of GPIO3 (changes take effect after reboot), c_name: 'gpio_modes[3]'} + gpio4_mode: {type: GpioMode, doc: Mode of GPIO4 (changes take effect after reboot), c_name: 'gpio_modes[4]'} + gpio5_mode: {type: GpioMode, doc: Mode of GPIO5 (changes take effect after reboot), c_name: 'gpio_modes[5]'} + gpio6_mode: {type: GpioMode, doc: Mode of GPIO6 (changes take effect after reboot), c_name: 'gpio_modes[6]'} + gpio7_mode: {type: GpioMode, doc: Mode of GPIO7 (changes take effect after reboot), c_name: 'gpio_modes[7]'} + gpio8_mode: {type: GpioMode, doc: Mode of GPIO8 (changes take effect after reboot), c_name: 'gpio_modes[8]'} + gpio9_mode: {type: GpioMode, doc: Mode of GPIO9 (changes take effect after reboot), c_name: 'gpio_modes[9]'} + gpio10_mode: {type: GpioMode, doc: Mode of GPIO10 (changes take effect after reboot), c_name: 'gpio_modes[10]'} + gpio11_mode: {type: GpioMode, doc: Mode of GPIO11 (changes take effect after reboot), c_name: 'gpio_modes[11]'} + gpio12_mode: {type: GpioMode, doc: Mode of GPIO12 (changes take effect after reboot), c_name: 'gpio_modes[12]'} + gpio13_mode: {type: GpioMode, doc: Mode of GPIO13 (changes take effect after reboot), c_name: 'gpio_modes[13]'} + gpio14_mode: {type: GpioMode, doc: Mode of GPIO14 (changes take effect after reboot), c_name: 'gpio_modes[14]'} + gpio15_mode: {type: GpioMode, doc: Mode of GPIO15 (changes take effect after reboot), c_name: 'gpio_modes[15]'} + gpio16_mode: {type: GpioMode, doc: Mode of GPIO16 (changes take effect after reboot), c_name: 'gpio_modes[16]'} + + enable_uart0: type: bool doc: 'TODO: changing this currently requires a reboot - fix this' - uart_baudrate: + uart0_baudrate: type: uint32 doc: | Defines the baudrate used on the UART interface. @@ -109,9 +127,17 @@ interfaces: For more information refer to Section 30.3.4 and Table 142 (the column with f_PCLK = 42 MHz) in the [STM datasheet](https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf). - enable_i2c_instead_of_can: + enable_can0: type: bool - doc: Changing this requires a reboot. + doc: | + Enables CAN. Changing this setting requires a reboot. + enable_i2c0: + type: bool + doc: | + Enables I2C. The I2C pins on ODrive v3.x are in conflict with CAN. + This setting has no effect if `enable_can0` is also true. + This setting has no effect on ODrive v3.2 or earlier. + Changing this setting requires a reboot. enable_ascii_protocol_on_usb: bool max_regen_current: float32 brake_resistance: @@ -173,13 +199,26 @@ interfaces: brief: Max current the power supply can sink. doc: You most likely want a non-positive value here. Set to -INFINITY to disable. - gpio1_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[0]'} # TODO: disable for ODrive v3.2 and older - gpio2_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[1]'} # TODO: disable for ODrive v3.2 and older - gpio3_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[2]'} # TODO: disable for ODrive v3.2 and older - gpio4_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[3]'} - gpio3_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[2]'} - gpio4_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[3]'} + gpio1_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[0]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_PWM0`.} + gpio2_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[1]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_PWM0`.} + gpio3_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[2]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_PWM0`.} + gpio4_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[3]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_PWM0`.} + gpio3_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[3]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_ANALOG_IN`.} + gpio4_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[4]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_ANALOG_IN`.} user_config_loaded: readonly bool + misconfigured: + type: readonly bool + doc: | + If this property is true, something is bad in the configuration. The + ODrive can still be used in this state but the user should investigate + which setting is problematic. This variable does not cover all + misconfigurations. + + Possible causes: + - A GPIO was set to a mode that it doesn't support + - A GPIO was set to a mode for which the corresponding feature was + not enabled. Example: `GPIO_MODE_UART0` was used without enabling + `config.enable_uart0`. axis0: {type: Axis, c_name: get_axis(0)} axis1: {type: Axis, c_name: get_axis(1)} @@ -189,7 +228,7 @@ interfaces: functions: test_function: {in: {delta: int32}, out: {cnt: int32}} get_oscilloscope_val: {in: {index: uint32}, out: {val: float32}} - get_adc_voltage: {in: {gpio: uint32}, out: {voltage: float32}} + get_adc_voltage: {in: {gpio: uint32}, out: {voltage: float32}, doc: Reads the ADC voltage of the specified GPIO. The GPIO should be in `GPIO_MODE_ANALOG_IN`.} save_configuration: erase_configuration: reboot: @@ -323,7 +362,7 @@ interfaces: enable_step_dir: type: bool doc: Enable step/dir input after calibration. - For M0 this has no effect if `config.enable_uart` is true. + Make sure to set the corresponding GPIO's mode to `GPIO_MODE_DIGITAL`. step_dir_always_on: type: bool doc: Keep step/dir enabled while the motor is disabled. @@ -766,7 +805,7 @@ interfaces: mode: Mode use_index: {type: bool, c_setter: set_use_index} find_idx_on_lockin_only: {type: bool, c_setter: set_find_idx_on_lockin_only} - abs_spi_cs_gpio_pin: {type: uint16, c_setter: set_abs_spi_cs_gpio_pin} + abs_spi_cs_gpio_pin: {type: uint16, c_setter: set_abs_spi_cs_gpio_pin, doc: Make sure that the GPIO is in `GPIO_MODE_DIGITAL`.} zero_count_on_find_idx: bool cpr: int32 offset: int32 @@ -779,8 +818,12 @@ interfaces: calib_scan_omega: float32 idx_search_unidirectional: bool ignore_illegal_hall_state: bool - sincos_gpio_pin_sin: uint16 - sincos_gpio_pin_cos: uint16 + sincos_gpio_pin_sin: + type: uint16 + doc: Analog sine signal of a sin/cos encoder. The corresponding GPIO must be in `GPIO_MODE_ANALOG_IN`. + sincos_gpio_pin_cos: + type: uint16 + doc: Analog cosine signal of a sin/cos encoder. The corresponding GPIO must be in `GPIO_MODE_ANALOG_IN`. functions: set_linear_count: {in: {count: int32}} @@ -823,7 +866,7 @@ interfaces: config: c_is_class: False attributes: - gpio_num: {type: uint16, c_setter: set_gpio_num} + gpio_num: {type: uint16, c_setter: set_gpio_num, doc: Make sure the corresponding GPIO is in `GPIO_MODE_DIGITAL`.} enabled: {type: bool, c_setter: set_enabled} offset: float32 is_active_high: bool @@ -832,6 +875,23 @@ interfaces: valuetypes: + ODrive.GpioMode: + values: + Digital: + doc: | + The pin can be used for one or more of these functions: + Step, dir, enable, encoder index, hall effect encoder, SPI encoder nCS (this one is exclusive). + AnalogIn: + doc: | + The pin can be used for one or more of these functions: + Sin/cos encoders, analog input, `get_adc_voltage`. + Uart0: {doc: See `config.enable_uart0`.} + Pwm0: {doc: See `config.gpio0_pwm_mapping`.} + Can0: {doc: See `config.enable_can0`.} + I2c0: {doc: See `config.enable_i2c0`.} + Enc0: {doc: The pin is used by quadrature encoder 0.} + Enc1: {doc: The pin is used by quadrature encoder 1.} + ODrive.Can.Protocol: values: {Simple: } diff --git a/docs/interfaces.md b/docs/interfaces.md index 01173e93..c3d28129 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -18,24 +18,38 @@ The ODrive can be controlled over various ports and protocols. If you're comfort ## Pinout -| GPIO | primary | step/dir | other | -|-----------|-----------|---------------|-------------------------| -| GPIO1 | UART TX | Axis0 Step | Analog input, PWM input | -| GPIO2 | UART RX | Axis0 Dir | Analog input, PWM input | -| GPIO3 | | Axis1 Step (+)| Analog input, PWM input | -| GPIO4 | | Axis1 Dir (+) | Analog input, PWM input | -| GPIO5 | | | Analog input (*) | -| GPIO6 (*) | | | | -| GPIO7 (*) | | Axis1 Step (*)| | -| GPIO8 (*) | | Axis1 Dir (*) | | +| # | Label | `GPIO_MODE_DIGITAL` | `GPIO_MODE_ANALOG_IN` | `GPIO_MODE_UART0` | `GPIO_MODE_PWM0` | `GPIO_MODE_CAN0` | `GPIO_MODE_I2C0` | `GPIO_MODE_ENC0` | `GPIO_MODE_ENC1` | +|----|---------------|------------------------|-----------------------|-------------------|------------------|------------------|------------------|------------------|------------------| +| 0 | _not a pin_ | | | | | | | | | +| 1 | GPIO1 (+) | general purpose | analog input | **UART0.TX** | PWM0.0 | | | | | +| 2 | GPIO2 (+) | general purpose | analog input | **UART0.RX** | PWM0.1 | | | | | +| 3 | GPIO3 | general purpose | **analog input** | | PWM0.2 | | | | | +| 4 | GPIO4 | general purpose | **analog input** | | PWM0.3 | | | | | +| 5 | GPIO5 | general purpose | **analog input** (*) | | | | | | | +| 6 | GPIO6 (*) (+) | **general purpose** | | | | | | | | +| 7 | GPIO7 (*) (+) | **general purpose** | | | | | | | | +| 8 | GPIO8 (*) (+) | **general purpose** | | | | | | | | +| 9 | M0.A | general purpose | | | | | | **ENC0.A** | | +| 10 | M0.B | general purpose | | | | | | **ENC0.B** | | +| 11 | M0.Z | **general purpose** | | | | | | | | +| 12 | M1.A | general purpose | | | | | I2C.SCL | | **ENC1.A** | +| 13 | M1.B | general purpose | | | | | I2C.SDA | | **ENC1.B** | +| 14 | M1.Z | **general purpose** | | | | | | | | +| 15 | _not exposed_ | general purpose | | | | **CAN0.RX** | I2C.SCL | | | +| 16 | _not exposed_ | general purpose | | | | **CAN0.TX** | I2C.SDA | | | -(+) on ODrive v3.4 and earlier
-(*) ODrive v3.5 and later + +(*) ODrive v3.5 and later
+(+) On ODrive v3.5 and later these pins have noise suppression filters. This is useful for step/dir input.
Notes: +* Changes to the pin configuration only take effect after `odrv0.save_configuration()` and `odrv0.reboot()` +* Bold font marks the default configuration. +* If a GPIO is set to an unsupported mode it will be left uninitialized. +* When setting a GPIO to a special purpose mode (e.g. `GPIO_MODE_UART0`) you must also enable the corresponding feature (e.g. `.config.enable_uart`). +* Digital mode is a general purpose mode that can be used for these functions: step, dir, enable, encoder index, hall effect encoder, SPI encoder nCS. * You must also connect GND between ODrive and your other board. * ODrive v3.3 and onward have 5V tolerant GPIO pins. -* ODrive v3.5 and later have some noise suppression filters on the default step/dir pins * You can change the step/dir pins using `axis.config._gpio_pin`. ### Pin function priorities diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 8ba173b7..9545e91a 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -3,6 +3,16 @@ # To regenerate this file, nagivate to the top level of the ODrive repository and run: # python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py +# ODrive.GpioMode +GPIO_MODE_DIGITAL = 0 +GPIO_MODE_ANALOG_IN = 1 +GPIO_MODE_UART0 = 2 +GPIO_MODE_PWM0 = 3 +GPIO_MODE_CAN0 = 4 +GPIO_MODE_I2C0 = 5 +GPIO_MODE_ENC0 = 6 +GPIO_MODE_ENC1 = 7 + # ODrive.Can.Protocol PROTOCOL_SIMPLE = 0 diff --git a/tools/odrive/tests/analog_input_test.py b/tools/odrive/tests/analog_input_test.py index 733f2a8d..3e4041e9 100644 --- a/tools/odrive/tests/analog_input_test.py +++ b/tools/odrive/tests/analog_input_test.py @@ -91,7 +91,8 @@ class TestAnalogInput(): None, #odrive.handle.config.gpio5_analog_mapping, ][analog_in_num] - odrive.unuse_gpios() + odrive.disable_mappings() + setattr(odrive.handle.config, 'gpio' + str(analog_in_num+1) + '_mode', GPIO_MODE_ANALOG_IN) analog_mapping.endpoint = odrive.handle.axis0.controller._remote_attributes['input_pos'] analog_mapping.min = min_val analog_mapping.max = max_val diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 93e72210..b329a003 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -107,9 +107,11 @@ class TestSimpleCAN(): yield (odrive, can_interfaces, 0xfedcba, True) # extended ID def run_test(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, node_id: int, extended_id: bool, logger: Logger): - - # make sure no gpio input is overwriting our values - odrive.unuse_gpios() + odrive.disable_mappings() + odrive.handle.config.gpio15_mode = GPIO_MODE_CAN0 + odrive.handle.config.gpio16_mode = GPIO_MODE_CAN0 + odrive.handle.config.enable_can0 = True + odrive.save_config_and_reboot() axis = odrive.handle.axis0 axis.config.enable_watchdog = False diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index dafb88dd..bf5c779f 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -185,7 +185,9 @@ class TestSinCosEncoder(TestEncoderBase): teensy.compile_and_program(code) if enc.handle.config.mode != ENCODER_MODE_SINCOS: - enc.parent.unuse_gpios() + enc.parent.disable_mappings() + enc.parent.handle.config.gpio3_mode = GPIO_MODE_ANALOG_IN + enc.parent.handle.config.gpio4_mode = GPIO_MODE_ANALOG_IN enc.handle.config.mode = ENCODER_MODE_SINCOS enc.parent.save_config_and_reboot() else: @@ -255,6 +257,14 @@ class TestHallEffectEncoder(TestEncoderBase): teensy.compile_and_program(code) if enc.handle.config.mode != ENCODER_MODE_HALL: + if enc.num: + enc.parent.handle.config.gpio9_mode = GPIO_MODE_DIGITAL + enc.parent.handle.config.gpio10_mode = GPIO_MODE_DIGITAL + enc.parent.handle.config.gpio11_mode = GPIO_MODE_DIGITAL + else: + enc.parent.handle.config.gpio12_mode = GPIO_MODE_DIGITAL + enc.parent.handle.config.gpio13_mode = GPIO_MODE_DIGITAL + enc.parent.handle.config.gpio14_mode = GPIO_MODE_DIGITAL enc.handle.config.mode = ENCODER_MODE_HALL enc.parent.save_config_and_reboot() else: @@ -450,6 +460,7 @@ class TestSpiEncoder(TestEncoderBase): logger.debug(f'Configuring absolute encoder in mode 0x{self.mode:x}...') enc.handle.config.mode = self.mode + setattr(enc.parent.handle.config, 'gpio' + str(odrive_ncs_gpio) + '_mode', GPIO_MODE_ANALOG_IN) enc.handle.config.abs_spi_cs_gpio_pin = odrive_ncs_gpio enc.handle.config.cpr = true_cpr # Also put the other encoder into SPI mode to make it more interesting diff --git a/tools/odrive/tests/pwm_input_test.py b/tools/odrive/tests/pwm_input_test.py index a1945997..82586638 100644 --- a/tools/odrive/tests/pwm_input_test.py +++ b/tools/odrive/tests/pwm_input_test.py @@ -61,7 +61,7 @@ class TestPwmInput(): teensy.compile_and_program(code) logger.debug("Set up PWM input...") - odrive.unuse_gpios() + odrive.disable_mappings() pwm_mapping = [ odrive.handle.config.gpio1_pwm_mapping, @@ -70,6 +70,7 @@ class TestPwmInput(): odrive.handle.config.gpio4_pwm_mapping ][odrive_gpio_num - 1] + setattr(odrive.handle.config, 'gpio' + str(odrive_gpio_num) + '_mode', GPIO_MODE_PWM0) pwm_mapping.endpoint = odrive.handle.axis0.controller._remote_attributes['input_pos'] pwm_mapping.min = min_val pwm_mapping.max = max_val diff --git a/tools/odrive/tests/step_dir_test.py b/tools/odrive/tests/step_dir_test.py index 7b864433..3d2e0e85 100644 --- a/tools/odrive/tests/step_dir_test.py +++ b/tools/odrive/tests/step_dir_test.py @@ -37,15 +37,16 @@ class TestStepDir(): yield (odrive.axes[1], 7, gpio_conns[6], 8, gpio_conns[7]) - def run_test(self, axis: ODriveAxisComponent, step_gpio_num: int, step_gpio: LinuxGpioComponent, dir_gpio_num: int, dir_gpio: LinuxGpioComponent, logger: Logger): + def run_test(self, axis: ODriveAxisComponent, step_gpio_num: int, step_gpio: LinuxGpioComponent, dir_gpio_num: int, dir_gpio: LinuxGpioComponent, logger: Logger): step_gpio.config(output=True) step_gpio.write(False) dir_gpio.config(output=True) dir_gpio.write(True) axis.parent.erase_config_and_reboot() - if axis.num == 0: - axis.parent.handle.config.enable_uart = False + setattr(axis.parent.handle.config, 'gpio' + str(step_gpio_num) + '_mode', GPIO_MODE_DIGITAL) + setattr(axis.parent.handle.config, 'gpio' + str(dir_gpio_num) + '_mode', GPIO_MODE_DIGITAL) + axis.parent.save_config_and_reboot() axis.handle.config.enable_step_dir = True axis.handle.config.step_dir_always_on = True # needed for testing axis.handle.config.step_gpio_pin = step_gpio_num diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index 9633b92f..4f8c807b 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -247,11 +247,8 @@ class ODriveComponent(Component): for axis_idx, axis_ctx in enumerate(self.axes): axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] - def unuse_gpios(self): - self.handle.config.enable_uart = False - self.handle.axis0.config.enable_step_dir = False - self.handle.axis1.config.enable_step_dir = False - self.handle.config.gpio1_pwm_mapping.endpoint = None + def disable_mappings(self): + self.handle.config.gpio1_pwm_mapping.endpoint = None # here self.handle.config.gpio2_pwm_mapping.endpoint = None self.handle.config.gpio3_pwm_mapping.endpoint = None self.handle.config.gpio4_pwm_mapping.endpoint = None diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index ff5a41de..962f3797 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -48,7 +48,9 @@ class TestUartAscii(): # GPIOs might be in use by something other than UART and some components # might be configured so that they would fail in the later test. odrive.erase_config_and_reboot() - odrive.handle.config.enable_uart = True + odrive.handle.config.enable_uart0 = True + odrive.handle.config.gpio1_mode = GPIO_MODE_UART0 + odrive.handle.config.gpio2_mode = GPIO_MODE_UART0 with port.open(115200) as ser: # reset port to known state @@ -161,10 +163,11 @@ class TestUartBaudrate(): yield (odrive, ports) def run_test(self, odrive: ODriveComponent, port: SerialPortComponent, logger: Logger): - odrive.handle.axis0.config.enable_step_dir = False - odrive.handle.config.enable_uart = True + odrive.handle.config.enable_uart0 = True + odrive.handle.config.gpio1_mode = GPIO_MODE_UART0 + odrive.handle.config.gpio2_mode = GPIO_MODE_UART0 - odrive.handle.config.uart_baudrate = 9600 + odrive.handle.config.uart0_baudrate = 9600 odrive.save_config_and_reboot() # Control test: talk to the ODrive with the wrong baudrate @@ -184,7 +187,7 @@ class TestUartBaudrate(): response = float(ser.readline().strip()) test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) - odrive.handle.config.uart_baudrate = 115200 + odrive.handle.config.uart0_baudrate = 115200 odrive.save_config_and_reboot() @@ -202,8 +205,9 @@ class TestUartBurnIn(): yield (odrive, ports) def run_test(self, odrive: ODriveComponent, port: SerialPortComponent, logger: Logger): - odrive.handle.axis0.config.enable_step_dir = False - odrive.handle.config.enable_uart = True + odrive.handle.config.enable_uart0 = True + odrive.handle.config.gpio1_mode = GPIO_MODE_UART0 + odrive.handle.config.gpio2_mode = GPIO_MODE_UART0 with port.open(115200) as ser: with open('/dev/random', 'rb') as rand: @@ -258,8 +262,9 @@ class TestUartNoise(): noise_enable.write(False) time.sleep(0.1) - odrive.handle.axis0.config.enable_step_dir = False - odrive.handle.config.enable_uart = True + odrive.handle.config.enable_uart0 = True + odrive.handle.config.gpio1_mode = GPIO_MODE_UART0 + odrive.handle.config.gpio2_mode = GPIO_MODE_UART0 with port.open(115200) as ser: # reset port to known state