Linkit One 空调遥控器

使用linkit one 控制空调

这一篇会比较长,会写很多踩过的坑,一些知识,弯路,废话.

如果你想直接看结果,可以直接看小标题Release

Context

这夏天真是热,虽然成都比杭州的温度低7-10度,但是相比较舒适的22-25度来说,还是比较燥热的.

所以空调肯定是要开的.

可是,一直开会比较冷,开一会儿关掉又会比较热,即使空调有定时开定时关的功能,那这个操作在睡觉期间,只能做一次.它并不能智能的开关开关开关……

但是,自己做一个遥控器,就可以啊!

于是就开始着手搞这个了!

My Limitation

我是个纯种的程序员,虽然说数电学的还不错,但是模电基本上都忘光了.所以要我画画板子,计算机算电阻电容,那我还不如直接放弃算了~

之前玩过Arduino,所以我对Arduino还算有点儿了解,所以肯定就选择这个平台了.

因为我们公司自己的智能硬件大部分都涉及到低功耗蓝牙,所以我觉得用蓝牙来进行互动对我来说开发起来也比较方便,所以我就选择了之前参加比赛骗来的Linkit One开发版.它自带了蓝牙、Wifi、GPS、GSM模块(一波广告)

Requirements

DHT22温湿度传感器,IR发射器,IR接收器.

面包版,线,线,线,线,线…

Linkit One,Arduino开发环境, Linkit One SDK.

Knowledge

以下内容基本上是摸索过程中学的,因为我一开始并没有觉得这个东西会那么的复杂

遥控器和空调的通信

一查就知道,他们用一个叫做38khz红外来通信.

那么38khz红外是个啥玩意儿呢?

先上图

irsignal

就是发射器在一定的时间内通过亮灭亮灭的快速切换来发送0,1,0,1

比如说 载波发射0.56ms,不发射0.56ms表示数字信号”0”;载波发射0.56ms,不发射1.68ms表示数字信号”1”

上面说的这个操作只是个例子,不代表所有的空调,电视什么的都用这种方式来表示0和1.

Arduino

这个怎么解释呢?

举个例子吧~

单片机工作起来就是在永远循环执行一个代码块儿.

在Arduino中有void loop()这个函数,在这个函数中把要循环做的事情放进去,就可以让它正常工作了.

比如说把某一个引脚置为高电平,只要简单的写一句DigitalWrite(Pin Number,HIGH);就OK了

读取某个引脚的点平值,只要简单的写一句DigitalRead(Pin Number);就OK了

差不多就这么个操作,基本上不用考虑什么中断、分片、多线程之类的东西

发送指令*

虽然我们知道了如何让IR发射器发送红外信号,但是我们依然不知道这个空调的发射指令是什么~

这个学习指令几年前在机顶盒遥控器上面,就见过了.在拥有了IR接收器之后,他的原理其实蛮简单的.

IR接收器在收到高低点平切换的时候,记录一下时间,就可以知道发射端高低信号的持续时间了.

比如说:

我抓到的关机指令

4360,4410,477,2205,237,266,520,1678,514,1734,484,693,367,527,533,1767,479,735,370,482,539,1956,309,491,630,550,477,2208,2234,473,532,1748,482,563,515,1585,1065,1449,281,1590,1090,1402,347,461,539,2139,208,1542,506,2262,185,283,769,343,536,516,551,522,942,1422,420,512,556,513,1150,1339,331,1584,544,1762,552,430,581,496,565,1133,198,316,846,266,539,489,574,526,807,339,531,1694,503,1729,517,1614,521,1753,456,1645,522,5371,4506,4419,463,1699,726,292,531,1601,838,1442,524,476,576,508,1098,1414,279,501,567,514,1063,1484,309,466,555,510,557,1766,693,1394,538,1135,205,1455,511,503,547,1773,450,1674,517,1719,480,1634,555,486,562,1736,535,1564,540,1758,737,288,499,520,553,540,822,339,522,1663,532,504,1096,200,381,1680,509,1762,436,1650,524,505,571,1044,205,389,753,322,560,527,565,1199,190,260,789,334,512,1625,903,1428,434,1599,1139,1335,316,1592,559

开机指令

