Welcome Guest, you are in: Login

wiki.crowe.co.nz

RSS RSS

Navigation





Search the wiki
»

PoweredBy

Mitsubishi Heat Pump

RSS
Modified on Saturday, 06 April 2013 13:51 by Administrator Categorized as Uncategorized
I have a Mitsubishi Heat Pump - model

It has a remote control model RKW502A200

Image

I wanted to see if I could control this with my Arduino but could not find anything that already existed in the community.

In order to read and write the IR signals I downloaded the IR Library written by Ken Shirriff from https://github.com/shirriff/Arduino-IRremote

See this article from the author of the library - probably explains this better than me on the IR part http://www.righto.com/2009/08/multi-protocol-infrared-remote-library.html

The IR Signal

The IR Signal from the Mitsubishi Heat Pump remote sends data and timing data when you press a key on the remote control. When you click a button on the remote all settings are passed to the heat pump at once.

Ken's library decodes this timing and data and needs a buffer of 180 bytes to store it.

The code from Ken Sherriff limits the size of the input buffer to 100 bytes so we will need to change this to 180 in his source code.

Also the timing results from Ken's library can produce values in the timing gaps that are not accurate enough, so we adjust the timer values that get returned.

The Mitsubishi is very dependant on exact timing.

Modify the IRRemote Source Code

I needed to make a few modifications to the IRREMOTE.CPP and IRREMOTE.H files in order to get the codes returned to be accurate and support the long lengths of IR codes from the Mitsubishi Heat Pump.

We need to modify the IRRREMOTE.H file to increase the size of the buffer.

Search for the line #define RAWBUF it is near the end of the file

Set it to

#define RAWBUF 180 // Length of raw duration buffer - NEEEDS TO BE AT LEAST 180 for MITSUBISHI HEAT PUMP

Save the file and now open the file IRREMOTE.CPP

In this file we need to add a new function and modify some existing code - all pretty easy.

Now search for the Interrupt Service code - it starts with

ISR(TIMER_INTR_NAME)

replace the whole function with the following code


unsigned int fixTimer(unsigned int t)
{

	if (t < 20)
		return 8;
	else if (t  < 31)
		return 24;
	else if (t  < 33)
		return 32;
	else

		return t;

}

// TIMER2 interrupt code to collect raw data.
// Widths of alternating SPACE, MARK are recorded in rawbuf.
// Recorded in ticks of 50 microseconds.
// rawlen counts the number of entries recorded so far.
// First entry is the SPACE between transmissions.
// As soon as a SPACE gets long, ready is set, state switches to IDLE, timing of SPACE continues.
// As soon as first MARK arrives, gap width is recorded, ready is cleared, and new logging starts
ISR(TIMER_INTR_NAME)
{
  TIMER_RESET;

  uint8_t irdata = (uint8_t)digitalRead(irparams.recvpin);

  irparams.timer++; // One more 50us tick
  if (irparams.rawlen >= RAWBUF) {
    // Buffer overflow
    irparams.rcvstate = STATE_STOP;
  }
  switch(irparams.rcvstate) {
  case STATE_IDLE: // In the middle of a gap
    if (irdata == MARK) {
      if (irparams.timer < GAP_TICKS) {
        // Not big enough to be a gap.
        irparams.timer = 0;
      } 
      else {
        // gap just ended, record duration and start recording transmission
        irparams.rawlen = 0;
        irparams.rawbuf[irparams.rawlen++] = irparams.timer;
        irparams.timer = 0;
        irparams.rcvstate = STATE_MARK;
      }
    }
    break;
  case STATE_MARK: // timing MARK
    if (irdata == SPACE) {   // MARK ended, record time
      irparams.rawbuf[irparams.rawlen++] = fixTimer(irparams.timer);
      irparams.timer = 0;
      irparams.rcvstate = STATE_SPACE;
    }
    break;
  case STATE_SPACE: // timing SPACE
    if (irdata == MARK) { // SPACE just ended, record it
      irparams.rawbuf[irparams.rawlen++] = fixTimer(irparams.timer);
      irparams.timer = 0;
      irparams.rcvstate = STATE_MARK;
    } 
    else { // SPACE
      if (irparams.timer > GAP_TICKS) {
        // big SPACE, indicates gap between codes
        // Mark current code as ready for processing
        // Switch to STOP
        // Don't reset timer; keep counting space width
        irparams.rcvstate = STATE_STOP;
      } 
    }
    break;
  case STATE_STOP: // waiting, measuring gap
    if (irdata == MARK) { // reset gap timer
      irparams.timer = 0;
    }
    break;
  }

  if (irparams.blinkflag) {
    if (irdata == MARK) {
      BLINKLED_ON();  // turn pin 13 LED on
    } 
    else {
      BLINKLED_OFF();  // turn pin 13 LED off
    }
  }
}

SPECIAL NOTE: The code that I replaced may have changed if Ken has updated his library so the only actual changes to be made are to insert the function


