[51单片机] 给大家提供一个51的软延时例程.

[复制链接]
ayb_ice 发表于 2021-8-31 17:01 | 显示全部楼层
本帖最后由 ayb_ice 于 2021-8-31 17:05 编辑

RT     

本帖子中包含更多资源

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

×
 楼主| datouyuan 发表于 2021-8-31 18:00 | 显示全部楼层
ayb_ice 发表于 2021-8-31 16:57
这种做法误差太快,循环控制导致误差太大

在中断发生非常频繁时,又对延时操作精度又要求,那当然用硬件定时器要更好。
帖子讨论的是51mcu,硬件资源相差极大,但大部分没有系统滴答定时器。
产生空操作我只会用_nop_(),没用过__nops(),不清楚__nops(10)是不是用10条nop指令,占用10个指令地址。
d_us(us)只需要2条指令,2个指令地址。能产生相当于4~514个nop操作的效果。
循环控制的误差是极小的,最坏情形发生在12M12T情形下欲延时1u微秒d_us(1)实际延时4微秒。误差总体能在接受范围。
从概念上看,us也比nop数量更好理解,也不容易被别人错误理解。








本帖子中包含更多资源

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

×
ayb_ice 发表于 2021-9-1 08:34 | 显示全部楼层
datouyuan 发表于 2021-8-31 18:00
在中断发生非常频繁时,又对延时操作精度又要求,那当然用硬件定时器要更好。
帖子讨论的是51mcu,硬件资 ...

明显受编译模式影响,还受不同的51MCU指令周期影响
ayb_ice 发表于 2021-9-1 08:36 | 显示全部楼层
datouyuan 发表于 2021-8-31 18:00
在中断发生非常频繁时,又对延时操作精度又要求,那当然用硬件定时器要更好。
帖子讨论的是51mcu,硬件资 ...

滴答定时器只要是个定时器就可以,没有其它特殊要求,哪个项目不用滴答时钟呢,应该说有,但不多
ayb_ice 发表于 2021-9-1 08:39 | 显示全部楼层
本帖最后由 ayb_ice 于 2021-9-1 08:51 编辑

__nops是个宏定义,__nops(10),产生10个NOPS指令,100%是10,
移植与时间相关,假设是单周期51,时钟源是6MHZ,那么1Vs就是6个NOP
#define vs  *6u
__nops(1vs);延时1VS,
延时0.5VS,__nops(1vs/2);
主要用于模拟时序的延时,也可以用于比较精确的延时,比如模拟串口发送
假设单周期51,6M时钟,波特率38400
位周期就是26.04VS,取26VS,完全满足要求
void Delay1Bit(void)
{
        __nops(26vs-6);        //-4~6
}

减掉几个周期,因为需要调用与返回,标准51是4,单周期51可能多1~3个周期,没有关系,适当补偿即可,不影响
压根不需要用示波器测量调整(循环控制也会带来点误差,不影响)
void UART_TxByte(u8 ucData)
{
        u8 i;
       
        EA = 0;
        GPIO_UART_TX = 0;        //START
        Delay1Bit();
       
        for (i=0; i<8; i++)
        {
                ucData >>= 1;
                GPIO_UART_TX = CY;
        }

        GPIO_UART_TX = 1;        //STOP
        EA = 1;
        Delay1Bit();
}



ayb_ice 发表于 2021-9-1 08:59 | 显示全部楼层
软件死定时效率不高,在实际项目中受中断影响过大,中断越多,越长影响越大,实际延时时间普遍比理论时间长,用定时器会好很多,因为定时器一直在运行,一般只适合很短的延时,主要用于时序模拟的短延时
 楼主| datouyuan 发表于 2021-9-1 10:15 | 显示全部楼层
ayb_ice 发表于 2021-9-1 08:34
明显受编译模式影响,还受不同的51MCU指令周期影响
我使用过的51型号MCU,有STC、中颖、华邦、atmel、NXP,最近在使用十速的。
编译器用keil7、8、9版本,优化等级随便设定。不管怎么编译,延时都是非常准确的,完全符合计算要求。(要填写正确晶振频率,定时器机器周期,程序机器周期这3个数值)。

前面提到过编译模式和指令周期对这个定义的影响。
 楼主| datouyuan 发表于 2021-9-1 10:29 | 显示全部楼层
ayb_ice 发表于 2021-9-1 08:36
滴答定时器只要是个定时器就可以,没有其它特殊要求,哪个项目不用滴答时钟呢,应该说有,但不多 ...

