搜索
bottom↓
回复: 19

探讨各种情况下51单片机的精确延时

[复制链接]

出0入0汤圆

发表于 2014-5-11 08:24:33 | 显示全部楼层 |阅读模式
本帖最后由 hotman_x 于 2014-5-11 08:26 编辑

事实上没有什么做到精确延时的通用方法,方法有二:
一是用汇编,根据晶振频率,对照数据手册,换算出指令周期,实现精确延时;
二是用C,通过实测调整,如果不是要求非常精密,也差不多少。

我先贴一个自己做的,抛转引玉。这个方案基于方法一,用于 STC12C5A60S2,在主频 22.1184mHz 下精确到微秒。可以直接换算到常用频率 11.0592mHz,只不过变成精确到 2 微秒就是了。
另外,我用的是坛子里比较少见的 SDCC 编译器,不过,汇编没多大区别,很容易换成 keil。

分析(STC12C5A60S2,主频 22.1184mHz):
CPU为所谓的 1T 机,即不对主频进行分频(传统8051CPU会对主频进行12分频使用)。这样的话:
1. 明显的,大约 22 个指令周期为 1 μs;
2. 根据1,每 100 μs 误差约为 -0.54 μs,大约相当于12个指令周期。
3. 根据2,每8 μs补偿一个指令周期,那么:每100 μs 误差 +0.0072 μs,误差率低于 0.01%,可以接受。

微秒级延时函数,误差在1个指令周期之内。
  1. void delayUs(unsigned char us) __naked {        // 6 入口
  2.         us;        // 抑制变量未用警告
  3. __asm
  4.         ; 第一个 μs 共 22T。用于出入口(已用 10T)
  5.         ; 第一个 μs 的前半部分,7T
  6.         MOV        A, DPL                        ; 2 DPL中是函数参数
  7.         DEC        A                        ; 2 减去第 1 个 μs
  8.         JZ        0001$                        ; 3 计数为0则直接跳到 1μs 的后半部分

  9.         ; 以下是第二个 μs,共22T。用于准备主循环
  10.         ; 第二个 μs 的前半部分,11T+3T
  11.         MOV        DPL, R0                        ; 3 保存R0
  12.         MOV        R0, A                        ; 2 以下用 R0 作为 μs 计数器
  13.         MOV        A, #6                        ; 2 补偿计数,去掉已过的 2μs
  14.         DJNZ        R0, 0003$                ; 4 减去第二个 μs。计数不为 0 则进入正常周期
  15.         SJMP        0002$                        ; 3 直接跳到 2μs 的后半部分

  16.         ; 以下是 3μs (含)之后周期延时部分,每周期 22T+1T(遇 8μs 周期时)
  17.         ; 补偿
  18.         0003$:
  19.         JNZ        0041$                        ; 3 如果未到补偿的时刻(A>0),跳过补偿部分
  20.         ; 这里是补偿部分,5T,比非补偿部分多1T
  21.         MOV        A, #7                        ; 2 每 8μs 补偿一次,当前这次自行减去
  22.         SJMP        0042$                        ; 3 回避非补偿部分
  23.         ; 这里是非补偿部分 4T
  24.         0041$:
  25.         DEC        A                        ; 2 计算补偿周期
  26.         NOP                                ; 1
  27.         NOP                                ; 1

  28.         ; 计算完补偿(7T)之后剩余的部分,15T
  29.         0042$:
  30.         CJNE        A, DPL, 0043$                ; 5
  31.         0043$:
  32.         CJNE        A, DPL, 0044$                ; 5
  33.         0044$:
  34.         NOP                                ; 1
  35.         DJNZ        R0, 0003$                ; 4 μs计数

  36.         ; 补偿 2μs 前半部分最后被跳过的 SJMP 指令
  37.         SJMP        0022$                        ; 3
  38.         0022$:
  39.         ; 以下是 2μs 的后半部分,8T
  40.         0002$:
  41.         SJMP        0021$                        ; 3
  42.         0021$:
  43.         NOP                                ; 1
  44.         MOV        R0, DPL                        ; 4

  45.         ; 以下是 1μs 的后半部分,5T
  46.         0001$:
  47.         CJNE        A, DPL, 0011$                ; 5
  48.         0011$:
  49.         RET                                                ; 4
  50. __endasm;
  51. }
复制代码


