cancel
Showing results for 
Search instead for 
Did you mean: 

i2c through interrupts fails from freertos generated by cubemx

Hvan .4
Associate II

Hi,

I'm using bno055 with stm32f402, and this IMU works in i2c, responding very slow to ackloledge/answer (few tenth of ms).

I tryed to use it in blocking mode in freertos, but very quickly, I check more than 1ms of data... it's too slow, and freertos crashes.

So I use i2c with interrupt and mutex. I manually failed, so I used stm32cubeMX and it still fails. Running with step by step debugger success much more, so I this this is a prioirity matter. I tryed with priorities from 0 to 15, (even if it should works from 5 to 15), it's quite the same.

When I say Freertos fails, a hardfault happens from prvPortStartFirstTask. I'm calling BNO055_I2C_bus_write from a Task (and the only other task blink a led and move a servomotor with large delays, it works pretty well.

Do you have some idea of how to solve this issue?

This is my i2c thread safe code:

s8 BNO055_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt) {
	s32 BNO055_iERROR = BNO055_INIT_VALUE;
	uint8_t tab[cnt + 1];
	tab[0] = reg_addr;
	memcpy(&tab[1], reg_data, cnt);
 
	BNO055_iERROR =HAL_I2C_Master_Transmit_IT(&hi2c1, dev_addr << 1, tab, cnt + 1);
	osMutexWait (&I2C_MUTEX,osWaitForever);
 
	return (s8) BNO055_iERROR ;
}
 
 
s8 BNO055_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt) {
	s32 BNO055_iERROR = BNO055_INIT_VALUE;
	uint8_t tab[1];
	tab[0] = reg_addr;
 
	BNO055_iERROR = HAL_I2C_Master_Transmit_IT(&hi2c1, dev_addr << 1, tab, 1);
	osMutexWait(&I2C_MUTEX, 1);
	BNO055_iERROR = HAL_I2C_Master_Receive_IT(&hi2c1, dev_addr << 1, reg_data,
			cnt);
	osMutexWait(&I2C_MUTEX, osWaitForever);
 
	return (s8) BNO055_iERROR;
}

This is my lock out code that only happens when using step by step debug

void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) {
	osMutexRelease(I2C_MUTEX);
}
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) {
	osMutexRelease(I2C_MUTEX);
}

This is the generated interruption handler:

/**
  * @brief This function handles I2C1 event interrupt.
  */
void I2C1_EV_IRQHandler(void)
{
  /* USER CODE BEGIN I2C1_EV_IRQn 0 */
 
  /* USER CODE END I2C1_EV_IRQn 0 */
  HAL_I2C_EV_IRQHandler(&hi2c1);
  /* USER CODE BEGIN I2C1_EV_IRQn 1 */
 
  /* USER CODE END I2C1_EV_IRQn 1 */
}
 
/**
  * @brief This function handles I2C1 error interrupt.
  */
void I2C1_ER_IRQHandler(void)
{
  /* USER CODE BEGIN I2C1_ER_IRQn 0 */
 
  /* USER CODE END I2C1_ER_IRQn 0 */
  HAL_I2C_ER_IRQHandler(&hi2c1);
  /* USER CODE BEGIN I2C1_ER_IRQn 1 */
 
  /* USER CODE END I2C1_ER_IRQn 1 */
}

The interruption config:

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hi2c->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspInit 0 */
 
  /* USER CODE END I2C1_MspInit 0 */
  
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2C1 GPIO Configuration    
    PB8     ------> I2C1_SCL
    PB9     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
    /* Peripheral clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE();
    /* I2C1 interrupt Init */
    HAL_NVIC_SetPriority(I2C1_EV_IRQn, 15, 0);
    HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
    HAL_NVIC_SetPriority(I2C1_ER_IRQn, 15, 0);
    HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);
  /* USER CODE BEGIN I2C1_MspInit 1 */
 
  /* USER CODE END I2C1_MspInit 1 */
  }
 
}

Called in hal_i2c_init from official file, in:

static void MX_I2C1_Init(void)
{
 
  /* USER CODE BEGIN I2C1_Init 0 */
 
  /* USER CODE END I2C1_Init 0 */
 
  /* USER CODE BEGIN I2C1_Init 1 */
 
  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
 
  /* USER CODE BEGIN I2C1_Init 2 */
 
  /* USER CODE END I2C1_Init 2 */
 
}

6 REPLIES 6
Piranha
Chief II

CMSIS-RTOS documentation clearly says that osMutexRelease() cannot be called from ISR. And the code itself is nonsense. In your code mutex doesn't block other threads from accessing a peripheral simultaneously. For waiting and signaling from ISR a semaphore must be used. For example:

  1. Lock the mutex.
  2. Start a non-blocking transmit/receive.
  3. Wait for a semaphore, which will be signaled from ISR.
  4. Release the mutex.

P.S. In CMSIS-RTOSv1 the function osMutexWait() name is misleading. ARM fixed it in CMSIS-RTOSv2 with renaming it to osMutexAcquire().

