51单片机入门——红外通信

红外光的基本原理

红外线是波长介于微波和可见光之间的电磁波,波长在 760 纳米到 1 毫米之间,是波形比红光长的非可见光。自然界中的一切物体,只要它的温度高于绝对零度(-273)就存在分子和原子的无规则运动,其表面就会不停的辐射红外线。当然了,虽然是都辐射红外线,但是不同的物体辐射的强度是不一样的,而我们正是利用了这一点把红外技术应用到我们的实际开发中。

红外发射管很常用,在我们的遥控器上都可以看到,它类似发光二极管,但是它发射出来的是红外光,是我们肉眼所看不到的。我们学过发光二极管的亮度会随着电流的增大而增加,同样的道理,红外发射管发射红外线的强度也会随着电流的增大而增强,常见的红外发射管如图所示:在这里插入图片描述
红外接收管内部是一个具有红外光敏感特征的 PN 节,属于光敏二极管,但是它只对红外光有反应。无红外光时,光敏管不导通,有红外光时,光敏管导通形成光电流,并且在一定范围内电流随着红外光的强度的增强而增大。典型的红外接收管如图所示:在这里插入图片描述
这种红外发射和接收对管在小车、机器人避障以及红外循迹小车中有所应用,如图所示:
在这里插入图片描述
在上图中,发射控制和接收检测都是接到单片机的 IO 口上的。

发射部分:当发射控制输出高电平时,三极管 Q1 不导通,红外发射管 L1 不会发射红外信号;当发射控制输出低电平的时候,通过三极管 Q1 导通让 L1 发出红外光。接收部分:R4 是一个电位器,我们通过调整电位器给 LM393 的 2 脚提供一个阈值电压,这个电压值的大小可以根据实际情况来调试确定。而红外光敏二极管 L2 收到红外光的时候,会产生电流,并且随着红外光的从弱变强,电流会从小变大。当没有红外光或者说红外光很弱的时候,3 脚的电压就会接近 VCC,如果 3 脚比 2 脚的电压高的话,通过 LM393 比较器后,接收检测引脚输出一个高电平。当随着光强变大,电流变大,3 脚的电压值等于 VCC-I*R3,电压就会越来越小,当小到一定程度,比 2 脚的电压还小的时候,接收检测引脚就会变为低电平。

这个电路用于避障的时候,发射管先发送红外信号,红外信号会随着传送距离的加大逐渐衰减,如果遇到障碍物,就会形成红外反射。当反射回来的信号比较弱时,光敏二极管 L2接收的红外光较弱,比较器 LM393 的 3 脚电压高于 2 脚电压,接收检测引脚输出高电平,说明障碍物比较远;当反射回来的信号比较强,接收检测引脚输出低电平,说明障碍物比较近了。

用于小车循迹的时候,必须要有黑色和白色的轨道。当红外信号发送到黑色轨道时,黑色因为吸光能力比较强,红外信号发送出去后就会被吸收掉,反射部分很微弱。白色轨道就会把大部分红外信号反射回来。通常情况下的循迹小车,需要应用多个红外模块同时检测,从多个角度判断轨道,根据判断的结果来调整小车使其按照正常循迹前行。

红外遥控通信原理

在实际的通信领域,发出来的信号一般有较宽的频谱,而且都是在比较低的频率段分布大量的能量,所以称之为基带信号,这种信号是不适合直接在信道中传输的。为便于传输、提高抗干扰能力和有效的利用带宽,通常需要将信号调制到适合信道和噪声特性的频率内进行传输,这就叫做信号调制。在通信系统的接收端要对接收到的信号进行解调,恢复出原来的基带信号。这部分通信原理的内容,大家了解一下即可。我们平时用到的红外遥控器里的红外通信,通常是使用 38K 左右的载波进行调制的,下面我把原理大概给大家介绍一下,先看发送部分原理。调制:就是用待传送信号去控制某个高频信号的幅度、相位、频率等参量变化的过程,即用一个信号去装载另一个信号。比如我们的红外遥控信号要发送的时候,先经过 38K 调制,如图所示:在这里插入图片描述
原始信号就是我们要发送的一个数据“0”位或者一位数据“1”位,而所谓 38K 载波就是频率为 38K 的方波信号,调制后信号就是最终我们发射出去的波形。我们使用原始信号来控制 38K 载波,当信号是数据“0”的时候,38K 载波毫无保留的全部发送出去,当信号是数据“1”的时候,不发送任何载波信号。

