自制电脑红外遥控接收器(PC软解码)

简介: 网上有很多介绍红外遥控接收器制作的文章,但其中大部分是用单片进行红外解码,然后再通过串口或USB把解码后的按键信息传入到PC的。这样的电路制作起来,不仅造价相对偏高,而且需要对单片编程,这会令大部分软件开发爱好者望而却步

网上有很多介绍红外遥控接收器制作的文章,但其中大部分是用单片进行红外解码,然后再通过串口或USB把解码后的按键信息传入到PC的。这样的电路制作起来,不仅造价相对偏高,而且需要对单片编程,这会令大部分软件开发爱好者望而却步。

最近看到一篇仅需要7个简单元器件的红外接收器,只需拿起烙铁,不需硬件编程就可以制作完成,原理图如下:

image.png

 

由原理图我们可知,红外接收头把接收的红外信号转换为高低电平通过串口的DSR管脚传入到PC,PC软件通过对DSR高低电平信号的时间曲线进行分析,从而获得相对应的按键信息。

红外遥控器一般采用脉宽调制的串行码,经38kHz的载频把红外信号发射出去。其编码信息一般由三部分组成:引导码、地址码和数据码。一般信号长度大约100ms左右,持续按键则重复发送(中间会有10ms以上的间歇)。

常态下,红外接收头的输出(OUT)都是高电平,引导码信号首先会令红外接收头输出一个大约10ms左右的低电平(不同遥控器有不同的时延),这可令接收设备从容判定信号的到来,而后面的地址码和数据码其电平高低变化就相对较快了,大概在几十或几百个微秒之间。

PC红外遥控软件一般选用Girder,在使用之前需要安装“SFH-56 plugin for Girder”这个插件(文件名"igor SFH-56P lug.dll"),否则不能正常处理我们这种电路的红外接收器信号。可悲的是我至今没找到这个插件,网上提供的很多链接都是坏的。

即使找到了这个插件,要想在我们自己编写的程序中使用也是困难的,因为Girder并没有为我们开发者提供API接口。

既然Girder能用软件实现红外解码,我们为什么不能呢?凡事都要开动大脑,积极行动才对,下面就是我自己焊接的一个红外接收器(元器件是在中发买的,一共不到10元钱,还富裕好多电阻、电容!)

image.png

 

(图下方的红外遥控器的接收器是基于USB的,仅支持Vista以上版本,并且不支持个人开发,不过今天它终于发挥了它应有的作用。当然用电视或VCD遥控器也是可以的)

硬件有了,但程序该从何编起呢?

1、由于接收到的红外信号在微秒级别中变化,对系统实时性要求较高,所以具备垃圾回收功能,实时性没有保证的C#,似乎完不成这种信号的接收功能,所以我们选择的是VC,由它实现高优先级的线程去进行信号接收。

2、由于红外遥控信号是脉宽调制的串行码,所以我们需要采集信号的宽度,显然采用一般的时钟函数来获取时间间隔是不可行的,因为精度太低,所以我们需用采用多媒体时钟和高精度计时的API函数。

3、一般我们按键持续时间为几秒钟,并且由于按键发出前有一个10ms左右的引导信号,所以我们的程序很容易判断出信号起始点,这样我们一次仅需要接收一定量的原始数据就可以完成初步信号采集工作。

4、对于我们的红外接收程序来说并不需要实际解码出红外信号到底包含了那些具体的信息,只要其能够区分出红外遥控上的各个按键就行。

5、由于红接收器是通过串口RTS管脚供电,且通过DSR传递红外信号的,所以我们的程序即使不接收数据,也要打开串口,不过仅需要处理RTS和DSR管脚的信号即可。

好了,动手去做,下面是用VC实现的一个DLL,其功能就是接收并记录红外信号的持续时间。核心代码如下:

DWORD WINAPI ThreadProc(LPVOID pParam)

