I'm trying to make an RF remote control using ATtiny85 running at 8MHz on internal oscillator, this cheap RF 434MHz transmitter module (like the one below), and VirtualWire lib, but had no success so far.

RF transmitter

So, before I go any further, is it possible to use VirtualWire with ATtiny85 running at 3.6V and 8MHz with its internal oscillator? If so, how?

I've found a few scattered sources of information on the Net about it, but couldn't make much sense of it.

I have build ATMega328 versions of the receiver and transmitter using VirtualWire without any major problems. Both modules talk to each other will very little data drop out. Now, when I tried to replace the ATMega328 transmitter with a smaller version using the ATtiny85, I'm having problems.

Here are the symptoms:

  1. The major one seem to be that when I add the VirtualWire include, my sketch seems to slow down a lot. So I figure it's a timing issue. But I'm not sure what the problem is.

  2. I put a scope on ATtiny85 pin 6 (PB1) but I don't see anything being transmitted. I think I've messed up with the pin names.

  3. I can make the LED blink and also can read the buttons without problems.

Here are the schematics and the board design for the remote controller:

RC schematic

The board is currently powered by 2 AA NiMH batteries that are currently at 2.6V. I can make the ATtiny85 blink with that voltage. I plan to power the RC with a 3.6V CR2 lithum battery.

Here are more details of my development enviroment:

  • Arduino IDE 1.05
  • I'm using ATtiny cores from here
  • I'm programming the ATtiny85 using an Arduino UNO R3 as ISP, without problems (it's working as I can make the ATtiny85 blink an LED).

And here's my code:

#include <VirtualWire.h>

#define VCC_PIN  A1
#define BTN_PIN  A2
#define LED_PIN  A3
#define TXD_PIN  PB1

#define BLINK_UP_TIME  25

#define BUTTON_NONE  0
#define BUTTON_1     1
#define BUTTON_2     2
#define BUTTON_3     3
#define BUTTON_4     4
#define BUTTON_5     5
#define BUTTON_6     6

void setup()
  pinMode(BTN_PIN, INPUT);
  pinMode(TXD_PIN, OUTPUT);     
  pinMode(VCC_PIN, OUTPUT);     
  pinMode(LED_PIN, OUTPUT);     

  // leave RF transmitter off for now
  digitalWrite(VCC_PIN, LOW);

  // Initialise the IO and ISR
  vw_setup(2000);    // Bits per sec

  // let the user know we are alive: heartbeat
  blink(3, 50);

uint8_t buf[VW_MAX_MESSAGE_LEN];
uint8_t buflen = VW_MAX_MESSAGE_LEN;

