发新帖我要提问
12
返回列表
打印
[51单片机]

给大家提供一个51的软延时例程.

[复制链接]
楼主: datouyuan
手机看帖
扫描二维码
随时随地手机跟帖
21
本帖最后由 ayb_ice 于 2021-8-31 17:05 编辑

RT     

QQ图片20210831170244.png (18.16 KB )

QQ图片20210831170244.png

使用特权

评论回复
22
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数量更好理解,也不容易被别人错误理解。








使用特权

评论回复
23
ayb_ice| | 2021-9-1 08:34 | 只看该作者
datouyuan 发表于 2021-8-31 18:00
在中断发生非常频繁时,又对延时操作精度又要求,那当然用硬件定时器要更好。
帖子讨论的是51mcu,硬件资 ...

明显受编译模式影响,还受不同的51MCU指令周期影响

使用特权

评论回复
24
ayb_ice| | 2021-9-1 08:36 | 只看该作者
datouyuan 发表于 2021-8-31 18:00
在中断发生非常频繁时,又对延时操作精度又要求,那当然用硬件定时器要更好。
帖子讨论的是51mcu,硬件资 ...

滴答定时器只要是个定时器就可以,没有其它特殊要求,哪个项目不用滴答时钟呢,应该说有,但不多

使用特权

评论回复
25
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();
}



使用特权

评论回复
26
ayb_ice| | 2021-9-1 08:59 | 只看该作者
软件死定时效率不高,在实际项目中受中断影响过大,中断越多,越长影响越大,实际延时时间普遍比理论时间长,用定时器会好很多,因为定时器一直在运行,一般只适合很短的延时,主要用于时序模拟的短延时

使用特权

评论回复
27
datouyuan|  楼主 | 2021-9-1 10:15 | 只看该作者
ayb_ice 发表于 2021-9-1 08:34
明显受编译模式影响,还受不同的51MCU指令周期影响
我使用过的51型号MCU,有STC、中颖、华邦、atmel、NXP,最近在使用十速的。
编译器用keil7、8、9版本,优化等级随便设定。不管怎么编译,延时都是非常准确的,完全符合计算要求。(要填写正确晶振频率,定时器机器周期,程序机器周期这3个数值)。

前面提到过编译模式和指令周期对这个定义的影响。

使用特权

评论回复
28
datouyuan|  楼主 | 2021-9-1 10:29 | 只看该作者
ayb_ice 发表于 2021-9-1 08:36
滴答定时器只要是个定时器就可以,没有其它特殊要求,哪个项目不用滴答时钟呢,应该说有,但不多 ...

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

使用特权

评论回复
29
ayb_ice| | 2021-9-1 10:44 | 只看该作者
datouyuan 发表于 2021-9-1 10:29
你所说滴答时钟我的每个项目也使用,用于精确定时、按键扫描、动态显示等功能。
考虑51mcu的算力、兼容、 ...

就利用那个已经在用的滴答定时器,不再做任何改动,可以实现精确的VS级延时

使用特权

评论回复
30
datouyuan|  楼主 | 2021-9-1 11:44 | 只看该作者
ayb_ice 发表于 2021-9-1 10:44
就利用那个已经在用的滴答定时器,不再做任何改动,可以实现精确的VS级延时 ...

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

谢谢你提到这点,我会仔细考虑可行性。

使用特权

评论回复
31
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位,效率很高了

使用特权

评论回复
32
datouyuan|  楼主 | 2021-9-1 17:54 | 只看该作者
ayb_ice 发表于 2021-9-1 13:03
8位机效率低点是必然的,原子操作可能通过关中断,飞读等来实现,只是简单的加减操作而已,效率不会低到哪里去 ...
/*
通过精心配置tick,使低8位重载值刚好为0。
所有中断累计操作要小于256步。
定时器时钟和Fsys分频比越大,效果越好,例如为Fsys/12。
*/
void d_us_tick_8(u8 n)
{
        u8 told,tnow;
        u8 tsum;
        told =TL0;//根据分频比进行补偿?
        tsum=0;
        while(1)
        {
                tnow =TL0;
                if(tnow!=told)
                {
//                        tsum+=(u8)(tnow-told);
                        told=tnow-told;
                        tsum+=told;

                        told=tnow;                       
                        if(tsum>=n)//n=0时 25步
                                break;
                }  
        }
}
#define RELOAD_T2 0x8000
void d_us_tick(u16 n)
{
        u16 told,tnow;
        u16 tsum;
        told=(TH2<<8)|TL2;//暂不考虑量子操作
        tsum=0;
        while(1)
        {
                tnow =(TH2<<8)|TL2;
                if(tnow!=told)
                {
                        if(tnow>told) tsum+=tnow-told;
                        else tsum+=tnow-told+RELOAD_T2;//((RCAP2H<<8)|RCAP2L)
                        told=tnow;                       
                        if(tsum>=n)
                                break;
                }  
        }
}

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



使用特权

评论回复
33
datouyuan|  楼主 | 2021-9-1 17:57 | 只看该作者

使用特权

评论回复
34
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);

使用特权

评论回复
35
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可选。

使用特权

评论回复
36
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)。
void d_us_tick_8(u16 n)
{
        u8 told,tnow;
        u16 tsum;
        told =TL0;//根据分频比进行补偿?
        tsum=0;
        while(1)
        {
                tnow =TL0;
                if(tnow!=told)
                {
//                        tsum+=(u8)(tnow-told);
                        told=tnow-told;
                        tsum+=told;

                        told=tnow;                       
                        if(tsum>=n)
                                break;
                }  
        }
}

使用特权

评论回复
37
ayb_ice| | 2021-9-2 17:33 | 只看该作者
datouyuan 发表于 2021-9-2 16:23
DelayEx(u16 usVs)通过限制ucThis值小于128,来保证while ((u8)(TL0-ucBase) < ucThis)循环不出错,这代 ...

前面已经说过了,你的代码在实际项目中受中断影响过大,延时越长,越受影响,只会大于你指定的时间
实际项目中不可能没有中断
,这个方案刚好相反,延时越长越准,也受中断影响,但影响小多了

使用特权

评论回复
38
ayb_ice| | 2021-9-2 17:35 | 只看该作者
datouyuan 发表于 2021-9-2 15:57
谢谢你提供的代码!!!我关注的是代码能适应所有的51mcu。

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

这只是举个例子而已,任何算法都要黄健花时间的,只是多少的问题,短的延时我一般直接NOP指令了,较长的用这个或其它的方式

使用特权

评论回复
39
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,软延时用你那种方式非常好。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则