那在原理上,我们如何从电路的角度去实现这个功能呢?
如下图所示:在这里插入图片描述
38K 载波,我们可以用 455K 晶振,经过 12 分频得到 37.91K,也可以由时基电路 NE555来产生,或者使用单片机的 PWM 来产生。当信号输出引脚输出高电平时,Q2 截止,不管38K 载波信号如何控制 Q1,右侧的竖向支路都不会导通,红外管 L1 不会发送任何信息。当信号输出是低电平的时候,那么 38K 载波就会通过 Q1 释放出来,在 L1 上产生 38K 的载波信号。这里要说明的是,大多数家电遥控器的 38K 的占空比是 1/3,也有 1/2 的,但是相对少一些。

正常的通信来讲,接收端要首先对信号通过监测、放大、滤波、解调等等一系列电路处理,然后输出基带信号。但是红外通信的一体化接收头 HS0038B,已经把这些电路全部集成到一起了,我们只需要把这个电路接上去,就可以直接输出我们所要的基带信号了,如图所示:在这里插入图片描述
由于红外接收头内部放大器的增益很大,很容易引起干扰,因此在接收头供电引脚上必须加上滤波电容,官方手册给的值是 4.7uF,我们这里直接用的 10uF,手册里还要求在供电引脚和电源之间串联 100 欧的电阻,进一步降低干扰。

上图所示的电路,用来接收波形,当 HS0038B 监测到有 38K的红外信号时,就会在 OUT 引脚输出低电平,当没有 38K 的时候,OUT 引脚就会输出高电平。那我们把 OUT 引脚接到单片机的 IO 口上,通过编程,就可以获取红外通信发过来的数据了。

大家想想,OUT 引脚输出的数据是不是又恢复成为基带信号数据了呢?那我们单片机在接收这个基带信号数据的时候,如何判断接收到的是什么数据,应该遵循什么协议呢?像我们前边学到的 UART、I2C、SPI 等通信协议都是基带通信的通信协议,而红外的 38K 仅仅是对基带信号进行调制解调,让信号更适合在信道中传输。

由于我们的红外调制信号是半双工的,而且同一时刻空间只能允许一个信号源,所以红外的基带信号不适合在 I2C 或者 SPI 通信协议中进行的,我们前边提到过 UART 虽然是 2 条线,但是通信的时候,实际上一条线即可,所以红外可以在 UART 中进行通信。当然,这个通信也不是没有限制的,比如在HS0038B 的数据手册中标明,要想让 HS0038B 识别到 38K的红外信号,那么这个 38K 的载波必须要大于 10 个周期,这就限定了红外通信的基带信号的比特率必须不能高于 3800,那如果把串口输出的信号直接用 38K 调制的话,波特率也就不能高于 3800。当然还有很多其它基带协议可以利用红外来调制,下面我们介绍一种遥控器常用的红外通信协议——NEC 协议。

NEC协议

家电遥控器通信距离往往要求不高,而红外的成本比其它无线设备要低的多,所以家电遥控器应用中红外始终占据着一席之地。遥控器的基带通信协议很多,大概有几十种,常用的就有 ITT 协议、NEC 协议、Sharp 协议、Philips RC-5 协议、Sony SIRC 协议等。用的最多的就是 NEC 协议了。

NEC 协议的数据格式包括了引导码、用户码、用户码(或者用户码反码)、按键键码和键码反码,最后一个停止位。停止位主要起隔离作用,一般不进行判断,编程时我们也不予理会。其中数据编码总共是 4 个字节 32 位,如图下所示。第一个字节是用户码,第二个字节可能也是用户码,或者是用户码的反码,具体由生产商决定,第三个字节就是当前按键的键数据码,而第四个字节是键数据码的反码,可用于对数据的纠错。

