场景功能

当你在一个FreeRTOS项目中需要用到多个模块并且其通讯方式为I2C时,I2C通信可能会乱套。

案例

你在使用OLED模块显示的同时,又需要MPU6050模块测量陀螺仪数据,其通讯方式均为I2C,若一方在通讯过程中(本身I2C相对就比较耗时又要发起始位又要发从机地址才开始传数据最后还要发停止位),若此时任务突然被切换,I2C传递的数据有可能就会乱套。

解决办法

  1. 在freertos.c中定义一个全局的static SemaphoreHandle_t g_xI2CMutex;I2C互斥量
  2. 所有模块在使用I2C操作前先使用xSemaphoreTake(g_xI2CMutex, portMAX_DELAY);
    2.1 要么拿到互斥量,开始占用I2C资源进行操作
    2.2 要么拿不到互斥量,等待 portMAX_DELAY 阻塞
  3. 使用完I2C操作后归还I2C互斥量 xSemaphoreGive(g_xI2CMutex);

易错的解决办法

  • 单靠所有I2C共同维护一个全局变量并不可靠
volatile int bInUsed = 0; // 其他I2C操作共同维护这一个bInUsed变量
void draw_flushArea(byte x, byte y, byte w, byte h)
{
    while (bInUsed); // I2C被使用就一直等待
    bInUsed++; // 等待结束就将其设为1,貌似其他任务要用的话就没办法用会被卡在while (bInUsed)
    LCD_FlushRegion(x, y, w, h); // 这里是I2C操作
    bInUsed--; // 用完再设为0
}

实际bInUsed++并不是单纯的一步,还要再分为三步

  1. CPU从内存中将 bInUsed 存进CPU寄存器 R0
  2. R0++
  3. CPU将R0写回内存

如果此时任务A正好在第一步操作完,一个其他中断插进来,去执行某个任务B需要操作I2C,同样使用这套逻辑的话,去读内存中的 bInUsed 就会发现内存里面的那个 bInUsed 也是0,因为任务A还没来得及将R0++后的值重新写回内存,此时就使得任务A B都可以去用I2C。

  • 弥补的办法
volatile int bInUsed = 0; // 其他I2C操作共同维护这一个bInUsed变量
void draw_flushArea(byte x, byte y, byte w, byte h)
{
    while (bInUsed); 
    vTaskSuspendAll(); // 关调度 保护临界资源
    bInUsed++;
    LCD_FlushRegion(x, y, w, h); // 这里是I2C操作
    bInUsed--; 
    xTaskResumeAll();  // 恢复调度
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