Showing results for 
Search instead for 
Did you mean: 

Injected ADC conversions set both JEOC and EOC flags, how to determine which finished?

Associate II

I'm working on a project with an STM32F103VCT6 where I'd like to use the ADCs in combined regular/injected simultaneous mode. At a high level, I want to measure the system voltage and current simultaneously every 1ms, and then I want to periodically retask the ADCs as needed throughout the program to measure other things like a specific subcircuit current and system voltage simultaneously.

I'm triggering the injected conversions inside an interrupt handler and both the normal end of conversion flag (EOC), and injected conversion flag (JEOC) are set by hardware when an injected conversion is complete, as per the docs. If the code inside the main program is waiting for a normal conversion to complete, how can I ever know that the normal conversion finished when an injected conversion during an interrupt could also set the EOC flag?


I think this is more of a broad question than a specific code issue, but here is my specific code anyways:

ADC setup:


void SetupInjectedDualADCReadForIRQ(void){
	ADC1->CR1 |= 1 << ADC_CR1_DUALMOD_Pos;	// Combined regular simultaneous + injected simultaneous mode

	ADC1->CR2 |= 7 << ADC_CR2_JEXTSEL_Pos;	//JSWSTART for injection trigger

	ADC2->CR2 |= 7 << ADC_CR2_JEXTSEL_Pos;	//JSWSTART for injection trigger




Code called during SysTick handler for injected read every 1ms. Note the polling of JEOC, which works fine because only the end of an injected conversion could set JEOC.


void MeasureSystemVoltageCurrent(void){

	while(!(ADC1->SR & ADC_SR_JEOC));	//assuming when ADC1 is done, ADC2 is done too

	uint32_t current_adc = ADC1->JDR1;
	uint32_t voltage_adc = ADC2->JDR1;

	//convert readings from ADC counts to real units
	uint32_t current_mA = ConvertSystemCurrentADCtomA(current_adc);
	uint32_t voltage_mV = ConvertVsenseADCtomV(voltage_adc);

	//store in average buffers



Non-interrupt code called during program execution. ADC EOC interrupts are only enabled here for debugging.


//accepts the stage number as an argument
//returns measured conductance value
uint32_t CalibrateSingleStage(uint32_t stage_num){
	// Turn on stage while simultaneously starting a timer that will trigger an ADC read after 50us
	// The counter of the timer will overflow after 100us and stop due to one shot mode.
	// TIM overflow will trigger an interrupt which will turn off the load stage.

	stage_being_calibrated = GetPointerToSingleStageConfig(stage_num);

	//Using timer 3
	TIM3->PSC = 0;
	TIM3->CCR1 = 3599;	//do ADC read after 50us
	TIM3->ARR = 7199;	//100us pulse
	TIM3->CCMR1 |= 7 << TIM_CCMR1_OC1M_Pos;	//OC1REF signal goes high when counter matches
	TIM3->CR2 |= 4 << TIM_CR2_MMS_Pos;	//OC1REF signal is used as trigger output (TRGO), used for ADC triggering
	TIM3->SR = 0;
	TIM3->DIER |= TIM_DIER_UIE;	//Enable interrupt on overflow/update

	//get mux address and decode
	uint32_t imux_address = stage_being_calibrated->imux_addr;
	uint32_t imux_addr0 = imux_address & 0x1;
	uint32_t imux_addr1 = (imux_address >> 1) & 0x1;
	HAL_GPIO_WritePin(IMUX_S0_GPIO_Port, IMUX_S0_Pin, imux_addr0);
	HAL_GPIO_WritePin(IMUX_S1_GPIO_Port, IMUX_S1_Pin, imux_addr1);

	//get adc pin to read
	uint32_t adc_pin_to_read = stage_being_calibrated->imux_adc_pin;

	//configure ADC
	//proper dual mode config already done in injected ADC setup func.


	//setup timer trigger on ADC1 and enable
	ADC1->CR2 &= ~((7 << ADC_CR2_EXTSEL_Pos) | ADC_CR2_EXTTRIG);
	ADC1->CR2 |= ((4 << ADC_CR2_EXTSEL_Pos) | ADC_CR2_EXTTRIG);	// Timer 3 TRGO event,	//NOTE: this won't work if field is already set to 7

	//setup SW trigger on ADC2, and enable trigger
	ADC2->CR2 |= (7 << ADC_CR2_EXTSEL_Pos) | ADC_CR2_EXTTRIG;

	//configure channels ADC1 = mux, ADC2 = vsense
	ADC1->SQR3 |= adc_pin_to_read << ADC_SQR3_SQ1_Pos;

	//Enable EOC interrupt

	//TODO: Fix editing status registers with Read-modify-write

	TIM3->CR1 |= TIM_CR1_CEN | TIM_CR1_OPM;	//Enable timer in oneshot mode
	stage_being_calibrated->io_port->BSRR = stage_being_calibrated->io_pin << 0;		//Turn on stage

	//wait for adc read to be done
	while (!(ADC1->SR & ADC_SR_EOC));

	volatile uint32_t current_adc = ADC1->DR;
	volatile uint32_t voltage_adc = ADC2->DR;

	uint32_t voltage_mV = ConvertVsenseADCtomV(voltage_adc);
	//uint32_t current_mA =

	return 0;	//remove me later

void TIM3_IRQHandler(void){

	if (TIM3->SR & TIM_SR_UIF){
		stage_being_calibrated->io_port->BSRR = stage_being_calibrated->io_pin << 16;	//Turn off stage
		TIM3->SR &= ~TIM_SR_UIF;	//Clear interrupt flag


void ADC1_2_IRQHandler(void){

	HAL_GPIO_WritePin(IO1_GPIO_Port, IO1_Pin, 1);
	HAL_GPIO_WritePin(IO1_GPIO_Port, IO1_Pin, 0);
	if (ADC1->SR & ADC_SR_EOC){

		ADC1->CR1 &= ~ADC_CR1_EOCIE;	//Disable interrupt so it doesn't loop


Note the use of "while (!(ADC1->SR & ADC_SR_EOC));" here, which doesn't mean anything if there is an interrupt firing every 1ms that triggers an injected conversion, and then the injected conversion will set the EOC flag too when done. So in the non-interrupt code, it doesn't seem like I can ever determine whether the normal conversion finished, or it just waited until the next SysTick interrupt which starts an injected conversion, which set the EOC flag.

Possible solutions:

  • I could do a delay and just assume the normal conversion is complete.
  • I could use DMA to move the data for the normal conversion to memory, and then use "while (data != 0);" instead of "while (!(ADC1->SR & ADC_SR_EOC));" However, what if the readings actually are 0? I suppose use an end of DMA transfer interrupt to set a flag? Seems excessive though. (Can't use normal ADC EOC interrupt, since injected conversion sets EOC which triggers interrupt, even if JEOCIE isn't set.
  • I could poll the EOC flag and the STRT flag so at least I'd know the normal conversion started, even if I don't know it finished.
  • I could set a global flag to disable the injected conversions while doing normal conversions, but then what's the point of the injected conversions at all?
  • I should check what the HAL does, to see if it handles this case.

Accepted Solutions

Use DMA and use the DMA interrupt flags (HT, TC) to see when it's done converting. Or monitor the NDTR register to see how many conversions are done.

The docs are ambiguous, have you verified that EOC is set when an injected group is done?

If you feel a post has answered your question, please click "Accept as Solution".

View solution in original post


Use DMA and use the DMA interrupt flags (HT, TC) to see when it's done converting. Or monitor the NDTR register to see how many conversions are done.

The docs are ambiguous, have you verified that EOC is set when an injected group is done?

If you feel a post has answered your question, please click "Accept as Solution".

Yes, I've confirmed EOC is set when an injected conversion is done. As a test I commented out all my normal ADC conversion setup and triggering code, so there is literally nothing to trigger a normal conversion, and added the following code to make a GPIO pulse when EOC is set. The pulse happens precisely at 1kHz, which corresponds perfectly with the Systick handler triggering the injected conversions.


while (1){
		if (ADC1->SR & ADC_SR_EOC){
			ADC1->SR &= ~ADC_SR_EOC;
			HAL_GPIO_WritePin(IO1_GPIO_Port, IO1_Pin, 1);
			HAL_GPIO_WritePin(IO1_GPIO_Port, IO1_Pin, 0);


Also, as soon as I step over 


 in the debugger, I can see JSTRT, JEOC and EOC get set.


Setting up DMA and then polling the transfer complete flag as suggested does work nicely though. Thank you.