在这里插入图片描述
这个 NEC 协议,表示数据的方式不像我们之前学过的比如 UART 那样直观,而是每一位数据本身也需要进行编码,编码后再进行载波调制。

  • 引导码:9ms 的载波+4.5ms 的空闲。
  • 比特值“0”:560us 的载波+560us 的空闲。
  • 比特值“1”:560us 的载波+1.68ms 的空闲。

结合上图我们就能看明白了,最前面黑乎乎的一段,是引导码的 9ms 载波,紧接着是引导码的 4.5ms 的空闲,而后边的数据码,是众多载波和空闲交叉,它们的长短就由其要传递的具体数据来决定。HS0038B 这个红外一体化接收头,当收到有载波的信号的时候,会输出一个低电平,空闲的时候会输出高电平,我们用逻辑分析仪抓出来一个红外按键通过HS0038B 解码后的图形来了解一下。
在这里插入图片描述
从图上可以看出,先是 9ms 载波加 4.5ms 空闲的起始码,数据码是低位在前,高位在后,数据码第一个字节是 8 组 560us 的载波加 560us 的空闲,也就是 0x00,第二个字节是 8 组 560us的载波加 1.68ms 的空闲,可以看出来是 0xFF,这两个字节就是用户码和用户码的反码。按键的键码二进制是 0x0C,反码就是 0xF3,最后跟了一个 560us 载波停止位。对于我们的遥控器来说,不同的按键,就是键码和键码反码的区分,用户码是一样的。这样我们就可以通过单片机的程序,把当前的按键的键码给解析出来。

我们前边学习中断的时候,学到 51 单片机有外部中断 0 和外部中断 1 这两个外部中断。我们的红外接收引脚接到了 P3.3 引脚上,这个引脚的第二功能就是外部中断 1。在寄存器TCON 中的 bit3 和 bit2 这两位,是和外部中断 1 相关的两位。其中 IE1 是外部中断标志位,当外部中断发生后,这一位被自动置 1,和定时器中断标志位 TF 相似,进入中断后会自动清零,也可以软件清零。bit2 是设置外部中断类型的,如果 bit2 为 0,那么只要 P3.3 为低电平就可以触发中断,如果 bit2 为 1,那么 P3.3 从高电平到低电平的下降沿发生才可以触发中断。此外,外部中断 1 使能位是 EX1。那下面我们就把程序写出来,使用数码管把遥控器的用户码和键码显示出来。

Infrared.c 文件主要是用来检测红外通信的,当发生外部中断后,进入外部中断,通过定时器 1 定时,首先对引导码判断,而后对数据码的每个位逐位获取高低电平的时间,从而得知每一位是 0 还是 1,最终把数据码解出来。虽然最终实现的功能很简单,但因为编码本身的复杂性,使得红外接收的中断程序在逻辑上显得就比较复杂,那么我们首先提供出中断函数的程序流程图,大家可以对照流程图来理解程序代码。
在这里插入图片描述

  • infrared.c
/* 本例程使用晶振为24MHz */
#include "infrared.h"
#include <reg52.h>

bit irflag = 0; //红外接收标志,收到一帧正确数据后置 1
unsigned char ircode[4]; //红外代码接收缓冲区

/* 初始化红外接收功能 */
void InitInfrared()
{
	IR_INPUT = 1; //确保红外接收引脚被释放
	TMOD &= 0xf0; //清零 T0 的控制位
	TMOD |= 0x01; //配置 T0 为模式 1
	TR0 = 0; //停止 T0 计数
	ET0 = 0; //禁止 T0 中断
	IT0 = 1; //设置 INT0 为负边沿触发
	EX0 = 1; //使能 INT0 中断
}

