使用 _nop_() 函数做延时遇到的一些问题 ...... by 矜辰所致
前言
最近还是继续做着项目,因为在某 8051 内核芯片上使用到了 I2C 通讯,又需要 _nop_()
函数来实现 us 延时,那么正好来写一篇与_nop_()
函数有关的文章 。
我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!
目录
- 前言
- 一、 NOP 指令
- 1.1 NOP 指令的作用
- 二、单片机中的 `_nop_()` 函数
- 1.1 C语言中的 NOP
- 1.2 _nop_ 函数消耗的时间
- 三、用 `_nop_()` 延时的注意事项
- 3.1 函数调用对延时的影响
- 3.2 调用函数中的语句对延时的影响
- 结语
一、 NOP 指令
_nop_()
函数产生的是 NOP 指令,先来简单介绍一下 NOP 指令,基本介绍走个流程把:
NOP 是编程语言中一个经常用到的指令,它的全称是 No Operation,即无操作指令。
NOP 是汇编语言中的一个伪指令,通过NOP一系列的编程语句,能够不改变任何程序可以访问的寄存器。
1.1 NOP 指令的作用
-
我们知道,指令、数据对齐可以有效地提高程序的性能, 使用 NOP 指令,可以使得指令按字对齐,从而提高效率 。
比如一条指令占用 3 个字节,再加上一个 NOP 指令,就使得指令 4 字节对齐了。 -
通过 NOP 指令产生一定的延迟,这与 CPU 的频率有关系,适用于一些频率低的 单片机 场合。
-
计算机在输入或者输出的过程中,使用 NOP 指令可可以很好的等待计算机缓冲区清空,等待总线恢复正常,其实也算是延时的一种了
二、单片机中的 _nop_()
函数
1.1 C语言中的 NOP
如果使用汇编语言,我们可以直接使用 NOP 指令的 ,直接写一个 nop 就可以,比如下面示例:
.text ; 代码段开始
.syntax unified ;
start:
mov r0, #0x55 ; 将0x55存储在寄存器R0中
nop ; 插入NOP指令
mov r1, #0xAA ; 将0xAA存储在寄存器R1中
add r2, r0, r1 ; 将R0和R1相加并将结果存储在R2中
但是我们在单片机中编程,现在都是使用 C 语言,对于 C 语言本身来说,是没有空语句的。
但是我们在做51单片机的开发中,在库文件中提供了一个void _nop_(void);
函数,这个函数声明一般在 intrins.h 头文件当中,我们只需要 #include <intrins.h>
就可以使用 _nop_();
函数了。
比如:
我们已经知道了 nop 是空语句,什么都不做,但是在这里我们还是得明确的知道 一个 _nop_()
表示空循环一个机器指令的时间。
1.2 nop 函数消耗的时间
那么在我们的单片机中,一个 nop 的时间是多少呢?、
上面说到,一个 nop 表示一个机器周期,那么一个机器周期是多少?
机器周期当然与主频有关,在单片机中指的就是晶振的频率。
首先基本的东西还是要知道的 一个机器周期包含12个晶振周期。 所以我们可以通过下面的计算得知 nop 函数消耗的时间:
假设单片机 12M 晶振,晶振周期1/12微秒,一个机器周期包含12个晶振周期,所以12M晶振时机器周期 = 12x(1/12)us = 1us 。
.
所以12M 晶振中一个 nop 表示延时1us;
6M 晶振中延时2us,24M 晶振中延时 0.5 us
至于其他的晶振频率,我们可以按照上面的计算代入即可。
对于 _nop_()
函数 其实在我以前的文章 BH1750 传感器实战教学 —— 驱动移植篇 中有过说明:
三、用 _nop_()
延时的注意事项
到此,我们已经可以知道在我们的程序中,一个 nop 函数执行所需要时间,我们可以利用多个 _nop_()
函数来实现一些 us 级别的延时。
比如我以前一些帖子里面提到的在 51 上面的 I2C 通讯:
在上图中,就是一个简单的 I2C 其实信号的实现方式, 在上图中,有说明 多几个 nop 少几个 nop 无所谓,实际上现在看来是有问题的,这让我付出了代价,这一点我后面会在写某个传感器测试博文的时候会提到。
3.1 函数调用对延时的影响
那么本文这里要说明的是一些使用时候的问题,依然是我以前文中提到的,在 STM32 HAL 库中没有 us 延时,所以我一直用的是:
void delay_us(uint32_t Delay)
{
uint32_t cnt = Delay * 8; // 32Mhz ,其他频率其他倍数
uint32_t i = 0;
for(i = 0; i < cnt; i++)__NOP();
}
于是乎,对于本次使用的 16MHZ 晶振的 51 芯片,我改成了如下:
void delay_us(uint32 Delay)
{
uint32 cnt = Delay * 4; // 32Mhz 8 ,其他频率其他倍数 16Mhz慢一点 4
uint32 i = 0;
for(i = 0; i < cnt; i++)_nop_();
}
然后自然的把上面的 I2C_Start
改成如下:
void I2C_Start1(void)
{
sda_high();
delay_us(5);
scl_high();
delay_us(10);
sda_low();
delay_us(10);
scl_low(); //使SCL置低,准备发送或者接受数据
delay_us(10);
}
反正改完以后传感器通讯是不正确的,于是乎最后上了示波器,惊讶的发现,在我使用的 51 上面采用上面的方式的波形图如下(注意看波形的时间):
是不是很意外,时间周期居然可以达到 ms 级别,就是使用一个一个循环调用 nop 的函数……
而在 STM32 平台下面,我观察到的波形图如下(us级别算是正常的):
虽然知道调用函数会占用时间,但是上面的情况也太离谱了点,即便我最后把循环里面的 *4 都直接删除,波形周期还是 ms 级别。
这…… 真的是有点难以理解,太离谱了:
反正最后我还是去掉了函数,采用直接使用很多个 nop 函数直接写的上面这种方式。
(待分析)
3.2 调用函数中的语句对延时的影响
本节待完善(目前从网上截取了部分说明)
在选择C51中循环语句时,要注意以下几个问题
第一、定义的C51中循环变量,尽量采用无符号字符型变量。
第二、在FOR循环语句中,尽量采用变量减减来做循环。
第三、在do…while,while语句中,循环体内变量也采用减减方法。
这因为在C51编译器中,对不同的循环方法,采用不同的指令来完成的。
(待分析)
结语
本文说明了博主在使用 _nop_() 函数
实现 us 延时的过程中遇到的一些问题,希望对大家有所帮助。
好了本文就到这里,谢谢大家,赶在 10.24.发,仓促结尾! 不好意思!