草庐IT

51单片机 | 红外遥控实验

Drill_ 2023-12-23 原文

文章目录


  这一节来介绍一种无线通信技术–红外遥控通信。我们开发板标配了一个一体化红外接收头和红外遥控器,我们来学习如何使用 51 单片机解码红外遥控器的信号。本节使用 51 单片机的 外部中断功能来解码红外遥控器的编码信号。本节要实现的功能是:使用外部中断功能将遥控器键值编码数据解码后通过数码管显示。

一、红外遥控介绍

1.红外线简介

  人的眼睛能看到的可见光按波长从长到短排列,依次为红、橙、黄、绿、青、蓝、紫。其中红光的波长范围为 0.62~0.76μm;紫光的波长范围为 0.38~0.46μm。比紫光波长还短的光叫紫外线,比红光波长还长的光叫红外线。红外线遥控就是利用波长为0.76~1.5μm 之间的近红外线来传送控制信号的。

2.红外遥控的原理

  红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。
  由于红外线遥控不具有像无线电遥控那样穿过障碍物去控制被控对象的能力,所以,在设计红外线遥控器时,不必要像无线电遥控器那样,每套(发射器和接收器)要有不同的遥控频率或编码(否则,就会隔墙控制或干扰邻居的家用电器),所以同类产品的红外线遥控器,可以有相同的遥控频率或编码,而不会出现遥控信号“串门”的情况。这对于大批量生产以及在家用电器上普及红外线遥控提供了极大的方便。由于红外线为不可见光,因此对环境影响很小,再由红外光波动波长远小于无线电波的波长,所以红外线遥控不会影响其他家用电器,也不会影响临近的无线电设备。
  红外遥控通信系统一般由红外发射装置和红外接收设备两大部分组成。

2.1 红外发射装置

  红外发射装置,也就是通常我们说的红外遥控器是由键盘电路、红外编码电路、电源电路和红外发射电路组成。红外发射电路的主要元件为红外发光二极管。它实际上是一只特殊的发光二极管;由于其内部材料不同于普通发光二极管,因而在其两端施加一定电压时,它便发出的是红外线而不是可见光。目前,大量的使用的红外发光二极管发出的红外线波长为 940nm 左右,外形与普通发光二极管相同。红外发光二极管有透明的,还有不透明的,在我们的红外遥控器上可以看到这个红外发光二极管。红外遥控器和红外发光二极管如下图所示:

  通常红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的方式传送二进制编码,常用的载波频率为 38kHz,这是由发射端所使用的 455kHz晶振来决定的。在发射端要对晶振进行整数分频,分频系数一般取 12,所以455kHz÷12≈37.9kHz≈38kHz。也有一些遥控系统采用 36kHz40 kHz56 kHz等,一般由发射端晶振的振荡频率来决定。所以,通常的红外遥控器是将遥控信号(二进制脉冲码)调制在 38KHz 的载波上,经缓冲放大后送至红外发光二极管,转化为红外信号发射出去的。
  二进制脉冲码的形式有多种,其中最为常用的是 NEC ProtocolPWM 码(脉冲宽度调制)和 Philips RC-5 ProtocolPPM 码(脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制)。如果要开发红外接收设备,一定要知道红外遥控器的编码方式和载波频率,我们才可以选取一体化红外接收头和制定解码方案。我们配套的红外遥控器使用的是 NEC 协议,其特征如下:

  1. 8 位地址和 8 位指令长度;
  2. 地址和命令 2 次传输(确保可靠性)
  3. PWM 脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
  4. 载波频率为 38Khz
  5. 位时间为 1.125ms 或 2.25ms;

  NEC 码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而红外接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。所以可以通过计算高电平时间判断接收到的数据是 0 还是 1。NEC 码位定义时序图如下图所示:

  NEC 遥控指令的数据格式为:引导码、地址码、地址反码、控制码、控制反码。引导码由一个 9ms 的低电平和一个 4.5ms的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)。数据格式如下:

  NEC 码还规定了连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平+97.94ms 高电平组成),如果在一帧数据发送完毕之后,红外遥控器按键仍然没有放开,则发射连发码,可以通过统计连发码的次数来标记按键按下的长短或次数。