unsigned int fixTimer(unsigned int t)
{

	if (t < 20)
		return 8;
	else if (t  < 31)
		return 24;
	else if (t  < 33)
		return 32;
	else

		return t;

}

and then to modify two lines to include a call to fixTimer()

Search for the following code in ISR(TIMER_INTR_NAME)


irparams.rawbuf[irparams.rawlen++] = irparams.timer;

and replace it with


irparams.rawbuf[irparams.rawlen++] = fixTimer(irparams.timer);

Those small changes just fix up the timing as I was finding the original code was returning timing values of 350, 1550 etc whereas the Mitsubishi liked 400, 1600 values and would not work with timing that was different.

Setting up the hardware to read the IR

Required Parts

  • Arduino of some description - I am using a Nano V3 as they are cheap on EBAY - about $11 USD with free shipping - see EBAY
  • 1 x RSOP4838 IR Receiver - again cheap on EBAY - $0.99 USD with free shipping - see EBAY - see DataSheet
  • Hook-up wires - again cheap on EBAY - $2.00 - $3.00 USD for about 65 pieces in multiple colours - see EBAY
  • Breadboard - again cheap on EBAY - $1.72 USD with free shipping - see EBAY

Note: As with most items I have purchased on EBAY they take about 4 weeks on average to arrive.

Here is a wiring diagram to show how to hook it all up.

Image

Image

and here is an image how I set it up on the Arduino Nano and the bread board.

Image

NOTE: Take note of the pin outs on the IR Receiver - mine was different to the diagram I created.

Setting up the Software

The following sketch allows you to view the bit pattern that is received on the IR Receiver



#include <IRremote.h>

int RECV_PIN = 4;
IRrecv irrecv(RECV_PIN);

decode_results results;

void setup()
{
        Serial.begin(57600);

	// Start the receiver
	irrecv.enableIRIn(); 

}

void dump(decode_results *results) {
	// # of bytes in the response stream
	int count = results->rawlen;
	// We start at the 3rd byte since the first two bytes are the initial header.
	// Byte 0 = ?
	// Byte 1 = 3200
	// Byte 2 = 1600;

	// There is a pair of points for every value - hence why we use the += 2
	for (int i = 3; i < count; i += 2) {

		// There is a pair of data points , the first is the delay and the 2nd the value
		int delay = results->rawbuf[i]*USECPERTICK;

		// If the value is 1200 it is a binary 1 otherwise a binary 0
		if (results->rawbuf[i+1]*USECPERTICK == 1200)
			Serial.print("1");
		else
			Serial.print("0");
	}
	Serial.println("");
}


void loop() {
	// Is there any data for us to display?
	if (irrecv.decode(&results)) {
		dump(&results);
		// Continue
		irrecv.resume(); 
	}
}

In the case of the following settings on the remote control we receive the following bit pattern

Image

FunctionValue
ModeCooling
Temperature18c
Air Flow (Up/Down)Down
Air Flow (Left/Right)Straight Out
Fan SpeedUltra Low

The resulting bit pattern (from bit 3)

01001010011101011100001101100100100110110111010110001010111000110001110001100111100110000

So how do we decode these bits into something meaningful?

Decoding the bit pattern

I have written my decoding in c# but any language could be used including Arduino. Generally we are just extracting different bits from the bit pattern.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public unsafe struct IRPacket
{
    [FieldOffset(0)]
    public fixed byte Start[40];
    // 01001010 01110101 11000011 01100100 10011011  hard coded does not seem to change ever
    
    [FieldOffset(40)]
    public fixed byte UpDownSwingManual2sCompiment[1];
    [FieldOffset(41)]
    public fixed byte Allergon2sCompliment[1];
    [FieldOffset(42)]
    public fixed byte Unknown42[2]; // Both 11  (2s compilment to offset 50)
    [FieldOffset(44)]
    public fixed byte LeftRightSwing2sCompliment[4];

    [FieldOffset(48)]
    public fixed byte UpDownSwingManual[1];
    [FieldOffset(49)]
    public fixed byte Allergon[1];
    [FieldOffset(50)]
    public fixed byte Unknown50[2];// Both 00 - 2s compilment to offset 42)
    [FieldOffset(52)]
    public fixed byte LeftRightSwing[4];

    [FieldOffset(56)]
    public fixed byte Unknown56[3]; // 111 - 2s compliment to offset 64)
    [FieldOffset(59)]
    public fixed byte UpDownSwing2sCompliment[2];   
    [FieldOffset(61)]
    public fixed byte FanSpeed2sCompliment[3];

    [FieldOffset(64)]
    public fixed byte Unknown64[3]; // 000 - 2s compliment to offset 56)
    [FieldOffset(67)]
    public fixed byte UpDownSwing[2];
    [FieldOffset(69)]
    public fixed byte FanSpeed[3];

    [FieldOffset(72)]
    public fixed byte Mode2sCompliment[3];
    [FieldOffset(75)]
    public fixed byte OnOff2sCompliment[1];
    [FieldOffset(76)]
    public fixed byte Temp2sCompliment[4];

    [FieldOffset(80)]
    public fixed byte Mode[3];
    [FieldOffset(83)]
    public fixed byte OnOff[1];
    [FieldOffset(84)]
    public fixed byte Temp[4];
   }

