[【本文发布于https://blog.youkuaiyun.com/Stack_/article/details/129259560,未经许可禁止转载,转载须注明出处】]
一、问题描述
入门51没多久后就主攻32了,最近又玩一玩51,移植一个软定时器代码到STC8上,结果出现了奇怪的问题,而这种问题在各种32位单片机上都是不曾有的。
有如下代码,实现了软定时器。使用内部IRC,22.1184MHz
定义了一个32位变量,在定时器0的1ms中断里面自增,作为系统时基
static __IO uint32_t xdata SystemTimer = 0;
void timer0_int (void) interrupt TIMER0_VECTOR
{
SystemTimer++; //时基自增
}
复位定时器
void UserTimer_Reset(uint32_t xdata *Timer)
{
*Timer = SystemTimer;
}
计算出用户定时器与时基之间的时差
uint32_t UserTimer_Read(uint32_t xdata *Timer)
{
return (SystemTimer - *Timer);
}
然后在主循环里面定义一个变量与时基进行比较,大于等于1000ms则通过串口发送计数值
int main(void)
{
static __IO uint32_t xdata timer_test = 0;
static __IO uint8_t xdata run_step = 0;
__IO uint32_t xdata val;
while (1)
{
if ((val = UserTimer_Read(&timer_test)) >= TIMEOUT_1S) //1000ms定时已到
{
uart_send_bytes(UART2, (uint8_t *)(&timer_test), 4); 串口打印定时器的值
UserTimer_Reset(&timer_test); 复位定时器
uart_send_bytes(UART2, (uint8_t *)(&SystemTimer), 4); 串口打印时基的值
uart_send_bytes(UART2, (uint8_t *)(&val), 4); 串口打印定时器与时基之间的差值
}
}
}
得到如下结果,相减的结果和打印的结果不一致(正确的是0x000003E8即1000)
[ ] 内的为打印的具体时间,正常时应该为两两之间间隔一秒的。每秒打印12字节,包含3个变量,每个变量4字节,从左到右分别是软定时器timer_test的值、时基SystemTimer的值、差值(SystemTimer减timer_test)。正常情况下,差值恒定为0x000003E8即1000ms。
二、问题猜想
1、以上图一为例,当指定到xdata区时,timer_test、SystemTimer和val三者的关系为val = SystemTimer - 0x100 - timer_test,即0x800-0x100-0x7D0 = 0xFFFFFF30。
而差值很大的时候均发生在SystemTimer 的低8位变为0x00的时候。
猜测是中断中自增时,自增前为0x7FF,自增后低8位溢出变为0x00,而在退出中断后并运行到UserTimer_Read()函数进行减法时,次低8位还未获得进位,于是减法算式拿到的值是0x700,于是就发生了SystemTimer(0x700) - *Timer(0x7D0) = 0xFFFFFF30的惨案。
2、以上图二为例,当指定到data区时timer_test、SystemTimer和val三者的关系为SystemTimer = val + timer_test - 0xFF,即0x437+0x7C3C8-0xFF=0x7C700。
同样是均发生在SystemTimer 的低8位变为0x00的时候。
猜测是中断中自增时,自增前0x07C6FF,自增后低8位溢出,应该变为0x00但内存中还是0xFF,而次低8位已经获得进位变为了0xC7。于是退出中断后,减法算式拿到的值是0x07C7FF,于是有SystemTimer(0x07C7FF) - *Timer(0x07C3C8) = 0x0437。
后续补充:
看了一些关于8位单片机对于32位数据处理的文章,8位机不像32能一次处理32位数据,即一次只能处理8位。结合这个现象想了想,有可能是这样的:
问题1的猜想:变量指定到xdata区时,在执行0x7FF-0x7D0的时候,已经执行了次低8位bit[15:8]的相减什么的,正要处理低8位bit[7:0],来中断了,中断中0x7FF变成了0x800,中断退出之后恢复,处理低8位时变成了0x00-0xD0=0xFFFFFF30,即最终的数值相减不是0x7FF-0x7D0而是0x700-0x7D0。
预想 | 实际 |
---|---|
0x07FF - 0x07D0 = 0x2F | |
32位内核一次性处理 | 8位内核将0x07FF-0x07D0拆分处理,先处理07-07,后处理FF-D0。但在处理这2字节的间隙,发生中断,FF变为00 |
问题2的猜想:data区的处理顺序貌似是反过来的,在执行0x7C6FF-0x7C3C8的时候,已经执行了低8位bit[7:0](0xFF-0xC8),正准备处理次低8位bit[15:8],来中断了,0x7C6FF变成了0x7C700,退出中断后继续处理bit[15:8]时变成了0xC7-0xC3,。即最终的数值相减不是0x7C6FF-0x7C3C8而是0x7C7FF-0x7C3C8=0x437。
预想 | 实际 |
---|---|
0x7C6FF-0x7C3C8 = 0x337 | |
32位内核一次性处理 | 8位内核将0x7C6FF-0x7C3C8拆分处理,先处理0xFF-0xC8,后处理C6-C3。但在这两者间隙,发生中断,C6变为C7 |
应该是这个原因吧,要验证猜想需要看看汇编代码,原有的猜想以及下方的文字就不删了。
在STM8上也使用过这种定时方式,却没有发现类似的问题 。。。
经验教训:8位机上尽量避免一个超8位的全局变量在中断中使用
三、试图解决
这或许跟8位单片机一个指令只能处理32位数据其中的8位有关,因对汇编不熟悉,不了解编译器具体实现,只能瞎改一下。
1、修改自增语句,令其执行周期变长,即先加上1再覆盖SystemTimer 。 实测没用,可能被编译器优化掉了
void timer0_int (void) interrupt TIMER0_VECTOR
{
SystemTimer = SystemTimer + 1;
}
或者
void timer0_int (void) interrupt TIMER0_VECTOR
{
__IO uint32_t t = SystemTimer + 1;
SystemTimer = t;
}
2、再定义一个变量,进行如下操作。错误变少了,但还是有。有用但不多
static __IO uint32_t xdata SystemTimer_1 = 0;
void timer0_int (void) interrupt TIMER0_VECTOR
{
SystemTimer = SystemTimer_1;
SystemTimer_1++;
}
3、在2的基础上,将进中断的时间由1ms改为10ms,也不行
static __IO uint32_t xdata SystemTimer_1 = 0;
void timer0_int (void) interrupt TIMER0_VECTOR
{
SystemTimer = SystemTimer_1;
SystemTimer_1 += 10;
}
为什么在中断中自加,不是更新了RAM才出来的?确切原因有时间再深究,或者哪位大佬能给小弟指点一下。 。
这51单片机给我整出心理阴影了,还是避免我这个用法吧