你所说滴答时钟我的每个项目也使用,用于精确定时、按键扫描、动态显示等功能。
考虑51mcu的算力、兼容、项目最小精确定时等因素,几乎每个项目都会调整它,我一般取值每秒100到500之间,换算成时间为2~10mS。
再加上波特率生成占用一个,没有多余的硬件定时器做软延时了。
ayb_ice 发表于 2021-9-1 10:44 | 显示全部楼层
datouyuan 发表于 2021-9-1 10:29
你所说滴答时钟我的每个项目也使用,用于精确定时、按键扫描、动态显示等功能。
考虑51mcu的算力、兼容、 ...

就利用那个已经在用的滴答定时器,不再做任何改动,可以实现精确的VS级延时
 楼主| datouyuan 发表于 2021-9-1 11:44 | 显示全部楼层
ayb_ice 发表于 2021-9-1 10:44
就利用那个已经在用的滴答定时器,不再做任何改动,可以实现精确的VS级延时 ...

我在arm下是采用这办法,51考虑下面几点,一直没敢尝试。
51读写定时器不是原子操作。
51做16位运算效率太低了,会花费不少时间。而d_uS()是定义,计算都在编译阶段完成,不会影响延时。

谢谢你提到这点,我会仔细考虑可行性。
ayb_ice 发表于 2021-9-1 13:03 | 显示全部楼层
本帖最后由 ayb_ice 于 2021-9-1 13:05 编辑
datouyuan 发表于 2021-9-1 11:44
我在arm下是采用这办法,51考虑下面几点,一直没敢尝试。
51读写定时器不是原子操作。
51做16位运算效率 ...

8位机效率低点是必然的,原子操作可能通过关中断,飞读等来实现,只是简单的加减操作而已,效率不会低到哪里去,可以精心设计初值,让低8位的初值刚好等于0,只计算8位,效率很高了
 楼主| datouyuan 发表于 2021-9-1 17:54 | 显示全部楼层
ayb_ice 发表于 2021-9-1 13:03
8位机效率低点是必然的,原子操作可能通过关中断,飞读等来实现,只是简单的加减操作而已,效率不会低到哪里去 ...
  1. /*
  2. 通过精心配置tick,使低8位重载值刚好为0。
  3. 所有中断累计操作要小于256步。
  4. 定时器时钟和Fsys分频比越大,效果越好,例如为Fsys/12。
  5. */
  6. void d_us_tick_8(u8 n)
  7. {
  8.         u8 told,tnow;
  9.         u8 tsum;
  10.         told =TL0;//根据分频比进行补偿?
  11.         tsum=0;
  12.         while(1)
  13.         {
  14.                 tnow =TL0;
  15.                 if(tnow!=told)
  16.                 {
  17. //                        tsum+=(u8)(tnow-told);
  18.                         told=tnow-told;
  19.                         tsum+=told;

  20.                         told=tnow;                       
  21.                         if(tsum>=n)//n=0时 25步
  22.                                 break;
  23.                 }  
  24.         }
  25. }
  26. #define RELOAD_T2 0x8000
  27. void d_us_tick(u16 n)
  28. {
  29.         u16 told,tnow;
  30.         u16 tsum;
  31.         told=(TH2<<8)|TL2;//暂不考虑量子操作
  32.         tsum=0;
  33.         while(1)
  34.         {
  35.                 tnow =(TH2<<8)|TL2;
  36.                 if(tnow!=told)
  37.                 {
  38.                         if(tnow>told) tsum+=tnow-told;
  39.                         else tsum+=tnow-told+RELOAD_T2;//((RCAP2H<<8)|RCAP2L)
  40.                         told=tnow;                       
  41.                         if(tsum>=n)
  42.                                 break;
  43.                 }  
  44.         }
  45. }

适用限制条件多,见注释。
8bit开销有20多步,误差大。在12MHz12T情形下,误差达20多uS。51的Fsys一般都小于10MHz,最好情形误差也有2uS。
16bit开销有60多步.
总体感觉这种软延时在51下不适合。
arm的指令效率高,Fsys高,定时器多又能分频,虽然适合。但arm外设也多,软延时在arm中几乎没啥作用。



 楼主| datouyuan 发表于 2021-9-1 17:57 | 显示全部楼层

本帖子中包含更多资源

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

×
ayb_ice 发表于 2021-9-2 08:42 | 显示全部楼层
本帖最后由 ayb_ice 于 2021-9-2 08:44 编辑
datouyuan 发表于 2021-9-1 17:54
适用限制条件多,见注释。
8bit开销有20多步,误差大。在12MHz12T情形下,误差达20多uS。51的Fsys一般都 ...

如果TL0的初值是0,假设单周期51,12M,T0 12分频
那么
#define vs        *1u
#define ms        *1000uvs

void Delay(u8 ucVs)
{
        u8 ucBase = TL0;
        while ((u8)(TL0-ucBase) < ucVs)
        {
                //__nop();
        }
}