/* 获取当前高电平的持续时间 */
unsigned int GetHighTime()
{
	TH0 = 0; //清零 T0 计数初值
	TL0 = 0;
	TR0 = 1; //启动 T0 计数
	while (IR_INPUT) //红外输入引脚为 1 时循环检测等待,变为 0 时则结束本循环
	{
		if (TH0 >= 0x8c) //当 T0 计数值大于 0x8c00,即高电平持续时间超过约 18ms 时
		{ 
			break; //强制退出循环,是为了避免信号异常时,程序假死在这里。
		}
	}
	TR0 = 0; //停止 T0 计数
	return (TH0 * 256 + TL0); //T0 计数值合成为 16bit 整型数,并返回该数
}

/* 获取当前低电平的持续时间 */
unsigned int GetLowTime()
{
	TH0 = 0; //清零 T0 计数初值
	TL0 = 0;
	TR0 = 1; //启动 T0 计数
	while (!IR_INPUT) //红外输入引脚为 0 时循环检测等待,变为 1 时则结束本循环
	{
		if (TH0 >= 0x8c) //当 T0 计数值大于 0x8c00,即低电平持续时间超过约 18ms 时
		{ 
			break; //强制退出循环,是为了避免信号异常时,程序假死在这里。
		}
	}
	TR0 = 0; //停止 T1 计数
	return (TH0 * 256 + TL0); //T0 计数值合成为 16bit 整型数,并返回该数
}

/* INT1 中断服务函数,执行红外接收及解码 */
void EXINT1_ISR() interrupt 0
{
	unsigned char i, j;
	unsigned char byt;
	unsigned int time;
	 
	//接收并判定引导码的 9ms 低电平
	time = GetLowTime();
	if ((time < (8500 * 2)) || (time > (9500 * 2))) //时间判定范围为 8.5~9.5ms,
	{ //超过此范围则说明为误码,直接退出
		IE0 = 0; //退出前清零 INT1 中断标志
		return;
	}
	//接收并判定引导码的 4.5ms 高电平
	time = GetHighTime();
	if ((time < (4000 * 2)) || (time > (5000 * 2))) //时间判定范围为 4.0~5.0ms,
	{ //超过此范围则说明为误码,直接退出
		IE0 = 0;
		return;
	}
	//接收并判定后续的 4 字节数据
	for (i = 0; i < 4; i ++) //循环接收 4 个字节
	{
		for (j = 0; j < 8; j ++) //循环接收判定每字节的 8 个 bit
		{
			//接收判定每 bit 的 560us 低电平
			time = GetLowTime();
			if ((time < (340 * 2)) || (time > (780 * 2))) //时间判定范围为 340~780us,
			{ //超过此范围则说明为误码,直接退出
				IE0 = 0;
				return;
			}
			//接收每 bit 高电平时间,判定该 bit 的值
			time = GetHighTime();
			if ((time > (340 * 2)) && (time < (780 * 2))) //时间判定范围为 340~780us,
			{ //在此范围内说明该 bit 值为 0
				byt >>= 1; //因低位在先,所以数据右移,高位为 0
			}
			else if ((time > (1460 * 2)) && (time < (1900 * 2))) //时间判定范围为 1460~1900us,
			{ //在此范围内说明该 bit 值为 1
				byt >>= 1; //因低位在先,所以数据右移,
				byt |= 0x80; //高位置 1
			}
			else //不在上述范围内则说明为误码,直接退出
			{
				IE0 = 0;
				return;
			}
		}
		ircode[i] = byt; //接收完一个字节后保存到缓冲区
	}
	irflag = 1; //接收完毕后设置标志
	IE0 = 0; //退出前清零 INT1 中断标志
}
  • infrared.h
#ifndef _INFRARED_H
#define _INFRARED_H

#include <reg52.h>
sbit IR_INPUT = P3^2; //红外接收引脚

extern void InitInfrared();
extern bit irflag; //红外接收标志,收到一帧正确数据后置 1
extern unsigned char ircode[4]; //红外代码接收缓冲区

#endif
  • mian.c
#include <reg52.h>
#include "infrared.h"

