单片机主循环与中断资源访问冲突案例分析
在嵌入式系统中,主循环(Main Loop)和中断服务程序(ISR)共享资源时,如果处理不当,会引发竞态条件(Race Condition)或数据不一致问题。下面通过几个典型案例进行说明:
案例1:共享变量访问冲突
场景描述:
- 主循环:周期性读取传感器数据并计算平均值
- 定时器中断:每10ms更新一次传感器原始数据
冲突代码示例:
c
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
,主循环可能读取到部分更新的数据 - 导致计算的平均值不准确
解决方案:
c
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:缓冲区访问冲突
场景描述:
- 主循环:处理串口接收缓冲区数据
- 串口中断:将接收到的字节存入缓冲区
冲突代码示例:
c
#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
,主循环可能误判缓冲区状态 - 导致数据丢失或缓冲区溢出
解决方案:
c
#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发送到外部设备
冲突代码示例:
c
// 主循环
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写入失败或数据传输错误
解决方案:
c
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;
}
}
冲突预防原则
- 最小化临界区:只在访问共享资源的短时间内关中断
- 使用原子操作:对标志位等简单变量使用原子操作
- 资源状态管理:使用标志位标记资源是否正在使用
- 中断优先级控制:关键任务使用更高优先级中断
- 数据复制:中断中只进行数据采集,处理放到主循环
通过合理的资源管理和同步机制,可以有效避免主循环与中断之间的冲突。