{

         LARGE_INTEGER litmp;

         LONGLONG QPart1,QPart2;

         double dfFreq;

    int iTime=0;  //微秒

 

         // 获得计数器的时钟频率

         QueryPerformanceFrequency(&litmp);

         dfFreq = (double)1000000.0/litmp.QuadPart;

            

         DWORD ModemState,oldModemState=MS_DSR_ON;

        

         //EV_BREAK or EV_CTS or EV_DSR or EV_ERR or EV_RING or EV_RLSD or EV_RXCHAR or EV_RXFLAG or EV_TXEMPTY

         //SetCommMask(HSC_COM_Handle,EV_DSR);

         //DWORD EvtMask,dwError;

         //COMSTAT cs;

  

         while(HSC_Thread_RunFlag)

         {

                   //等待DSR信号发生变化

        //WaitCommEvent(HSC_COM_Handle,&EvtMask,&HSC_Ovread);

             //ClearCommError(HSC_COM_Handle,&dwError,&cs);

 

                   //获得DSR的状态

                   GetCommModemStatus(HSC_COM_Handle,&ModemState);

                   ModemState = (ModemState & MS_DSR_ON);

                  

                   if(ModemState == oldModemState)  continue;

                   oldModemState=ModemState;

 

             //清计数

                   InterlockedExchange(&HSC_NUM,0);

      

                   //开始接收数据

                   if(HSC_State == 0 && ModemState == 0)

                   {

                      QueryPerformanceCounter(&litmp);

                QPart1 = litmp.QuadPart;

 

           HSC_State=1;

 

                      //复位计数

                      InterlockedExchange(&HSC_NUM,0);

           InterlockedExchange(&HSC_Index,0);

 

                      //开启定时器

                      HSC_TimerID = timeSetEvent(10,HSC_Accuracy,MMTimer,NULL,TIME_PERIODIC);

 

                      continue;

                   }

 

                   //接收数据状态

                   if(HSC_State == 1)

                   {

             QueryPerformanceCounter(&litmp);

                       QPart2 = litmp.QuadPart;

                            //--

                            if(ModemState == 0)

                            {                          

               iTime = (int)((QPart2-QPart1)*dfFreq);

                            }

                            else

                            {

               iTime = (int)((QPart1-QPart2)*dfFreq);

                            }

 

                            if(HSC_Index < HSC_BufferSize)

                                     *(HSC_Buffer+HSC_Index) = iTime;

                           

                            InterlockedIncrement(&HSC_Index);

            //--

            QPart1=QPart2;

                   }

         }

     return STILL_ACTIVE;

}

如果采用WaitCommEvent函数,你会发现CPU使用时间会很低,不过它会让接收程序无法正常退出,所以只好注释掉该函数了,此时你会发现CPU使用时间会很高。

原始数据一旦采集完毕,剩下的就由C#程序大显身手吧。

C#中DLL的接口函数如下:

     const string DllPath = @"YFHSCollect.dll";

     [DllImport(DllPath)]

     public static extern Int32 HSCStart(Int32 COM, Int32 delay, Int32 BufferSize);

     [DllImport(DllPath)]

     public static extern Int32 HSCEnd();

     [DllImport(DllPath)]

 public static extern Int32 HSCData(int[] intData);

我封装了一个类,一旦有按键信息,就会触发一个Click事件。此外程序还具备自学习功能,并且可以把学习后的结果序列化到一个XML文件中去,这样下次再按键就可以识别出键名了。

主程序中测试代码如下:

public partial class frmMain : Form

    {

        YFHWCollect hw =null;

        int[] hwData = null;

 

        public frmMain()

        {

            InitializeComponent();

            hw = new YFHWCollect(this, 1);

            hw.Click += new YFHWCollect.HWEventHandler(hw_Click);

        }

 

        void hw_Click(object sender, HWEventArgs e)

        {

            string strInfo = "";

            for (int i = 0; i < e.lstData.Count; i++)

            {

                for (int j = 0; j < e.lstData[i].Length; j++)

                {

                    strInfo += e.lstData[i][j].ToString() + " ";

                }

                strInfo += "/r/n";

            }

            txtInfo.Text = strInfo;

            lblKeyName.Text = e.KeyName+ " (" + (e.Interval /10).ToString() + "ms)";

            hwData = e.Data;

            picBar.Refresh();

        }

        private void btnCommand_Click(object sender, EventArgs e)

        {

            if (btnCommand.Text == "开始")

            {

                btnCommand.Text = "停止";

                hw.Start();

            }

            else

            {

                btnCommand.Text = "开始";

                hw.End();

            }

        }

 

        private void btnStudy_Click(object sender, EventArgs e)

        {

            hw.Study(txtKeyName.Text);

        }

 

        private void picBar_Paint(object sender, PaintEventArgs e)

        {

            int width = picBar.Width, height = picBar.Height;

            e.Graphics.DrawLine(new Pen(Color.Gray), 0, height / 2, width, height / 2);

 

            if (hwData != null)

            {

                float Len=0;

                foreach(int l in hwData)

                {

                   Len+=l;

                }

                float dx = width / Len,DX=0;

                Pen p = new Pen(Color.Green);

                float Y=0, Y1=height/4,Y2=(float)(height*3.0/4.0);

                float X=0;

                for(int i=0;i<hwData.Length;i++)

                {

                    Y = ((i % 2)==0 ? Y2:Y1);

                    DX = hwData[i] * dx;

                    e.Graphics.DrawLine(p, X, Y, X + DX, Y);

                    X += DX;

                    e.Graphics.DrawLine(p, X, Y1, X, Y2);

                }

            }

        }

}

测试程序运行结果如下:

image.png

 

(上面显示的数据为高电平和低电平的持续时间(低高低高…),单位为1/10毫秒)

注意事项:

1、红外遥控器按键偶数次和奇数次的编码是不同的,程序需要学习两次,才能正常识别按键信息。

