8位单片机(51 STC8)C语言处理32位unsigned long型数据之计算出错

33 篇文章 11 订阅

[【本文发布于https://blog.csdn.net/Stack_/article/details/129259560,未经许可禁止转载,转载须注明出处】]


一、问题描述


入门51没多久后就主攻32了,最近又玩一玩51,移植一个软定时器代码到STC8上,结果出现了奇怪的问题,而这种问题在各种32位单片机上都是不曾有的。


有如下代码,实现了软定时器。使用内部IRC,22.1184MHz


定义了一个32位变量,在定时器01ms中断里面自增,作为系统时基

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。



在这里插入图片描述

变量指定到xdata区时,时不时会出现差值非常大但是实际只过了几百ms的情况

在这里插入图片描述

变量指定到data区时,时不时会出现差值大于1000ms但是实际只过了几百ms的情况

二、问题猜想


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 = 0x2F0x07FF 0x0700 - 0x07D0 = 0xFFFFFF30
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 = 0x3370x7C6FF 0x7C7FF-0x7C3C8 = 0x437
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单片机给我整出心理阴影了,还是避免我这个用法吧



51计算器程序#include #include #define uchar unsigned char #define uint unsigned int sbit lcden=P3^4; sbit lcdrw=P3^6; sbit lcdrs=P3^5; uchar num,temp,jia=0,cheng=0,chu=0,jian=0,qing=0; uint key,key1,shu; uchar fuhao,flag1,flag=0; uchar table[]={ 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,}; void delay(uint z) { uint x,y; for(x=z;x>0;x--) for(y=114;y>0;y--); } bit lcd_bz() { bit result; lcdrs = 0; lcdrw= 1; lcden = 1; _nop_(); _nop_(); _nop_(); _nop_(); result=(bit)(P0&0x80;); lcden=0; return result; } void write_com(uchar com) { while(lcd_bz()); lcdrs=0; lcden=0; lcdrw=0; P0=com; delay(5); lcden=1; delay(5); lcden=0; } void write_date(uchar date) { while(lcd_bz()); lcdrs=1; lcden=0; lcdrw=0; P0=date; delay(5); lcden=1; delay(5); lcden=0; } void lcd_init() { lcden=0; write_com(0x38); write_com(0x0c); write_com(0x06); write_com(0x01); } void keyscan() { P3=0xfe; temp=P3; temp=temp&0xf0; while(temp!=0xf0) { delay(5); temp=P3; temp=temp&0xf0; while(temp!=0xf0) { temp=P3; switch(temp) { case 0xee:key=1;num=0;break; case 0xde:key=2;num=0;break; case 0xbe:key=3;num=0;break; case 0x7e:num=1;break; //¼ÓºÅ } while(temp!=0xf0) { temp=P3; temp=temp&0xf0; } } } P3=0xfd; temp=P3; temp=temp&0xf0; while(temp!=0xf0) { delay(5); temp=P3; temp=temp&0xf0; while(temp!=0xf0) { temp=P3; switch(temp) { case 0xed:key=4;num=0;break; case 0xdd:key=5;num=0;break; case 0xbd:key=6;num=0;break; case 0x7d:num=2;break; //¼õºÅ } while( temp!=0xf0) { temp=P3; temp=temp&0xf0; } } } P3=0xfb; temp=P3; temp=temp&0xf0; while(temp!=0xf0) { delay(5); temp=P3; temp=temp&0xf0; while(temp!=0xf0) { temp=P3; switch(temp) { case 0xeb:key=7;num=0;break; case 0xdb:key=8;num=0;break; case 0xbb:key=9;num=0;break
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值