2.2 红外接收设备

  红外接收设备是由红外接收电路、红外解码、电源和应用电路组成。红外遥控接收器的主要作用是将遥控发射器发来的红外光信号转换成电信号,再放大、限幅、检波、整形,形成遥控指令脉冲,输出至遥控微处理器。近几年不论是业余制作还是正式产品,大多都采用成品红外接收头。成品红外接收头的封装大致有两种:一种采用铁皮屏蔽;一种是塑料封装。均有三只引脚,即电源正( VDD)、电源负(GND)和数据输出(VOUT)。其外观实物图如下图所示:

  正对接收头的凸起处看,从左至右,管脚依次是 1:VOUT2:GND3:VDD
  由于红外接收头在没有脉冲的时候为高电平,当收到脉冲的时候为低电平,所以可以通过外部中断的下降沿触发中断,在中断内通过计算高电平时间来判断接收到的数据是 0 还是 1

  • 为什么配置外部中断?
    因为遥控器什么时候按下是未知的,但是遥控器一按下就会产生下降沿,如果配置外部中断的下降沿功能,只要一按下就会产生下降沿触发进入外部中断,再进行解码。

二、硬件设计

  本实验使用到硬件资源如下:

  • 动态数码管
  • 红外接收头和遥控器

  动态数码管电路在前面章节都介绍过,这里就不再重复。红外遥控器和接收头是一体的,内部结构不用去管它。下面来看下开发板上红外接收模块电路,如下图所示:

  从上图中可知,红外接收头的输出管脚接至单片机 P3.2 管脚上,为了保证红外接收头输出管脚默认为高电平,需外接一个 10K 上拉电阻,可在上图并没有看到有上拉电阻,在前面介绍最小系统时已知道,单片机 IO 口都增加了 10K 上拉电阻,所以此处可省略。

三、软件设计

  本节所要实现的功能是:数码管上显示红外解码遥控器键值
  程序框架如下:

  • 编写公共文件
    打开public.c,代码如下:

    #include "public.h"
    
    /*
    函 数 名 : delay_10us
    函数功能 : 延时函数,ten_us=1 时,大约延时 10us
    输 入 : ten_us
    输 出 : 无
    */
    void delay_10us(u16 ten_us)
    {
    	while(ten_us--);
    }
    
    /*
    函 数 名 : delay_ms
    函数功能 : ms 延时函数,ms=1 时,大约延时 1ms
    输 入 : ten_us
    输 出 : 无
    */
    void delay_ms(u16 ms)
    {
    	u16 i,j;
    	for (i=ms;i>0;i--)
    	{
    		for (j=110;j>0;j--);
    	}
    }
    

    其头文件public.h,代码如下:

    /*
    头文件作用如下:包含头文件,定义全局变量,声明函数
    */
    #ifndef _public_H
    #define _public_H
    
    #include "reg52.h"
    
    //对系统默认数据类型进行重定义 
    typedef unsigned char u8;
    typedef unsigned int u16;
    
    //函数声明
    void delay_10us(u16 ten_us);
    void delay_ms(u16 ms);
    
    
    #endif
    
  • 编写数码管显示功能
    打开smg.c,代码如下:

    #include "smg.h"
    
    //使用数组保存共阴极数码管0-F的段码数据
    u8 gsmg_code[17] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
    					0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
    
    
    /*
    函 数 名 : smg_display
    函数功能 : 动态数码管显示
    输入 : 无
    输出 : 无
    */
    void smg_display(u8 dat[],u8 pos)
    {
    	u8 i = 0;
    	u8 pos_temp = pos - 1; //位置与位选进行统一
    	for (i=pos_temp;i<8;i++)
    	{
    		//位选
    		switch (i)
    		{
    			case 0 : LSC = 1;LSB = 1;LSA = 1;break;
    			case 1 : LSC = 1;LSB = 1;LSA = 0;break;
    			case 2 : LSC = 1;LSB = 0;LSA = 1;break;
    			case 3 : LSC = 1;LSB = 0;LSA = 0;break;
    			case 4 : LSC = 0;LSB = 1;LSA = 1;break;
    			case 5 : LSC = 0;LSB = 1;LSA = 0;break;
    			case 6 : LSC = 0;LSB = 0;LSA = 1;break;
    			case 7 : LSC = 0;LSB = 0;LSA = 0;break;
    		}
    		//给当前位传送段选数据
    		SMG_A_DP_PORT = dat[i-pos_temp];
    		delay_10us(100);  //延时1ms,循环一轮8ms,小于肉眼可区分的24ms
    		SMG_A_DP_PORT =0x00; //消音,消除上一次数码管段选数据对下一次的影响
    	}
    }
    

    其头文件smg.h,代码如下:

    #ifndef _smg_H
    #define _smg_H
    
    #include "public.h"
    
    //使用宏定义数码管段码口
    #define SMG_A_DP_PORT P0
    //定义数码管位选信号控制管脚
    sbit LSA = P2^2;
    sbit LSB = P2^3;
    sbit LSC = P2^4;
    
    extern u8 gsmg_code[17];
    
    void smg_display(u8 dat[],u8 pos);
    
    #endif
    
  • 编写红外解码函数

  • 编写主函数