2、普通的USB转串口由于仅连接了2、3、5管脚,所以不能正常使用,对比较好的USB转串口(比如Moxa的三百多一根),虽然所有的管脚都引出了,但是由于是通过USB转换的,所以响应时间很是问题,我就因为这个差一点功亏一篑,幸好把程序又在PC机跑了一遍。

//获得DSR的状态

GetCommModemStatus(HSC_COM_Handle,&ModemState);

上面的指令如果采用的是USB转串口,运行时间会是7ms左右,而用主板自带串口仅是几个微秒,相差实在太大了。所以上面的红外接收器程序在没有自带串口的笔记本上是无法正常工作的。

 

源码下载地址:http://www.sky-walker.com.cn/yefan/SourceCode/YFHSCollectTest.rar

相关文章
|
监控 数据处理
基于STC89C52RC单片机的MODBUS RTU协议从机的实现
基于STC89C52RC单片机的MODBUS RTU协议从机的实现
630 0
|
存储
openzfs draid是什么
openzfs draid是什么
2441 0
openzfs draid是什么
|
5月前
|
人工智能 开发者
2025魔搭开发者大会!来了!
2025魔搭开发者大会!来了!
947 1
|
4月前
|
人工智能 自然语言处理 搜索推荐
AI赋能教育与阿里云通义千问的结合
本简介介绍了AI技术如何赋能教育行业,结合阿里云“通义千问”大模型,助力海豚大数据及人工智能实验平台实现个性化教学、智能答疑与资源优化,推动高校与企业人才培养模式革新,构建终身学习生态体系。
339 1
|
9月前
|
人工智能 自然语言处理 API
快速使用 DeepSeek-R1 满血版
DeepSeek是一款基于Transformer架构的先进大语言模型,以其强大的自然语言处理能力和高效的推理速度著称。近年来,DeepSeek不断迭代,从DeepSeek-V2到参数达6710亿的DeepSeek-V3,再到性能比肩GPT-4的DeepSeek-R1,每次都带来重大技术突破。其开源策略降低了AI应用门槛,推动了AI普惠化。通过阿里云百炼调用满血版API,用户可以快速部署DeepSeek,享受高效、低成本的云端服务,最快10分钟完成部署,且提供免费token,极大简化了开发流程。
191699 31
快速使用 DeepSeek-R1 满血版
|
5月前
|
运维 Ubuntu Linux
Linux重置root用户密码
本文详细介绍了Linux系统中root密码重置的核心技能,涵盖主流发行版如RHEL、CentOS、Debian、Ubuntu、Arch、openSUSE等的实操方法。内容包括通过GRUB引导编辑、单用户模式和Live CD救援三种方式重置密码的具体步骤,适配物理机、虚拟机及云服务器环境。文章分步解析了启动拦截、权限获取和密码重置三大阶段,并提供各发行版的实际操作代码示例,帮助管理员快速解决忘记root密码的问题。
|
10月前
|
机器学习/深度学习 人工智能 自然语言处理
Baichuan-M1-14B:AI 助力医疗推理,为患者提供专业的建议!百川智能开源业内首个医疗增强大模型,普及医学的新渠道!
Baichuan-M1-14B 是百川智能推出的首个开源医疗增强大模型,专为医疗场景优化,支持多语言、快速推理,具备强大的医疗推理能力和通用能力。
711 17
Baichuan-M1-14B:AI 助力医疗推理,为患者提供专业的建议!百川智能开源业内首个医疗增强大模型,普及医学的新渠道!
|
10月前
|
人工智能 运维 监控
别再熬夜调模型——从构想到落地,我们都管了!
本文将以 Qwen2.5 : 7B 为例进行演示,介绍如何通过人工智能平台 PAI实现AI 研发的全链路支持,覆盖了从数据标注、模型开发、训练、评估、部署和运维管控的整个AI研发生命周期。
1289 37
|
算法 关系型数据库 MySQL
揭秘MySQL中的版本号排序:这个超级算法将颠覆你的排序世界!
【8月更文挑战第8天】在软件开发与数据管理中,正确排序版本号对软件更新及数据分析至关重要。因MySQL默认按字符串排序版本号,可能出现&#39;1.20.0&#39;在&#39;1.10.0&#39;之前的不合理情况。解决办法是将版本号各部分转换为整数后排序。例如,使用`SUBSTRING_INDEX`和`CAST`函数从`software`表的`version`字段提取并转换版本号,再按这些整数排序。这种方法可确保版本号按逻辑正确排序,适用于&#39;major.minor.patch&#39;格式的版本号。对于更复杂格式,需调整处理逻辑。掌握此技巧可有效应对版本号排序需求。
459 3
|
运维 算法 物联网
五大智能运维场景
【5月更文挑战第3天】智能运维场景分5类:异常检测、根因诊断、故障自愈、事件预警、效能优化。