最近写了个用环形缓冲区发送数据的STM32串口程序:使用头指针(front)指向下一个要发送数据,使用尾指针(rear)指向新数据存储的地方。中断里面会判断front和rear是否相等,如果相等则表示缓冲区为空,发送已经完成,关闭中断;反过来说,front和rear相等表示缓冲区还有数据要发送,那么就在中断里面把数据一个一个地发送出去。
那么问题来了,我在存数据的时候写了这么一行代码:
USART1_SendQueue[USART1_SendRear ++] = data;
也就是说,把数据存入缓冲区后,尾指针自+1。这看起来没毛病,但编译器给出汇编代码是这样的:
0x0800213C LDR r0,[pc,#492] ;取出USART1_SendRear的地址存到r0
……
0x08002148 LDR r1,[r0,#0x00] ;将USART1_SendRear的值存入r1
0x0800214A LDR r0,[r0,#0x00] ;将USART1_SendRear的值存入r0
0x0800214C ADDS r0,r0,#1 ;将r0自加1
0x0800214E LDR r2,[pc,#476] ;取出USART1_SendRear的地址存到r2
0x08002150 STR r0,[r2,#0x00] ;将r0的值存入USART1_SendRear的地址
0x08002152 LDR r0,[pc,#480] ;取出USART1_SendQueue的首地址存到r0
0x08002154 STRH r5,[r0,r1,LSL #1] ;将data(r5)存入r0+(USART1_SendRear<<1)的地址中(左移1位相当于*2,是因为数组一个元素有两个字节)
在C的代码的逻辑中
USART1_SendQueue[USART1_SendRear ++] = data;
应该等效于:
USART1_SendQueue[USART1_SendRear] = data;
USART1_SendRear ++;
然而汇编的代码的逻辑转换成C等效于:
temp = USART1_SendRear;
USART1_SendRear ++;
USART1_SendQueue[temp] = data;
那么问题来了,如果按照原来的C逻辑,因为数据存入后指针才会自+1,所以即使发送过程中触发中断,也只有数据存入缓冲区以后才会判断出缓冲区非空。而在汇编的逻辑中, 指针+1后才把数据存入缓冲区。如果指针+1之后突然来了个中断,那么有可能会把数据即将写入的那个地址的数据先发出去,然后再写入,发送就出错了!
形象一点就是说:
temp = USART1_SendRear;
USART1_SendRear ++;
//这个位置突然来了一个中断
//中断发送了当前USART1_SendQueue[temp] 的数据
//中断返回后再执行下面那句
USART1_SendQueue[temp] = data;
好吧,如果不考虑中断的话,汇编代码这么做不会带来错的结果,可惜中断来得就是这么巧。
这个问题找了好多天,今天终于找到了。编译器这么编译,也是很无奈。所以代码改成如下就好了:
//把这两步拆开写,免得编译器整出幺蛾子
USART1_SendQueue[USART1_SendRear] = data;
USART1_SendRear ++;
T=T
上一篇:STM32F4各外设时钟配置总结
下一篇:深度讨论32复位及SystemInit函数在程序中的作用
推荐阅读最新更新时间:2024-03-16 16:20