Bit Banging an ATTiny85 and a Pi (Part 1)

The ATTiny85 is truly an amazing little device. In my current project, I’ve used it to create a rotary encoder peripheral which I mean to hook up to the raspberry pi. The peripheral on its own took me weeks to design correctly, but now that it works beautifully I have to ask… How can I get those readings back to the raspberry pi? The answer is, of course, bit banging!

Some people may scoff at the idea of wasting software cycles to do the menial tasks required to communicate between devices. But to me, it is a fun challenge with very achievable results. Don’t get me wrong, I love reading data-sheets as much as the next… But I’m ready to see some results! And with the awesome power of a raspberry pi, I don’t feel too bad utilizing some of those cycles if it means a quick and easy communication protocol. So here’s how I managed implement simplex binary encoding between an ATTiny85 and a raspberry pi…

In this example, I ended up writing my own bit transmission protocol. Perhaps a smidge overkill and admittedly a very slow implementation due to the need for syncing the cycles often. That being said, it’s also a great starting point so that you can further improve the design (or write some other protocol entirely). Someday, I’d really love to implement this with Manchester Encoding. But today is not that day.

Before we get further, let’s define some project objectives:

  • One-way communication from the peripheral to a raspberry pi
  • A maximum of 10 bits are required per “packet” (maximum number you can send: 1023)
  • Ideally it will only require 1 GPIO wire
  • The library must be non-blocking

It would be easy to change this to support more bits. Or bytes. Or whatever requirements your peripheral has. Though my encoder really only goes up to 500. So 10 bits seemed more than enough for my purposes.

This blog post will document my very light-weight library that I’ve created. You can see the full source code on my github repo here: https://github.com/SharpCoder/attiny85-bit-banging

The Protocol

Let’s define the specifics of the protocol we will be implementing. I chose a naiive approach which is very very simple to implement, unfortunately it’s also very slow. Here’s why…

  • Each bit will be either ON or OFF
  • We will use a static timing defined in milliseconds. So for the duration of this time sequence, we will pull the receiving pin either HIGH or LOW and then move on to the next bit
  • To verify our sequence, we will have a synchronization stage which pulls the output pin HIGH for an entire cycle

Here’s an example of the number 170 encoded. You can see why this protocol is very slow. More than half of it is used creating a synchronization signal! But that’s fine. It’s still fast enough for our needs and incredibly easy to implement.

data signal

You may notice that we pull the receive pin LOW before the true sync signal occurs. This is to make sure that we know when the data signal ended and the synchronization signal begins. I believe there’s still an edge case that could cause momentary confusion if you are sending all 1’s intentionally.

CommConfig.h

Now that we’ve defined the protocol. Let’s start implementing it! The following code will be written in c++ and is intended for the ATTiny85 chip.

This first file that we create will be a simple place to define the library variables. I decided to use a config file so these were very explicitly defined. They are tightly bound to the receiving hardware as well as the physical device you are programming.

#ifndef COMM_CONFIG_H
#define COMM_CONFIG_H

#define COMM_TIMING    32 // Timing (in ms) between each bit
#define COMM_WIRE      3  // Which pin to use for transmission

#endif

This should be relatively straightforward.

Comm1W.h

Next we will define the actual .h file for our library. There are only 3 publicly defined methods that we will need to expose.

  • void commInit() - This will initialize the output pin. It MUST be called in the arduino setup(){ } method.
  • void commSet() - You can invoke this method each time you want to send a new value. It’s important to note that this does NOT queue up messages. It simply updates the value that will be used in the next communication cycle.
  • void commLoop() - This method MUST be called in arduino loop(){ } It is non-blocking and will handle all communication over the specified output wire.

Another important thing to note about this library is that you should avoid using delay() anywhere. It will mess up the timing of the data signals.

#ifndef COMM_1_WIRE
#define COMM_1_WIRE

void commInit();
void commSet(short value);
void commLoop();

#endif

Comm1W.cpp

In the first part of Comm1W.cpp we will define all the variables necessary to run the library. Since this is an even smaller arduino than common ones, it’s best to code as “defensively” as possible.

