历史上的今天
返回首页

历史上的今天

今天是:2024年09月20日(星期五)

正在发生

2019年09月20日 | 关于单片机上for循环中运用ACC的隐蔽错误

2019-09-20 来源:eefocus

最近写了几个程序,一个是用51单片机读取模数传感器adc0832的电压值,一个是读取ds1302的时间值,结果都出现了读数一直为0的情况。我调试了近一个星期,修改了一个我认为不可能会错的句子,程序运行成功了,这才发现了一个极其隐蔽的错误。(我用的是xp系统,用keil4软件编译)


先上代码:第一个为错误代码,第二个为正确代码。这是用来向ds1302芯片写入命令或数据的函数。实现把8位的数据dat一位一位地写入ds1302的io口。其中ACC0为ACC的第0位。

//for(i=0;i<8;i++),ACC版


void ds1302_input(uchar dat)

{

   uchar i;      //uchar= unsigned char

  ACC = dat;   //ACC为51的累加器

   for(i=0;i<8;i++)

   {

    DS1302_SCLK = 0;    //ds1302的时钟引脚

DS1302_IO = ACC0;    //ds1302的io口

DS1302_SCLK = 1;     //上升沿写入数据

ACC >>= 1;       //第一个不移动

   }

}


//for(i=8;i>0;i--),ACC版


void ds1302_input(uchar dat)

{

   uchar i;

  ACC = dat;

   for(i=8;i>0;i--)

   {

    DS1302_SCLK = 0;

DS1302_IO = ACC0;

DS1302_SCLK = 1; //上升沿写入数据

ACC >>= 1; // 第一个不移动

   }

}


认真对比这两个代码,可能会觉得没区别,而且这两个代码都可以通过编译(加上reg52.h和一些宏定义)。我也是一直认为for()这里边没有错误,结果。。。试着修改时钟信号,增加延时之类的,调了好久还是错,严重打击我的自信心。这两个代码的区别就只有for(i=0;i<8;i++)和for(i=8;i>0;i--)了。学过c语言的人都知道,这两个句子都是实现一个8次的循环,功能一模一样。怎么会因为这个句子的区别就导致单片机控制的错误呢?神奇!


接着我试着把错误程序中的ACC改为51芯片的寄存器B,烧录进单片机,程序运行成功,跟“for(i=8;i>0;i--),ACC版”一样,lcd在很嚣张地显示着正确的时间( for(i=0;i<8;i++),ACC版lcd的时间显示为0)。附:

//for(i=0;i<8;i++),寄存器B版

void ds1302_input(uchar dat)

{

   uchar i;

  B = dat;

   for(i=0;i<8;i++)

   {

    DS1302_SCLK = 0;

DS1302_IO = B0;

DS1302_SCLK = 1; //上升沿写入数据

B >>= 1;  //第一个不移动

   }

}


这样就知道原因了,使用for(i=0;i<8;i++)的运算中可能有累加器ACC参与了,导致修改了ACC的值,使写入的命令出现错误。但为什么for(i=8;i>0;i--)就没有ACC的参与呢?一个大大的问号。基于我调试了一个星期的程序,皆因为这一个神奇的错误,我实在不甘心,决定研究到底。于是,分别查看了这三个程序代码用 keil4 编译后得到的 汇编代码。(学过汇编就是爽啊,哈)

 

;for(i=0;i>8;i++),ACC版     汇编中“;”后的这行句子是注释

RSEG  ?PR?_ds1302_input?DS1302

_ds1302_input:

USING 0

;---- Variable 'dat?141' assigned to Register 'R7' ----

MOV  A,R7   ;实参值dat的数据存储到r7寄存器,把r7的值赋给累加器ACC

CLR  A      ;把ACC清零(就是因为这句子导致程序有问题)

MOV  R7,A   ;把ACC的值赋给R7

?C0005:

CLR  DS1302_SCLK  ;时钟线sclk清零

MOV  C,ACC0       ;把ACC第0位赋值到进位寄存器C中

MOV  DS1302_IO,C  ;把c赋值给IO口,改变单片机的引脚值

SETB DS1302_SCLK  ;时钟线sclk置1

CLR  C            ;进位寄存器c清零

RRC  A            ;ACC右移一位

INC  R7           ;寄存器R值加1

CJNE R7,#08H,?C0005  ;r7的值与8比较,不相等跳转到?c005:循环

?C0008:

RET  ;子函数返回


;for(i=8;i>0;i--)ACC版

RSEG  ?PR?_ds1302_input?DS1302

_ds1302_input:

USING 0

;---- Variable 'dat?040' assigned to Register 'R7' ----

MOV  A,R7     ;把实参值dat赋给寄存器ACC

;---- Variable 'i?041' assigned to Register 'R7' ----

MOV  R7,#08H  ;把8赋值给寄存器R7

?C0001:

CLR  DS1302_SCLK  ;此处跟i=0版的一样

MOV  C,ACC0

MOV  DS1302_IO,C

SETB DS1302_SCLK

CLR  C

RRC  A

DJNZ R7,?C0001   ;r7的值减1,不是零跳转到?c001,循环   

?C0004:

RET 


对比后,可以发现,出错的原因是for(i=0;i<8;i++)ACC版中,用ACC接收了实参(存储的为要写入的指令),然后在 for 循环前要给变量  “ i  "   赋值时,要用到ACC清零,再把ACC中的零赋给 R7 ("i"的值存储在R7)。这样的话,原来存储在ACC中的写入指令就被清零,自然会导致控制出现错误,最终没法读取ds1302芯片的时间,故显示为零。


而在for(i=8;i>0;i--)ACC版中,也用ACC接收了实参的值,但在 for 循环前,给变量“ i ” 赋值时,赋值为8,不需要用到ACC,所以ACC一直是存储着实参中的指令,没有被清零,所以能够顺利地向ds1302发送指令,从而能够读取到时间。


总结:


因为用for(i=0;i0;i--)类的指令多了 CLR A  和 INC R7 两条指令,CJNE 指令又比较DJNZ指令多了一个字节的程序代码存储空间,在频率为12M的51单片机上体现为执行同样功能的程序,要多用2us,代码空间花多一字节。所以前者是毫无优势的,以后应养成用


for(i=n;i>0;i--)的习惯。


请不要反驳我用了这么长的时间去研究,只能使单片机执行快2us,而说我钻牛角尖,只是因为,这个错误导致我整个程序无法正常运行,这不是一件小事。


至于为什么要用到累加器ACC来接收实参,是因为后面的程序要把一个8位的实参一位一位地输出到一个io口,自定义一个变量的话,按位寻址好像比较麻烦,要经过一系列 位运算 ,或者用bit定义8个位(有好的方法请告诉我,哈),而且我写不出来。而用ACC的话,可以很轻易地操作ACC的任意一位,如ACC0,ACC7。在网上查了一下,好像还有一种方法是定义 一种叫 位域 的东东,我看的c语言的书都没介绍,所以还不是很了解。


/************************************************************/


刚刚想了一下,不用ACC 的方法,作一个位运算dat &0x01,修改如下:


//for(i=0;i<8;i++),ACC版


void ds1302_input(uchar dat)