//测试扩展的延时有多少误差
//有有中断的时候有多少误差
//可以轻松扩展成32位的延时
void DelayEx(u16 usVs)
{
        u8 ucBase = TL0;
        while (usVs)
        {
                u8 ucThis = (usVs >= 256/2)?256/2:usVs;
                while ((u8)(TL0-ucBase) < ucThis)
                {
                        //__nop();
                }
                usVs -= ucThis;
                ucBase += ucThis;
        }
}

// DelayEx(10ms);
 楼主| datouyuan 发表于 2021-9-2 15:57 | 显示全部楼层
谢谢你提供的代码!!!我关注的是代码能适应所有的51mcu。
while ((u8)(TL0-ucBase) < ucVs)

这个循环花费7步,要求51定时器时钟是系统频率12分频,这点不是所有的51mcu都能做到的。下面3种型号只有中颖的能胜任。

型号AT89C2051,外振12MHz,Fosc分频比为12,定时器固定为Fosc。
十速TM5268,内振7.3728MHz,Fosc分频比为2、4、16可选,定时器固定为Fosc/2。
中颖SH88F2051,内振16.6MHz,Fosc分频比为1、2、4、12可选,定时器Fosc/1、Fosc/12可选。
 楼主| datouyuan 发表于 2021-9-2 16:23 | 显示全部楼层
ayb_ice 发表于 2021-9-2 08:42
如果TL0的初值是0,假设单周期51,12M,T0 12分频
那么
#define vs        *1u

DelayEx(u16 usVs)通过限制ucThis值小于128,来保证while ((u8)(TL0-ucBase) < ucThis)循环不出错,这代码OK,但花费还是比较大。
我之前函数void d_us_tick_8(u8 n)稍做修改(把n和tsum声明为u16),效果等同DelayEx(u16 usVs)。
  1. void d_us_tick_8(u16 n)
  2. {
  3.         u8 told,tnow;
  4.         u16 tsum;
  5.         told =TL0;//根据分频比进行补偿?
  6.         tsum=0;
  7.         while(1)
  8.         {
  9.                 tnow =TL0;
  10.                 if(tnow!=told)
  11.                 {
  12. //                        tsum+=(u8)(tnow-told);
  13.                         told=tnow-told;
  14.                         tsum+=told;

  15.                         told=tnow;                       
  16.                         if(tsum>=n)
  17.                                 break;
  18.                 }  
  19.         }
  20. }
ayb_ice 发表于 2021-9-2 17:33 | 显示全部楼层
datouyuan 发表于 2021-9-2 16:23
DelayEx(u16 usVs)通过限制ucThis值小于128,来保证while ((u8)(TL0-ucBase) < ucThis)循环不出错,这代 ...

前面已经说过了,你的代码在实际项目中受中断影响过大,延时越长,越受影响,只会大于你指定的时间
实际项目中不可能没有中断
,这个方案刚好相反,延时越长越准,也受中断影响,但影响小多了
ayb_ice 发表于 2021-9-2 17:35 | 显示全部楼层
datouyuan 发表于 2021-9-2 15:57
谢谢你提供的代码!!!我关注的是代码能适应所有的51mcu。

这个循环花费7步,要求51定时器时钟是系统频率 ...

这只是举个例子而已,任何算法都要黄健花时间的,只是多少的问题,短的延时我一般直接NOP指令了,较长的用这个或其它的方式
 楼主| datouyuan 发表于 2021-9-4 11:31 | 显示全部楼层
本帖最后由 datouyuan 于 2021-9-4 11:37 编辑

软延时适用于小于100us的场合,大于滴答时间尽量不要使用软延时。

中颖SH88F2051在16.6MHz内振情形下,无中断:
1.0us延时插入17字节的nop指令(1.024uS),循环控制插入4字节共2条指令(0.964uS)。
2.0us延时插入33字节的nop指令(1.988uS),循环控制插入4字节共2条指令(2.048uS)。
3.0us延时插入50字节的nop指令(3.012uS),循环控制插入4字节共2条指令(3.012uS)。
4.0us延时插入67字节的nop指令(4.036uS),循环控制插入4字节共2条指令(3.975uS)。
10us延时插入166字节的nop指令(10.00uS),循环控制插入4字节共2条指令(10.00uS)。
因一次循环需要2步,所以循环控制在最不好的情形下,误差会比插入nop大一步。
实际工程对软延时精度要求非常低,延时1us,偏差个3、4us都没问题。
以这么微小的误差节约大量的存储空间,循环控制应该比插入nop指令要好。由于arm的运算效率、主频速度远高于51,软延时用你那种方式非常好。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 在线客服 返回列表 返回顶部