I'am working on a current controller to control the current in two coils simultaneously. Therefor I want to measure two analog pins simultaneously and synchronised w.r.t. the PWM timer.
For PWM, I use timer TIM2. PWM is running properly. Additionally, I configured ADC for dual injected simultaneous mode.
Now my question: When I start the ADC by setting the JSWSTART-Bit of register ADC1_CR1 within the timer IRQ-Handler, the measurement is performed (software triggered ADC execution). But when I want to use the timer update event as ADC trigger, no measurement will be performed. What I'am doing wrong?
I use only low level functions of HAL library.
This is my code when using software triggered 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");
}
}
Changes to use timer trigger: Selected update event as trigger output for timer TIM2, and select TIM2_TRGO as ADC trigger source. And I also commented the function setting the JSWSTART-Bit within the timer IRQ-Handler to not start ADC2 accidentally.
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);
...
}
Solution is quiet easy but not so easy to find :D
When using the low level HAL function LL_ADC_INJ_Init(...)
, then the JEXTEN-bits in control register CR2 are cleared. And cleard JEXTEN-bits mean "no trigger evaluation". Additionally, the value assigned to ADC_INJ_InitStruct.TriggerSource = LL_ADC_INJ_TRIG_EXT_TIM2_TRGO;
is masked so it isn't possible to set trigger source and trigger detection using the init function.
Doing it by myself, soves the problem. So the "solution" is
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);
...
}
The same behaviour should occure when using regular channels, because ADC_REG_Init(...)
also clears EXTEN-bits. So in this case add
ADC1->CR2 |= (0x01 << ADC_CR2_EXTEN_Pos);