STC89C52RC单片机额外篇 | 05 - 把NOP指令封装成微秒级延时函数

本文介绍了在单片机开发中实现微秒级延时函数的方法,通过分析简单的while循环延时和使用NOP指令或_nop_函数来精确控制延时。讲解了如何通过NOP指令和函数调用开销来实现不同微秒级别的延时,并提供了相应的代码示例。同时提到了在Keil环境下使用#pragma ASM和#pragma ENDASM进行嵌入式汇编的注意事项,以及如何利用_nop_函数替代NOP指令实现延时。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

延时函数是单片机开发中是必不可少的功能函数,在每个工程里都能找到它的踪影。虽然看起来不起眼,但在有些时序控制的场合,使用了一点点延时,往往能解决大问题。特别对于某些模块,往往需要微秒级的延时,例如超声波模块与红外接收器等。

以前我们使用延时函数的时候,基本上类似这样:

/*******************************************************************************
* 函 数 名         : delay
* 函数功能		   : 延时函数,i=1时,大约延时10us
*******************************************************************************/
void delay(u16 i)
{
	while(i--);	
}

我们看到函数体内部只有简单的一条语句while(i--);,看起来好像的确只是一条语句,多么简单!实际上从脑中跑一下逻辑,实际上是这样的:

/*******************************************************************************
* 函 数 名         : delay
* 函数功能		   : 延时函数,i=1时,大约延时10us
*******************************************************************************/
void delay(u16 i)
{
	if (i == 0) {
		return;
	} else {
		i--;
	}

	if (i == 0) {
		return;
	} else {
		i--;
	}

	if (i == 0) {
		return;
	} else {
		i--;
	}
	...
}

delay函数传递的i有多大,像这样的语句就有多少条:

	if (i == 0) {
		return;
	} else {
		i--;
	}

所以注释中说i=1,大约延时10us完全是有可能的,那我们想实现精确性稍微好点的微秒级延时函数怎么办?

其实这块有很多大神已经帮我们封装好了,我们直接使用即可:

  • 延时5us
void delay_5us(void)
{
#pragma ASM
	NOP
#pragma ENDASM
}

这就是一个延时5us的函数,只需要在需要延时5us时调用此函数即可。从前面博文《STC89C52RC单片机额外篇 | 04 - 认识头文件<intrins.h>与_nop_函数》可知,_nop_函数的执行需要消耗一个机器周期,对于12MHz晶振,就是1us,_nop_函数与NOP指令是一样的。那为啥函数delay_5us()只有一条NOP指令就需要5us?

答案是:在调用此函数时,需要一个调用指令,此指令消耗2个周期(即2us);函数执行完毕时要返回主调函数,需要一个返回指令,此指令消耗2个周期(2us)。调用和返回消耗了2us+2us=4us。然后再加上一个NOP指令消耗1us,就是5us了,所以说函数的调用本身也有一定的技术细节的。

  • 延时10us
void delay_10us(void)
{
#pragma ASM
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
#pragma ENDASM
}

这就是延时10us的函数。同延时5us函数一样,调用和返回消耗4us,加上函数中的6个NOP指令6us,正好是10us。

  • 延时20us
void delay_20us(void)
{
#pragma ASM
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
#pragma ENDASM
}
  • 延时50us
void delay_50us(void)
{
#pragma ASM
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
#pragma ENDASM
}

最后别忘了头文件<intrins.h>下的_nop_函数——延时1us!有了延时1us、5us、10us、20us、50us基本上就可以实现100us以内大部分的微秒级延时,这就像我们熟悉的纸币,有1元、2元、5元、10元、20元、50元,用它们进行组合,我们就可以买很多的一般商品了!

我们可以把这些函数放到delay.c文件中,然后在delay.h中声明函数即可,使用的时候直接调用即可!下一篇博文中,我会带领大家了解多文件编程,敬请期待!

这里注意一下:使用#pragma ASM#pragma ENDASM之前需要对相应的C源文件进行配置,否则会出现编译错误!具体参考这篇博文:《Keil4中嵌入汇编语句

如果不想使用嵌入汇编语句的这种方式,我们也可以使用_nop_函数代替NOP指令:

  • 延时10us
void delay_10us(void)
{
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
}
  • 延时20us
void delay_20us(void)
{
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
}
  • 延时50us
