In the previous post, an example of using STM32 DMA to perform a simple data copy between 2 arrays was introduced. Now, I will show another example with DMA and I2C to read raw data from MPU6050 acceleration and gyroscope sensor directly. Besides, a comparison to show timing difference between using and not using DMA is also mentioned.

stm32 mpu6050

stm32 mpu6050

MPU6050 is a very popular MEMS acceleration and gyroscope sensor and other devices can connect and get data from it through I2C connection. There are a lot of libraries for Arduino that are available on the internet for connecting with MPU6050 and few libraries for STM32. Harinadha has done the porting job from MPU6050 Arduino library of Jeff Rowberg to STM32 here as well: http://harinadha.wordpress.com/2012/05/23/mpu6050lib/ without using INT pin (interrupt pin) of MPU6050. I also used this library at the first time and found it was quite difficult to get the most updated gyro data for calculation. So, I got the wrong gyro angle all the time. Moreover, I noticed that the code took lots of time to read 14 bytes of data (including 6 bytes acceleration, 2 bytes of temperature and 6 bytes of gyro), nearly 2ms, so there is no chance to get the sample rate at 1ms.

MPU6050 STM32 connection

MPU6050 STM32 connection

Then I tried to look back at the code for Arduino and they actually used INT pin of MPU6050 to trigger the reading routine. So, I tried to edit the code of Harinadha to implement both INT triggering and DMA reading from I2C with some fine tune to give the best processing time. First, let’s have a look at the initialize routine for MPU6050. Again, Stdperiph driver V3.5.0 of ST was used here for basic I2C peripheral functions:

With the MPU6050_Write function and I2C routine as follow:

And those define registers of MPU6050 can be found here:

MPU6050.h

Here, we finish setting up the MPU6050 sensor. From now on, the sensor will run with the following configuration:

  • Sample rate : 2ms
  • Gyro full scale for X, Y and Z axis : +- 2000 degree/second. This means for example if the sensor is rotated in X axis with maximum angular velocity of 2000 degrees per second, the gyro X data will be maximum value of 16bit integer variable: 32768. In the other hand, the readout value will be -32768 if the angular velocity is -2000 degrees per second. From here, we can come out with the conversion ratio from raw sensor data to real angular velocity: r = 32768 / full scale value = 32768 / 2000 = 16.384.
  • Accelerometer full scale: +- 16g.
  • Fire interrupt signal when data is available. Clear interrupt flag whenever the data is completely read out.

Next, we need to configure DMA peripheral to connect with I2C as well:

The purpose of this configuration is to connect the I2C1 RX with the memory buffer directly using the corresponding DMA channel. We have some important points to notice here in order for you to be able to edit the code yourself in the future if you use a another I2C peripheral or different type of sensor with different byte to transfer through DMA:

  • Which DMA to use ?

dma1 dma2

As you can see from the two tables from STM32F1 reference manual, there are 2 DMA blocks connected to different type of peripherals using different channels. Here we connect MPU6050 with I2C1 of STM32, so that the only choice is DMA1. Then, for reading data, we need to consider channel 7 that is connected to the RX register of I2C1 where all incoming data is stores. Later, if you want to use other peripherals with DMA in your own project, this table can be useful.

  • What is the physical peripheral address:

Each peripheral inside the STM32 has a boundary address which can be found in Table 3 of the reference manual.

dma3

And inside that peripheral, there are several registers whose address are inside that peripheral boundary. For example, in our case, we need to locate the address of I2C_DR register (Data register) to assign to DMA controller.

dma4a

Notice the “Offset” column in Table 189, it means the physical address of I2C_DR register will be offset from the initial address of I2C1 (0x40005400) by 0x10 -> I2C_DR address is 0x40005410

  • Number of byte to transfer:

As mentioned before, 14 bytes will be read from MPU6050, so DMA_Buffersize here should be 14 bytes.

After finishing the configuration parts, we move to the reading part. MPU6050 uses INT pin to trigger STM32 to read out its data as set before and we can use External Interrupt peripheral to capture it. The interrupt routine is as follow:

Noted the define “USE_I2C_DMA” I used to choose between regular way and DMA way of reading. Here the INT pin is connected to GPIO_Pin_4 of GPIOB so EXTI4 is activated. Then the I2C_DMA_Read function is presented:

And DMA interrupt routine:

Now, the reading sequence will be done automatically and store into AccelGyro variable with the minimum time needed. I have also done a timing test to check how efficient this method could be. Following figures show the timing consume of two method: regular reading and DMA reading.

