10.1 单片机数字秒表程序

本文探讨了单片机实现数字秒表的程序设计,包括不同数据类型间的转换,确保定时时间的精准性,讲解了字节操作技巧以修改位,以及数码管扫描函数的算法改进,旨在提升秒表程序的效率和显示效果。
摘要由CSDN通过智能技术生成

不同数据类型间的相互转换

在 C 语言中,不同数据类型之间是可以混合运算的。当表达式中的数据类型不一致时,首先转换为同一种类型,然后再进行计算。C 语言有两种方法实现类型转换,一是自动类型转换,另外一种是强制类型转换。这块内容是比较繁杂的,因此我们根据常用的编程应用来讲部分相关内容。

当不同数据类型之间混合运算的时候,不同类型的数据首先会转换为同一类型,转换的主要原则是:短字节的数据向长字节数据转换。比如:
   
   
   
  1. unsigned char a;
  2. unsigned int b;
  3. unsigned int c;
  4. c = a *b;
在运算的过程中,程序会自动全部按照 unsigned int 型来计算。比如 a=10,b=200,c 的结果就是 2000。那当 a=100,b=700,那 c 是 70000 吗?新手最容易犯这种错误,大家要注意每个变量类型的取值范围,c 的数据类型是 unsigned int 型,取值范围是 0~65535,而 70000超过 65535 了,其结果会溢出,最终 c 的结果是(70000 - 65536) = 4464。

那要想让 c 正常获得 70000 这个结果,需要把 c 定义成一个 unsigned long 型。我们如果写成:
   
   
   
  1. unsigned char a=100;
  2. unsigned int b=700;
  3. unsigned long c=0;
  4. c = a*b;
有做过实验的同学,会发现这个 c 的结果还是 4464,这个是个什么情况呢?

大家注意,C 语言不同类型运算的时候数值会转换同一类型运算,但是每一步运算都会进行识别判断,不会进行一个总的分析判断。比如我们这段代码中 a 和 b 相乘的时候,是按照 unsigned int 类型运算的,运算的结果也是 unsigned int 类型的 4464,只是最终把 unsigned int类型 4464 赋值给了一个 unsigned long 型的变量而已。我们在运算的时候如何避免这类问题的产生呢?可以采用强制类型转换的方法。

在一个变量前边加上一个数据类型名,并且这个类型名用小括号括起来,就表示把这个变量强制转换成括号里的类型。如 c = (unsigned long)a * b;由于强制类型转换运算符优先级高于*,所以这个地方的运算是先把 a 转换成一个 unsigned long 型的变量,而后与 b 相乘,根据 C 语言的规则 b 会自动转换成一个 unsigned long 型的变量,而后运算完毕结果也是一个unsigned long 型的,最终赋值给了 c。

不同类型变量之间的相互赋值,短字节类型变量向长字节类型变量赋值时,其值保持不变,比如:
   
   
   
  1. unsigned char a=100;
  2. unsigned int b=700;
  3. b=a;
那么最终 b 的值就是 100 了。但是如果我们的程序是
   
   
   
  1. unsigned char a=100;
  2. unsigned int b=700;
  3. a=b;
那么 a 的值仅仅是取了 b的低 8 位,我们首先要把 700 变成一个 16 位的二进制数据,然后取它的低 8 位出来,也就是 188,这就是长字节类型给短字节类型赋值的结果,会从长字节类型的低位开始截取刚好等于短字节类型长度的位,然后赋给短字节类型。

在 51 单片机里边,有一种特殊情况,就是 bit 类型的变量,这个 bit 类型的强制类型转换,是不符合上边讲的这个原则的,比如:
   
   
   
  1. bit a=0;
  2. unsigned char b;
  3. a=(bit)b;
这个地方要特别注意,使用 bit 做强制类型转换,不是取 b 的最低位,而是它会判断 b 这个变量是 0 还是非 0的值,如果 b 是 0,那么 a 的结果就是 0,如果 b 是任意非 0 的其它值,那么 a 的结果都是 1。

定时时间精准性调整