1.红外初始化函数

  打开 ired.c 文件,红外初始化代码如下:

/*
* 函 数 名 : ired_init
* 函数功能 : 红外端口初始化函数,外部中断 0 配置
* 输 入 : 无
* 输 出 : 无
*/
void ired_init(void)
{
	IT0=1; //下降沿触发
	EX0=1; //打开中断 0 允许
	EA=1; //打开总中断
	IRED=1; //初始化端口
}

因为我们使用外部中断 0 来解码红外遥控数据,所以需初始化配置外部中断0。初始化配置在外部中断实验章节已详细介绍,此处不再重复。

2.红外解码函数

  初始化外部中断后,中断就已经开启了,当 P32 引脚来一个下降沿,就会触发一次中断,在中断内我们可以计算高电平时间,通过高电平时间判断是否进入引导码及数据 0 和 1。具体代码如下:

#include "ired.h"

u8 gired_data[4];  //保存地址码、地址反码、控制码、控制反码

/*
* 函 数 名 : ired_init
* 函数功能 : 红外端口初始化函数,外部中断 0 配置
* 输 入 : 无
* 输 出 : 无
*/
void ired_init(void)
{
	IT0=1; //下降沿触发
	EX0=1; //打开中断 0 允许
	EA=1; //打开总中断
	IRED=1; //初始化端口
}