毫秒级延时函数,误差依然是1个指令周期。
  1. void delayMs(unsigned char ms) __naked {                // 6
  2.         ms;        // 抑制警告
  3. __asm
  4.         ; 第一个ms,共 35T + 10T(进出) + 5T(周期补偿) + 800us + 196us
  5.         ; 本应是34T+10T,但考虑错过了一次8us补偿,所以加到35T
  6.         ; 1ms 部分,共 40T(含周期补偿,不含出入部分)
  7.         ; 1ms 前半,21T
  8.         PUSH        _R0                        ; 4
  9.         PUSH        _R1                        ; 4
  10.         MOV        R0, DPL                        ; 4 用 R0 计 ms 数
  11.         MOV        R1, #4                        ; 3 前800us的循环计数
  12.         MOV        DPL, 198                ; 3 相当于从第一次循环中减去 2 us
  13.         SJMP        0001$                        ; 3 直接跳到 1ms 后半,直接跳入周期,所以补偿5T(见开头处)

  14.         ; 主体循环部分。每周期 45T + 998us
  15.         ; 本应 44T,考虑错过一次 8us 补偿
  16.         0002$:
  17.         MOV        R1, #4                        ; 2
  18.         0021$:
  19.         MOV        DPL, #200                ; 3 x 4 = 12
  20.         0001$:
  21.         LCALL        _delayUs                ; 200us x 4 = 800us
  22.         NOP                                ; 1 x 4 = 4
  23.         NOP                                ; 1 x 4 = 4
  24.         DJNZ        R1, 0021$                ; 4 x 4 = 16
  25.         MOV        DPL, #198                ; 3
  26.         LCALL        _delayUs                ; 198us
  27.         DJNZ        R0, 0002$                ; 4

  28.         ; 1ms 的后半 19T
  29.         NOP                                ; 1
  30.         MOV        R0, #2                        ; 2
  31.         0011$:
  32.         DJNZ        R0, 0011$                ; 4 x 2 = 8
  33.         POP                _R1                ; 4
  34.         POP                _R0                ; 4
  35.         RET                                ; 4
  36. __endasm;
  37. }
复制代码


最后,秒级延时函数。有了前面的基础,不必那么讲究了,直接用C写,误差也不过是10微秒这个级别。
  1. void delayS(unsigned char sec) {
  2.         unsigned char i;
  3.         unsigned char j;
  4.         for(i = 0; i < sec; ++i) {
  5.                 for(j = 0; j < 4; ++j)
  6.                         delayMs(250);
  7.         }
  8. }
复制代码

阿莫论坛20周年了!感谢大家的支持与爱护!!

知道什么是神吗?其实神本来也是人,只不过神做了人做不到的事情 所以才成了神。 (头文字D, 杜汶泽)

出0入0汤圆

发表于 2014-5-11 08:42:22 | 显示全部楼层
不错,收藏一下,平时都大概的用

出0入0汤圆

发表于 2014-5-11 08:44:02 | 显示全部楼层
看看,怎么样

出0入93汤圆

发表于 2014-5-11 08:44:03 | 显示全部楼层
楼主是学院派??

如果不是,那么:
1、精确定时有通用方法,就是利用硬件定时器(含看门狗定时器等等),如果使用中断,优先选择自动重载。
2、如果你需要使用软件延时,请确保你的延时时间非常短(比如你的us级延时),并且确保你一定要关闭了中断。
3、延时达到了ms级、s级,你的延时时间要求还是那么重要吗。如果还是那么重要,请利用硬件定时器,并使用中断。如果不是那么重要,那参考本坛中NNN多的事件触发系统啊、状态机啊、OS啊进行非精确的硬件延时。

出0入0汤圆

 楼主| 发表于 2014-5-11 08:50:37 | 显示全部楼层
takashiki 发表于 2014-5-11 08:44
楼主是学院派??

如果不是,那么:

如果用定时器自然是极好的 ,不过:
1、用了双串口后不一定还能有空余的内置定时器;
2、用定时器+中断响应后远不如指令延时准确——光是现场保护与恢复的代码通常都要付出相当代价;
3、真要精确延时的场合,关闭中断是题中应有之义,谢谢指出;
4、中断响应事实上是一种并发,编程模式完全不同,在很多场合都不方便的。

新手发帖,多谢指教。

出0入0汤圆

发表于 2014-5-11 09:38:07 | 显示全部楼层
收藏起来

出0入93汤圆

发表于 2014-5-11 09:46:27 | 显示全部楼层
hotman_x 发表于 2014-5-11 08:50
如果用定时器自然是极好的 ,不过:
1、用了双串口后不一定还能有空余的内置定时器;
2、用定 ...

我的看法和你完全不一样,看来您还是学生,或者刚走上工作岗位,或者纯理论,总是,目前还是学院派的。

1、看来你局限于51了。事实上,我用过的N种处理器中,只有51的串口要占用额外的定时器,其他的基本上使用自己的定时器。
2、这个根据你延时时间长短而定。短时间用查询,长时间用中断。长时间为什么要用到中断呢,就是为了不要让无谓的长延时影响了其他操作,而且一般来说,对长延时的精确度根本不需要那么高,RTC除外。
3、关中断是迫不得以的办法,因为它会阻塞其他有用操作。
4、中断多好啊,你居然……你可以类比一下多线程和单线程的不同。

出0入0汤圆