void loop()
  byte button = ReadButtons();
  if (button != BUTTON_NONE) {
    // transmit button pressed
    digitalWrite(LED_PIN, HIGH); // Flash a light to show transmitting
    digitalWrite(VCC_PIN, HIGH); // turn tranmitter on
    buf[0] = button;
    vw_send(buf, 1);
    vw_wait_tx(); // Wait until the whole message is gone
    digitalWrite(LED_PIN, LOW); // turn off LED
    digitalWrite(VCC_PIN, LOW); // turn tranmitter off

byte ReadButtons()
  unsigned int buttonVoltage = analogRead(BTN_PIN);
  byte button = BUTTON_NONE;   // return no button pressed if the below checks don't write to btn
  if      (buttonVoltage < 10)   {    button = BUTTON_1; } 
  else if (buttonVoltage < 200)  {    button = BUTTON_2; } 
  else if (buttonVoltage < 400)  {    button = BUTTON_3; } 
  else if (buttonVoltage < 550)  {    button = BUTTON_4; } 
  else if (buttonVoltage < 720)  {    button = BUTTON_5; } 
  else if (buttonVoltage < 900)  {    button = BUTTON_6; }
  else if (buttonVoltage > 1000) {    button = BUTTON_NONE; }
  return( button );

So, What's wrong with my setup?

  • 1
    @FakeName well, to be honest, I didn't even solder the RF module to the board yet, as I can't even get the MCU to send the bits to the TX pin yet. So I'm putting the scope probe to the TX pin and if I see na attempt at a transmission, then I'll solder the module in.
    – Ricardo
    Commented Mar 18, 2014 at 1:02
  • 4
    The issue is that it looks like the static variables that PBx is referring to are not defined by the ATtiny libraries, so they may just kind of incidentally work. Have you verified you can controll PORT B 1 normally (e.g. with plain digitalWrite() commands? Alternatively, just swap the LED and TX pins, and use the LED pin (which you know works) for the data output. Commented Mar 18, 2014 at 1:55
  • 1
    Indeed, PBn is defined by AVR-Libc. It just so happens that arduino-tiny maps PBn to Dn, and each PBn is defined to have the value of n for use with _BV(). Commented Mar 18, 2014 at 2:40
  • 1
    @jfpoilpret The attiny-master package installs a whole bunch of new ATtiny boards on the IDE. It gives support for several combinations of ATtiny45/84/85 with 1/8/16MHz with internal and external oscillators. I'm able to program the 85 with all 3 clocks when I comment out VW.
    – Ricardo
    Commented Mar 18, 2014 at 11:05
  • 1
    My idea was actually to keep the include but remove the calls (which should compile), then check if you experiment a slowdown or not. If yes, that would means there's something in the include that overwrites one macro like F_CPU, or a static variable (class instance) that, when constructed, changes something important.
    – jfpoilpret
    Commented Mar 18, 2014 at 19:14

4 Answers 4


I tried those Tiny cores you are using and had a lot of problems. I switched to http://code.google.com/p/arduino-tiny/ and it seems much better. Create a second Arduino installation folder and add the arduino-tiny cores. The pin name constants are like PIN_A0, PIN_A1,...,PIN_B1, etc. So, try that out.

I looked briefly at the VirtualWire code and there are ifdefs specifically for the Attiny85, so it should work. Here is a derivative project with working ATTINY85 examples: http://cosa-arduino.blogspot.de/2013/03/news-virtual-wire-interface.html

  • +1 Excelent, thanks! I'll give it a try and will let you know.
    – Ricardo
    Commented Mar 21, 2014 at 0:38

Updated 26-08-2018: added tx(false); at the end of send. Without it, the TX pin could remain high, flooding the 433MHz band and making any other communication on it almost impossible!!!

Although these are all pretty old posts, I've still been struggling to get VirtualWire or its successor RadioHead working on an Attiny85. Typically the problem is that VirtualWire uses timer 0 and thereby breaks other stuff.

Now, for RX you typically don't want to spend all your processing cycles waiting for bits to come in, so you want it to be interrupt-driven, but for TX you can often do without that ... So, I did a bit of reverse-engineering of the VirtualWire TX code and implemented it as a simple send routine which can be used without needing a dedicated timer-interrupt. Using the code below on Attiny85 and an Arduino Uno as receiver with the normal VirtualWire library and the same baudrate, works just great for me, so if someone else can benefit from it, then here it is (including some code to read out an ultrasonic distance sensor and driving some leds - up to the reader to strip out what he doesn't need):

#include <util/crc16.h>

#define LED_PIN           3
#define LED2_PIN          4

#define TX_PIN            2
#define TX_BAUDRATE       2000
#define TX_MAXLEN         12

#define TRIGGER_PIN       0
#define ECHO_PIN          1

inline void led(const bool onOff) { digitalWrite(LED_PIN, onOff ? HIGH : LOW); }
inline void led2(const bool onOff) { digitalWrite(LED2_PIN, onOff ? HIGH : LOW); }
inline void tx(const bool onOff) { digitalWrite(TX_PIN, onOff ? HIGH : LOW); }
inline void trigger(const bool onOff) { digitalWrite(TRIGGER_PIN, onOff ? HIGH : LOW); }

const uint8_t header[8] = { 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x38, 0x2C };
const uint8_t conv4to6[16] = { 0x0D, 0x0E, 0x13, 0x15, 0x16, 0x19, 0x1A, 0x1C,
                               0x23, 0x25, 0x26, 0x29, 0x2A, 0x2C, 0x32, 0x34 };

void rawSend(const uint8_t * p, uint8_t len) {
  while (len--) {
    const uint8_t val = *p++;
    for (uint8_t mask = 1; mask != 0x40; mask <<= 1) {
      tx(val & mask);

void send(const uint8_t * buf, const uint8_t len) {
  //  First create the payload ...
  static uint8_t payload[(TX_MAXLEN+3)*2];
  uint8_t * p = payload;
  uint16_t crc = 0xFFFF;
  uint8_t v = len + 3;
  crc = _crc_ccitt_update(crc, v);
  *p++ = conv4to6[v >> 4];
  *p++ = conv4to6[v & 0x0F];
  for (uint8_t l = len; l--;) {
    v = *buf++;
    crc = _crc_ccitt_update(crc, v);
    *p++ = conv4to6[v >> 4];
    *p++ = conv4to6[v & 0x0F];
  crc = ~crc;
  v = (uint8_t)crc;
  *p++ = conv4to6[v >> 4];
  *p++ = conv4to6[v & 0x0F];
  v = (uint8_t)(crc >> 8);
  *p++ = conv4to6[v >> 4];
  *p++ = conv4to6[v & 0x0F];

  //  Now transmit the header and the payload ...
  rawSend(header, sizeof(header));
  rawSend(payload, (len + 3)*2);

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);

  pinMode(TX_PIN, OUTPUT);

  pinMode(ECHO_PIN, INPUT);

int measure() {
  trigger(false); delay(10); trigger(true); delay(10); trigger(false);

  //  We support up to 6m, i.e. 6000mm, so that would take
  //  6000mm * 2 / 0.34mm/ms = 35294ms to get to the end
  //  and back, hence let's use a timeout along those
  //  lines ...
  unsigned long duration = pulseIn(ECHO_PIN, HIGH, 36000);
  if (!duration || (duration > 35500)) return 0;
  return (int)(duration * 34 / 200);

int bufferedMeasure() {
  static int lastVal = 0;
  static bool toggle = true;
  int newVal = measure();
  if (newVal) {
    toggle = !toggle;
    lastVal += (newVal-lastVal)/10;
  return lastVal;

// the loop routine runs over and over again forever:
void loop() {
    //  Measure the distance ...
    int val = bufferedMeasure();
    if (val) {
      static unsigned long data;
      data = 0xD1570000UL|val;
      send((const uint8_t *)&data, sizeof(data));
    } else {
      led(true); delay(100); led(false); delay(100);
      led(true); delay(100); led(false); delay(100);
      led(true); delay(100); led(false);

Try the manchester library instead. This post includes all the necessary info except wiring diagrams: Manchester Library Won't Compile for Attiny85


I spent some time getting VirtualWire to work on attiny84. I see two issues with your code:

  1. Since the tiny85 only has one timer, that one is being used by both VirtualWire and millis/micros. So your delays won't work as expected.
  2. VirtualWire uses default values for vw_ptt_pin (10) and vw_rx_pin (11). Those pins don't exist on the attiny85. When vwsetup calls pinMode with these values, it causes writes to arbitrary memory addresses. This is likely to make your sketch behave unpredictably. To fix set vw_set_ptt_pin() and vw_set_rx_pin() to pins that exist on the tiny85 or modify the VirtualWire source.
  • Thanks for your answer. I had notice symptoms of the item 1 above: the timing got all wrong when I tried to make VirtualWire work. Item 2 seems to be good reasons for the whole thing not to work. My workaround for this problem was to stick with ATmega328 for RF stuff. I'll take a look and see if I can get back to using VW with my ATTinies.
    – Ricardo
    Commented Jul 21, 2014 at 1:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.