{

   uchar i;      //uchar= unsigned char

    for(i=0;i<8;i++)

   {

    DS1302_SCLK = 0;    //ds1302的时钟引脚

    DS1302_IO = dat & 0x01;    //ds1302的io口

    DS1302_SCLK = 1;     //上升沿写入数据

    dat >>= 1;       //第一个不移动

   }

想到了这个方法后,觉得自己好白痴,以后都不用ACC了。

推荐阅读

史海拾趣

璟德(ACX)公司的发展小趣事

随着技术的不断进步,璟德(ACX)的产品线也逐渐拓展,涵盖了滤波器、双工器、三工器、天线及其模组、蓝牙模组、射频前端模组等一系列高频陶瓷元件及模组。这些产品广泛应用于手机、无线网络、蓝牙、GPS、物联网等移动终端设备中,得到了市场的广泛认可。公司的业绩逐年攀升,客户群体也不断扩大。

Displaytech公司的发展小趣事

2012年,Displaytech进行了公司重组,SEACOMP成为公司各部门的主要实体。这次重组不仅优化了公司的组织架构,也进一步整合了公司的资源,提高了运营效率。同时,公司还在中国东莞购买了一家制造工厂,命名为MH MFG,加强了电子合同制造部门的力量。

这些故事只是Displaytech公司发展历程中的一部分,但它们充分展示了公司在电子行业中的实力、创新精神和国际化视野。通过不断的技术创新、产品升级和市场拓展,Displaytech已经成为电子行业中一家具有影响力的企业。

ACL staticide公司的发展小趣事

2008年,Displaytech推出了HDP Power,这是一项创新的电力解决方案,旨在支持公司客户的电力需求。这一举措不仅体现了Displaytech对客户需求的深刻洞察,也展示了公司在电源领域的技术实力。

Herth+Buss Fahrzeugteile GmbH & Co KG公司的发展小趣事
用于防止非法入侵或提醒人员注意。
CNC Tech公司的发展小趣事

CNC Tech公司深知,在竞争激烈的电子行业中,品质是赢得客户信任和市场口碑的关键。因此,公司始终坚持品质至上的原则,从原材料采购到生产制造的每一个环节都严格把控品质。CNC Tech还建立了完善的品质管理体系,通过严格的质量检测和持续的技术改进,确保每一台出厂的设备都能达到客户的期望和要求。正是凭借这种对品质的执着追求,CNC Tech赢得了广大客户的信赖和好评。

CLARE公司的发展小趣事

CLARE公司的创业之路始于对电子行业深厚的技术积累和敏锐的市场洞察力。公司的创始人凭借其深厚的专业知识和经验,准确把握了行业的发展趋势,成功开发了一系列具有竞争力的电子产品。这些产品在市场上获得了良好的反响,为CLARE公司奠定了坚实的技术和市场基础。

问答坊 | AI 解惑

德州仪器TMS320DM6467介绍

Datasheet下载: 功能框图: DM6467实现了在各种视频终端产品间的无缝内容传输。它集成了ARM926EJ-S内核与600MHz C64x+ DSP内核,并采用高清视频/影像协处理器(HD-VICP)、视频数据转换引擎以及目标视频端口接口。在执行高达H.264 HP@ L4(10 ...…

查看全部问答>

Linux下烧写镜像

小弟刚刚接触ARM,跟着开发板的说明移植内核,发现自己的Linux系统下不会烧写镜像,可以说是啥也不会,望有经验的同志告之 PS:小弟用惯了Linux,不想回到Windows下去,望能有详细的方法(小白一个)…

查看全部问答>

S3C2440硬件定时器

我想用用定时器输出微秒级的控制(mirco2440的板子),控制普通GPIO口输出高低电平(PWM被占用),整体思路是什么样的(驱动,应用程序调用)。。求助…

查看全部问答>

求WINCE5.0下JAVA语言的开发软件~~

RT 求 用JAVA语言的做WINCE5.0下应用程序 的软件 感激不尽~~ …

查看全部问答>

WINCE5.0 显示和ImageViewer问题

接触WINCE5.0不久,不知道怎么办. WINCE 原来的缺省显示支持240*320的LCD,而我们用的LCD是320*240.在PB中把WINCE5.0自带的ImageViewer软件加入过来,发现很多地方,打开该应用程序菜单项窗口以后,有些窗口显示框超出了我的LCD.由于看不到WINCE源代码, ...…

查看全部问答>

AN016 — 电源管理技术及功耗计算.pdf

本帖最后由 dontium 于 2015-1-23 13:22 编辑 C8051 …

查看全部问答>

430两路捕获计时问题

我用P1.2,1.3口捕获两个输入方波信号,均上升沿促发。两个捕获计数值差值得出时间差。但是计数差值很不稳定。。一段时间较为正常,一千多。一段时间突然保持在-几万。求助 #include   long int cap1=0; long int cap2=0; long int ca ...…

查看全部问答>

FatFs应用总结

多年来一直在使用FatFs开发各种项目(特别感谢FatFs作者的奉献),但都是或多或少的应用,并没有全面的应用到FatFs的所有功能。最近一个项目需要操作大量的文件,终于进入FstFs应用的全面期,也遇到了不少问题,现将逐步总结这些应用中遇到的各种问 ...…

查看全部问答>

西门子电路板元件怎样拆除

我想问一下,有谁知道西门子电路板(工业用的)的直插电子元器件怎样拆除,它的耐温怎么那么高,我把烙铁温度调到300度,那焊锡还是不化?…

查看全部问答>

【转】Altera SoC的Linux内核编译方法

本帖最后由 chenzhufly 于 2015-1-19 15:54 编辑 SoC的Linux内核编译方法 这里介绍如何编译SD Card的image。这里并没有太多的原理需要讲述,但是大多数刚刚接触到linux 嵌入式的朋友还是需要花些时间找编译方法。这里提供了为SoCFPGA编译内核 ...…

查看全部问答>