4385,4385,471,1690,447,579,527,1600,996,1236,548,498,584,490,8521,561,106,171,65,62,61,64,67,58,62,450,226,562,504,1590,571,528,722,1503,546,1593,839,312,555,481,571,1630,583,1695,528,1577,572,1693,516,1589,560,518,658,1564,553,1582,753,405,535,524,591,491,571,511,771,410,497,1621,563,1747,514,466,579,1601,567,726,457,459,537,509,627,491,557,531,653,473,527,1620,609,489,852,1388,518,1776,437,1712,485,1740,441,5368,4596,4410,400,1570,553,523,556,1700,567,1552,569,526,809,351,491,1634,554,528,560,621,506,1597,594,493,592,786,360,1571,572,1590,657,463,554,1599,566,1721,502,535,549,513,563,1701,506,1607,562,1771,463,1596,552,1806,458,474,580,1582,556,1709,500,510,567,522,558,536,820,320,534,525,564,1611,584,1677,526,488,561,1874,338,519,565,517,569,512,575,520,660,479,547,523,560,1618,735,377,564,1606,576,1685,520,1619,557,1721,505

有了这一坨数据,后面只要控制IR发射器Data引脚*,就可以发送红外指令了!

PWM(脉冲宽度调制)

嗯,这个东西我琢磨了会儿才明白.

一开始我一直不明白,我要这玩意儿干嘛.

引用百度百科里面的解释: 脉冲宽度调制是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中.

不过后面在pj大佬的教导下,我明白了它的意思,简单说就是我要发高电平,就发PWM波;我要发低点平,就不发PWM波.这看起来和AM调制一样,不同的是AM调制是对一个模拟信号进行调制;而我这里用到的是对数字信号进行调制.

我就假设我说明白了~

Dev

对于DHT和蓝牙的开发我就不多写了,因为真的蛮简单的,也没什么大坑.最多吐槽一下Linkit One这个板子对BLE蓝牙的限制吧~这家伙不能修改广播数据(改不了名字、服务UUIDs、制造商数据……),而且外设模式下Notify貌似是没有用的~

好嘞,蓝牙和温湿度都讲完了!下面可以一心一意的讲IR发射的坑了.

Arduino-IRRemote

在我真正开始这个项目的之前,我做了一些估计.查了我需要的各种模块的开发难度,找找有没有什么可以用的库可以直接调用.

DHT温湿度传感器

IR发射接收

美的空调Arduino发射接收

我找到了这三个库,觉得这个项目在元件齐全了之后大概只要一个下午就可以搞定了.

于是,收到IR发射器之后,我就迫不及待的开工了!

~~~/Arduino/libraries/IRremote/irISR.cpp:18:5: error: expected constructor, destructor, or type conversion before '(' token
ISR (TIMER_INTR_NAME)

编译错误!我溯源找到了这个类,发现了ISR()这个函数,并没有写他的来源,说明这个函数可能是板子自带的.于是我就用Arduino UNO来编译相同的代码,编译通过!

天啦噜,Arduino-IRRemote这个库是不支持Linkit One的!

吓得我赶紧去看看那个直接发美的空调指令的库,这个库,也是调用Arduino-IRRemote的

异想天开的做法

然后我就把发射代码搞出来,放到了Arduino UNO上面来运行.竟然可以成功的关闭空调了!

于是我陷入了深深的沉思~

难道说,我得用两颗单片机来做这个简单的小玩意儿??

说实话,我真的这么折腾了一段时间,效果和想象中的并不一样,这可能和模拟电路的知识有关~

手动生成PWM!

回到原始的方法,我开始理解IRRemote发送数据的工作原理

发送原始数据

void  IRsend::sendRaw (unsigned int buf[],  int len,  int hz)
{
    // Set IR carrier frequency
    enableIROut(hz);

    for (int i = 0;  i < len;  i++) {
        if (i & 1)  space(buf[i]) ;
        else        mark (buf[i]) ;
    }

    space(0);  // Always end with the LED off
}

这好像是发高电平

void  IRsend::mark (int time)
{
    TIMER_ENABLE_PWM; // Enable pin 3 PWM output
    if (time > 0) delayMicroseconds(time);
}

这好像是发低电平

void  IRsend::space (int time)
{
    TIMER_DISABLE_PWM; // Disable pin 3 PWM output
    if (time > 0) delayMicroseconds(time);
}

这就当作是配置PWM的频率吧

void  IRsend::enableIROut (int khz)
{
    // Disable the Timer2 Interrupt (which is used for receiving IR)
    TIMER_DISABLE_INTR; //Timer2 Overflow Interrupt

    pinMode(TIMER_PWM_PIN, OUTPUT);
    digitalWrite(TIMER_PWM_PIN, LOW); // When not sending PWM, we want it low
    TIMER_CONFIG_KHZ(khz);
}

