Introduction: ATtiny85 RF Remote Control
NOTE: My Instructable "Virtual Hide-and-Seek Game" shows how to use this type of remote with an RXC6 module which automatically decodes the message.
As I mentioned in a previous Instructable I recently started playing with some ATtiny85 chips. The initial project I had in mind was to make an RF remote control that could operate on a coin battery. I needed to go with a raw chip because none of the Arduinos I have can meet both the need for very low power and relatively small size. A modified LilyPad came close but the chip is a better answer. The idea was not so much to duplicate an existing remote but to demonstrate how you can make up your own transmitter and receiver set. Besides being a fun learning project, it also allows you to create your own “secret” code combination. I put “secret” in quotes because it’s pretty easy to crack these simple codes.
Step 1: RF Message Format
For this project I chose to replicate the signals for one of my Etekcity RF wireless switches (refer to my Instructable on those modules). I did that because I was able to verify that my transmitter works with the Etekcity receiver and that my receiver works with the Etekcity remote. I also happen to know exactly what the correct codes and format are for those devices because I captured them previously. Refer to my “Arduino RF Sensor Decoder” Instructable for the code capture sketch.
The codes and formats for the Etekcity outlets are very typical of inexpensive RF devices. I have cheap security devices that use very similar formats with just some timing variations. The message length is a convenient 24 bits with a long start bit and a short stop bit. You can easily modify the code to add more bytes of data and to change the timing of the sync and data bits. Again, this sketch is just a starting template.
Step 2: Hardware
The transmitter runs on a coin battery (2032) so low power consumption is key. Most of that is accomplished in the software but it is helped by the fact that the ATtiny85 normally runs on the 1-MHz internal clock. The rule is that lower clock frequencies require less power and 1-MHz is perfect for the transmitter logic.
The actual RF transmitter module I like to use is an FS1000A that is commonly available. It comes in both 433-MHz and 315-MHz versions. The software doesn’t care which one you use, but you need to make sure that the receiver board operates at the same frequency. Most of my projects use 433-MHz devices because that is what is used by the various inexpensive wireless devices I have accumulated. The transmitter board layout shown in the picture fits nicely into an old pill bottle. It’s not pretty but good enough for a proof-of-concept.
The receiver is on a solderless breadboard because its only purpose is to show how to receive signals and how to turn something on/off based on the codes received. It uses an LED to indicate on/off status but you could replace that with a relay driver, etc. Any Arduino can be used for the receiver because it doesn’t need to run off of a battery. If size is still a consideration you can use another ATtiny85 chip. The key is that the ATtiny85 needs to run at 8-MHz in the receiver. Refer to my earlier ATtiny85 Instructable for a simple sketch that verifies that you have successfully changed the internal clock to 8-MHz. At the end of my Instructable on sensor decoding I include an Arduino Nano version of the receiver software. It’s identical to the ATtiny85 version included here except for a couple of chip register differences.
As I detailed in my earlier RF Instructables, I prefer to use a receiver like the common RXB6. It’s a super-heterodyne receiver which works much better than the super-regenerative receivers commonly bundled with the FS1000A transmitters.
Both the transmitter and receiver modules work better with the proper antennas but they are often not supplied. You can buy them (get the correct frequency) or you can make your own. At 433-MHz, the right length is about 16 cm for a straight wire antenna. To make a coiled one, take about 16 cm of insulated, solid core wire and wrap it around something like a 5/32-inch drill bit shank in a single layer. Strip the insulation off of a short straight section at one end and connect it to your transmitter/receiver board. I’ve found that the wire from a scrap Ethernet cable works well for antennas. The transmitter board usually has a place to solder the antenna but the receiver board may only have pins (like the RXB6). Just make sure that the connection is secure if you don’t solder it.
Step 3: Software
The transmitter software uses common techniques to put the chip into sleep mode. In that mode it draws less than 0.2ua of current. The switch inputs (D1-D4) have the internal pull-up resistors turned on but they don’t draw any current until a switch is pressed. The inputs are configured for interrupt-on-change (IOC). When a switch is pressed, an interrupt is generated and it forces the chip to wake up. The interrupt handler performs about 48msec of delay to allow the switch to debounce. A check is then made to determine which switch was pressed and the appropriate routine is called. The transmitted message is repeated several times (I chose 5 times). This is typical of commercial transmitters because there is so much RF traffic on 433-MHz and 315-MHz out there. The repeated messages help to ensure that at least one gets through to the receiver.
The sync and bit times are defined at the front of the transmitter software but the data bytes are embedded in each of the four button routines. They are obvious and easy to change and adding bytes to make a longer message is also easy. All of the same defines are included in the receiver software as well as the data byte definitions. If you add data bytes to your message, you will need to change the define for “Msg_Length” and add bytes to the variable “RF_Message”. You will also need to add code to the “RF_Message” check in “loop” to verify the proper receipt of the extra bytes and define those bytes.
39 Comments
9 months ago
Hi Boomer, nice project, thank you for the share. I was hoping to find a great solution to use these cheap RF transmitters with ATtiny85. Until now I have not found anything else than virtualWire, but this library only works with ATmega328p and it is quite big.... Unfortunately I may have spent so many hours to try your project, with no success. It is working fine in direct wire, but even if I'm living in the country, there is so many noise on the 433MHz and this way to transmit seems too much noise sensitive.
1 year ago
Hi,
"1-MHz is perfect for the
transmitter logic...need
to make sure
that the receiver board operates at the same frequency...The key is that the
ATtiny85 needs to run at 8-MHz in the receiver"
They seem conflict. What clock rate should be for the TX and RX? Anyone please help. Thanks!
Reply 1 year ago
Sorry for the confusion. The processor clock rates do not need to match. What needs to match is the RF frequencies of transmitter and receiver - both 433-MHz or both 315-MHz.
Reply 1 year ago
Hi Boomer,
I appreciate your reply! Yes, I tested I received sent signal from the transmitter (Pls see attached file). It looks right. However, the RX_LED didn't turn ON. It seems like the sent messages and received messages are mismatched. I don't know how to display signal back to HEX format to compare.
Any advice would be appreciated!
Reply 1 year ago
Are you using the ATtiny85 for the receiver? If not, then the interrupt handler will be different. If you are using a different RF receiver than the one I show then it may be the problem. A lot of those really cheap receivers are flaky. Try capturing a single message (like my screenshot in the Instructable) to see if the individual TX and RX bits line up. A couple of things you could try:
- Turn on the LED in the interrupt handler when "Started = true" to see if the start of the message is getting recognized.
- Add "volatile" in front of all the variable declarations at the top of the software (like this: volatile byte RF_Bit_Count;).
- Displaying the bytes in hex can be done as shown in my "Arduino RF Sensor Decoder" Instructable. It uses a Nano or Uno plugged into the computer USB port so it can display the information on the Serial Monitor built into the Arduino IDE.
Reply 1 year ago
Hi Boomer48,
Please see attached file and advise. I carefully followed your instruction to test the code but the LED is still NOT ON. Looking for and appreciate any of your help to me.
Sincerely,
MP
Reply 1 year ago
The codes you are transmitting are for buttons 3 and 4 (third bytes are 0xC3 and 0xCC). The sample RX software only recognizes the codes for buttons 1 and 2 (third bytes are 0x33 and 0x3C).
// Defines for expected on/off commands
#define RF_On_1 0x44
#define RF_On_2 0x15
#define RF_On_3 0x33 // on
#define RF_Off_1 0x44
#define RF_Off_2 0x15
#define RF_Off_3 0x3C // off
Reply 1 year ago
I saw that. My captures, as samples, is to prove that the signals received from the RX is correct but the LED is still off. I always tested for all 4 buttons.
Any other advice? Thank you!
Reply 1 year ago
Did you make sure that the RX ATtiny85 is running at 8-MHz? I have an Instructable that shows how to verify that. The only other thing I noticed is that the "Gap Time" of the captured data (>6300) is higher than what the RX software wants to see. I'm not sure why it would be given that the TX is sending 5640 but try increasing the following number to 7000:
#define Max_Gap_Time 6200 // nominal = 5600
Reply 1 year ago
Yes, I set both TX and RX at 8MHz. I will learn how to verify soon. I created a "better look" circuit and changed the Gap Time to 7000. At first the issue stayed the same, then I kept changing the RF receiver RXB6 (Since I remember you said cheap ones are flaky), and finally it worked!
PS:
Button 1=ON, Button 2 = OFF
Button 3=ON, Button 4 = OFF
I am not a software guy, so I am sorry for any dumb question I asked that might waste your time. Anyway, APPRECIATE your help!
Reply 1 year ago
I am glad to help. Thank you for providing good information to help me help you. Yes, the RF modules are sometimes a problem. In your setup you can work the software problems by disconnecting the RF modules and just running a jumper wire from the output pin of the TX ATtiny85 to the input pin of the RX ATtiny85. I would have mentioned that sooner but your data capture made it look like the RF modules were working ok.
Reply 1 year ago
It helps to have the proper antennas on the RF boards and sometimes they may not work well if the RX and TX are too close together.
Reply 1 year ago
For last question, I used delay time for multiple of One_Bit_Time (such as 1200). Theorically, it is correct? Thanks!
Reply 1 year ago
This is OK: delay(One_Bit_Time * 2);
Reply 1 year ago
Either I used x2 or x10, the LEDs turn ON in different time duration, and usually it hangover very long. What caused this issue? The receiver didn't terminate the rest packages kept sending from the transmitter (5 packages)?
Reply 1 year ago
Do you get the same results with just the ATtiny85 transmit and receive pins connected (no RF boards)? The five messages being sent should complete in much less than a second. You can use your scope to see how many messages are actually getting sent and received each time a button is pressed. If you want me to look at your programs you can send me a message through Instructables. I think it will let you attach the software.
Reply 1 year ago
Yes the same. 5 msgs sent/rxed each time button pressed. I don't understand what you meant "a message through Instructables". You meant to copy and paste the sketch in here? PS: I didn't touch any of the function ISR()
#define Pulse_In 1 // PIN6
#define RX_LED1 0 // PIN5. Shows on/off state for received commands
#define RX_LED2 2 // PIN7
#define RX_LED3 3 // PIN2
#define RX_LED4 4 // PIN3
// All times in microseconds
#define Min_Gap_Time 5000 // nominal = 5600
#define Max_Gap_Time 7000 // nominal = 5600 set 6200 (Boomer48 suggested 7000)
#define One_Bit_Time 120 // nominal = 188
#define Zero_Bit_Time 500 // nominal = 564
#define Msg_Length 3 // Defined message length for specific transmitter
// RX_LED1 = ON
#define RF_On_11 0x44 // 01000100
#define RF_On_12 0x15 // 00010101
#define RF_On_13 0x33 // 00110011
// RX_LED2 = ON
#define RF_On_23 0x3C // 00111100
// RX_LED3 = ON
#define RF_On_33 0xC3 // 11000011
// RX_LED4 = ON
#define RF_On_43 0xCC // 11001100
byte RF_Bit_Count;
byte RF_Byte_Count;
byte RF_Byte = 0;
byte RF_Message[] = {0, 0, 0}; // 24 bits
unsigned long Start_Time = 0;
int Pulse_Width;
byte Started = false;
void setup() {
pinMode(RX_LED1, OUTPUT);
digitalWrite(RX_LED1, 0);
pinMode(RX_LED2, OUTPUT);
digitalWrite(RX_LED2, 0);
pinMode(RX_LED3, OUTPUT);
digitalWrite(RX_LED3, 0);
pinMode(RX_LED4, OUTPUT);
digitalWrite(RX_LED4, 0);
digitalWrite(Pulse_In, HIGH); //turn on pullup
// Set interrupt-on-change registers
bitClear(GIMSK, INT0); // disable INT0 external interrupt
bitSet(GIMSK, PCIE); // enable interrupt-on-change
bitSet(PCMSK, Pulse_In);
}
void loop() {
//byte i; //MP uncommented it which has never been used
int HoldTime = One_Bit_Time*2;
if (RF_Byte_Count == Msg_Length) {
noInterrupts();
if ((RF_Message[0] == RF_On_11) && (RF_Message[1] == RF_On_12) && (RF_Message[2] == RF_On_13)){
digitalWrite(RX_LED1, 1);
delay(HoldTime);
digitalWrite(RX_LED1, 0);
}
else if ((RF_Message[0] == RF_On_11) && (RF_Message[1] == RF_On_12) && (RF_Message[2] == RF_On_23)){
digitalWrite(RX_LED2, 1);
delay(HoldTime);
digitalWrite(RX_LED2, 0);
}
else if ((RF_Message[0] == RF_On_11) && (RF_Message[1] == RF_On_12) && (RF_Message[2] == RF_On_33)){
digitalWrite(RX_LED3, 1);
delay(HoldTime);
digitalWrite(RX_LED3, 0);
}
else if ((RF_Message[0] == RF_On_11) && (RF_Message[1] == RF_On_12) && (RF_Message[2] == RF_On_43)){
digitalWrite(RX_LED4, 1);
delay(HoldTime);
digitalWrite(RX_LED4, 0);
}
Started = false;
interrupts();
}
}
Reply 1 year ago
Add RF_Byte_Count = 0; just before the interrupts(); line.
The sample code just turned the LED on or off so the byte count didn't need to be cleared. Your changes require it to be cleared or else it will just repeat the on/off software part all of the time.
Reply 1 year ago
Thank you! It worked.
Reply 1 year ago
Thank you so much. So far, it works fine!