/* main.c - Multi-Sensor I2C Coordinator with Mutex Protection
* Target: STM32F103C8T6 (Blue Pill) with FreeRTOS
* Sensors: BME280 on I2C1 (address 0x76)
#include "stm32f1xx_hal.h"
* 0 = Normal operation (mutex with priority inheritance)
* 1 = Priority inversion demo (binary semaphore, no inheritance)
* 2 = Deadlock demo (two mutexes, wrong order)
/* BME280 register addresses */
#define BME280_REG_TEMP 0xFA
#define BME280_REG_HUM 0xFD
#define BME280_REG_PRESS 0xF7
#define BME280_REG_CTRL_HUM 0xF2
#define BME280_REG_CTRL_MEAS 0xF4
static I2C_HandleTypeDef hi2c1;
/* Synchronization primitives */
static SemaphoreHandle_t xI2CMutex;
static SemaphoreHandle_t xI2CBinarySem; /* No priority inheritance */
static SemaphoreHandle_t xDisplayMutex; /* Second mutex for deadlock demo */
/* Shared sensor data (protected by mutex in normal mode) */
static volatile float fTemperature = 0.0f;
static volatile float fHumidity = 0.0f;
static volatile float fPressure = 0.0f;
static TaskHandle_t xTempTaskHandle;
static TaskHandle_t xHumidityTaskHandle;
static TaskHandle_t xPressureTaskHandle;
/* Forward declarations */
static void SystemClock_Config(void);
static void I2C1_Init(void);
static void USART1_Init(void);
static void BME280_Init(void);
static int32_t BME280_ReadRaw(uint8_t reg, uint8_t *buf, uint8_t len);
/* Retarget printf to USART1 */
extern UART_HandleTypeDef huart1;
int _write(int file, char *ptr, int len)
HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);
/*-----------------------------------------------------------
* Helper: read raw bytes from BME280 over I2C
*-----------------------------------------------------------*/
static int32_t BME280_ReadRaw(uint8_t reg, uint8_t *buf, uint8_t len)
HAL_StatusTypeDef status;
status = HAL_I2C_Mem_Read(&hi2c1, BME280_ADDR << 1, reg,
I2C_MEMADD_SIZE_8BIT, buf, len, 100);
return (status == HAL_OK) ? 0 : -1;
/*-----------------------------------------------------------
* Temperature Task (Priority 3, highest)
* Reads BME280 temperature every 500 ms
*-----------------------------------------------------------*/
static void vTempTask(void *pvParameters)
TickType_t xLastWake = xTaskGetTickCount();
printf("[TEMP ] Waiting for I2C mutex (tick %lu)\r\n",
(unsigned long)xTaskGetTickCount());
/* Deadlock demo: acquire I2C first, then Display */
if (xSemaphoreTake(xI2CMutex, pdMS_TO_TICKS(5000)) == pdTRUE)
printf("[TEMP ] Acquired I2C mutex, now taking Display mutex...\r\n");
vTaskDelay(pdMS_TO_TICKS(10)); /* Small delay to ensure overlap */
if (xSemaphoreTake(xDisplayMutex, pdMS_TO_TICKS(5000)) == pdTRUE)
BME280_ReadRaw(BME280_REG_TEMP, buf, 3);
int32_t raw = ((int32_t)buf[0] << 12) |
fTemperature = (float)raw / 5120.0f;
printf("[TEMP ] %.1f C\r\n", fTemperature);
xSemaphoreGive(xDisplayMutex);
printf("[TEMP ] TIMEOUT waiting for Display mutex! Possible deadlock.\r\n");
xSemaphoreGive(xI2CMutex);
/* Normal mode and priority inversion demo */
if (xSemaphoreTake(xLock, portMAX_DELAY) == pdTRUE)
printf("[TEMP ] Acquired I2C mutex (tick %lu)\r\n",
(unsigned long)xTaskGetTickCount());
BME280_ReadRaw(BME280_REG_TEMP, buf, 3);
int32_t raw = ((int32_t)buf[0] << 12) |
fTemperature = (float)raw / 5120.0f;
printf("[TEMP ] %.1f C, releasing mutex (tick %lu)\r\n",
fTemperature, (unsigned long)xTaskGetTickCount());
vTaskDelayUntil(&xLastWake, pdMS_TO_TICKS(500));
/*-----------------------------------------------------------
* Humidity Task (Priority 2, medium)
* Reads BME280 humidity every 1000 ms
*-----------------------------------------------------------*/
static void vHumidityTask(void *pvParameters)
TickType_t xLastWake = xTaskGetTickCount();
printf("[HUMID ] Waiting for I2C mutex (tick %lu)\r\n",
(unsigned long)xTaskGetTickCount());
/* Deadlock demo: acquire Display first, then I2C (opposite order) */
if (xSemaphoreTake(xDisplayMutex, pdMS_TO_TICKS(5000)) == pdTRUE)
printf("[HUMID ] Acquired Display mutex, now taking I2C mutex...\r\n");
vTaskDelay(pdMS_TO_TICKS(10));
if (xSemaphoreTake(xI2CMutex, pdMS_TO_TICKS(5000)) == pdTRUE)
BME280_ReadRaw(BME280_REG_HUM, buf, 2);
int32_t raw = ((int32_t)buf[0] << 8) | (int32_t)buf[1];
fHumidity = (float)raw / 1024.0f;
printf("[HUMID ] %.1f %%\r\n", fHumidity);
xSemaphoreGive(xI2CMutex);
printf("[HUMID ] TIMEOUT waiting for I2C mutex! Possible deadlock.\r\n");
xSemaphoreGive(xDisplayMutex);
if (xSemaphoreTake(xLock, portMAX_DELAY) == pdTRUE)
printf("[HUMID ] Acquired I2C mutex (tick %lu)\r\n",
(unsigned long)xTaskGetTickCount());
BME280_ReadRaw(BME280_REG_HUM, buf, 2);
int32_t raw = ((int32_t)buf[0] << 8) | (int32_t)buf[1];
fHumidity = (float)raw / 1024.0f;
/* Simulate heavy computation to trigger priority inversion.
* While this medium-priority task runs, the low-priority task
* holding the semaphore cannot finish, and the high-priority
printf("[HUMID ] Starting heavy computation...\r\n");
volatile uint32_t count = 0;
for (uint32_t i = 0; i < 2000000; i++)
printf("[HUMID ] Computation done (tick %lu)\r\n",
(unsigned long)xTaskGetTickCount());
printf("[HUMID ] %.1f %%, releasing mutex (tick %lu)\r\n",
fHumidity, (unsigned long)xTaskGetTickCount());
vTaskDelayUntil(&xLastWake, pdMS_TO_TICKS(1000));
/*-----------------------------------------------------------
* Pressure Task (Priority 1, lowest)
* Reads BME280 pressure every 2000 ms
*-----------------------------------------------------------*/
static void vPressureTask(void *pvParameters)
TickType_t xLastWake = xTaskGetTickCount();
printf("[PRESS ] Waiting for I2C mutex (tick %lu)\r\n",
(unsigned long)xTaskGetTickCount());
if (xSemaphoreTake(xLock, portMAX_DELAY) == pdTRUE)
printf("[PRESS ] Acquired I2C mutex (tick %lu)\r\n",
(unsigned long)xTaskGetTickCount());
BME280_ReadRaw(BME280_REG_PRESS, buf, 3);
int32_t raw = ((int32_t)buf[0] << 12) |
fPressure = (float)raw / 25600.0f;
/* Hold the lock for a long time to create the
* inversion window. With a real mutex, priority
* inheritance would boost this task. With a binary
* semaphore, the medium-priority task can preempt
* and block the high-priority task indirectly.
printf("[PRESS ] Holding lock during slow read...\r\n");
HAL_Delay(50); /* Simulate slow I2C transaction */
printf("[PRESS ] %.1f hPa, releasing mutex (tick %lu)\r\n",
fPressure, (unsigned long)xTaskGetTickCount());
vTaskDelayUntil(&xLastWake, pdMS_TO_TICKS(2000));
/*-----------------------------------------------------------
*-----------------------------------------------------------*/
static void BME280_Init(void)
/* Set humidity oversampling x1 */
HAL_I2C_Mem_Write(&hi2c1, BME280_ADDR << 1, BME280_REG_CTRL_HUM,
I2C_MEMADD_SIZE_8BIT, &val, 1, 100);
/* Set temp and pressure oversampling x1, normal mode */
val = 0x27; /* osrs_t=001, osrs_p=001, mode=11 */
HAL_I2C_Mem_Write(&hi2c1, BME280_ADDR << 1, BME280_REG_CTRL_MEAS,
I2C_MEMADD_SIZE_8BIT, &val, 1, 100);
/*-----------------------------------------------------------
* Peripheral initialization (abbreviated, see project files
* for full SystemClock_Config and I2C1_Init)
*-----------------------------------------------------------*/
UART_HandleTypeDef huart1;
static void USART1_Init(void)
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef gpio = {0};
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &gpio);
gpio.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(GPIOA, &gpio);
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
static void I2C1_Init(void)
__HAL_RCC_I2C1_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef gpio = {0};
gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7;
gpio.Mode = GPIO_MODE_AF_OD;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &gpio);
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.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
static void SystemClock_Config(void)
RCC_OscInitTypeDef osc = {0};
osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
osc.HSEState = RCC_HSE_ON;
osc.PLL.PLLState = RCC_PLL_ON;
osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
osc.PLL.PLLMUL = RCC_PLL_MUL9;
RCC_ClkInitTypeDef clk = {0};
clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
clk.AHBCLKDivider = RCC_SYSCLK_DIV1;
clk.APB1CLKDivider = RCC_HCLK_DIV2;
clk.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_2);
/*-----------------------------------------------------------
*-----------------------------------------------------------*/
printf("\r\n=== Multi-Sensor I2C Coordinator ===\r\n");
printf("Mode: Normal (mutex with priority inheritance)\r\n\r\n");
xI2CMutex = xSemaphoreCreateMutex();
configASSERT(xI2CMutex != NULL);
printf("Mode: Priority Inversion Demo (binary semaphore, no inheritance)\r\n\r\n");
xI2CBinarySem = xSemaphoreCreateBinary();
xSemaphoreGive(xI2CBinarySem); /* Start available */
xI2CMutex = xSemaphoreCreateMutex(); /* Unused but keeps code simple */
configASSERT(xI2CBinarySem != NULL);
printf("Mode: Deadlock Demo (two mutexes, opposite acquisition order)\r\n\r\n");
xI2CMutex = xSemaphoreCreateMutex();
xDisplayMutex = xSemaphoreCreateMutex();
configASSERT(xI2CMutex != NULL);
configASSERT(xDisplayMutex != NULL);
xTaskCreate(vTempTask, "Temp", 256, NULL, 3, &xTempTaskHandle);
xTaskCreate(vHumidityTask, "Humidity", 256, NULL, 2, &xHumidityTaskHandle);
xTaskCreate(vPressureTask, "Pressure", 256, NULL, 1, &xPressureTaskHandle);
printf("Starting scheduler...\r\n\r\n");
/* Should never reach here */
Comments