Pavel A.
Evangelist III

Use native FreeRTOS API instead of the CMSIS crutches. FreeRTOS can release semaphore from ISR (xSemaphoreGiveFromISR) and your design will work.

Even better, use notification instead of a semaphore.

By the way, CMSIS RTOS v2 supports non-recursive semaphores which can be released from ISR - but this is not implemented (developer was busy?)

So as a quick fix the OP can implement it themselves.

// FIX PROPOSAL - NOT TESTED!
 
osStatus_t osMutexRelease (osMutexId_t mutex_id) {
  SemaphoreHandle_t hMutex;
  osStatus_t stat;
  uint32_t rmtx;
 
  hMutex = (SemaphoreHandle_t)((uint32_t)mutex_id & ~1U);
 
  rmtx = (uint32_t)mutex_id & 1U;
 
  stat = osOK;
 
  if (hMutex == NULL) {
    return osErrorParameter;
  }
 
  if (rmtx != 0U) {
      if (IS_IRQ()) {
        return osErrorISR;
      }
      if (xSemaphoreGiveRecursive (hMutex) != pdPASS) {
        stat = osErrorResource;
      }
    }
    else {
      if (IS_IRQ()) {
        if (xSemaphoreGiveFromISR (hMutex, NULL) != pdPASS) {
          stat = osErrorResource;
        }
	else
          if (xSemaphoreGive (hMutex) != pdPASS) {
            stat = osErrorResource;
          }
    }
  }
 
  return (stat);
}

-- pa

Hvan .4
Associate II

(sent twice)

Hvan .4
Associate II

Thank you for all your answers.

@Piranha​ Thanks, And sorry for my stupid mistakes. For now, I'm running i2c from one thread, I'll deal with multi-thread later, but your comment is absolutely right.

@Pavel A.​ 

I'm very suprised that osMutexRelease can't work from ISR, even if I didn't read the manual, because I read the code, and it does (or get_IPSR(), that returns __ASM("ipsr") doesn't work well):

/**
* @brief Release a Mutex that was obtained by \ref osMutexWait
* @param mutex_id      mutex ID obtained by \ref osMutexCreate.
* @retval  status code that indicates the execution status of the function.
* @note   MUST REMAIN UNCHANGED: \b osMutexRelease shall be consistent in every CMSIS-RTOS.
*/
osStatus osMutexRelease (osMutexId mutex_id)
{
  osStatus result = osOK;
  portBASE_TYPE taskWoken = pdFALSE;
  
  if (inHandlerMode()) {
    if (xSemaphoreGiveFromISR(mutex_id, &taskWoken) != pdTRUE) {
      return osErrorOS;
    }
    portEND_SWITCHING_ISR(taskWoken);
  }
  else if (xSemaphoreGive(mutex_id) != pdTRUE) 
  {
    result = osErrorOS;
  }
  return result;
}

But my problem comes earlyier, I would love to enter to "HAL_I2C_MasterTxCpltCallback"/"HAL_I2C_MasterTxCpltCallback", and it does, only from step by step debug.

For each data sent through i2c, I2C1_EV_IRQHandler is called few times: once per byte (and maybe to release i2c bus)

From step by step debug, and logic analyser, we can see the different bytes sent.

When running directly, I just set the lines from transmission, and boom.

This is when I run with no breakpoint (it remains like that and there is really no signal even very fast)

0693W000007DLglQAG.pngwhen running step by step, it's very slooow, but it works better (it still finishes by a hardfault, maybe from mutex I'll deal with it later). Each 3 peaks are a byte sent from the same 3byte frame.

0693W000007DLggQAG.pngBy the way, In continuous mode with a breakpoint on I2C1_EV_IRQHandler, the code actually hardfault before reaching this interruption.

From my code:

s8 BNO055_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt) {
	s32 BNO055_iERROR = BNO055_INIT_VALUE;
	uint8_t tab[cnt + 1];
	tab[0] = reg_addr;
	memcpy(&tab[1], reg_data, cnt);
 
	HAL_I2C_Master_Transmit_IT(&hi2c1, dev_addr << 1, tab, cnt + 1); 
	osMutexWait (&I2C_MUTEX,osWaitForever); // I can breakpoint here
 
	return (s8) BNO055_iERROR;
}

If I comment HAL_I2C_Master_Transmit_IT, then I fall in the "mutexAcquiere".

I'm wondering if it comes from something else: heap/stack configuration, or so, task priority, etc...

you can find sources here: https://nextcloud.creable.fr/index.php/s/847AxfNaiSEtgJ5 , and reproduce on nucleo-f401re board, through atollic studio.

Hvan .4
Associate II

really no clues? it's a quite straight forward case...adding, safe i2c with interruption on freertos :\ There is no application to disturb...

I would appreciate a lot some help :)

Hvan .4
Associate II

I did it with normal freertos function and... it works fine :( I'm feeling stupid I did trust cmsis api... I should have tryed better, thanks for your help!