mpu6050_i2c

Fig: regular I2C reading

mpu6050_i2c_dma

Fig: DMA supported reading

Channel 3 in both figures shows the timing period when CPU is dealing with I2C reading. With the normal way of reading I2C data, CPU is busy for the whole period and cannot do anything else. This could lead to delay in reading other sensor data as well. By using DMA, we can free the CPU to do other task as DMA handles all the reading part from I2C peripheral.

Hope you can find this article useful and let’s wait for more to come 😀

STM32 MPU6050 DMA

http://mayaptrung.com/index.php/product/at300

31 Thoughts on “Using STM32 DMA and I2C to read data from MPU6050 – Updated

  1. Jacek on 17/09/2014 at 7:06 AM said:

    Hello, I’m really interested i furder part of this tutorial. Coud you please continue it?

  2. can you provide the full code please?
    My code got stuck at while(I2C_GetFlagStatus(MPU6050_I2C, I2C_FLAG_BUSY))

  3. Redifei on 18/07/2015 at 4:53 PM said:

    Hi, I want use DMA and Interrupt at the same time. In other words your source use polling method in DMA code. but I want to use interrupt code in DMA code. So, I need your advice. What should I do?

    • Le Tan Phuc on 19/07/2015 at 11:36 AM said:

      Hi, I used DMA interrupt in this post actually. DMA interrupt is triggered when it received enough 14 bytes of data from I2C. Is that what you need to do with interrupt or what kind of interrupt you want ?

      • Redifei on 20/07/2015 at 9:39 PM said:

        Thanks for your reply! But I mean instead of using an interrupt as a trigger, I want to the basic communication with the interrupt without ‘while’

  4. nathan on 07/03/2016 at 8:22 AM said:

    Hello again.
    I was wondering if you have tried to modify this tutorial to run with the HAL libraries?
    I’ve been trying myself, and so far I’ve gotten it to work in polling mode without any interrupts by using

    HAL_I2C_Mem_Write(&hi2c1,(uint16_t) slaveAddr, regAddr, I2C_MEMADD_SIZE_8BIT,(uint8_t*) MPU6050bootupTX,1,100);

    to initialize the device (MPU6050bootupTX is a single value array containing the values to each regAddr. eg: MPU6050bootupTX[0]=0x00; sent to redAddr 0x6B (MPU6050_RA_PWR_MGMT_1) will disable sleep mode and start the gyro.

    Similarly to read:

    HAL_I2C_Mem_Read(&hi2c1,(uint16_t) MPU6050_DEFAULT_ADDRESS, MPU6050_RA_ACCEL_XOUT_H, I2C_MEMADD_SIZE_8BIT,(uint8_t*) MPU6050dataReadRX,14,100);
    AcX = (MPU6050dataReadRX[0]<<8)|MPU6050dataReadRX[1];
    AcY = (MPU6050dataReadRX[2]<<8)|MPU6050dataReadRX[3];
    AcZ = (MPU6050dataReadRX[4]<<8)|MPU6050dataReadRX[5];
    Tmp= (MPU6050dataReadRX[6]<<8)|MPU6050dataReadRX[7];
    GyX= (MPU6050dataReadRX[8]<<8)|MPU6050dataReadRX[9];
    GyY= (MPU6050dataReadRX[10]<<8)|MPU6050dataReadRX[11];
    GyZ= (MPU6050dataReadRX[12]<<8)|MPU6050dataReadRX[13];
    Tmp2 = Tmp/340.00+36.53;

    The above works fine. If I change it to:
    HAL_I2C_Mem_Read_DMA(&hi2c1,(uint16_t) MPU6050_DEFAULT_ADDRESS, MPU6050_RA_ACCEL_XOUT_H, I2C_MEMADD_SIZE_8BIT,(uint8_t*) MPU6050dataReadRX,14);

    Then it only sends one data bit. I'm fairly sure this is because the register shifts are happening before the DMA is completed. In your original code I see you use a DMA channel interrupt, to handle the byte-shifting after the DMA is completed. I think this is the part I'm stuck on with the HAL libraries.

    Thanks again!
    Nathan

    • Sergey Kirichok on 12/07/2016 at 5:29 AM said:

      Hi,
      You use the polling mode, you should use oher HAL functions to use DMA with I2C.
      /*
      DMA mode IO operation
       Transmit in master mode an amount of data in non blocking mode (DMA) using
      HAL_I2C_Master_Transmit_DMA()
       At transmission end of transfer HAL_I2C_MasterTxCpltCallback is executed and user
      can add his own code by customization of function pointer
      HAL_I2C_MasterTxCpltCallback
       Receive in master mode an amount of data in non blocking mode (DMA) using
      HAL_I2C_Master_Receive_DMA()
       At reception end of transfer HAL_I2C_MasterRxCpltCallback is executed and user
      can add his own code by customization of function pointer
      HAL_I2C_MasterRxCpltCallback
       Transmit in slave mode an amount of data in non blocking mode (DMA) using
      HAL_I2C_Slave_Transmit_DMA()
       At transmission end of transfer HAL_I2C_SlaveTxCpltCallback is executed and user
      can add his own code by customization of function pointer
      HAL_I2C_SlaveTxCpltCallback
       Receive in slave mode an amount of data in non blocking mode (DMA) using
      HAL_I2C_Slave_Receive_DMA()
       At reception end of transfer HAL_I2C_SlaveRxCpltCallback is executed and user can
      add his own code by customization of function pointer HAL_I2C_SlaveRxCpltCallback
       In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can
      add his own code by customization of function pointer HAL_I2C_ErrorCallback
      */

  5. what about DMP ?

  6. Bilal on 24/03/2016 at 4:56 PM said:

    Nathan,

    You should wait until DMA process is completed. You can do that by HAL Libraries.
    Firstly open stm32cubemx. Configuration->DMA1->then choose DMA request as I2C1 RX. Then open your code and apply the code which is below.

    HAL_I2C_Mem_Read_DMA(&hi2c1,(uint16_t) MPU6050_DEFAULT_ADDRESS, MPU6050_RA_ACCEL_XOUT_H, I2C_MEMADD_SIZE_8BIT,(uint8_t*) MPU6050dataReadRX,14);

    while( HAL_DMA_GetState ( &hdma_i2c1_rx ) != HAL_DMA_STATE_READY )
    ; //wait here until Dma process is completed.

    AcX = (MPU6050dataReadRX[0]<<8)|MPU6050dataReadRX[1];

    • Nathan on 30/03/2016 at 1:58 PM said:

      Thanks Bilal,
      After a lot of mucking around, I tried that method, and for some reason, it just hangs forever on the != HAL_DMA_STATE_READY flag; for some reason (maybe peculiar to the L152) it doesn’t seem to clear this flag automatically. It’ll also periodically get stuck in the I2C communication timeout routine in the stm32LXX_HAL_I2C.c file:

      {hi2c -> state = HAL_DMA_STATE_READY ;
      __HAL_UNLOCK(hi2c);
      return HAL_TIMEOUT;

      The peculiar thing is there’s no timeout set with DMA, so it just seems to hang here forever.

      I’ve gotten around it (seemingly) by including the line

      while (HAL_I2C_IsDeviceReady(&hi2c1, MPU6050_DEFAULT_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);

      and defining I2Cx_TIMEOUT_MAX and EEPROM_MAX_TRIALS. This gets defined in the external interrupt handler (since I’m also using the interrupt pin on the gyro to denote when there’s data ready.) So my complete interrupt handler looks like:

      void EXTI0_IRQHandler(void)
      {
      /* USER CODE BEGIN EXTI0_IRQn 0 */

      /* Check if the EEPROM is ready for a new operation */
      while (HAL_I2C_IsDeviceReady(&hi2c1, MPU6050_DEFAULT_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);

      HAL_I2C_Mem_Read_DMA(&hi2c1,(uint16_t) MPU6050_DEFAULT_ADDRESS, MPU6050_RA_ACCEL_XOUT_H, I2C_MEMADD_SIZE_8BIT,(uint8_t*) MPU6050dataReadRX,14)!= HAL_OK;

      /* USER CODE END EXTI0_IRQn 0 */
      HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
      /* USER CODE BEGIN EXTI0_IRQn 1 */

      /* USER CODE END EXTI0_IRQn 1 */
      }

      This works, and seems stable (without that HAL_I2C_IsDeviceReady routine, it’ll usually work for a few seconds and then hang as soon as the gyro gives another interrupt before the previous read has finished) But obviously having while loops in an interrupt routine is less than ideal.

      Interestingly I did originally have

      /* USER CODE BEGIN EXTI0_IRQn 0 */
      while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
      {
      }

      as the first line in the IRQHandler, but it’d just hang forever on this line.

      Thanks again.
      Nathan

  7. Ludovic on 17/04/2016 at 9:18 PM said:

    Hello Le Tan Phuc,

    There is only few month I work on STM32, thank you for your article (and the others), very useful!

    I’ve just added things to manage timeout on I2C and it works perfectly… up to the time I’ve added another use of DMA with USART1 Tx (channel 4):

    When usart1 DMA channel 4 is not used, the problem never appears.
    When it is used, sometime (1/10000 to 1/100000) the DMA channel5 IRQ fills the buffer with first byte set to 0 and the 13 others set to the same but random byte.

    I’ve added the check of DMA1_IT_TE5 which gives no error.
    I spent several hours to understand the problem but I currently have no more idea.
    Is it possible that the use of DMA channel 4 could interact with channel 5?
    Do you have an idea of what could disturb the I2C DMA transfer?
    There is no other transfer on I2C.
    There is no interrupt used with channel 4 (using !DMA_GetFlagStatus(DMA1_FLAG_TC4) )
    The problem seems to occur only during DMA ch 4 transfer (mem -> ch4 -> usart1 Tx)

    Thanks
    Ludovic

    • Le Tan Phuc on 19/04/2016 at 5:59 PM said:

      Hi, I haven’t faced this problem before, and usually, if you don’t activate other DMA channel, it shouldn’t be affected. But you used DMA channel 5 for what occasion ? for I2C ?

      • Ludovic on 19/04/2016 at 9:33 PM said:

        Hi, yes I use channel 4 for usart1/Tx and channel 5 for I2C.
        You are right, if channel 4 is not active, I2C/DMA-ch5 works nicely without no error at all, which is not the case when the usart is connected.
        It seems that the problem happens during the DMA mem->usart1 transfer, but not sure yet…

  8. Dang Hong Quan on 19/04/2016 at 4:33 PM said:

    Chào bạn, mình cũng đang sử dụng DMA-I2C để lấy dữ liệu từ sensor PMU 6050. Mình có một vấn đề như sau :
    – Khi sử dụng command MPU6050_RA_ACCEL_XOUT_H để đọc giá trị sensor và khởi động DMA mình chỉ đọc được 1 byte chứ không phải 14 byte. Phải thiết lập ở sensor hoặc DMA như thế nào để đọc được 1 lần 14 byte.
    Mong nhận được trả lời của bạn.

    • Le Tan Phuc on 19/04/2016 at 6:02 PM said:

      Bạn xem lại phần cài đặt DMA ở trên mình có chú thích đó (DMA_InitStructure.DMA_BufferSize = 14;).

      • Dang Hong Quan on 19/04/2016 at 6:26 PM said:

        Mình đã thiệt lập DMA_BufferSize = 14. Nhưng tại buffer memory , mình vẫn chỉ nhận được 1 byte giá trị. Mình vẫn chưa hiểu nguyên nhân là ở đâu?
        Khi sử dụng câu lệnh này I2C_DMA_Read(MPU6050_DEFAULT_ADDRESS, MPU6050_RA_ACCEL_XOUT_H,MPU6050) và khởi động DMa thì 14 byte giá trị sẽ được đọc từ sensor phải không bạn ?

  9. taice on 21/10/2016 at 4:26 PM said:

    Hi,i thanks your post,but use this,it can gei back some one, After a few times,it will stop at while(I2C_GetFlagStatus(MPU6050_I2C, I2C_FLAG_BUSY));. i don’t know why.

    • Le Tan Phuc on 07/11/2016 at 12:15 AM said:

      HI, this problem could be from unstable connection or the MPU6050 chip’s quality is not good. Also, this library is quite old now, which is not so robust to handle error in the communication. Current HAL library is a better choice for sure.

  10. sinan on 20/12/2016 at 2:26 AM said:

    Eywallah gardaş

  11. Abi on 29/12/2016 at 6:35 PM said:

    Hi Le tan phuc,

    could you make videos on HAL I2c applications?!

    • Le Tan Phuc on 04/01/2017 at 11:33 PM said:

      Hi, I will try to make I2C and SPI soon after I found something (chip, sensor) to use as an example.

      • Abi on 04/01/2017 at 11:43 PM said:

        Thankyou, I’d appreciate if you could explain in detail handler part which is so confusing like (hi2c3, htim2) etc. By the way you are doing a great job!!!

        • Le Tan Phuc on 18/01/2017 at 5:30 PM said:

          Hi, that is just the name for those peripherals. For example, htim2 is for TIM2, htim3 is TIM3, hadc1 is ADC1…

  12. chào anh. e cũng đang muốn dùng DMA để code cho mpu nhưng khie e tham khảo code anh AccelGyro khai báo kiểu dữ liệu j vậy. e cảm ơn.

Leave a Reply

Post Navigation