/*
* 函 数 名 : ired  
* 函数功能 : 当 P32 引脚来一个下降沿,就会触发一次中断,在中断内我们可以计算高电平时间,通过高电平时间判断是否进入引导码及数据 0 和 1。
* 输 入 : 无
* 输 出 : 无
*/
void ired() interrupt 0 //外部中断0服务函数
{
	u8 ired_high_time = 0; //高电平时间计数
	u16 time_cnt = 0; //时间计数
	u8 i=0;
	u8 j=0;

	if (IRED == 0)//引导码低电平的判断
	{
		time_cnt = 1000;
		//等待引导信号 9ms 低电平结束,若超过 10ms,强制退出
		while ((!IRED) && (time_cnt))
		{
			delay_10us(1); //延时10us
			time_cnt--;
			if (time_cnt == 0) return;
		}
		
		//引导信号 9ms 低电平已过,进入 4.5ms 高电平
		if (IRED)
		{
			time_cnt = 500;
			//等待引导信号 4.5ms 高电平结束,若超过 5ms 强制退出
			while (IRED && time_cnt)
			{
				delay_10us(1);
				time_cnt--;
				if (time_cnt == 0)return;
			}

			//根据高电平的时间,循环判断每一位数据是0还是1,
			for (i=0;i<4;i++) //循环 4 次,读取 4 个字节数据
			{
				for (j=0;j<8;j++) //循环 8 次读取每位数据即一个字节
				{
					time_cnt = 600;
					//等待数据 1 或 0 前面的 0.56ms结束,若超过 0.6ms 强制退出
					while ((IRED == 0) && time_cnt)
					{
						delay_10us(1);
						time_cnt--;
						if(time_cnt==0)return;
					}

					time_cnt = 20;
					//等待数据 1 或 0 后面的高电平结束,若超过 2ms 强制退出
					while (IRED)
					{
						delay_10us(10);//约 0.1ms
						ired_high_time++;
						if(ired_high_time>20)return;
					}
					gired_data[i]>>=1;//先读取的为低位,然后是高位,需要移位
					if(ired_high_time>=8)//如果高电平时间大于 0.8ms,数据则为1,否则为 0
						gired_data[i]|=0x80;
					ired_high_time=0;//重新清零,等待下一次计算时间
				}		
			}
		}
		//校验控制码与反码,错误则返回
		if (gired_data[2] != ~gired_data[3])
		{
			for (i=0;i<4;i++)
				gired_data[i] = 0;
			return;
		}

	}
		
}

  进入中断函数,表示已来下降沿,然后判断管脚是否为低电平,如果为低电平则首先判断引导信号,根据前面 NEC 协议可知,引导信号有 9ms 的低电平和4.5ms 的高电平,因此通过 time_cnt赋值 1000,然后在 while 循环内判断,time_cnt 每递减一次约 10us,1000 次则为 10ms,在解码时,这个时间要适当放宽一点范围,因为不同传感器性能会有差异,所以此处以 10ms 的低电平为界限,如果超过 10ms 则强制退出,防止系统死机。判断完引导信号的低电平,接着判断高电平,实现方法一样。当引导信号判断完成后进入地址码、地址反码、控制码及控制反码共 4 个字节的数据判断,也就是数据 0 和 1 的判断,实现方法也是和前面判断引导信号一样,这里使用到了嵌套循环,外层循环次数是 4,表示读取 4 个字节,内层循环次数是 8,表示读取每个字节的 8 位。注意,红外遥控解码数据是从低位开始,最后是高位。最后将读取的 4 个字节数据存储在全局变量数组 gired_data 中,外部可直接使用这四个字节。

3.主函数

  打开 main.c 文件,代码如下:

/*
实验名称:红外遥控实验
接线说明:
实验现象:下载程序后,数码管上显示数码管上显示红外解码遥控器键值
注意事项:红外接收头凸起处要与 PCB 板接口凸起丝印处对应
*/
#include "public.h"
#include "smg.h"
#include "ired.h"
	
/*
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*/
void main(void)
{
	u8 ired_buf[3];
	//红外初始化
	ired_init();

	while (1)
	{
		ired_buf[0]=gsmg_code[gired_data[2]/16];//将控制码高 4 位转换为数码管段码
		ired_buf[1]=gsmg_code[gired_data[2]%16];//将控制码低 4 位转换为数码管段码
		ired_buf[2]=0X76;//显示 H 的段码
		smg_display(ired_buf,6);
	}
}

四、实验现象

  使用 USB 线将开发板和电脑连接成功后(电脑能识别开发板上 CH340 串口),把编译后产生的.hex 文件烧入到芯片内,实现现象如下:数码管上显示红外解码遥控器键值。