TIMER_DISABLE_PWMTIMER_ENABLE_PWM里面调用了很多和ISR,PWM有关的东西,所以这里开始就要重新了

先写个高电平

void mark(int time){
    int t=0;
    boolean isHigh=false;
    while(t<=time){
        isHigh=!isHigh;
        t+=enable_pwm(isHigh);
    }
}

int enable_pwm(boolean high){
    if(high){
        digitalWrite(3,HIGH);
    }else{
        digitalWrite(3,LOW);
    }
    delayMicroseconds(13);
    return 13;
}

通过计算38khz的一个周期是26微秒,所以我们13微秒就要转换一次电平~

再写个低电平

void space(int time){
    digitalWrite(3,LOW);
    delayMicroseconds(time);
}

这个不需要发转电平,所以就轻松一些了~

由于我已经固定了13微秒转换一次,所以也不用实现enableIROut(int khz)这个函数了.

运行!编译通过!发送!IR接收机采集到了IR信号!可是~空调好像没反应~

还有这种操作

气氛一度很尴尬,不过我毕竟只是个普通人,看不到发送的数据长什么样子,所以在添哥大佬的帮助下我使用了高档示波器

这是空调遥控器发出的信号在接受端的样子

right signal

这是我发出的信号在接受端的样子

error signal

尖峰哪儿来的?

检查代码真的没有问题,这个异常我只能归结为硬件问题了,要知道linkit one是一个很高级,很牛逼的硬件,它甚至有多线程的操作,所以,它会不会强行的我把我操作所在的线程给中断了呢?又或者linkit one并没有能力做那么高频的电平转换.

analogWriteAdvance

气氛再一度尴尬,不过添哥大佬在官方文档的Analog I/O中找到了analogWriteAdvance()这个函数!

void analogWriteAdvance(
uint32_t pin, 
uint32_t sourceClock, 
uint32_t clockDivider, 
uint32_t cycle, 
uint32_t dutyCycle
);

而他的例子里面就是生成了一个PWM波!

在经过简单的计算之后,我改写了PWM波的生成和暂停

//Enable 38khz PWM
analogWriteAdvance(PWM_PIN,PWM_SOURCE_CLOCK_13MHZ,PWM_CLOCK_DIV1,342,171);
//Disable 38khz PWM
analogWriteAdvance(PWM_PIN,PWM_SOURCE_CLOCK_13MHZ,PWM_CLOCK_DIV1,342,0);

编译上传运行,,空调开了~(由于这是在公司做的,所以信号序列也是公司空调的序列,所以回到寝室,还得再改一下)

时好时坏的遥控器

回到了寝室,我赶紧把新的数据放进去,对准空调,发了开机指令……再发一次……再发一次

还是没用??不过关机指令倒是可以了

这说明发送是可以的,只是内容还有问题.

在网上搜寻了很久的开关机数据,都不太行,这是我想起了那个美的空调遥控指令的库!

之前,我不能使用它,是因为IRRemote中无法使用Linkit One的PWM波发生器,所以如果我改了那个发射函数,不就OK了嘛!

它需要什么方法我就给什么方法,连名字和参数都一样!

VKIRSender.h

class VKIRSender{
public:
    void mark(int time);
    void space(int time);
    void enableIROut(int hz);
};

VKIRSender.cpp

#include "VKIRSender.h"
#include <Arduino.h>

#define PWM_PIN 3

void VKIRSender::mark(int time){
    analogWriteAdvance(PWM_PIN,PWM_SOURCE_CLOCK_13MHZ,PWM_CLOCK_DIV1,342,171);
    delayMicroseconds(time);
}

void VKIRSender::space(int time){
    analogWriteAdvance(PWM_PIN,PWM_SOURCE_CLOCK_13MHZ,PWM_CLOCK_DIV1,342,0);
    delayMicroseconds(time);
}

void VKIRSender::enableIROut(int hz){

}

在这个库的帮助下,我甚至可以不使用抓来的数据,我甚至可以设定温度,冷热模式,风速……

试验了一下,!成功了

TODO

开发历程差不多就是这样,真的好艰辛~

当然,现在还存在一个比较大的问题,就是发射距离!

我只能在离空调1M的位置,让空调听话,稍微远一点,他就不听话了~

在解决了这个问题之后,就可以配合温湿度传感器,让它智能起来了!

RELEASE

前面废话那么多,这下面才是最有用的~

代码开源了,其实并没有什么很难得地方,只是由于踩了很多坑,所以我觉得公开给大家伙用用,可能还是蛮好的.

Github看,给个Star当然也是极好的~

接线差不多是这个样子的

board