The data stream appears to always start with 40 bytes of the same pattern - I have never seen it change.

public fixed byte Start [40];

01001010 01110101 11000011 01100100 10011011

Maybe Mitsubishi uses this to identify the device? as Ascii it resolves to is Ju_dø which means nothing to me. But we can just know that it does not change.

The next two 8 bit blocks are:


    [FieldOffset(40)]
    public fixed byte UpDownSwingManual2sCompiment[[1]];
    [FieldOffset(41)]
    public fixed byte Allergon2sCompliment[1];
    [FieldOffset(42)]
    public fixed byte Unknown42[2]; // Both 11  (2s compilment to offset 50)
    [FieldOffset(44)]
    public fixed byte LeftRightSwing2sCompliment[4];

    [FieldOffset(48)]
    public fixed byte UpDownSwingManual[1];
    [FieldOffset(49)]
    public fixed byte Allergon[1];
    [FieldOffset(50)]
    public fixed byte Unknown50[2];// Both 00 - 2s compilment to offset 42)
    [FieldOffset(52)]
    public fixed byte LeftRightSwing[4];

You will notice I have two's compliment here - what that means is that say you have the following bit pattern

11110000

The two's compliment is simply

00001111

Or the reverse bit pattern.

The first byte is this

public fixed byte UpDownSwingManual2sCompiment[1];

This just means in c# that it is a single byte[1] or 1 character long, some you will see have [40] for 40 bytes or [2], [4] etc..

To make this a bit easier we will add a space every 8 bits to the data we received.

01001010 01110101 11000011 01100100 10011011 01110101 10001010 11100011 00011100 01100111 10011000 0

Remember the first 40 bits are the same so lets remove them and we are left with this

01110101 10001010 11100011 00011100 01100111 10011000 0

So the first bit above is a 0 - this matches the UpDownSwingManual2sCompiment value, notice the 2nd block contains a 1 - ie the reverse of the first value.

So this is how you determine what value does what

OffsetName# of Bits ValueComment
40UpDownSwingManual2sCompiment10
41Allergon2sCompliment11
42Unknown42211(2s compilment to offset 50)
44LeftRightSwing2sCompliment40101
OffsetName# of Bits ValueComment
48UpDownSwingManual11
49Allergon10
50Unknown50200(2s compilment to offset 42)
52LeftRightSwing41010

Now we just proceed though all the data to process its values as we have done here. But how do we decode LeftRightSwing for example? it is 4 bytes long!

In my c# code I have created an enumeration of the different values that correlate to different settings:


public enum IRCommandAirFlowLeftRight
    {
        Fingers = 8,
        AutomaticLeftRight =4,
        FixedCurrent = 0,
        FixedLeft = 12,
        FixedLeftStraight = 2,
        FixedStraight = 10,
        FixedRightStraight = 6,
        FixedRight = 14,
        FixedOuter = 1,
        FixedInner = 9
    }

So you can see from above we have created a number of constant values for the different types of swings available for the Left/Right swing.

In our case the bit pattern we had is 1010 which is binary for decimal 10

The way binary numbers are created is like this little table below.

128 64 32 16 8 4 2 1

You then place your bits right aligned to this little table so we end up with

1286432168421
1010

So we simply add up 8 + 2 = 10

And from the enumeration we see that 10 decimal means FixedStraight

Here is a list of all the enumerations I have found:



public enum IRCommandMode
    {
        Auto = 0,
        Cool = 4,
        Heat = 1,
        Dry = 2,
        Air =6
    }

public enum IRCommandFanSpeed
    {
        Auto = 0,
        Hi = 1,
        Medium = 6,
        Low = 2,
        UltraLow = 4,
        HighPower = 3,
        Economy = 7
    }

public enum IRCommandAirFlowUpDown
    {
        Fingers =2,
        AutomaticUpDown=1,
        Fixed=0,
        FixedHorizontal=3,
        FixedHorizontalThreeQuarters=5,
        FixedHorizontalHalf=6,
        FixedHorizontalOneQuarter=4,
        FixedDown=7
    }

public enum IRCommandAirFlowLeftRight
    {
        Fingers = 8,
        AutomaticLeftRight =4,
        FixedCurrent = 0,
        FixedLeft = 12,
        FixedLeftStraight = 2,
        FixedStraight = 10,
        FixedRightStraight = 6,
        FixedRight = 14,
        FixedOuter = 1,
        FixedInner = 9
    }

So using this we can determine what was sent in the IR signal and then we can use this information to rebuild a set of bits to control the heat pump.

Image
to be continued.....

ScrewTurn Wiki version 3.0.4.560. Some of the icons created by FamFamFam.