#include "Arduino.h"
#include "CommConfig.h"
#include "Comm1W.h"

// Define all the variables we need to handle non-blocking state
boolean comm_sending_sync = false;
boolean comm_sending_sync_1 = false;
boolean comm_sending_sync_2 = false;

// Define all of the other variables we need to support the protocol
char comm_index = 0;
short comm_value = 0;
short comm_value_next = 0;
unsigned long comm_current_millis = 0;
unsigned long comm_target_millis = 0;

In keeping with our non-blocking requirement, you may notice that we use a bunch of state booleans. These will be used in conjunction with the millis() function to keep track of what portion of the code we should be running at any given time.

// Entry point to initialize the PIN we need.
void commInit() {
  pinMode(COMM_WIRE, OUTPUT);
}

boolean comm_can_execute() {
  if (comm_target_millis > comm_current_millis) {
    return false;
  }
  return true;
}

// Update the target delay
void yield_delay(unsigned long ms)
{
  comm_target_millis = comm_current_millis + (ms * 1);
}

The first crucial functionality is the comm_can_execute() and yield_delay() methods which are the heart of our non-blocking sleep mechanism. Essentially it just sets a target variable equal to the millisecond reading that we should be at or beyond before running the next stage of code.

void commSet(short number) {
  comm_value_next = number;
}

We also need to define the exposed function which allows anyone to set the next number that we will send.

void commLoop() {
  comm_current_millis = millis();
  // return if we need to delay longer.
  if (comm_can_execute()) {
    if (!comm_sending_sync) {
      if ((comm_value >> comm_index) & 0x01 == 1) {
        // High value
        digitalWrite(COMM_WIRE, true);
      } else {
        // Low value
        digitalWrite(COMM_WIRE, false);
      }
      yield_delay(COMM_TIMING);
      comm_index++;

      if (comm_index >= 10) {
        comm_index = 0;
        digitalWrite(COMM_WIRE, true);
        comm_sending_sync_1 = true;
        comm_sending_sync_2 = false;
      }
    } else {
      if (comm_sending_sync_1) {
        digitalWrite(COMM_WIRE, false);
        yield_delay(COMM_TIMING * 1);
        comm_sending_sync_1 = false;
        comm_sending_sync_2 = true;
       } else {
        digitalWrite(COMM_WIRE, true);
        yield_delay(COMM_TIMING * 10);
        comm_sending_sync = false;
        comm_sending_sync_1 = false;
        comm_sending_sync_2 = false;

        // Now that we've completed one full transmission
        // update the next value to send.
        comm_value = comm_value_next;
      }
    }
  }
}

There are three parts to this block of code, so I’ll address each one in turn.

Part 1

If we can execute code, we need to read which state we are currently in. If we’re not sending the synchronization signal, then we’re free to set the next bit in the sequence for our current send value. Check if the bit on our current index is high or low and then set the output accordingly. Simple as that!

Part 2

Okay now it’s time to start the synchronization signal. We know that we’ve transmitted 10 bits already. So we enter the sync mode. Simply pull the output LOW. This will make sure it breaks any continuous HIGH sequences that may have been left off. Just so the receiving hardware knows unequivocally that we are not sending a synchronize signal before this point. Wait the COMM_TIMING amount of milliseconds, and move on to the final point in our transmission.

Part 3

Last thing we need to do is pull the output HIGH for 10 bits. Thus, completing the synchronization sequence.

Conclusion

That’s all there is to the bitbanging library I wrote. Again, if you want to see the code simply go to my github repo https://github.com/SharpCoder/attiny85-bit-banging. In the next article, I’ll explain how to receive this signal using Java and a raspberry pi!

Troubleshooting

Here are some common problems I ran into while getting this setup.

  • The GPIO pins appear to “float” even when they are definitely hooked up correctly. You might assume I had forgotten a pull up/down resistor, but no. While developing the code, I was using an Uno for quick prototyping which happened to be hooked up to my MacBook Pro through the large USB port. Well, turns out that was causing inconsistent voltage drops which made any sort of communication between the arduino and the raspberry pi completely void. Beware of this! My solution was to use VIN on the arduino sourced from the Raspberry Pi. Suddenly my floating GPIO reads went away… -_-

