diff --git a/Firmware/Board/v3/0003-disable-IRQ-for-DMA2_Stream0.patch b/Firmware/Board/v3/0003-disable-IRQ-for-DMA2_Stream0.patch new file mode 100644 index 00000000..282317d1 --- /dev/null +++ b/Firmware/Board/v3/0003-disable-IRQ-for-DMA2_Stream0.patch @@ -0,0 +1,33 @@ +From ab5ca860b3729d76a9c43c485776147ab69d2342 Mon Sep 17 00:00:00 2001 +From: Samuel Sadok +Date: Mon, 26 Mar 2018 19:02:45 -0700 +Subject: [PATCH] disable IRQ for DMA2_Stream0 + +This DMA stream is used to read values from ADC1 +while ADC1 cycles through it's sequence of input +channels. No interrupts are required to make +this work. +--- + Firmware/Board/v3/Src/dma.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/Firmware/Board/v3/Src/dma.c b/Firmware/Board/v3/Src/dma.c +index 55d5e03..3de873a 100644 +--- a/Firmware/Board/v3/Src/dma.c ++++ b/Firmware/Board/v3/Src/dma.c +@@ -78,8 +78,10 @@ void MX_DMA_Init(void) + HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); + /* DMA2_Stream0_IRQn interrupt configuration */ +- HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 5, 0); +- HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); ++ // Dear STM, no we _don't_ want to fire an interrupt for this DMA ++ // (it's not possible to deselect this in CubeMX) ++ //HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 5, 0); ++ //HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); + + } + +-- +2.16.2 + diff --git a/Firmware/Board/v3/Inc/gpio.h b/Firmware/Board/v3/Inc/gpio.h index f8ffe61b..6ec71035 100644 --- a/Firmware/Board/v3/Inc/gpio.h +++ b/Firmware/Board/v3/Inc/gpio.h @@ -71,11 +71,11 @@ void MX_GPIO_Init(void); /* USER CODE BEGIN Prototypes */ void SetGPIO12toUART(); -void SetupENCIndexGPIO(); bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, uint32_t pull_up_down, void (*callback)(void*), void* ctx); void GPIO_unsubscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); +void GPIO_set_to_analog(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); /* USER CODE END Prototypes */ diff --git a/Firmware/Board/v3/Odrive.ioc b/Firmware/Board/v3/Odrive.ioc index 1e22adaf..84db04ed 100644 --- a/Firmware/Board/v3/Odrive.ioc +++ b/Firmware/Board/v3/Odrive.ioc @@ -78,9 +78,20 @@ ADC3.ScanConvMode=DISABLE CAN1.CalculateTimeBit=1142 CAN1.CalculateTimeQuantum=380.95238095238096 CAN1.IPParameters=CalculateTimeQuantum,CalculateTimeBit +Dma.ADC1.2.Direction=DMA_PERIPH_TO_MEMORY +Dma.ADC1.2.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.ADC1.2.Instance=DMA2_Stream0 +Dma.ADC1.2.MemDataAlignment=DMA_MDATAALIGN_HALFWORD +Dma.ADC1.2.MemInc=DMA_MINC_ENABLE +Dma.ADC1.2.Mode=DMA_CIRCULAR +Dma.ADC1.2.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD +Dma.ADC1.2.PeriphInc=DMA_PINC_DISABLE +Dma.ADC1.2.Priority=DMA_PRIORITY_LOW +Dma.ADC1.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode Dma.Request0=UART4_RX Dma.Request1=UART4_TX -Dma.RequestsNb=2 +Dma.Request2=ADC1 +Dma.RequestsNb=3 Dma.UART4_RX.0.Direction=DMA_PERIPH_TO_MEMORY Dma.UART4_RX.0.FIFOMode=DMA_FIFOMODE_DISABLE Dma.UART4_RX.0.Instance=DMA1_Stream2 @@ -199,6 +210,7 @@ NVIC.ADC_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.DMA1_Stream2_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.DMA1_Stream4_IRQn=true\:5\:0\:false\:false\:true\:true\:false +NVIC.DMA2_Stream0_IRQn=true\:5\:0\:false\:false\:false\:true\:false NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:true diff --git a/Firmware/Board/v3/Src/adc.c b/Firmware/Board/v3/Src/adc.c index bb536024..ed9c1bdc 100644 --- a/Firmware/Board/v3/Src/adc.c +++ b/Firmware/Board/v3/Src/adc.c @@ -51,6 +51,7 @@ #include "adc.h" #include "gpio.h" +#include "dma.h" /* USER CODE BEGIN 0 */ @@ -66,6 +67,7 @@ ADC_HandleTypeDef hadc1; ADC_HandleTypeDef hadc2; ADC_HandleTypeDef hadc3; +DMA_HandleTypeDef hdma_adc1; /* ADC1 init function */ void MX_ADC1_Init(void) @@ -257,6 +259,25 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + /* ADC1 DMA Init */ + /* ADC1 Init */ + hdma_adc1.Instance = DMA2_Stream0; + hdma_adc1.Init.Channel = DMA_CHANNEL_0; + hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; + hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + hdma_adc1.Init.Mode = DMA_CIRCULAR; + hdma_adc1.Init.Priority = DMA_PRIORITY_LOW; + hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1); + /* ADC1 interrupt Init */ HAL_NVIC_SetPriority(ADC_IRQn, 5, 0); HAL_NVIC_EnableIRQ(ADC_IRQn); @@ -354,6 +375,9 @@ void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle) HAL_GPIO_DeInit(GPIOA, M1_TEMP_Pin|AUX_TEMP_Pin|VBUS_S_Pin); + /* ADC1 DMA DeInit */ + HAL_DMA_DeInit(adcHandle->DMA_Handle); + /* ADC1 interrupt Deinit */ /* USER CODE BEGIN ADC1:ADC_IRQn disable */ /** diff --git a/Firmware/Board/v3/Src/dma.c b/Firmware/Board/v3/Src/dma.c index a725a585..3de873ad 100644 --- a/Firmware/Board/v3/Src/dma.c +++ b/Firmware/Board/v3/Src/dma.c @@ -68,6 +68,7 @@ void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); + __HAL_RCC_DMA2_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Stream2_IRQn interrupt configuration */ @@ -76,6 +77,11 @@ void MX_DMA_Init(void) /* DMA1_Stream4_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 5, 0); HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); + /* DMA2_Stream0_IRQn interrupt configuration */ + // Dear STM, no we _don't_ want to fire an interrupt for this DMA + // (it's not possible to deselect this in CubeMX) + //HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 5, 0); + //HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); } diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 85b9028f..130708bf 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -257,6 +257,17 @@ void GPIO_unsubscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { HAL_NVIC_DisableIRQ(get_irq_number(GPIO_pin)); } +// @brief Configures the specified GPIO as an analog input. +// This disables any subscriptions that were active for this pin. +void GPIO_set_to_analog(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_unsubscribe(GPIO_port, GPIO_pin); + GPIO_InitStruct.Pin = GPIO_pin; + GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIO_port, &GPIO_InitStruct); +} + //Dispatch processing of external interrupts based on source void HAL_GPIO_EXTI_Callback(uint16_t GPIO_pin) { for (size_t i = 0; i < n_subscriptions; ++i) { diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 66a71605..44075136 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -38,6 +38,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * **USB Bootloader** * `make erase_config` to erase the configuration with an STLink (the configuration can also be erased from within explore_odrive.py, using `odrv0.erase_configuration()`) * Travis-CI builds firmware for all board versions and deploys the binaries when a tag is pushed to master +* General purpose ADC API. See function get_adc_voltage() in low_level.cpp for more detais. ### Changed * Most of the code from `lowlevel.c` moved to `axis.cpp`, `encoder.cpp`, `controller.cpp`, `sensorless_estimator.cpp`, `motor.cpp` and the corresponding header files diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 8477ee32..9b701609 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -321,6 +321,112 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, htim_b->Instance->BDTR |= MOE_store_b; } +// @brief ADC1 measurements are written to this buffer by DMA +uint16_t adc_measurements_[ADC_CHANNEL_COUNT] = { 0 }; + +// @brief Starts the general purpose ADC on the ADC1 peripheral. +// The measured ADC voltages can be read with get_adc_voltage(). +// +// ADC1 is set up to continuously sample all channels 0 to 15 in a +// round-robin fashion. +// DMA is used to copy the measured 12-bit values to adc_measurements_. +// +// The injected (high priority) channel of ADC1 is used to sample vbus_voltage. +// This conversion is triggered by TIM1 at the frequency of the motor control loop. +void start_general_purpose_adc() { + ADC_ChannelConfTypeDef sConfig; + + // Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) + hadc1.Instance = ADC1; + hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; + hadc1.Init.Resolution = ADC_RESOLUTION_12B; + hadc1.Init.ScanConvMode = ENABLE; + hadc1.Init.ContinuousConvMode = ENABLE; + hadc1.Init.DiscontinuousConvMode = DISABLE; + hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; + hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; + hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc1.Init.NbrOfConversion = ADC_CHANNEL_COUNT; + hadc1.Init.DMAContinuousRequests = ENABLE; + hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + if (HAL_ADC_Init(&hadc1) != HAL_OK) + { + _Error_Handler((char*)__FILE__, __LINE__); + } + + // Set up sampling sequence (channel 0 ... channel 15) + sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES; + for (uint32_t channel = 0; channel < ADC_CHANNEL_COUNT; ++channel) { + sConfig.Channel = channel << ADC_CR1_AWDCH_Pos; + sConfig.Rank = channel + 1; // rank numbering starts at 1 + if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) + _Error_Handler((char*)__FILE__, __LINE__); + } + + HAL_ADC_Start_DMA(&hadc1, reinterpret_cast(adc_measurements_), ADC_CHANNEL_COUNT); +} + +// @brief Returns the ADC voltage associated with the specified pin. +// GPIO_set_to_analog() must be called first to put the Pin into +// analog mode. +// Returns NaN if the pin has no associated ADC1 channel. +// +// On ODrive 3.3 and 3.4 the following pins can be used with this function: +// GPIO_1, GPIO_2, GPIO_3, GPIO_4 and some pins that are connected to +// on-board sensors (M0_TEMP, M1_TEMP, AUX_TEMP) +// +// The ADC values are sampled in background at ~30kHz without +// any CPU involvement. +// +// Details: each of the 16 conversion takes (15+26) ADC clock +// cycles and the ADC, so the update rate of the entire sequence is: +// 21000kHz / (15+26) / 16 = 32kHz +// The true frequency is slightly lower because of the injected vbus +// measurements +float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { + uint32_t channel = UINT32_MAX; + if (GPIO_port == GPIOA) { + if (GPIO_pin == GPIO_PIN_0) + channel = 0; + else if (GPIO_pin == GPIO_PIN_1) + channel = 1; + else if (GPIO_pin == GPIO_PIN_2) + channel = 2; + else if (GPIO_pin == GPIO_PIN_3) + channel = 3; + else if (GPIO_pin == GPIO_PIN_4) + channel = 4; + else if (GPIO_pin == GPIO_PIN_5) + channel = 5; + else if (GPIO_pin == GPIO_PIN_6) + channel = 6; + else if (GPIO_pin == GPIO_PIN_7) + channel = 7; + } else if (GPIO_port == GPIOB) { + if (GPIO_pin == GPIO_PIN_0) + channel = 8; + else if (GPIO_pin == GPIO_PIN_1) + channel = 9; + } else if (GPIO_port == GPIOC) { + if (GPIO_pin == GPIO_PIN_0) + channel = 10; + else if (GPIO_pin == GPIO_PIN_1) + channel = 11; + else if (GPIO_pin == GPIO_PIN_2) + channel = 12; + else if (GPIO_pin == GPIO_PIN_3) + channel = 13; + else if (GPIO_pin == GPIO_PIN_4) + channel = 14; + else if (GPIO_pin == GPIO_PIN_5) + channel = 15; + } + if (channel < ADC_CHANNEL_COUNT) + return ((float)adc_measurements_[channel]) * (3.3f / (float)(1 << 12)); + else + return 0.0f / 0.0f; // NaN +} + //-------------------------------- // IRQ Callbacks //-------------------------------- diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index 015d1b2d..095881cc 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -42,6 +42,10 @@ void start_adc_pwm(); void start_pwm(TIM_HandleTypeDef* htim); void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, uint16_t TIM_CLOCKSOURCE_ITRx, uint16_t count_offset); +void start_general_purpose_adc(); + +float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); + void update_brake_current(); #ifdef __cplusplus diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 1e18a744..7451a422 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -141,6 +141,9 @@ int odrive_main(void) { *encoder, *sensorless_estimator, *controller, *motor); } + // 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 (board_config.enable_uart) { diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 4228720a..d6e6f7e9 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -35,6 +35,9 @@ extern bool user_config_loaded_; extern uint64_t serial_number; extern char serial_number_str[13]; +#define ADC_CHANNEL_COUNT 16 +extern uint16_t adc_measurements_[ADC_CHANNEL_COUNT]; + typedef struct { bool fully_booted; uint32_t uptime; // [ms] diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 272c0d28..46fb4263 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -147,6 +147,10 @@ static inline auto make_obj_tree() { make_protocol_property("test_property", &test_property), make_protocol_function("test_function", static_functions, &StaticFunctions::test_function, "delta"), make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), +#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 4 + make_protocol_property("adc_gpio1", &adc_measurements_[0]), + make_protocol_property("adc_gpio2", &adc_measurements_[1]), +#endif make_protocol_function("save_configuration", static_functions, &StaticFunctions::save_configuration_helper), make_protocol_function("erase_configuration", static_functions, &StaticFunctions::erase_configuration_helper), make_protocol_function("reboot", static_functions, &StaticFunctions::NVIC_SystemReset_helper),