注意:红外接收头的凸起位置需对到 PCB 板丝印凸出位置,否则功能无法正常实现。出厂时的遥控器电池盖内都有一张绝缘片,使用时必须将此绝缘片拿掉。还有键值码不是大家所想的按键上的数字字符码,这个要区分开。

  实验说明:我们配的遥控器所有键的地址码与反码都是相同的,不同的是控制码和控制反码,其实知道了控制码就知道了控制反码,所以如果要使用红外遥控控制其他设备,可以通过区分控制码来实现。
  如果程序下载了,能显示“00H”,按键时却没有反应,首先检查遥控器是否拔下绝缘片。如果要检查遥控器好坏,可以在光线暗的地方,打开手机摄像机,将遥控器前方的发射灯对准手机摄像头,按下按键,在手机上是否能看见灯闪,来判断遥控器好坏。

有关51单片机 | 红外遥控实验的更多相关文章

  1. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  2. 网络实验之RIPV2协议(一) - 2

    一、RIPV2协议简介  RIP(RoutingInformationProtocol)路由协议是一种相对古老,在小型以及同介质网络中得到了广泛应用的一种路由协议。RIP采用距离向量算法,是一种距离向量协议。RIP-1是有类别路由协议(ClassfulRoutingProtocol),它只支持以广播方式发布协议报文。RIP-1的协议报文无法携带掩码信息,它只能识别A、B、C类这样的自然网段的路由,因此RIP-1不支持非连续子网(DiscontiguousSubnet)。RIP-2是一种无类别路由协议(ClasslessRoutingProtocol),支持路由标记,在路由策略中可根据路由标记对

  3. micropython复现经典单片机项目(二)可视化音频 频谱解析(基本搞定) - 2

    本人是音乐爱好者,从小就特别喜欢那个随着音乐跳动的方框效果,就是这个:arduino上一大把对,我忍你很久了,我就想用mpy做,全网没有,行我自己研究。果然兴趣是最好的老师,我之前有篇博客专门讲音频,有兴趣的可以回顾一下。提到可视化频谱,必然绕不开fft,大学学过这玩意,当时一心玩,老师讲的一个字都么听进去,网上教程简略扫了一下,大该就是把时域转频域的工具,我大mpy居然没有fft函数,奶奶的,先放着。音频信息如何收集?第一种傻瓜式的ADC,模拟转数字,原始粗暴,第二种,I2S库,我之前博客有讲过,数据是PCM编码。然后又去学PCM编码,一学豁然开朗,舒服,以代码为例:audio_in=I2S

  4. 第1部分 实验拓扑、终端服务器 - 2

    目录1.1访问Cisco路由器的方法1.1.1通过Console口访问路由器1.1.2通过Telnet访问路由器1.1.3终端访问服务器1.2终端访问服务器配置命令汇总1.1访问Cisco路由器的方法    路由器没有键盘和鼠标,要初始化路由器需要把计算机的串口和路由器的Console口进行连接。访问Cisco路由器的方法还有Telnet、WebBrowser和网络管理软件(如CiscoWorks)等,本节讨论前2种。1.1.1通过Console口访问路由器    计算机的串口和路由器的Console口是通过反转线(Rollover)进行连接的,反转线的一端接在路由器的Console口上,另一

  5. 【操作系统实验】Ubuntu Linux 虚拟机用户管理 - 2

    文章目录一、用户二、用户分类1、普通用户2、超级用户3、系统用户三、用户相关文件1、/etc/passwd文件2、/etc/shadow文件四、用户管理命令1、useradd2、adduser3、passwd4、usermod5、userdel一、用户Linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户都必须先向系统管理员申请一个账号,然后以这个账号的身份进入系统。在Linux系统中,任何文件都属于某一特定用户,而任何用户都隶属于至少一个用户组。用户名(username):每个用户账号都拥有一个惟一的用户名和各自的口令。用户在登录时键入正确的用户名和口令后,就能够进入系

  6. 51单片机——74HC595的应用(SPI实践) - 2

    目录SPI总线SPI总线概述 SPI总线分类SPI优点及缺点SPI接口硬件原理SPI四种工作模式 74HC595应用74HC595芯片概述74HC595封装及管脚功能74HC595工作原理 ​编辑 74HC595串行转并行点亮LED灯 程序实现  Proteus运行结构示意图SPI总线SPI总线概述 SPI(SerialPeripheralinterface):串行外围设备接口 用途:用来在微控制器与外围设备芯片之间实现数据交换 特点:高速、全双工、同步 SPI总线分类四线制全双工SPI(同时收发)MISO    主机输入/从机输出MOSI    主机输出/从机输入SCLK   串行时钟CS或

  7. 【毕业设计】基于单片机的智能温控农业大棚系统 - 物联网 stm32 - 2

    文章目录1简介2绪论2.1课题背景与目的3系统设计详细设计描述3.2硬件部分温度测量电路其他电路部分3.3软件部分主程序子系统程序温湿度程序流程键盘显示子程序3.4实现效果3.5部分相关代码4最后1简介Hi,大家好,这里是丹成学长,今天向大家介绍一个单片机项目基于单片机的智能温控农业大棚系统大家可用于课程设计或毕业设计单片机-嵌入式毕设选题大全及项目分享:https://blog.csdn.net/m0_71572576/article/details/1254090522绪论2.1课题背景与目的近年来我国的温室控制取得了长足的进步,首先在温室群控制方面,进行了初步的探索和理论研究,其次在温室

  8. AT24C04、AT24C08、AT24C16系列EEPROM芯片单片机读写驱动程序 - 2

    一、概述在之前的一篇博文中,记录了AT24C01、AT24C02芯片的读写驱动,先将之前的相关文章include一下:1.IIC驱动:4位数码管显示模块TM1637芯片C语言驱动程序2.AT24C01/AT24C02读写:AT24C01/AT24C02系列EEPROM芯片单片机读写驱动程序本文记录分享AT24C04、AT24C08、AT24C16芯片的单片机C语言读写驱动程序。二、芯片对比介绍型号容量bit容量byte页数字节/页器件寻址位可寻址器件数WordAddress位数/字节数备注AT24C044k5123216A2A149/1WordAddress使用P0位AT24C088k1024

  9. 基于51单片机、DS1302时钟模块的电子闹钟设计 - 2

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录一、设计原理1.DS1302介绍2.闹钟音乐播放原理二、程序设计1.DS1302.h2.ds1302.c3.music.h4.main.c三、电路图四、运行结果1.proteus仿真2.开发板实验五、总结六、附件提示:以下是本篇文章正文内容,下面案例可供参考一、设计原理1.DS1302介绍DS1302是美国DALLAS公司推出的一种高性能、低功耗、带RAM的实时时钟电路,它可以对年、月、日、周、时、分、秒进行计时,具有闰年补偿功能,工作电压为2.0V~5.5V。该芯片采用普通32.768kHz晶振,DS1302工作时功耗很

  10. [蓝桥杯单片机]学习笔记——串口通信的基本原理与应用 - 2

    目录一、原理部分1、什么是串行通信(1)并行通信与串行通信(2)串行通信的制式(3)串行通信的主要方式  2、配置串口(1)SCON和PCON:串行口1的控制寄存器(2)SBUF:串行口数据缓冲寄存器 (3)AUXR:辅助寄存器​编辑(4)ES、PS:与串行口1中断相关的寄存器(5)波特率设置  3、串口框架编写二、程序案例一、原理部分1、什么是串行通信(1)并行通信与串行通信微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:并行通信和串行通信。并行通信:数据的各位同时发送与接收,每个数据位使用一条导线,这种方式传输快,但是需要多条导线进行信号传输。串行通信:数据一位一

随机推荐