STM32F767ZI双ADC模式定时器触发

STM32F767ZI dual ADC mode triggered by timer

我正在研究电流控制器,以同时控制两个线圈中的电流。 因此,我想同时测量两个模拟引脚并同步 w.r.t。 PWM 定时器。

对于PWM,我使用定时器TIM2。 PWM 运行 正确。 此外,我将 ADC 配置为双注入同步模式。

现在我的问题是: 当我通过在定时器 IRQ 处理程序中设置寄存器 ADC1_CR1 的 JSWSTART 位启动 ADC 时,执行测量(软件触发 ADC 执行)。但是当我想使用定时器更新事件作为 ADC 触发时,将不会执行任何测量。我做错了什么?

我只使用 HAL 库的低级函数。

这是我使用软件触发 ADC 时的代码。

void adcInit(){
    /* prepare ADC for synchronous measurement */
    LL_GPIO_InitTypeDef GPIO_InitStruct;
    LL_ADC_CommonInitTypeDef ADC_CommonInitStruct;
    LL_ADC_InitTypeDef ADC_InitStruct;
    LL_ADC_INJ_InitTypeDef ADC_INJ_InitStruct;

    /* enable clocks */
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOC);
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC1);
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC2);

    /* configure input channels */
    /* common to all pins */
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    /* internal current sensor coil C1 */
    GPIO_InitStruct.Pin = ANALOG_CS_INT_C1_PIN;
    LL_GPIO_Init(ANALOG_CS_INT_C1_PORT, &GPIO_InitStruct);
    /* internal current sensor coil C2 */
    GPIO_InitStruct.Pin = ANALOG_CS_INT_C2_PIN;
    LL_GPIO_Init(ANALOG_CS_INT_C2_PORT, &GPIO_InitStruct);
    /* external current sensor coil C1 */
    GPIO_InitStruct.Pin = ANALOG_CS_EXT_C1_PIN;
    LL_GPIO_Init(ANALOG_CS_EXT_C1_PORT, &GPIO_InitStruct);
    /* external current sensor coil C2 */
    GPIO_InitStruct.Pin = ANALOG_CS_EXT_C2_PIN;
    LL_GPIO_Init(ANALOG_CS_EXT_C2_PORT, &GPIO_InitStruct);

    /* initialize ADC register */
    /* use ADC1 & ADC2 in dual combined injected mode */
    ADC_CommonInitStruct.Multimode = LL_ADC_MULTI_DUAL_INJ_SIMULT;
    ADC_CommonInitStruct.CommonClock = LL_ADC_CLOCK_SYNC_PCLK_DIV4;     // 27MHz
    ADC_CommonInitStruct.MultiTwoSamplingDelay = LL_ADC_MULTI_TWOSMP_DELAY_5CYCLES;
    ADC_CommonInitStruct.MultiDMATransfer = LL_ADC_MULTI_REG_DMA_EACH_ADC;
    LL_ADC_CommonInit(ADC123_COMMON, &ADC_CommonInitStruct);

    ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B;
    ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
    ADC_InitStruct.SequencersScanMode = LL_ADC_SEQ_SCAN_DISABLE;
    LL_ADC_Init(ADC1, &ADC_InitStruct);
    LL_ADC_Init(ADC2, &ADC_InitStruct);

    ADC_INJ_InitStruct.SequencerLength = LL_ADC_INJ_SEQ_SCAN_DISABLE;
    ADC_INJ_InitStruct.SequencerDiscont = LL_ADC_INJ_SEQ_DISCONT_DISABLE;
    ADC_INJ_InitStruct.TrigAuto = LL_ADC_INJ_TRIG_INDEPENDENT;
    ADC_INJ_InitStruct.TriggerSource = LL_ADC_INJ_TRIG_SOFTWARE;
    LL_ADC_INJ_Init(ADC1, &ADC_INJ_InitStruct);
    ADC_INJ_InitStruct.TriggerSource = LL_ADC_INJ_TRIG_SOFTWARE;    // disable trigger of ADC2, triggered by ADC1
    LL_ADC_INJ_Init(ADC2, &ADC_INJ_InitStruct);

    /* select channels and set sampling time*/
    LL_ADC_INJ_SetSequencerRanks(ADC1, LL_ADC_INJ_RANK_1, ANALOG_CS_C1_ADC_CH);
    LL_ADC_INJ_SetSequencerRanks(ADC2, LL_ADC_INJ_RANK_1, ANALOG_CS_C2_ADC_CH);

    LL_ADC_SetChannelSamplingTime(ADC1, ANALOG_CS_C1_ADC_CH, LL_ADC_SAMPLINGTIME_3CYCLES);
    LL_ADC_SetChannelSamplingTime(ADC2, ANALOG_CS_C2_ADC_CH, LL_ADC_SAMPLINGTIME_3CYCLES);

    /* enable interrupts */
    NVIC_SetPriority(ADC_IRQn, NVIC_PRIORITY_ADC);
    NVIC_EnableIRQ(ADC_IRQn);
    LL_ADC_EnableIT_JEOS(ADC1);
    // JEOC-interrupt for ADC1 is sufficient, because interrupt is generated when injected channels have all been converted (manual p. 458)
    // LL_ADC_EnableIT_JEOS(ADC2);

    /* enable ADCs */
    LL_ADC_Enable(ADC1);
    LL_ADC_Enable(ADC2);
}