void delay_50us(void)
{
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
}
<think>好的,我现在需要帮用户编写一个基于STC89C52RC单片机控制HC-SR04超声波避障模块的智能小车程序。首先,我得理清整个系统的各个部分以及它们之间的交互。 首先,硬件连接部分。用户用的是STC89C52RC单片机,需要确定HC-SR04的Trig和Echo引脚连接到单片机的哪个IO口。通常Trig接一个输出口,Echo接一个输入口,可能还需要外部中断或者定时器来处理Echo信号。比如Trig接P1^0,Echo接P1^1。然后是小车的电机驱动,可能用的是L298N之类的模块,需要确定控制引脚的连接,比如P2^0到P2^3来控制两个电机的正反转。 接下来是程序结构。主程序应该初始化定时器和中断,然后进入循环,不断触发超声波测距,根据距离判断是否需要避障。超声波模块的工作原理是发送一个10us的高脉冲触发信号,然后等待Echo引脚的高电平持续时间,这个时间与距离正比。计算距离的公式是距离=高电平时间*声速/2,声速取340m/s或者34cm/ms,所以时间除以58得到厘米,或者除以148得到英寸。 然后需要考虑如何测量Echo的高电平时间。可以使用外部中断来检测Echo的上升沿和下降沿,或者用定时器来计时。这里可能需要用到定时器0来计时高电平的持续时间,同时用外部中断0来捕获Echo的边沿变化。当Echo变为高电平时,启动定时器;变为低电平时,停止定时器并计算时间。 避障逻辑方面,当检测到距离小于某个阈值(比如20cm)时,小车需要做出反应,比如停止、后退、转向。可能需要根据不同的距离范围采取不同的动作,比如距离越近,反应越迅速。 在编写代码时,要注意变量的定义,比如距离变量distance,时间变量time,以及标志位比如flag。中断服务函数需要处理定时器溢出,因为当定时器溢出时,需要记录溢出次数,以便计算更长的时间。 此外,电机的控制函数需要根据不同的动作设置相应的IO口电平,比如前进时两个电机正转,左转时一个停一个转,后退时两个反转等。需要注意的是电机的驱动电路是否允许同时正反转,避免短路。 还要考虑超声波模块的触发周期,不宜过频繁,每次触发后需要等待一段时间再下一次触发,避免信号干扰。比如每次测距后延时一段时间再重新触发。 可能遇到的问题包括:超声波模块的响应时间、定时器的精度、中断冲突、电机控制的延迟等。调试时可能需要逐步测试各个部分,比如先测试超声波测距是否准确,再测试电机动作是否正确,最后整合整个避障逻辑。 需要检查代码中的语法错误,比如中断服务函数的格式是否正确,定时器的初始化设置是否正确,IO口的模式是否设置为准双向或者推挽输出等。另外,延时函数可能需要调整,确保触发信号的长度符合HC-SR04的要求。 最后,整合所有部分,编写主循环,不断获取距离并控制小车动作。测试时需要注意障碍物的位置和角度,确保超声波能够正确检测到障碍物,并且小车能够及时响应。</think>以下为STC89C52RC单片机控制HC-SR04超声波避障小车的程序开发指南,采用模块化设计思路: ```c #include <reg52.h> #include <intrins.h> // 硬件定义 #define TRIG P1_0 // 触发引脚 #define ECHO P1_1 // 回响引脚 #define MOTOR_R1 P2_0 // 右电机引脚1 #define MOTOR_R2 P2_1 // 右电机引脚2 #define MOTOR_L1 P2_2 // 左电机引脚1 #define MOTOR_L2 P2_3 // 左电机引脚3 // 全局变量 unsigned int time = 0; // 高电平时间 float distance = 0; // 测量距离 bit flag = 0; // 测量完标志 /*---- 定时器0初始化 ----*/ void Timer0_Init(){ TMOD |= 0x01; // 模式1,16位定时器 TH0 = 0; TL0 = 0; ET0 = 1; // 允许定时器0中断 EA = 1; // 开启总中断 } /*---- 超声波触发信号 ----*/ void Trigger(){ TRIG = 1; _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); TRIG = 0; } /*---- 电机控制函数 ----*/ void Forward(){ MOTOR_R1 = 1; MOTOR_R2 = 0; MOTOR_L1 = 1; MOTOR_L2 = 0; } void TurnLeft(){ MOTOR_R1 = 1; MOTOR_R2 = 0; MOTOR_L1 = 0; MOTOR_L2 = 1; } void Stop(){ MOTOR_R1 = 0; MOTOR_R2 = 0; MOTOR_L1 = 0; MOTOR_L2 = 0; } /*----函数 ----*/ void main(){ Timer0_Init(); while(1){ Trigger(); // 发送触发脉冲 while(!flag); // 等待测量完 // 计算距离公式:$$ \text{distance} = \frac{\text{time} \times 340}{2 \times 10000} $$ distance = time * 0.017; // 单位:cm // 避障判断(阈值20cm) if(distance < 20){ Stop(); TurnLeft(); // 遇到障碍左转 DelayMs(500); // 转向持续时间 } else{ Forward(); // 无障碍直行 } flag = 0; // 清除测量标志 DelayMs(100); // 测量间隔 } } /*---- 中断服务程序 ----*/ void Timer0() interrupt 1{ TH0 = 0; // 重装初值 TL0 = 0; time++; } void EXTI0() interrupt 0{ static unsigned char state = 0; if(ECHO){ TR0 = 1; // 上升沿启动定时器 } else{ TR0 = 0; // 下降沿停止定时器 time = TH0 << 8 | TL0; flag = 1; // 设置测量完标志 } } ``` **关键要素解析:** 1. **硬件连接说明:** - Trig引脚:P1.0 - Echo引脚:P1.1(需接外部中断0) - 电机驱动:L298N驱动模块,接P2.0-P2.3 2. **测距原理:** $$ \text{距离} = \frac{\text{高电平时间} \times \text{声速}}{2} $$ 其中声速取340m/s,定时器每计数值对应的时间需根据晶振频率计算 3. **避障策略优化建议:** - 分级判断:设置多级距离阈值(如10cm紧急停止,20cm转向) - 方向记忆:增加转向方向随机性,避免原地打转 - 滤波处理:连续3次测量取平均值,提高稳定性 4. **调试要点:** - 确保触发脉冲宽度10μs - 检查Echo信号电压是否匹配(需5V TTL电平) - 调整电机转向延时(示例中500ms需实际测试) **注意事项:** 1. 需根据实际晶振频率修改定时器时间计算 2. 建议增加PWM调速功能提升控制精度 3. 可在程序中添加串口输出用于距离调试 完整工程还需包含`DelayMs()`延时函数实现,以及根据实际硬件调整电机驱动逻辑。建议使用Keil μVision开发环境进行编译调试。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式逍遥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值