Search code examples
callbackstm32sensorsi2cdma

NUCLEO F401RE and multiple reading with HAL_I2C_Mem_Read_DMA


I'm doing a project involving STM32 and an accelerometer sensor (LIS2DE) I have to read every second the register from the sensor and send it via UART to arduino IDE but, idk why, the other two HAL_I2C_Mem_Read_DMA won't work

I tried to delete the first Mem_Read and only the second would work, so I checked the return value of the second call and it gives me HAL_BUSY. Using HAL_I2C_Master_Transmit_DMA and HAL_I2C_Master_Receive_DMA works correctly, but I wanted to experiment this way because professors suggested us to use this function to do automatically Transmit+Receive. Is better to use Transmit and Receive separately or this one in your opinion? And why?

Furthermore, is there a function to get all 3-axis value? I inspected the datasheet and I saw an auto increment feature, but I dind't find any example with that

I'll leave my code, obviously I set a timer of 1 second and I set up DMA for UART_TX, I2C_TX and I2C_RX, enabled interrupts from UART, TIM and I2C event

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim){
    HAL_I2C_Mem_Read_DMA(&hi2c1, ACCEL_ADD, OUT_X_ADD, I2C_MEMADD_SIZE_8BIT, (uint8_t*)&acc[0], 1);
    HAL_I2C_Mem_Read_DMA(&hi2c1, ACCEL_ADD, OUT_Y_ADD, I2C_MEMADD_SIZE_8BIT, (uint8_t*)&acc[1], 1);
    HAL_I2C_Mem_Read_DMA(&hi2c1, ACCEL_ADD, OUT_Z_ADD, I2C_MEMADD_SIZE_8BIT, (uint8_t*)&acc[2], 1);
}

void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef* hi2c){
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        if(count==2){    //because I'll receive a single interrupt for each Mem_Read, so after the third MemRxCpltCallback will transmit thanks UART values
        count = 0;

        len = snprintf(string, sizeof(string), "X: %+.3fg   Y: %+.3fg   Z: %+.3fg\n",
                acc[0] / 256.0 * 4.0,
                acc[1] / 256.0 * 4.0,
                acc[2] / 256.0 * 4.0);
        HAL_UART_Transmit_DMA(&huart2, (unsigned char*)string, len);
         }
         else
                count++;
}

Thanks for any help, if you need more information or there is something not good in the question, tell me and I'll update the question. I can post a link for the zip file of my code too if someone needs it

--- UPDATE ---

Now I just start the first one with the TIM IT, then other ones are triggered by previous one and last one triggers the print, just using a global variable to keep track of which call is executed. Do you think it's a quite decent solution? It works but seems very dirty to me, but it's mandatory to use I2C with DMA Btw thanks again for every contribution

    #if VERSION == 2
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim){
    HAL_I2C_Mem_Read_DMA(&hi2c1, ACCEL_ADD, OUT_X_ADD, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(acc+count), 1);
    count++;
}

void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef* hi2c){
    switch(count){
        case 1:
            HAL_I2C_Mem_Read_DMA(&hi2c1, ACCEL_ADD, OUT_Y_ADD, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(acc+count), 1);
            count++;
            break;
        case 2:
            HAL_I2C_Mem_Read_DMA(&hi2c1, ACCEL_ADD, OUT_Z_ADD, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(acc+count), 1);
            count++;
            break;
        case 3:
            count=0;

            len = snprintf(string, sizeof(string), "X: %+.3fg   Y: %+.3fg   Z: %+.3fg, (Tot:%+.3fg | %+.3fm/s²)\n",
                    acc[0] / 256.0 * 4.0,
                    acc[1] / 256.0 * 4.0,
                    acc[2] / 256.0 * 4.0,
                    sqrt(pow(acc[0],2)+pow(acc[1],2)+pow(acc[2],2))/64.0,
                    sqrt(pow(acc[0],2)+pow(acc[1],2)+pow(acc[2],2))/64.0 * 9.80665);
            HAL_UART_Transmit_DMA(&huart2, (unsigned char*)string, len);
    }
}
#endif