在 6.5.2 章节有一个数码管秒表显示程序,那个程序是 1 秒数码管加 1,但是细心的同学做了实验后,经过长时间运行会发现,和我们实际的时间有了较大误差了,那如何去调整这种误差呢?要解决问题,先找到问题是什么原因造成的。

先对我们前面讲过的中断内容做一个较深层次的补充。还是讲解中断的那个场景,当我们在看电视的时候,突然发生了水开的中断,我们必须去提水的时候,第一,我们从电视跟前跑到厨房需要一定的时间,第二,因为我们看的电视是智能数字电视,因此在去提水之前我们可以使用遥控器将我们的电视进行暂停操作,方便回来后继续从刚才的剧情往下进行。

那么暂停电视,跑到厨房提水,这一点点时间是很短的,在实际生活中可以忽略不计,但是在单片机秒表程序中,误差是会累计的,每 1 秒钟都差了几个微妙,时间一久,造成的累计误差就不可小觑了。

单片机系统里,硬件进入中断需要一定的时间,大概是几个机器周期,还要进行原始数据保护,就是把进中断之前程序运行的一些变量先保存起来,专业术语叫做中断压栈,进入中断后,重新给定时器 TH 和 TL 赋值,也需要几个机器周期,这样下来就会消耗一定的时间,我们得把这些时间补偿回来。

方法一,使用软件 debug 进行补偿。
我们在前边讲过使用 debug 来观察程序运行时间,那我们可以把我们 2 次进入中断的时间间隔观察出来,看看和我们实际定时的时间相差了几个机器周期,然后在进行定时器初值赋值的时候,进行一个调整。我们用的是 11.0592M 的晶振,发现差了几个机器周期,就把定时器初值加上几个机器周期,这样就相当于进行了一个补偿。

方法二,使用累计误差计算出来。
有的时候,除了程序本身存在的误差外,硬件精度也可能会影响到时钟的精度,比如晶振,会随着温度变化出现温漂现象,就是实际值和标称值要差一点。那么我们还可以采取累计误差的方法来提高精度。比如我们可以让时钟运行半个小时或者一个小时,看看最终时间差了几秒,然后算算一共进了多少次定时器中断,把这差的几秒平均分配到每次的定时器中断中,就可以实现时钟的调整。

大家要明白,这个世界上本就没有绝对的精确,我们只能在一定程度上提高精确度,但是永远都不会使误差为零,如果在这个基础上还感觉精度不够的话,不要着急,后边我们会专门讲时钟芯片的,通常时钟芯片计时的精度比单片机的精度要高一些。

字节操作修改位的技巧

这里再介绍个编程小技巧,在编程时,有的情况下需要改变一个字节中的某一位或者几位,但是又不想改变其它位原有的值,该如何操作呢?

比如我们学定时器的时候遇到一个寄存器 TCON,这个寄存器是可以进行位操作的,可以直接写 TR0=1;TR0 是 TCON 的一个位,因为这个寄存器是允许位操作,这样写是没有任何问题的。还有一个寄存器 TMOD,这个寄存器是不支持位操作的,那如果我们要使用 T0的模式 1,我们希望达到的效果是 TMOD 的低 4 位是 0b0001,但如果我们直接写成 TMOD =0x01 的话,实际上已经同时操作到了高 4 位,即属于 T1 的部分,设置成了 0b0000,如果T1 定时器没有用到的话,那我们随便怎么样都行,但是如果程序中既用到了 T0,又用到了T1,那我们设置 T0 的同时已经干扰到了 T1 的模式配置,这是我们不希望看到的结果。

在这种情况下,就可以用我们前边学过的“&”和“|”运算了。对于二进制位操作来说,不管该位原来的值是 0 还是 1,它跟 0 进行&运算,得到的结果都是 0,而跟 1 进行&运算,将保持原来的值不变;不管该位原来的值是 0 还是 1,它跟 1 进行|运算,得到的结果都是 1,而跟 0 进行|运算,将保持原来的值不变。

利用上述这个规律,我们就可以着手解决刚才的问题了。如果我们现在要设置 TMOD 使定时器 0 工作在模式 1 下,又不干扰定时器 1 的配置,我们可以进行这样的操作:TMOD =TMOD & 0xF0; TMOD = TMOD | 0x01;第一步与 0xF0 做&a
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值