Sniff is a "Scratch-like" programming language that's designed to help Scratchers move gently from Scratch to more conventional languages. They can start writing programs, without having to learn a new language because Sniff is based on Scratch. They learn a little more about variables, compiling, syntax errors (!), and they can have fun controlling real hardware while they're doing it.

Friday, 20 November 2015

433MHz Remote Control

A lot of Arduino projects that we've done have been about measuring, and sensing the world. That's fun, but that's only half of the potential... We also want to be able to control things. When we put those two parts together things get really exciting.

The main problem of controlling things - particularly high power electrical things is doing it safely. in the past I've done it using a DMX Dimmer pack. I was lucky enough to already have access to plenty of DMX equipment, and using a dimmer pack allows easy control over 4 mains sockets, including (as the name suggests) the ability to dim lights connected to them. Unfortunately a cheap dimmer pack costs about £70, plus you'll need a DMX interface, and cabling on top of that. While its not impossibly expensive it isn't the "pocket money prices" that allow you to throw together a system just to play with it.


However Alastair has been asking me for a while to add support for 433MHz RC remote controls. He pointed out that you could get these plugs for about £5 each, and you can buy the transmitter and receiver as a set for under £2. You'll also need a "real" remote to get started, but you can be up and running for about £10. As there's not physical connection to the mains equipment there's no electrical "risk assessment" issues (though you absolutely shouldn't connect anything to these plugs that you wouldn't be happy to leave running, unattended 24/7 - always consider what would happen if the equipment was turned on unexpectedly, or when you're not there!).

Well I finally got round to getting them running and they're a lot of fun!


There are various versions of these plugs which means that you'll need to do some figuring out and testing to get things working. Some plugs have switches to set a unique number for each plug, while the ones I got are programmable. It can be a bit hit and miss figuring out how a particular device works, bit once you've got the encoding you're good to go!

Start by setting up your plug so it works as its supposed to - its always a good idea to start by following the instructions and get everything working before you start making "improvements".

Decoding Messages

