A failed(?) experiment in wifi on an ATTiny.
This is a quick sidebar away from Veronica for a moment. That project is very popular, and it’s my favorite to work on, but my side projects are gathering too much dust. Fellow low-level software geeks will recognize this as the process-starvation problem inherent in fixed priority pre-emptive scheduling. When you do everything that comes along in order of highest priority, low priority tasks will never get serviced. It’s counterintuitive, but sometimes you have to ignore all the super-urgent requests and go do something else.
With this article, I’m trying something different. Failure is a very big part of hacking and making things. Failure is where most of the learning takes place, whether we realize it or not. Furthermore, I feel like the community doesn’t write enough about our failures. You see a lot of short write-ups about impressive successful projects, but I would find the details of all the missteps far more interesting reading. This is rather like the File Drawer Problem in science. Negative results are almost never published, but in fact that data is just as valuable as positive results. Not publishing it can lead to bad science in the long run.
If you like this “failure report” style of article, let me know in the comments. I may do more of them!
* * *
I’ve been curious what the smallest and cheapest way to do wifi on an AVR microcontroller might be. My ultimate goal was wifi running on an ATTiny85. Full disclosure so as not to disappoint- this experiment did not work. However, I feel like I got quite close, and I feel I have established that what I’m attempting is possible. Therefore, a worthwhile starting endeavor. Perhaps someone can pick up where I’ve left off here. Read on to see how close I got.
The fine folks over at Element14/Farnell/Newark hooked me up with a Microchip WiFi module to evaluate. It’s a pretty interesting device (here’s the datasheet). I know what you’re thinking- worlds colliding! Propeller ≠ AVR! Well, the fact is, Microchip makes the cheapest and nicest wifi module I’ve seen, and Newark sells it for the lowest price I’ve seen. A complete wifi solution for around twenty bucks? Hard to beat that.
The question now becomes, can I get an AVR to talk to this thing, and how small can that AVR be? I set off to find out.
“Wifi” is easy to say, but there’s a lot of layers of software needed to make it work. Much more than most things you might do with a microcontroller. This is really a software challenge. The wifi module is just a serial device that takes commands through a standard SPI interface. The challenge is getting all the layers of software on top of that as are needed to talk to a wifi network. You need a driver for the wifi module (in this case based on a ZeroG 2100 chip), a TCP/IP stack, the ability to handle wifi authentication (WEP keys, WPA passwords, MAC authorization, etc), and the ability to manage the connection in case of noise or interference. No small feat, all that.
I’m not about to sit down and write all that from scratch. Instead, I found the software for the now-defunct WiShield online. This was a wifi shield for the Arduino that was (wait for it) based on the very same wifi module I have. Huzzah! As near as I can tell, Async Labs is still in business, but the WiShield is no more. Too bad- it looks like a neat product. They did some very fine work with the software layer for it, I can tell you.
I’m not an Arduino person. They seem like neat devices, and I get why people like them, but I’ve just never dipped my toe into that whole pool. I like working directly with the AVR microcontrollers. The WiShield software package is designed as an add-on for Arduino version 0023. Since I don’t have anything Arduino related, I set about getting the WiShield code to build independently of the Arduino code base. This turned out to be quite difficult, and I learned way more about Arduino than I ever intended.
Have you ever had the feeling that, after banging your head against the wall for a while, maybe your entire approach needs rethinking? That’s where I got to after a few hours of trying to port this code. Getting the code to build outside the Arduino environment is certainly possible, but as I started hacking more and more things to look like Arduino code to make it compile, I had a brain flash. If this thing wants to be built inside Arduino so badly, why not let it? Why am I fighting it? Ultimately, the Arduino environment is just a wrapper around avr-gcc that hides the details of include paths, makefiles, and library linking from the user. Instead of reverse-engineering all that, I just downloaded the Arduino environment and installed the WiShield code within it as intended.
It built on the first try.
Okay, now we’re getting somewhere. However, I still need to de-Arduino this code somewhat, because it’s all based around Arduino pin numbers and such. After learning way more than I wanted to about the Arduino code base, I was able to modify the ZeroG driver in the WiShield package to talk directly to AVR pins. The Arduino code was now acting as dead weight, just making things compile, and ultimately getting removed by the linker.
However, just building isn’t enough. I need to get this code onto an AVR. The Arduino environment is building my code, so can I get it to upload to a raw AVR as well? You bet. I started with this fine work from MIT, where they demonstrate running Arduino code on an ATTiny85. My hacked WiShield code is intended to run on an ATMega168-based Arduino, so I started with that chip. My plan was to take the path of least resistance to working code at first, then see if I could optimize it down to a smaller chip.
Using MIT’s technique, I built a custom Boards file and Arduino Core for a naked ATMega168 and the USBTinyISP programmer:
atmega168ausbtinyisp.name=ATmega168 (w/ USB Tiny ISP) atmega168ausbtinyisp.upload.using=arduino:usbtinyisp atmega168ausbtinyisp.upload.maximum_size=16384 atmega168ausbtinyisp.build.mcu=atmega168 atmega168ausbtinyisp.build.f_cpu=800000L atmega168ausbtinyisp.build.core=atmega168a
Then, I duplicated the base Arduino Core, added my hacked WiShield extension, and used that as the core for my new ATMega168A device.
Remarkably, this worked on the first try. The Arduino would build my code, and upload it to a naked ATMega168 using my USBTinyISP. With that settled, I figured it was time for some hardware.
I was expecting to need to iterate a lot on this (and I was right), so I built an ATMega168 version of my Bread Head. There’s no substitute for being able to iterate on code really quickly without having to touch the circuit itself.
Next, I needed to be able to breadboard the Microchip Wifi module. It’s a surface mount package, so normally a break-out board would be in order. However, I didn’t want to go to the trouble of etching one when I wasn’t yet sure if I could pull off this whole thing. Instead, I dead-bugged some wires on to it.
Next, I needed to figure how to actually hook this thing up. I determined the pin assignments that the WiShield code is expecting by tracing through the ZeroG wireless chip driver code. It boils down to:
ZeroG Interrupt = ATMega168 Port D, bit 0
ZeroG Serial Clock = ATMega168 Clock Port B, bit 5
ZeroG Serial In = ATMega168 Port B, bit 4
ZeroG Serial Out = ATMega168 Port B, bit 3
ZeroG Chip Select = ATMega168 Port B, bit 2
There also seemed to be some dormant code in the driver for an LED to indicate connection status to the network. This goes to Port B, pin 1, and would come in handy later.
Now I had enough information to wire it up, so off to the breadboard I went.
The WiFi module has a lot of connections, but you really only need five of them. The rest are power, grounds (seven of them!), JTAG debugging, and unused pins.
With everything hooked up, and the WiShield/Arduino hybrid software installed, I fired it up and started testing. It didn’t work immediately, as you might guess. Debugging this was very challenging, because I can’t really see what the code is doing, and there’s a lot of code. However, remember that latent connection LED? Well, I repurposed that and used it to debug. Using it as a single state bit, I could determine where code execution was getting to. By doing this, I traced through the driver code and found that it was crashing on the first attempt at receiving a command via SPI from the wireless module. It was crashing because the SPI message header was garbage. How did I determine it was garbage?
I needed the value of the SPI command code, which is a small value within a known range. So I modified the code to turn on the LED if the value was less than a constant or greater than a constant at that moment. Using that technique, I could binary-search the possible value space of that variable, and determined it was an illegal SPI message. That led me to inspect the SPI data lines themselves. Per the Microchip module’s datasheet, I had tied the SPI lines high with 4.7k resistors, so they would never float, even if the microcontroller wasn’t driving them. However, that seems to be a requirement of the Propeller chip it is designed to talk to. For the AVR, these resistors were creating junk on those lines. Off came the resistors, and the driver was no longer crashing. Huzzah!
Unfortunately, that’s where my small victories came to an end. After a few hours of trying to debug the remainder of the code using this same method, I determined my LED technique was unreliable. There was some latent Arduino code somewhere that was modifying the state of Port B on occasion, making it untrustworthy for debugging beyond the first moments of execution. I also tried using other output pins on the ATMega168 (it has many spares), but found the same problem. The hairy arm of the Arduino environment is long, and I was not as successful as I thought at neutering it. Wait…. how do you neuter an arm? Okay, that was a dreadful metaphor. You get what you pay for with writing, you know.
At this point, the ZeroG driver is reporting that it is connected to my house wifi. However, I cannot ping it, or see it in my router’s tables. I can’t tell if the connection LED is legit, or if it is lying to me due to the aforementioned Arduino contention.
This is where I took a step back and decided to call this one for now. It’s either a failure, or a good starting point, depending on your perspective. I learned enough to firmly believe that my end goal of wifi on an ATTiny85 is achievable:
- The Arduino codebase can made to run on any raw AVR quite easily
- The compiled WiShield/Arduino hybrid is ~10k in size. Some serious optimization work could probably squeeze this into 8k
- The Microchip module ultimately only needs 5 pins for control. Precisely what the ATTiny85 has to spare.
- The ATTiny does not have SPI support, but it does do USI, which is compatible with SPI Mode 0, as used by the Microchip module
As you can see, the pieces to this puzzle are all there. The bottom line is that there’s just too much code here for me to debug without better tools. Perhaps a Bus Pirate, or a JTAG debugger could be used. I could also trace some signals on my vanilla logic analyzer, but that would be a major exercise in frustration. If anyone would like to take a shot at finishing what I started, here’s what you’ll need:
- My hacked WiShield package. Install it like any other Arduino library, in your sketch folder.
The WiShield code is GPLed, so I assume this is okay to share. If Async Labs tells me otherwise, I’ll take it down.
- My Arduino core package for uploading to an ATMega168 from the Arduino environment. Install this in your <sketch>/Hardware folder.
Once installed, you can now select ATMega168 w/ USBTinyISP from the Tools menu, and code will build and upload directly to the AVR.
In theory, my hacked package has no dependencies on Arduino hardware. It builds and uploads from within the Arduino environment (version 0023), but that’s only skin-deep. I am the furthest thing from an expert in Arduino or networking, so I’m honestly not sure at this point whether the thing is actually working but my router is configured wrong to talk to it, or whether some latent Arduino code is messing things up, or some other situation is preventing it from working.
If you liked this “failure report” style of article, let me know in the comments. I may do more of them!