定时器触发的STM32F767ZI双ADC模式

Posted

技术标签:

【中文标题】定时器触发的STM32F767ZI双ADC模式【英文标题】:STM32F767ZI dual ADC mode triggered by timer 【发布时间】:2021-10-22 03:18:25 【问题描述】:

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

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

现在我的问题是: 当我通过在定时器 IRQ-Handler 中设置寄存器 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 的触发输出,并选择 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);
    ...

【问题讨论】:

【参考方案1】:

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

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

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-&gt;CR2 |= (0x01 &lt;&lt; ADC_CR2_EXTEN_Pos);

【讨论】:

以上是关于定时器触发的STM32F767ZI双ADC模式的主要内容,如果未能解决你的问题,请参考以下文章

stm32f1adc同步模式没数据

stm32f103 adc 怎么触发

STM32F7:ADC DMA 传输只工作一次

STM32H7教程第45章 STM32H7的ADC应用之定时器触发配合DMA双缓冲

STM32F767ZI nucleo板没有连接USB

stm32f767的adc采样率是多少