/* Copyright 2018 Benjamin Vedder benjamin@vedder.se This file is part of the VESC firmware. The VESC firmware 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, either version 3 of the License, or (at your option) any later version. The VESC firmware 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 . */ #include "gpdrive.h" #include "ch.h" #include "hal.h" #include "stm32f4xx_conf.h" #include "digital_filter.h" #include "utils.h" #include "ledpwm.h" #include "terminal.h" #include "commands.h" #include "timeout.h" #include "mc_interface.h" #include "timer.h" #include #include #include // Settings #define SAMPLE_BUFFER_SIZE 2048 // Private types typedef struct { float current_set; float voltage_now; float voltage_int; } cc_state; typedef struct { float buffer[SAMPLE_BUFFER_SIZE]; int read; int write; } sample_buffer; // Private variables static volatile mc_configuration *m_conf; static volatile float m_fsw_now; static volatile float m_mod_now; static volatile float m_current_now; static volatile float m_current_now_filtered; static volatile bool m_init_done = false; static volatile gpd_output_mode m_output_mode; static volatile sample_buffer m_sample_buffer; static volatile float m_buffer_int_scale; static volatile bool m_is_running; static volatile float m_output_now; static volatile bool m_dccal_done = false; static volatile int m_curr_samples; static volatile int m_curr0_sum; static volatile int m_curr1_sum; static volatile int m_curr0_offset; static volatile int m_curr1_offset; #ifdef HW_HAS_3_SHUNTS static volatile int m_curr2_sum; static volatile int m_curr2_offset; #endif static volatile float m_last_adc_isr_duration; static volatile cc_state m_current_state; // Private functions static void stop_pwm_hw(void); static void adc_int_handler(void *p, uint32_t flags); static void set_modulation(float mod); static void do_dc_cal(void); // Threads static THD_WORKING_AREA(timer_thread_wa, 2048); static THD_FUNCTION(timer_thread, arg); static volatile bool timer_thd_stop; void gpdrive_init(volatile mc_configuration *configuration) { utils_sys_lock_cnt(); m_init_done = false; // Restore timers TIM_DeInit(TIM1); TIM1->CNT = 0; // Disable channel 2 pins palSetPadMode(GPIOA, 9, PAL_MODE_OUTPUT_PUSHPULL); palClearPad(GPIOA, 9); palSetPadMode(GPIOB, 14, PAL_MODE_OUTPUT_PUSHPULL); palClearPad(GPIOB, 14); m_conf = configuration; m_fsw_now = 40000; m_mod_now = 0.0; m_current_now = 0.0; m_current_now_filtered = 0.0; m_output_mode = GPD_OUTPUT_MODE_NONE; memset((void*)&m_sample_buffer, 0, sizeof(m_sample_buffer)); m_buffer_int_scale = 1.0 / 128.0; m_is_running = false; m_output_now = 0.0; m_curr0_sum = 0; m_curr1_sum = 0; m_curr_samples = 0; m_curr0_offset = 0; m_curr1_offset = 0; m_dccal_done = false; #ifdef HW_HAS_3_SHUNTS m_curr2_sum = 0; m_curr2_offset = 0; #endif m_last_adc_isr_duration = 0; memset((void*)&m_current_state, 0, sizeof(m_current_state)); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_BDTRInitTypeDef TIM_BDTRInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = SYSTEM_CORE_CLOCK / (int)m_fsw_now; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; TIM_OCInitStructure.TIM_Pulse = TIM1->ARR / 2; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set; TIM_OC1Init(TIM1, &TIM_OCInitStructure); TIM_OC2Init(TIM1, &TIM_OCInitStructure); TIM_OC3Init(TIM1, &TIM_OCInitStructure); TIM_OC4Init(TIM1, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable; TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable; TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF; TIM_BDTRInitStructure.TIM_DeadTime = conf_general_calculate_deadtime(HW_DEAD_TIME_NSEC, SYSTEM_CORE_CLOCK); TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High; TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable; TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure); TIM_CCPreloadControl(TIM1, ENABLE); TIM_ARRPreloadConfig(TIM1, ENABLE); ADC_CommonInitTypeDef ADC_CommonInitStructure; DMA_InitTypeDef DMA_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2 | RCC_APB2Periph_ADC3, ENABLE); dmaStreamAllocate(STM32_DMA_STREAM(STM32_DMA_STREAM_ID(2, 4)), 3, (stm32_dmaisr_t)adc_int_handler, (void *)0); DMA_InitStructure.DMA_Channel = DMA_Channel_0; DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADC_Value; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC->CDR; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize = HW_ADC_CHANNELS; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA2_Stream4, &DMA_InitStructure); DMA_Cmd(DMA2_Stream4, ENABLE); DMA_ITConfig(DMA2_Stream4, DMA_IT_TC, ENABLE); // Note that the ADC is running at 42MHz, which is higher than the // specified 36MHz in the data sheet, but it works. ADC_CommonInitStructure.ADC_Mode = ADC_TripleMode_RegSimult; ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1; ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; ADC_CommonInit(&ADC_CommonInitStructure); ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Falling; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC2; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion = HW_ADC_NBR_CONV; ADC_Init(ADC1, &ADC_InitStructure); ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStructure.ADC_ExternalTrigConv = 0; ADC_Init(ADC2, &ADC_InitStructure); ADC_Init(ADC3, &ADC_InitStructure); ADC_TempSensorVrefintCmd(ENABLE); ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE); hw_setup_adc_channels(); ADC_Cmd(ADC1, ENABLE); ADC_Cmd(ADC2, ENABLE); ADC_Cmd(ADC3, ENABLE); TIM_Cmd(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); // Always sample ADC in the beginning of the PWM cycle TIM1->CCR2 = 200; utils_sys_unlock_cnt(); ENABLE_GATE(); DCCAL_OFF(); do_dc_cal(); // Start threads timer_thd_stop = false; chThdCreateStatic(timer_thread_wa, sizeof(timer_thread_wa), NORMALPRIO, timer_thread, NULL); stop_pwm_hw(); // Check if the system has resumed from IWDG reset if (timeout_had_IWDG_reset()) { mc_interface_fault_stop(FAULT_CODE_BOOTING_FROM_WATCHDOG_RESET); } m_init_done = true; } void gpdrive_deinit(void) { if (!m_init_done) { return; } m_init_done = false; timer_thd_stop = true; while (timer_thd_stop) { chThdSleepMilliseconds(1); } TIM_DeInit(TIM1); TIM_DeInit(TIM12); ADC_DeInit(); DMA_DeInit(DMA2_Stream4); nvicDisableVector(ADC_IRQn); dmaStreamRelease(STM32_DMA_STREAM(STM32_DMA_STREAM_ID(2, 4))); // Restore pins palSetPadMode(GPIOA, 9, PAL_MODE_ALTERNATE(GPIO_AF_TIM1) | PAL_STM32_OSPEED_HIGHEST | PAL_STM32_PUDR_FLOATING); palSetPadMode(GPIOB, 14, PAL_MODE_ALTERNATE(GPIO_AF_TIM1) | PAL_STM32_OSPEED_HIGHEST | PAL_STM32_PUDR_FLOATING); } bool gpdrive_init_done(void) { return m_init_done; } bool gpdrive_is_dccal_done(void) { return m_dccal_done; } float gpdrive_get_switching_frequency_now(void) { return m_fsw_now; } void gpdrive_set_configuration(volatile mc_configuration *configuration) { // Stop everything first to be safe m_output_mode = GPD_OUTPUT_MODE_NONE; stop_pwm_hw(); utils_sys_lock_cnt(); m_conf = configuration; utils_sys_unlock_cnt(); } void gpdrive_output_sample(float sample) { m_output_now = sample; switch (m_output_mode) { case GPD_OUTPUT_MODE_MODULATION: set_modulation(sample); break; case GPD_OUTPUT_MODE_VOLTAGE: set_modulation(sample / GET_INPUT_VOLTAGE()); break; case GPD_OUTPUT_MODE_CURRENT: m_current_state.current_set = sample; break; default: break; } } void gpdrive_fill_buffer(float *samples, int sample_num) { for (int i = 0;i < sample_num;i++) { m_sample_buffer.buffer[m_sample_buffer.write++] = samples[i]; m_sample_buffer.write %= SAMPLE_BUFFER_SIZE; } } void gpdrive_add_buffer_sample(float sample) { m_sample_buffer.buffer[m_sample_buffer.write++] = sample; m_sample_buffer.write %= SAMPLE_BUFFER_SIZE; } void gpdrive_add_buffer_sample_int(int sample) { m_sample_buffer.buffer[m_sample_buffer.write++] = (float)sample * m_buffer_int_scale; m_sample_buffer.write %= SAMPLE_BUFFER_SIZE; } void gpdrive_set_buffer_int_scale(float scale) { m_buffer_int_scale = scale; } void gpdrive_set_switching_frequency(float freq) { m_fsw_now = freq; TIM1->ARR = SYSTEM_CORE_CLOCK / (int)m_fsw_now; set_modulation(m_mod_now); } int gpdrive_buffer_size_left(void) { return (m_sample_buffer.write > m_sample_buffer.read) ? m_sample_buffer.write - m_sample_buffer.read : SAMPLE_BUFFER_SIZE - m_sample_buffer.read +m_sample_buffer.write; } void gpdrive_set_mode(gpd_output_mode mode) { m_output_mode = mode; if (m_output_mode == GPD_OUTPUT_MODE_NONE) { stop_pwm_hw(); } } float gpdrive_get_current(void) { return m_current_now; } float gpdrive_get_current_filtered(void) { return m_current_now_filtered; } float gpdrive_get_modulation(void) { return m_mod_now; } float gpdrive_get_last_adc_isr_duration(void) { return m_last_adc_isr_duration; } // Private functions static void set_modulation(float mod) { utils_truncate_number_abs(&mod, m_conf->l_max_duty); m_mod_now = mod; if (m_output_mode == GPD_OUTPUT_MODE_NONE || mc_interface_get_fault() != FAULT_CODE_NONE) { return; } if (m_conf->pwm_mode == PWM_MODE_BIPOLAR) { uint32_t duty = (uint32_t) (((float)TIM1->ARR / 2.0) * mod + ((float)TIM1->ARR / 2.0)); TIM1->CCR1 = duty; TIM1->CCR3 = duty; // + TIM_SelectOCxM(TIM1, TIM_Channel_1, TIM_OCMode_PWM1); TIM_CCxCmd(TIM1, TIM_Channel_1, TIM_CCx_Enable); TIM_CCxNCmd(TIM1, TIM_Channel_1, TIM_CCxN_Enable); // - TIM_SelectOCxM(TIM1, TIM_Channel_3, TIM_OCMode_PWM2); TIM_CCxCmd(TIM1, TIM_Channel_3, TIM_CCx_Enable); TIM_CCxNCmd(TIM1, TIM_Channel_3, TIM_CCxN_Enable); } else { uint32_t duty = (uint32_t)((float)TIM1->ARR * fabsf(mod)); TIM1->CCR1 = duty; TIM1->CCR3 = duty; if (mod >= 0) { // + TIM_SelectOCxM(TIM1, TIM_Channel_1, TIM_OCMode_PWM1); TIM_CCxCmd(TIM1, TIM_Channel_1, TIM_CCx_Enable); TIM_CCxNCmd(TIM1, TIM_Channel_1, TIM_CCxN_Enable); // - TIM_SelectOCxM(TIM1, TIM_Channel_3, TIM_OCMode_Inactive); TIM_CCxCmd(TIM1, TIM_Channel_3, TIM_CCx_Enable); TIM_CCxNCmd(TIM1, TIM_Channel_3, TIM_CCxN_Enable); } else { // + TIM_SelectOCxM(TIM1, TIM_Channel_3, TIM_OCMode_PWM1); TIM_CCxCmd(TIM1, TIM_Channel_3, TIM_CCx_Enable); TIM_CCxNCmd(TIM1, TIM_Channel_3, TIM_CCxN_Enable); // - TIM_SelectOCxM(TIM1, TIM_Channel_1, TIM_OCMode_Inactive); TIM_CCxCmd(TIM1, TIM_Channel_1, TIM_CCx_Enable); TIM_CCxNCmd(TIM1, TIM_Channel_1, TIM_CCxN_Enable); } } TIM_GenerateEvent(TIM1, TIM_EventSource_COM); m_is_running = true; } static void stop_pwm_hw(void) { m_is_running = false; m_sample_buffer.write = 0; m_sample_buffer.read = 0; #ifdef HW_HAS_DRV8313 DISABLE_BR(); #endif TIM_SelectOCxM(TIM1, TIM_Channel_1, TIM_ForcedAction_InActive); TIM_CCxCmd(TIM1, TIM_Channel_1, TIM_CCx_Enable); TIM_CCxNCmd(TIM1, TIM_Channel_1, TIM_CCxN_Disable); TIM_SelectOCxM(TIM1, TIM_Channel_3, TIM_ForcedAction_InActive); TIM_CCxCmd(TIM1, TIM_Channel_3, TIM_CCx_Enable); TIM_CCxNCmd(TIM1, TIM_Channel_3, TIM_CCxN_Disable); TIM_GenerateEvent(TIM1, TIM_EventSource_COM); } static void do_dc_cal(void) { DCCAL_ON(); // Wait max 5 seconds int cnt = 0; while(IS_DRV_FAULT()){ chThdSleepMilliseconds(1); cnt++; if (cnt > 5000) { break; } }; chThdSleepMilliseconds(1000); m_curr0_sum = 0; m_curr1_sum = 0; #ifdef HW_HAS_3_SHUNTS m_curr2_sum = 0; #endif m_curr_samples = 0; while(m_curr_samples < 4000) {}; m_curr0_offset = m_curr0_sum / m_curr_samples; m_curr1_offset = m_curr1_sum / m_curr_samples; #ifdef HW_HAS_3_SHUNTS m_curr2_offset = m_curr2_sum / m_curr_samples; #endif DCCAL_OFF(); m_dccal_done = true; } static void adc_int_handler(void *p, uint32_t flags) { (void)p; (void)flags; uint32_t t_start = timer_time_now(); // Reset the watchdog timeout_feed_WDT(THREAD_MCPWM); int curr0 = GET_CURRENT1(); int curr1 = GET_CURRENT2(); #ifdef HW_HAS_3_SHUNTS int curr2 = GET_CURRENT3(); #endif m_curr0_sum += curr0; m_curr1_sum += curr1; #ifdef HW_HAS_3_SHUNTS m_curr2_sum += curr2; #endif curr0 -= m_curr0_offset; curr1 -= m_curr1_offset; #ifdef HW_HAS_3_SHUNTS curr2 -= m_curr2_offset; #endif m_curr_samples++; // Update current #ifdef HW_HAS_3_SHUNTS float i1 = -(float)curr2; #else float i1 = -(float)curr1; #endif float i2 = (float)curr0; m_current_now = utils_max_abs(i1, i2) * FAC_CURRENT; UTILS_LP_FAST(m_current_now_filtered, m_current_now, m_conf->gpd_current_filter_const); // Check for most critical faults here, as doing it in mc_interface can be too slow // for high switching frequencies. const float input_voltage = GET_INPUT_VOLTAGE(); static int wrong_voltage_iterations = 0; if (input_voltage < m_conf->l_min_vin || input_voltage > m_conf->l_max_vin) { wrong_voltage_iterations++; if ((wrong_voltage_iterations >= 8)) { mc_interface_fault_stop(input_voltage < m_conf->l_min_vin ? FAULT_CODE_UNDER_VOLTAGE : FAULT_CODE_OVER_VOLTAGE); } } else { wrong_voltage_iterations = 0; } if (m_conf->l_slow_abs_current) { if (fabsf(m_current_now) > m_conf->l_abs_current_max) { mc_interface_fault_stop(FAULT_CODE_ABS_OVER_CURRENT); } } else { if (fabsf(m_current_now_filtered) > m_conf->l_abs_current_max) { mc_interface_fault_stop(FAULT_CODE_ABS_OVER_CURRENT); } } // Buffer handling static bool buffer_was_empty = true; static int interpol = 0; static float buffer_last = 0.0; static float buffer_next = 0.0; interpol++; if (interpol > m_conf->gpd_buffer_interpol) { interpol = 0; if (m_sample_buffer.read != m_sample_buffer.write) { buffer_last = buffer_next; buffer_next = m_sample_buffer.buffer[m_sample_buffer.read++]; m_sample_buffer.read %= SAMPLE_BUFFER_SIZE; m_output_now = buffer_last; m_is_running = true; buffer_was_empty = false; } else { if (!buffer_was_empty) { stop_pwm_hw(); } buffer_was_empty = true; } } else if (!buffer_was_empty) { m_output_now = utils_map((float)interpol, 0.0, (float)m_conf->gpd_buffer_interpol + 1.0, buffer_last, buffer_next); m_is_running = true; } if (m_is_running) { gpdrive_output_sample(m_output_now); if (m_output_mode == GPD_OUTPUT_MODE_CURRENT) { float v_in = GET_INPUT_VOLTAGE(); float err = m_current_state.current_set - m_current_now_filtered; m_current_state.voltage_now = m_current_state.voltage_int + err * m_conf->gpd_current_kp; m_current_state.voltage_int += err * m_conf->gpd_current_ki * (1.0 / m_fsw_now); utils_truncate_number_abs((float*)&m_current_state.voltage_int, v_in); set_modulation(m_current_state.voltage_now / v_in); } } ledpwm_update_pwm(); m_last_adc_isr_duration = timer_seconds_elapsed_since(t_start); } static THD_FUNCTION(timer_thread, arg) { (void)arg; chRegSetThreadName("gpdrive timer"); for(;;) { if (timer_thd_stop) { timer_thd_stop = false; return; } static bool buffer_empty_before = true; if (gpdrive_buffer_size_left() > 0 && gpdrive_buffer_size_left() < m_conf->gpd_buffer_notify_left) { if (!buffer_empty_before) { commands_send_gpd_buffer_notify(); } buffer_empty_before = true; } else { buffer_empty_before = false; } chThdSleepMilliseconds(1); } }