Next we need to plug the receiver (the rectangular board) into an Arduino. Most of the documentation suggests that these can be plugged into a 5V digital input and used to drive an interrupt...  I tried this... it didn't work very well... If you've got an arduino Due (which is 3.3v) then this works, but I could never get the receiver to work reliably when powered by 5V, and at 3.3v the output never exceeded 2.5V. The output pins (there are two - they're wired together, so use either) are analog and represent the signal strength (I think!). They're not going reliably to generate a logic high on a 5V Arduino.

Instead we're going to have to connect the data in to an Analog pin... This works OK, but its slow and means we can't use use efficient interrupt driven version of the code that I first wrote (and couldn't get working!!).

make rc analog input A0
make hi number
make lo number

when start
.make startTime number
.set hi to 0.4
.set lo to 0.1
.forever
..wait until rc>hi
..set startTime to fasttimer
..wait until rc<lo
..wait until rc>hi
..say [(fasttimer-startTime)*1000000]
..
..#We've wasted too much time printing out the duration, so skip next pulse
..wait until rc<lo

These transmitters work by sending a sequence of pulses. Sometimes they send a long pulse followed by a short gap, or sometimes they send a short pulse followed by a long gap... The first part of their trick is that the pulse plus the gap alway take the same amount of time. Or to look at it another way the time between the start of consecutive pulses is always the same. We need to find the time between consecutive pulses.

Here we wait until the pin is greater than 0.4 (2Volts) and then measure the time until the pin goes low and then high again. We do some printing, and then make sure the pin is low before going round again.

If you run this you'll see a lot of random times printed as the receiver searches for a signal. However once you press a button on the remote you should see it lock in pretty quickly, and print out some consistent results. For my transmitter I got a time of about 1200 microSeconds(uS) between pulses. Mixed in with these was a larger value of about 9000uS. This it the "break period" - when the button on the remote is held down it sends the same message over and over with a gap between it. You'll see quite a bit of variation in both these values, but they're not critical so pick something representative.

This is all the information we need to start decoding messages. There are minor variations on the way the data can be encoded, but basically a 0 is a short pulse/long gap while a 1 is long pulse/short gap... The trick is that start of the pulse is the beginning of a bit, and half way through the 1200uS bit period if we read the input we can find the value of the bit. We don't have to worry about pulse durations - just look for the start of a bit, wait half a 600uS and read in the value!

make rc analog input A0

make code string

make codeAvailable boolean
when start
.forever
..if codeAvailable
...say code
...set codeAvailable to no
..wait 0.1 secs


Lets start with the boring/housekeeping bits... We don't want to be printing things out in the main code as this would mess the timing (more!), so we write a script to print out the code whenever its available. Now for the good bits:

when start
.make gotPartial boolean
.make startTime number
.make r string
.forever
..set startTime to fasttimer
..repeat until rc>0.3
...if gotPartial and  fasttimer-startTime >0.005 #break time goes here
....if length of r >8
.....set code to r
.....set codeAvailable to yes
....set r to ""
....set gotPartial to no
..wait 500 microsecs
..if rc>0.3
...set r to join r "1"
..else
...set r to join r "0"
..set gotPartial to yes
..wait until rc<0.1

This is a bit tricky, but basically waits for rc to go high. If this takes longer than 0.005 seconds then that's longer than the 1200uS we're expecting, so we must be between messages. If we've found something so far we make it available to the previous script.

If rc does go high then we wait half a bit period, and then check rc again  - if its high we add 1 to the end of the message, otherwise we add 0. However there's a gotcha... Rc is an analog input, which means the Arduino needs to measure the input voltage which takes time - up to 100uS. We should be taking this reading after 600uS (half of the 1200uS I measured earlier), but we could be up to 100uS late getting to the wait. Similar even if the measurement of starts after 600uS, it wouldn't finish till 700, which is to late.

Using 600uS started to produce some results but they were unreliable. If we want the measurement to represent 600uS we should start it after a 550uS delay so it runs from 550 to 650. On average the wait will start 50uS late, so we use an actual delay of 500uS (bitPeriod/2 -100).

With this we should now start seeing the data values being sent from the controller. In my case my plug is responding to two keys:

On:1010111111000000111011110
Off:1010111111000000111011100


You should note that the code ALWAYS ends in 0. This is a stop bit, and its part of the structure of the system, rather than actual data.

If you're working with kids then you probably want to do most of previous prep work yourself, and give them examples that are set up to actually work. Getting this far can be a bit frustrating - or at least it was for me. From here it gets a LOT easier.

Controlling Stuff

Setting up the transmitter is much easier, as there's and rcSwitch device which does the work, and the Transmitter works when plugged into a standard digital output. Here its connected to pin 5.

make rcSwitch transmit433 device 5

make rcPayload string
make rcBitPeriod number
make rcBreakPeriod number


when start
.set rcBitPeriod to 1200
.set rcBreakPeriod to 9000
.
.forever
..set rcPayload to "101011111100000011101111"
..repeat 10
...tell rcSwitch to "send"
..wait 3 secs
..
..set rcPayload to "101011111100000011101110"
..repeat 10
...tell rcSwitch to "send"
..wait 3 secs

To control our plug we set the bit period and break period to the values we previously measured, then set the payload to be the message we decoded from the remote handset. Do not include  the final 0 stop bit, as this gets added automatically. Typically to make sure the message gets through we send it a few times in rapid succession. In this case 10 times. This code sends the "turn on" button, waits 3 seconds and then sends the turn off button.

when start
.set rcBitPeriod to 750
.set rcBreakPeriod to 1500
.
.forever
..set rcPayload to "0101101010100101"
..repeat 10
...tell rcSwitch to "send"
..wait 3 secs
..
..set rcPayload to "1010010101011010"
..repeat 10
...tell rcSwitch to "send"
..wait 3 secs

The 1200uS bit period is very common but other timings are also used. Recording the messages from a different remote device we found it had a bit period of 750uS and a break period of 1500uS. The payload format was also very different.

Wrap up!

And that's all there is to it! Getting the parameters for your system is a bit tricky, but once you've got those its not to painful to record the messages, and playing them back is really easy. You could write something to check the time from a real time clock and turn the lights on in the evening. You could measure the temperature and humidity and turn a fan on if it gets too hot. When you're ready to get fancy you could program "scenes", where pressing a button dims the lights and turns on the TV at the same time!

In addition to simple remotes/plugs there are a whole range of cheap devices operating on this frequency range - motion sensors, alarms, smoke detectors etc. Go have some fun!


In the released code there's a folder of example code which includes a receiveRC device. However the receive examples all require that the receiver is capable of driving a digital input. For these to work you'll need to either run them on a 3.3V CPU,  or add a level shifting/ buffering circuit. The examples here will work fine on a 5V CPU (Arduino Uno) but the receive code is really only suitable for testing and diagnostic work rather than as a remote.

No comments:

Post a Comment