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:

    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.