--- UPDATE2 --- Here there is my main, other code is generated automatically plus the two callbacks that @pmacfarlane suggested

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_I2C1_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */

  if (HAL_I2C_Master_Transmit_DMA(&hi2c1, ACCEL_ADD, (uint8_t*)CTRL_REG1, sizeof(CTRL_REG1)) == HAL_OK)
      len = snprintf(string, sizeof(string), "LIS2DE found!\n");
  else{
      ACCEL_ADD = LIS2DE12_ADD;
      if (HAL_I2C_Master_Transmit_DMA(&hi2c1, ACCEL_ADD, (uint8_t*)CTRL_REG1, sizeof(CTRL_REG1)) == HAL_OK)
          len = snprintf(string, sizeof(string), "LIS2DE12 found!\n");
      else
          len = snprintf(string, sizeof(string), "Accelerator error!\n");
  }
  HAL_UART_Transmit_DMA(&huart2, (unsigned char*)string, len);

  HAL_I2C_Master_Transmit_DMA(&hi2c1, ACCEL_ADD, (uint8_t*)CTRL_REG2, sizeof(CTRL_REG2));
  HAL_I2C_Master_Transmit_DMA(&hi2c1, ACCEL_ADD, (uint8_t*)CTRL_REG4, sizeof(CTRL_REG4));

  TIM2->SR &= ~TIM_SR_UIF;          //or __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE); or HAL_Delay(500);
  HAL_TIM_Base_Start_IT(&htim2);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
#if VERSION == 3
      while (!timer_elapsed){/*do nothing*/}

      for (int i=0; i<3; i++){
          dma_complete = false;
          HAL_I2C_Mem_Read_DMA(&hi2c1, ACCEL_ADD, regs[i], I2C_MEMADD_SIZE_8BIT, (uint8_t*)(acc+i), 1);

          while (!dma_complete){/*do nothing*/}
      }

      len = snprintf(string, sizeof(string), "X: %+.3fg   Y: %+.3fg   Z: %+.3fg (Tot:%+.3fg | %+.3fm/s²)\n",
              acc[0] / 256.0 * 4.0,
              acc[1] / 256.0 * 4.0,
              acc[2] / 256.0 * 4.0,
              sqrt(pow(acc[0],2)+pow(acc[1],2)+pow(acc[2],2))/64.0,
              sqrt(pow(acc[0],2)+pow(acc[1],2)+pow(acc[2],2))/64.0 * 9.80665);
      HAL_UART_Transmit_DMA(&huart2, (unsigned char*)string, len);

      timer_elapsed = false;
#endif
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Solution

  • A few points:

    • DMA is normally used if you want some transaction to take place while the CPU continues to run and do other processing. If your code starts a DMA transaction then basically just waits until it is complete, you're not really gaining much benefit.
    • Interrupt Service Routines (ISRs) such as your two callbacks should ideally do the bare minimum required, and should do it as quickly as possible. If an Operating System is used, they might give a semaphore, or add something to a message queue. With no OS, typically they might just set a flag. All "heavy" processing would take place in your main (non-ISR) code.
    • When running in an ISR, lower priority interrupts are blocked. So if your system tick interrupt is lower priority than your timer elapsed callback interrupt, functions like HAL_Delay() will not work in the timer interrupt, because the sysTick is not incrementing.

    With those points in mind, you could have a design something like this. (Untested, incomplete, but hopefully shows the intent.)

    #include <stdbool.h>
    
    static volatile bool timer_elapsed = false;
    static volatile bool dma_complete = false;
    
    void main(void)
    {
        // Configure all peripherals etc.
        
        
        for (;;)
        {
            // Wait for timer to elapse
            while (!timer_elapsed)
            {
                // You could be doing other things while you wait...
            }
            
            // Read the three registers
            for (int i = 0; i < 3; ++i)
            {
                static const uint8_t regs[] = {OUT_X_ADD, OUT_Y_ADD, OUT_Z_ADD};
                
                dma_complete = false;
                do_i2c_dma(regs[i]); // Call the HAL DMA function
    
                // Wait for DMA to complete
                while (!dma_complete)
                {
                    // You could be doing things here too...
                }
            }
            
            print_results(); // To be implemented
            
            timer_elapsed = false;
        }
    }
    
    void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef* hi2c)
    {
        dma_complete = true;
    }
    
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
    {
        timer_elapsed = true;
    }
    

    The flags are volatile because they are modified in the ISRs, otherwise the main code might assume they never change.