发表于 2014-5-11 10:12:13 | 显示全部楼层
  51中精确延时US可以用到.但MS和S级都要用定时器,然后MS级和S级有需要那么精准? 
        然后在程序中,要是用到MS级和S级的这种延时,我第一个PASS掉,会影响其它操作. 
        51中,除了IIC有可能要用到MS的的写延时,其它很少看到. 
        可以参考UC/OS软时基

出0入0汤圆

 楼主| 发表于 2014-5-11 10:27:06 | 显示全部楼层
takashiki 发表于 2014-5-11 09:46
我的看法和你完全不一样,看来您还是学生,或者刚走上工作岗位,或者纯理论,总是,目前还是学院派的。

...

可是,这里讨论的本来就是51啊。

出0入0汤圆

 楼主| 发表于 2014-5-11 10:32:30 | 显示全部楼层
slzm40 发表于 2014-5-11 10:12
51中精确延时US可以用到.但MS和S级都要用定时器,然后MS级和S级有需要那么精准? 
        然后在程序中,要是 ...

IIC需要的是微秒级的延时,您大概是笔误了
毫秒级延时,只能是“聊备一格”而已,我自己也没用上。至于秒级延时,连中断都不必关,用在任务不重的主循环上还是说得过去。

出0入0汤圆

发表于 2014-5-11 10:34:43 | 显示全部楼层
hotman_x 发表于 2014-5-11 10:32
IIC需要的是微秒级的延时,您大概是笔误了 。
毫秒级延时,只能是“聊备一格”而已,我自己也 ...

没笔误,.我说的是写延时. 就算任务不重.作为一个系统的实时性.能不用尽量不用

出0入0汤圆

 楼主| 发表于 2014-5-11 10:43:25 | 显示全部楼层
slzm40 发表于 2014-5-11 10:34
没笔误,.我说的是写延时. 就算任务不重.作为一个系统的实时性.能不用尽量不用 ...

不会吧,即使按低速的100K,延时也不过10微秒。IIC对信号升降边沿敏感,对时序要求倒是不严,长到毫秒当然也行,但是……
说真的,不太了解这个,好奇。

出0入0汤圆

发表于 2014-5-11 11:40:34 | 显示全部楼层
定时器最精确

出0入0汤圆

发表于 2014-5-11 12:26:22 来自手机 | 显示全部楼层
hotman_x 发表于 2014-5-11 10:43
不会吧,即使按低速的100K,延时也不过10微秒。IIC对信号升降边沿敏感,对时序要求倒是不严,长到毫秒当 ...

是我写的不对,时序上。肯定是。微秒的。我这里写延时是指两字节间的。

出0入0汤圆

 楼主| 发表于 2014-5-11 13:20:39 | 显示全部楼层
本帖最后由 hotman_x 于 2014-5-11 13:22 编辑


大部分情况下,定时器是比较准的。特别是在高频、流水线CPU环境下,比如对于PC来说,指令延时根本没有可能,而多媒体定时器完全可以实现微秒级的精确定时。

但在特定应用环境下则不然。无论如何,这里是51区,在需要精确定时的时候,指令延时比定时器靠谱。
最简单的定时器用法大概是这样:
1、预先准备好定时器状态;
2、到达需要延时的点位时,清除C标志位;
3、启动定时器,并轮询 C 标志位;
4、用汇编完成定时器中断响应,只有两条指令:SETB C 和 RETI

这已经是可以想像的最快定时器响应了,连中断跳转都可以不要,这两条指令只有 2 字节可以直接写在中断向量表里(这意味着你需要绕过 C 编译器亲自操刀,在PC编程中通常把这叫做 thunk 技术,不知道单片机领域怎么称呼)。但对于微秒级响应,这依然麻烦:轮询与响应过程本身,就有 1 微秒(以常见的 11.0592mHz 主频计)左右的消耗了。

出0入0汤圆

发表于 2014-5-11 17:14:23 来自手机 | 显示全部楼层
没必要这么玩命的算时间,有人开发了个小工具,延时我测试过,比较精确。

出0入0汤圆

发表于 2014-5-11 17:14:44 来自手机 | 显示全部楼层
了解原理就可

出0入0汤圆

 楼主| 发表于 2014-5-12 09:06:58 | 显示全部楼层
一匹狼 发表于 2014-5-11 17:14
没必要这么玩命的算时间,有人开发了个小工具,延时我测试过,比较精确。 ...

这个就是前面说的“方法二”了。完全实用主义的态度,其实是最常见的。大凡用51单片机的,大概都这样搞过,呵呵。

出0入0汤圆

发表于 2014-5-12 10:17:24 | 显示全部楼层
单片机小精灵v1.3完美破解.rar
attach://190524.rar

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2014-5-13 12:47:49 | 显示全部楼层
好东西  谢谢分享
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-7-23 12:27

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表