If you ever have a reason or desire to program Arduino over air using Bluetooth, This aims to be a useful starting point.
RFduinos are powerful little Bluetooth devices that you can program with the Arduino IDE. Go to RFduino site for links to products, downloads, and how to enable your Arduino IDE to program RFduinos. For this project, I'm using
- 2 RFduino 22102 DIP Bluetooth modules
- 2 RFduino 22121 USB Shields (could get away with using one if you only have one)
- 1 Arduino UNO
- 1 MM74HC14N Hex Schmitt Inverter
- 1 330 Ohm resistor
- 1 3V Zener diode
- 1 1uF Capacitor
- 1 LED and 330 ohm resistor
- Breadboard and jumpers to hold it all together
- RFduino Host and Device code (shown here, and also on github)
The Arduino UNO is powered by a DC power supply, and runs on 5V. RFduinos can only handle 3V, so I need a level shifter for them to communicate together. There are many ways to make logic level shifters in order to interface 3V and 5V systems. I just happen to have these parts in my lab, and so that's why I'm using them. Also, if I can get the thing to work with this crazy level shifter setup, it will likely work with anything ;)
Let's start with a description of the level shifter circuit!
Step 1: Logic Level Setup
This is my hacked together 5V <> 3V level shifter circuit. I used this because it's what I have. Since this funky shifter circuit works, I would bet that any other level shifter circuit should work. ;)
The UNO TX is buffered to the RFduino RX through a 330 ohm series resistor. There is a 3V Zener connected between the RFduino RX and GND to keep the levels correct.
The RFduino TX is buffered to the UNO RX through 2 Schmitt inverters. The 74HC14 IC is powered with 5V, and will output 5V high signal to UNO. RFduino high signal is 3V, but this is enough to trigger the inverter. Then the inverted signal is inverted again.
The reset signal is sent from RFduino GPIO6 to a Schmitt inverter input, that output is inverted again, and the result goes to a 1uF cap in series with the UNO reset pin. This sends the signal to UNO to reset before uploading new code.
I put an LED there to see the state of the DTR pin for debugging. It's not really necessary.
The RFduino in this circuit is set up as a DEVICE.
Next up, how to modify the HOST RFduino so that it will be able to upload over air!
Step 2: HOST Hardware Setup
In order to program the target Arduino UNO, we need to make sure that we don't try to program the HOST RFduino. they both use the same avrdude programming protocol, so we need to disable our ability to program the RFduino. For this, I'm gently bending the RESET pin on the RFduino DIP radio module, and using a jumper from the RFduino USB shield RST pin (FTDI DTR output) to connect to GPIO6 on the module. That way, I can always connect the jumper to RFduino RESET if I want to update HOST firmware, then back to GPIO6 if I want the option of programming the Target UNO over air.
In my development, I used a second USB Shield to program the DEVICE, but you could certainly just use one USB Shield and swap out the Host and Device as you develop.
Next, we'll take a look at the Host code!
Step 3: Host Code
In my setup, I'm attaching the HOST to my laptop, and it will parse the avrdude commands, upload hex, and verification. The DEVICE is attached to the target Arduino UNO.
In a previous incarnation of this instructable, I had a much more complicated code structure to parse and pass-through the avrdude upload commands/requests, upload pages, and verification pages. Since then, I've discovered that the avrdude uses different protocols for uploading to different target devices. For example, the Microchip MPIDE uses avrdude to program chipKIT boards, and the protocol is extremely different! My original firmware was too specific! Also, it didn't have any way of handling simple pass-through... So,
Back to the keyboard!
I'm using a timer on the serial input in order to find the end of the varying length messages during upload. in addition to varying avr commands and requests, Arduino UNO uploads and verifies in 128 byte pages, and chipKIT uploads and verifies in 256 byte pages. In order to manage the 32 byte packet limit in RFduinoGZLL library, I set up a 2D array buffer to collect serial data. My buffer is 10 levels deep, which is more than enough to handle what I need in this case. To keep track of things, I'm reserving the first index of the first array to hold the number of packets that eventually comes in on the radio. Kind of a checksum.
The nice thing is that this RFduino firmware will also handle serial pass-through between the target and PC without needing to know if it's uploading or not. The code is alot nicer to look at, and more versatile. Rock!
One thing that came up was on the DEVICE side, I am unable to use the serialEvent() function. Probably due to some kind of interrupt issue with the radio call backs. So instead, I'm polling the serial port in the loop().
I'm polling the GPIO6 to see when it goes LOW, signifying target reset attempt. I'm then sending a special character '$' DEVICE to put it's own GPIO6 LOW. Then, when HOST GPIO6 goes HIGH, it will send '#' to tell DEVICE to put it's own GPIO6 HIGH.
Lots more comments in the attached code!
Next, we'll take a look at the DEVICE code!
Step 4: DEVICE Code
The DEVICE code acts as dumb as possible, packetizing the incoming serial message until it times out and then sending the packets to the radio. The only 'smart' thing is that it looks at the very first character to see if there the reset pin needs to be toggled
Lots more comments and verbosity in the code attached
Also have repository on github here
That's it! Hope this helps somebody else with their project!