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);
我正在研究电流控制器,以同时控制两个线圈中的电流。 因此,我想同时测量两个模拟引脚并同步 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);