AnsweredAssumed Answered

Alternate DMA I2C with normal operation

Question asked by Martin van Es on Jun 4, 2018

I'm trying to rewrite the I2C gyro (MPU6050) read operation of an STM32F0xx based flight controller (Silverware) to partially TIM_Update/DMA based I2C readout for accurate looptime reasons (the project is NOT my work, I'm just trying to improve the codebase where possible).

This works, but only if I keep reading only the gyro and stick to DMA. For altitude hold however I want to read out the barometer in mainloop as well and hope to stay under TIM_Update refresh TIM_Period. What I did was generate a TIM_Update event, and in the TIM_Update interrupt handler, start a fresh I2C DMA read process for 14 bytes, and wait for DMA_TC. In DMA_TC handler I process the new gyro values and trigger mainloop.

The I2C hw readout lib that comes with the package guards for I2C jams by checking I2C_FLAG_BUSY before commencing a new I2C read operation (in the i2c sendheader function that is shared with all i2c read operations).


The problem is: this works if I stick to non-DMA I2C and for DMA-only I2C, but as soon as gyro-DMA is done and I start a new hw_i2c_readdata for 1 byte I end up in an endless I2C_FLAG_BUSY loop that only ends because of a timeout.


I have tried every combination of disabling I2C DMA and clearing pending DMA TC/IC2 TC bits but nothing helps:

// Handle I2C TC
void DMA1_Channel2_3_IRQHandler(void)
I2C_ClearITPendingBit(I2C1, I2C_IT_TCI);
DMA_ClearFlag( DMA1_FLAG_GL3 );

DMA_Cmd( DMA1_Channel3, DISABLE );

// RCC->APB1ENR |= (1UL << 21);
// I2C1->CR1 |= 0x8000;
// I2C1->CR1 &= (uint16_t) ~((uint16_t) 0x8000);

// while(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == SET);
// while(I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE) == RESET);

// while(I2C_GetFlagStatus(I2C1, I2C_FLAG_STOPF) == RESET);

// TIM_SetCounter( TIM17, 0 );
// TIM_Cmd( TIM17, ENABLE );
sixaxis_read(1); // 1 = DMA mainloop

// I2C_GenerateSTOP(I2C1, ENABLE);
liberror = 0;
i2c_readreg(0x76, 0x08); <- This 'normal' I2C read is pestered with I2C_FLAG_BUSY timeouts.

// mainloop();

// TIM_Cmd( TIM17, ENABLE );


liberror is supposed to be 0 all times, if it's nonzero, some sort of I2C BUSY/TC/TXE timeout has occured. I've inserted the liberror=0 for debug reasons, to prevent the code from bailing out on i2c problems and to have it available just before i2c_readreg while it contains last iteration errors.


The DMA setup routine looks like this:

void TIM17_IRQHandler(void)
TIM_ClearITPendingBit( TIM17, TIM_IT_Update );
DMA1_Channel3->CNDTR = 14;
DMA_Cmd( DMA1_Channel3, ENABLE );
I2C_DMACmd( I2C1, I2C_DMAReq_Rx, ENABLE );
hw_i2c_sendheader(SOFTI2C_GYRO_ADDRESS, 59 , 1 );
//send restart + readaddress
I2C_TransferHandling(I2C1, (SOFTI2C_GYRO_ADDRESS)<<1 , 14, I2C_AutoEnd_Mode, I2C_Generate_Start_Read);

As can be seen, DMA CNDTR is set to 14 and I2C_TransferHandling asks for 14 bytes. I expect everyting to be done on DMA TC and I2C bus to be available for anything if I disable DMA Channel3 and I2C_DMAReq_Rx?


As can be seen, the project is based on Std Periperal lib.


Hope there's enough information to start discussing the problem, if not please let me know.

I deliberately didn't include the DMA /I2C initialisation because they both work seperately. It's only the transition from DMA to normal that's not working?