单片机,主循环和中断资源访问冲突的案例
单片机主循环与中断资源访问冲突案例分析
在嵌入式系统中,主循环(Main Loop)和中断服务程序(ISR)共享资源时,如果处理不当,会引发竞态条件(Race Condition)或数据不一致问题。下面通过几个典型案例进行说明:
案例1:共享变量访问冲突
场景描述:
- 主循环:周期性读取传感器数据并计算平均值
- 定时器中断:每10ms更新一次传感器原始数据
冲突代码示例:
uint16_t sensorData; // 共享资源// 主循环
void main(void) {uint32_t sum = 0;uint8_t count = 0;while(1) {sum += sensorData; // ① 读取共享变量count++;if(count >= 100) {printf("Average: %lu\n", sum / count);sum = 0;count = 0;}}
}// 定时器中断服务程序
void TIMER_ISR(void) {sensorData = ReadSensor(); // ② 更新共享变量
}
冲突原因:
- 当主循环执行①行读取
sensorData
时,可能被②行的中断打断 - 若中断更新了
sensorData
,主循环可能读取到部分更新的数据 - 导致计算的平均值不准确
解决方案:
uint16_t sensorData;
bool dataUpdated = false;// 主循环
void main(void) {uint32_t sum = 0;uint8_t count = 0;while(1) {uint16_t localData;__disable_irq(); // 关中断if(dataUpdated) {localData = sensorData;dataUpdated = false;}__enable_irq(); // 开中断if(!dataUpdated) {sum += localData;count++;// ...}}
}// 定时器中断服务程序
void TIMER_ISR(void) {sensorData = ReadSensor();dataUpdated = true;
}
案例2:缓冲区访问冲突
场景描述:
- 主循环:处理串口接收缓冲区数据
- 串口中断:将接收到的字节存入缓冲区
冲突代码示例:
#define BUFFER_SIZE 16
uint8_t rxBuffer[BUFFER_SIZE];
uint8_t bufferHead = 0;
uint8_t bufferTail = 0;// 主循环
void main(void) {while(1) {if(bufferHead != bufferTail) { // ① 检查缓冲区是否有数据uint8_t data = rxBuffer[bufferTail]; // ② 读取数据bufferTail = (bufferTail + 1) % BUFFER_SIZE; // ③ 更新尾指针ProcessData(data);}}
}// 串口接收中断
void UART_RX_ISR(void) {uint8_t data = UART_Read();rxBuffer[bufferHead] = data; // ④ 写入数据bufferHead = (bufferHead + 1) % BUFFER_SIZE; // ⑤ 更新头指针
}
冲突原因:
- 当主循环执行①-③行操作时,可能被④-⑤行的中断打断
- 若中断更新了
bufferHead
,主循环可能误判缓冲区状态 - 导致数据丢失或缓冲区溢出
解决方案:
#define BUFFER_SIZE 16
uint8_t rxBuffer[BUFFER_SIZE];
uint8_t bufferHead = 0;
uint8_t bufferTail = 0;// 主循环
void main(void) {while(1) {uint8_t localHead;__disable_irq();localHead = bufferHead;__enable_irq();if(localHead != bufferTail) {uint8_t data = rxBuffer[bufferTail];__disable_irq();bufferTail = (bufferTail + 1) % BUFFER_SIZE;__enable_irq();ProcessData(data);}}
}// 串口接收中断
void UART_RX_ISR(void) {uint8_t data = UART_Read();uint8_t nextHead = (bufferHead + 1) % BUFFER_SIZE;if(nextHead != bufferTail) { // 检查缓冲区是否已满rxBuffer[bufferHead] = data;bufferHead = nextHead;} else {HandleBufferOverflow();}
}
案例3:外设操作冲突
场景描述:
- 主循环:配置SPI接口并发送数据到Flash
- 定时器中断:周期性采集ADC数据并通过SPI发送到外部设备
冲突代码示例:
// 主循环
void main(void) {while(1) {SPI_Configure(SPI_MODE_FLASH); // ① 配置SPI为Flash模式SPI_Write(flashData, FLASH_SIZE); // ② 发送数据到Flash// ...}
}// 定时器中断
void TIMER_ISR(void) {uint16_t adcData = ADC_Read();SPI_Configure(SPI_MODE_SENSOR); // ③ 配置SPI为传感器模式SPI_Write(&adcData, 2); // ④ 发送ADC数据
}
冲突原因:
- 主循环执行①-②行时,可能被③-④行的中断打断
- 中断修改了SPI配置,导致主循环发送的数据格式错误
- 造成Flash写入失败或数据传输错误
解决方案:
bool spiBusy = false;// 主循环
void main(void) {while(1) {if(!spiBusy) {spiBusy = true;SPI_Configure(SPI_MODE_FLASH);SPI_Write(flashData, FLASH_SIZE);spiBusy = false;}}
}// 定时器中断
void TIMER_ISR(void) {if(!spiBusy) {uint16_t adcData = ADC_Read();spiBusy = true;SPI_Configure(SPI_MODE_SENSOR);SPI_Write(&adcData, 2);spiBusy = false;}
}
冲突预防原则
- 最小化临界区:只在访问共享资源的短时间内关中断
- 使用原子操作:对标志位等简单变量使用原子操作
- 资源状态管理:使用标志位标记资源是否正在使用
- 中断优先级控制:关键任务使用更高优先级中断
- 数据复制:中断中只进行数据采集,处理放到主循环
通过合理的资源管理和同步机制,可以有效避免主循环与中断之间的冲突。