void TIM2_IRQHandler(void){
    static uint32_t ctrlExecCnt = PWM_TIMER_FREQ/CTRL_EXEC_FREQ;
    if(timer2.timer->SR & TIM_SR_UIF_Msk){
        timer2.timer->SR = ~(TIM_SR_UIF_Msk);
        if(!(timer2.timer->CR1 & TIM_CR1_DIR_Msk)){
            // reached counter bottom value, now counting up
            // update TIMx_CCRy value -> because of enabled preload, value will be applied when reaching timer top update event
            LL_TIM_OC_SetCompareCH3(timer2.timer, ctrlState.outC1);
            LL_TIM_OC_SetCompareCH4(timer2.timer, ctrlState.outC2);
            if(!--ctrlExecCnt){
                ctrlExecCnt = PWM_TIMER_FREQ/CTRL_EXEC_FREQ;
                execControlLoop();
            }
            LL_ADC_INJ_StartConversionSWStart(ADC1);
        }else{
            // reaching counter top value -> OC pin is low, so now direction could be changed
            if(ctrlState.dirC1 == POSITIVE){
                LL_GPIO_ResetOutputPin(DIR_C1_PORT, DIR_C1_PIN);
            }else{
                LL_GPIO_SetOutputPin(DIR_C1_PORT, DIR_C1_PIN);
            }
            if(ctrlState.dirC2 == POSITIVE){
                LL_GPIO_ResetOutputPin(DIR_C2_PORT, DIR_C2_PIN);
            }else{
                LL_GPIO_SetOutputPin(DIR_C2_PORT, DIR_C2_PIN);
            }
        }
    }
}

void ADC_IRQHandler(void){
    LL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
    // get interrupt source
    // checking ADC1 JEOC-interrupt is sufficient, because interrupt will be generated when all injected channels have been converted (manual p. 458)
    if(ADC1->SR & ADC_SR_JEOC_Msk){
        // both ADC1 & ADC2 injected conversion finished
        LL_ADC_ClearFlag_JEOS(ADC1);
        //LL_ADC_ClearFlag_JEOS(ADC2);
        // read in data
        adcData[0] = ADC1->JDR1;
        adcData[1] = ADC2->JDR1;
        char tmp[20];
        STM32_usartPrintf(&usart3, "ADC: ");
        utoa(adcData[0],tmp,10);
        STM32_usartPrintf(&usart3, tmp);
        STM32_usartPrintf(&usart3, "\t");
        utoa(adcData[1],tmp,10);
        STM32_usartPrintf(&usart3, tmp);
        STM32_usartPrintf(&usart3, "\r");
    }
}

更改为使用计时器触发器: 选择更新事件作为定时器 TIM2 的触发输出,select TIM2_TRGO 作为 ADC 触发源。 我还评论了在定时器 IRQ-Handler 中设置 JSWSTART-Bit 以不意外启动 ADC2 的功能。

void adcInit(){
    ...
    LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_UPDATE);
    ADC_INJ_InitStruct.TriggerSource = LL_ADC_INJ_TRIG_EXT_TIM2_TRGO;
    LL_ADC_INJ_Init(ADC1, &ADC_INJ_InitStruct);
    ADC_INJ_InitStruct.TriggerSource = LL_ADC_INJ_TRIG_SOFTWARE;    // disable trigger of ADC2, triggered by ADC1
    LL_ADC_INJ_Init(ADC2, &ADC_INJ_InitStruct);
    ...
}

void TIM2_IRQHandler(void){
    ...
//          LL_ADC_INJ_StartConversionSWStart(ADC1);
    ...
}

解决方案很简单,但并不容易找到 :D

当使用低级HAL功能LL_ADC_INJ_Init(...)时,控制寄存器CR2中的JEXTEN位被清除。清除 JEXTEN 位意味着“无触发评估”。此外,分配给 ADC_INJ_InitStruct.TriggerSource = LL_ADC_INJ_TRIG_EXT_TIM2_TRGO; 的值被屏蔽,因此无法使用 init 函数设置触发源和触发检测。 自己做,soves the problem。所以“解决方案”是

void adcInit(){
    ...
    LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_UPDATE);
    ADC_INJ_InitStruct.TriggerSource = LL_ADC_INJ_TRIG_EXT_TIM2_TRGO;
    LL_ADC_INJ_Init(ADC1, &ADC_INJ_InitStruct);
    ADC1->CR2 |= (0x01 << ADC_CR2_JEXTEN_Pos);
    ADC_INJ_InitStruct.TriggerSource = LL_ADC_INJ_TRIG_SOFTWARE;    // disable trigger of ADC2, triggered by ADC1
    LL_ADC_INJ_Init(ADC2, &ADC_INJ_InitStruct);
    ...
}

使用常规通道时应该会发生相同的行为,因为 ADC_REG_Init(...) 也会清除 EXTEN 位。所以在这种情况下添加 ADC1->CR2 |= (0x01 << ADC_CR2_EXTEN_Pos);