react-basic-router

I published my first npm package today! It is called react-basic-router and it does pretty much what you would expect. Although it was a small project, I personally will find a lot of use from it. win-win.

The concept is simple. It is a hash router for reactJS. You can use it like this:

  import React from 'react';
  import {Router, Route} from 'react-basic-router';

  class App extends React.Component {
    render() {
      return(
        <Router>
          <Route hash="#/" component={PageA} />
          <Route hash="#/about" component={PageB} />
        </Router>
      );
    }
  }

It supports prop pass-through, so if you add more props to the <Route /> object, they will be pushed to the component when it renders. You can load one of these pages through regular anchor / hash links which must exactly match the hash prop.

There are some improvements I would like to make, such as an optional fuzzy matching for anchors. And perhaps an “Error” route for pages that don’t exist.

More to come. Enjoy!

MeteorJS on Amazon Lightsail

Learning meteorjs can be overwhelming at first. Especially once you decide to host your applications, well, anywhere! According to the official meteorjs documentation, amazon aws is way too plebeian and you should obviously be using their overly expensive, in-home service: Galaxy!

Galaxy costs a staggering $30.00 USD/mo (not including the costs of staging a separate mongodb). Enter Amazon Lightsail! The server specs are limited but it will work well for small applications and it only costs $5.00/mo (including mongo!) Allow me to reveal the magic and demonstrate just how easy it is to host your meteor project! We’ll be using nginx, because I always have trouble binding anything to port 80 without some kind of wrapper. Plus I like the flexibility nginx gives.

Amazon Lightsail can be quickly setup. Go checkout their landing page and get the configuration pre-loaded with nginx. Amazon lightsail offers a nifty browser-based ssh tool. So let’s go ahead and use that to install some necessary packages.

sudo apt-get install npm mongodb
curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
sudo apt-get install -y nodejs

Next we need to configure nginx.

sudo nano /opt/bitnami/nginx/conf/bitnami/bitnami.conf

Let’s add a tried and true nginx proxy so meteor can be happy and nginx can be happy. Remove everything in location and make it look similar to this:

location / {
  proxy_pass http://localhost:3000/;
}

You will have to reload the nginx configuration for the changes to take effect.

sudo nginx -s reload

Your system is now setup. Back home, on your dev machine, you can generate a nodejs meteor “package” with this simple command. The output will contain a .tar.gz file.

meteor build ./output_dir

And once you have that tar.gz file, scp it up to your lightsail box. Here’s a little upgrade script I wrote which will extract the .tar.gz and set everything up according to the meteor README.

#!/bin/bash
FILE=$1
OUTPUT=$2
if [ ! -f $FILE ]; then
    echo "File not found!"
    exit 1
fi

echo "Upgrading meteor project"
tar -xzf $FILE --directory $OUTPUT

cd $OUTPUT
meteor npm install --save bcrypt
cd $OUTPUT/bundle/programs/server
sudo npm install

# Run the program
export PORT=3000
export MONGO_URL='mongodb://localhost:27017/meteor'
export ROOT_URL='http://127.0.0.1:3000/'
nodejs $OUTPUT/bundle/main.js

For my own project, I wrapped that latter part in an ubuntu service. But even without it… here’s a $5.00 meteorjs server!

Boom! mic drop

Welcome to Jekyll!

You’ll find this post in your _posts directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run jekyll serve, which launches a web server and auto-regenerates your site when a file is updated.

To add new posts, simply add a file in the _posts directory that follows the convention YYYY-MM-DD-name-of-post.ext and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works.

Jekyll also offers powerful support for code snippets:

def print_hi(name)
  puts "Hi, #{name}"
end
print_hi('Tom')
#=> prints 'Hi, Tom' to STDOUT.

Check out the Jekyll docs for more info on how to get the most out of Jekyll. File all bugs/feature requests at Jekyll’s GitHub repo. If you have questions, you can ask them on Jekyll Talk.