unsigned char ledChar[18] = { //共阴极数码管显示字符转换表
	0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 
	0x7F, 0x6F, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71, 
	0x40, 0x00 
};

unsigned char ledBuff[8] = { //数码管显示缓冲区
 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

unsigned char T1RH = 0; //T1 重载值的高字节
unsigned char T1RL = 0; //T1 重载值的低字节

void ConfigTimer1(unsigned int ms);

void main()
{
	EA = 1; //开总中断

	InitInfrared(); //初始化红外功能
	ConfigTimer1(1); //配置 T1 定时 1ms
	PT1 = 1; //配置 T1 中断为高优先级,启用本行可消除接收时的闪烁
	while (1)
	{
		if (irflag) //接收到红外数据时刷新显示
		{
			irflag = 0;
			ledBuff[5] = ledChar[ircode[0] >> 4]; //用户码显示
			ledBuff[4] = ledChar[ircode[0]&0x0F];
			ledBuff[1] = ledChar[ircode[2] >> 4]; //键码显示
			ledBuff[0] = ledChar[ircode[2]&0x0F];
		}
	} 
}

/* 配置并启动 T1,ms-T0 定时时间 */
void ConfigTimer1(unsigned int ms)
{
	unsigned long tmp; //临时变量
 
	tmp = 24000000 / 12; //定时器计数频率
	tmp = (tmp * ms) / 1000; //计算所需的计数值
	tmp = 65536 - tmp; //计算定时器重载值
	tmp = tmp + 13; //补偿中断响应延时造成的误差

	T1RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
	T1RL = (unsigned char)tmp;
	TMOD &= 0x0f; //清零 T1 的控制位
	TMOD |= 0x10; //配置 T1 为模式 1
	TH1 = T1RH; //加载 T1 重载值
	TL1 = T1RL;
	ET1 = 1; //使能 T1 中断
	TR1 = 1; //启动 T1
}

/* 数码管动态扫描刷新函数,需在定时中断中调用 */
void LedScan()
{
	static unsigned char i = 0; //动态扫描的索引
 
	P0 = ledChar[11]; //显示消隐
	P2 = ~(0x80 >> i);
	P0 = ledBuff[i];
	i ++;
	i &= 0x07;
}

/* T0 中断服务函数,执行数码管扫描显示 */
void InterruptTimer0() interrupt 3
{
	TH1 = T1RH; //重新加载重载值
	TL1 = T1RL;
	LedScan(); //数码管扫描显示
}

main.c 文件的主要功能就是把获取到的红外遥控器的用户码和键码信息,传送到数码管上显示出来,并且通过定时器 T0 的 1ms 中断进行数码管的动态刷新。

  • 26
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
步进电机是一种特殊的电机,其特点是精确度高、控制简单、运行平稳等。它通过根据特定的控制信号来控制电机的转动角度和速度。 51单片机是一种常用的单片机,具有成本低、性能较好、易于学习和使用等优点。它是一种非常适合控制步进电机的控制器。 在51单片机控制下,步进电机可以实现精确的位置控制。通过给定相应的输入信号,可以让步进电机按照预定的角度和速度旋转。这种精确控制使得步进电机可以广泛应用于一些需要准确转动位置的系统中。 步进电机的控制方式包括全步进和半步进两种。全步进是指每次控制电机转动一个步进角度,可实现较精确的位置控制。而半步进是指每次控制电机转动半个步进角度,可以实现更高的分辨率和更平滑的转动。 在进行步进电机的控制时,首先需要确定步进电机的型号和驱动方式。然后,根据控制要求,编写相应的程序,并利用51单片机进行控制。具体控制步骤包括初始化步进电机参数、设定目标位置和速度、循环控制电机运行等。 通过51单片机的控制,可以实现步进电机的精确控制。这为许多需要精确位置控制的应用提供了便利,例如打印机、纺织机械、数控机床等。 总之,步进电机是一种精确控制电机转动位置和速度的设备,而51单片机是一种常用的控制器。通过51单片机的编程控制,可以实现步进电机的精确控制,满足不同系统的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

倾晨灬雨曦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值