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)


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.

Oh Two-Part Epoxy, why are you so awesome?

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.

It's not pretty, but it works! Everything checks out on the meter, and the connections are solid. The free ends of the wires are tinned so they can be stuck into the breadboard.

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.

Here we are, all wired up. There's an ATMega168A under the Bread Head, there. Supply voltage is 3.3V, so I'm using the bypass feature on Juice Bridge to get Vcc directly from the bench supply. Juice Bridge is only designed to provide 5V.

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:

  1. The Arduino codebase can made to run on any raw AVR quite easily
  2. The compiled WiShield/Arduino hybrid is ~10k in size. Some serious optimization work could probably squeeze this into 8k
  3. The Microchip module ultimately only needs 5 pins for control. Precisely what the ATTiny85 has to spare.
  4. 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!


41 thoughts on “AVR WiFi

  1. I enjoyed the write up, I agree that the set backs and dead-ends of a project are often the most interesting to learn from. Thanks!

  2. Failures are what you learn from most, so go ahead, fail and write articles about it.

    It also shows that you’re willing to accept input from others: I understand how difficult it must be to squeeze an entire wireless stack into a microcontroller and I can’t help you there; however others may have some experience with it, or might point you to other projects that had more success.

    Finally, writing down what went wrong or why you can’t get something to work might help in organizing your thoughts, and analyzing and improving your process.

    With my own project, I’m running into a similar situation where I’m running into a wall where I just KNOW something is possible and I KNOW I could accomplish it if I could only find some time, like an entire weekend without distractions, to rewrite my firmware. I started on a side project to improve the software tools (mainly the logic analyzer) that I’m using, which will hopefully help me get ahead on the main project. Hopefully it will help to be distracted a little bit so I can back to it soon.

    So basically: kudos! And keep up the good (and bad) work!


  3. Hey don’t knock your writing skills! I mean that *was* a dreadful metaphor but… heck you keep me interested enough to be excited for your new posts, even though most of the Veronica stuff is way over my arduino-mongering head.

    I like hearing about project failures (I always learn the most from these things too) and I also like that you took time to go back to a somewhat neglected project – I have that same kind of process-starvation problem myself. Anyhow, thanks for writing and sharing these projects with the world. I learn a lot here and I always look forward to reading!

    Cheers! JC

  4. Nice job, i’m not surprised your having issues though. The TCP stack is very picky, its basically a state machine so timing can be critical. I haven’t used the tools you guys use..I’m an MPLAB user, do you have no way to run the code in debug mode?

    1. Sadly, no, I don’t have any tools for doing in-circuit debugging. I generally get by with adding status LEDs and building up the code gradually. Dumping a large volume of someone else’s code in there all at once and trying to debug it after the fact is not something I’m really equipped to do on microcontrollers. I have a low-end logic analyzer, but it’s of limited use in this case. Decoding SPI packets with it would be tremendously tedious and unreliable, and it’s unclear if it would even tell me much about what’s wrong. The actual I/O is a pretty small part of what could be going wrong here.

      1. No debugging?? geez your life must be so hard…. I remember when I used to use LED statuses for debugging, trust me the best thing you can do is go out and buy a (if you have enough monies) JTAG ICE MKII debugger from atmel, it does almost the entire product range! If you don’t have the monies, buy a clone version on ebay for like 90 bucks or so.. (Make sure you get an avrstudio upgradable one otherwise you need to rely on some dude in china to provide updates)… if you still don’t have that much money you can make a debugger from an ATMEGA16 and a MAX232, you just flash the mark 1 debugger firmware and you are set to go!

        Well worth the money! and gives you more time to work on other stuff…
        Good write up though, cheap wifi module is definitely on my horizons of things to do, will let you know if I get something built!

  5. I’d just like to say that I find these sorts of stories interesting and well worth reading. Please do write more like this!

    Oh, and carry on mixin’ those metaphors, too! Perhaps sneak a few puns in, too?

    1. Naturally. Puns are really the core of my material when discussing microcontrollers. I find they really register with the audience. I try to cycle them through the pipeline as much as possible. If I get complaints, I may shift to something else.

  6. give u a big ‘LIKE’ . .

    I m also thinking of a low cost wifi solution ..
    go on…hope i can read your successful report very soon.

    1. Hi Evan! The unit you’ve linked to is indeed cheaper. However, that’s a wholesale Chinese factory site, and they have a large minimum order. I’m guessing documentation for such a unit is hard to come by, and you’re probably on your own for software. Remember the software is really the bigger challenge with wifi. It’s not clear to me whether that site would sell to an individual, and you’re definitely rolling the dice on legitimacy. A lot of fake stuff is coming out of “after hours” factory runs and being sold online ridiculously cheap. Feel free to look through all my notes and files here in the article. If you have any questions, ask here in the comments so we can hopefully all learn something. 🙂

    2. I just took a closer look at that site, and I believe what’s being sold there isn’t actual product, but rather contracts to produce and supply product to another factory.

    3. Evan,
      just a few words regarding the $5 solution ..
      according to the production description:
      that’s a wifi module for Win 7/XP/ ME / 2000 / 98 / 64-bit XP/2003 /Linux/Android…
      the wifi IC is RTC8188CUS from realtek.
      the min order is 100 pieces.

      * I have no any relation with that company.

  7. Excellent write up, thanks a lot. amusing to read and motivating to go back to the lab and hack something.. tied to my desk for my exams at the moment.
    if you think arduino fossils are making trouble, try to search in the code for arduino functions that manipulate the ports, like digitalWrite() or for direct port access. like portb etc..
    you should also use Serial.print(var) a lot in your code, maybe a little bit easier to print the variables instead of countling the led blinks ;P

    good luck

    1. Well, I can’t totally disable all the Arduino I/O, because WiShield is using some of it. Unfortunately, the Arduino code does a lot of things with remap tables and arrays of pointers, so it’s difficult to know who’s touching the pins just by reading the code. A proper debugging environment is needed. As for the Serial library, well, that assumes I have a way read the serial output on something. At the moment, I don’t have a way to do that.

      1. well for the arduino I/O, that can be a pain, i know.. if i were you i would write #macros to remap all the needed pins to #defines. so you can, step by step quit the arduiono craddle.
        as for the serial output: identify the spi function and cache the return value in a local var. then output that before processing it. or add a debug functionality that outputs all spi communication to console.

      2. If the wifi code needs digitalwrite() clouldn’t you copy the arduino digitalwrite() code to a new function, say mydigitalwrite(), and make the original digitalwrite() a no-op, then change all references to digitalwrite in the wifi code to mydigitalwrite()?

        I’m just a network admin trying to learn something new, so i might be completely wrong here 🙂

        1. Well, functions like digitalwrite() weren’t really the problem. All those are doing is remapping Port I/O pins on the AVR to Arduino pins, which is easily compensated for by editing the remapping tables. The real problem was debugging- if, even after you’ve done everything that should be logically necessary to make it work, and it still doesn’t, then what? You need to debug. The more code, or the more remote that code is hosted, the more difficult it is to debug. Here we have a relatively large volume of code (for a uC) hosted very remotely (on an external device which we have no realtime link to). That’s pretty much the worst case for debugging. Fixing that would require investment in better tools, and time to learn those tools, and I have other fish I want to fry right now. 🙂

      3. I’ve done all of my embedded work on AVR, I have used Serial very successfully to debug ASM, C and Arduino code.

        I use a FT232RL based module (https://www.sparkfun.com/products/718) <$20, supports 5V and 3.3V, even has a regulator on board which you can break out. It uses two pins TX, RX on the uc and it plugs right into USB on the laptop next to my AVRISP and then I run a terminal program like Termite or Hercules to pick up the serial data. You basically just have to match the parity and baud rates with the settings on the micro and then you have comms up in a few minutes.

        Then I just transmit a known character at critical points in code. If you are working with C or ASM then its simple to transmit one byte at a time. Arduino obfuscates it a bit but you can still do it. Since there are so many unique characters its much easier to work out where you are and also because the characters are stored in the terminal you don't have to worry about the code being too fast to read. You can, as Jan says, start printing actual variable values as well

        The module is also very useful as an interface before you get around to setting up displays and buttons etc because of the extensive AVR serial interrupts.

  8. FlyPort Wi-Fi module
    A miniature web server module featuring a fully integrated 802.11 b/g/n Wi-Fi interface and several interfaces for the ‘real world’. The module integrates a powerful 16 bit processor which runs your custom applications and a Wi-Fi certified transceiver which handles the connectivity. It provides the embedded world with a powerful ‘Internet engine’ and a browser based interface, in a small footprint.

  9. IIRC the atmega168 can be debugged using the DEBUGwire interface, but you’ll need at least an AVR Dragon for that. The ATmega16 (atmega164) is the smallest AVR that supports a full JTAG interface, again you’ll need a Dragon. (There is a mod for the Arduino IDE out there that supports the atmega644). If you really get into debugging AVR code the $49 (Mouser) that the Dragon costs is CHEAP! You might have to replace local stack variables with static ones because sometimes the debugger does not give focus to stack variables (and it SUCKS having to examine memory to see the stack structure to read those variables).

    I have an AVRJTAGMKII that got thrown out from where I used to work because it got fried (Literally. Smoke came out of it). After repairing a broken switch (it was dropped and guess where it landed?) and patching burned traces on the PC as well as shorting out the blown fuse device (I don’t normally put pennies in the fuse box) I got it to show up Window’s hardware manager. I replaced the missing target cable by making my own out of common ribbon cable soldering it directly to the JTAG PC board (I wasn’t going to pay Atmel $50 for a stinking cable!). So much for doing things on the cheap. Anyway, it’s a VERY desirable tool, well worth the $300 it costs new.

  10. I really enjoyed this writeup. It doesn’t sound like a failure to me, more of an incomplete success. 😉 I have hundreds of such projects (mostly software, but some hardware) that taught me a lot of the skills I have now. I would certainly love to read more posts like this.
    Also, allow me to second what John Honniball about puns. I can’t mask, even a bit, how excited puns make me.

  11. For debugging digital circuits, I can recommend the following product:


    The software runs on Linux, Mac and Windoze. Various protocol analyzers are available for use including SPI.

    If 8 channels are not enough, they have a 16 channel version.

  12. Thanks for a very interesting article! The microchip Wifi module indeed looks interesting.

    I am left with two questions:

    – Why the goal of getting things squeezed into an 8 kB ATtiny? I would imagine that web enabled devices in any case would require a MCU with some more flash / ram / IO breathing room.

    – Did you search for other software stacks supporting the microchip wifi module, that may have been more easily portable to AVR platform?


    1. Hi Lars! Good questions. As for why I wanted to do it, it’s good old fashioned hacker curiosity. I love the idea of serving a tiny useless webpage from a chip the size of my thumbnail. 🙂 To your second question, I looked around a bit for TCP/IP stacks, and the WiShield code was most appealing because it was already very close to what I was trying to do- talk to a Microchip wifi modue with an AVR chip. Peeling back the layers of Arduino seemed easier than, say, porting the official Microchip library written for the Propeller chip over to AVR.

  13. Hi.
    Thanks for writing it up. Don’t think of it as a failure — it is just a project you haven’t finished yet 🙂

    If you have a PC with a wifi antenna sticking out the back, you might trying ‘sniffing’ the traffic using wireshark: http://www.wireshark.org/
    That should tell you if anything at all is being transmitted by the device.

  14. I just ran across this entry, fascinating in that I think we were on parallel paths with this same Wifi device, (I even got mine from the ‘fine folks at Farnell’). I’ve also suffered from failures/set-backs, but also in the ‘process starvation’ department.

    It sounds like you’ve gotten farther along than I have so far. I went with the Microchip TCP stack code base, and initially started porting it to ARM/gcc, but I found that the Mircrochip flavor of C was annoyingly different enough from standard C in ways I didn’t fully understand. I didn’t want to invest the time to learn all the compiler specifics for a chip family that I didn’t know and didn’t intend to use. I figured that mostly what I needed to do was alter the code at the SPI level to work with my ARM board and a little interrupt/GPIO work, and the higher level stuff would all just fall into place. I didn’t realize what strange beasts PICs are.

    I ended up scrapping that idea, and for reasons that don’t really make sense when I take a step back, decided to invest in some PIC chips and a programmer, and to try to just make the stack work mostly “as is” for my application. I still have not figured out what “my application” is, so I’m working on a solution without a problem so far, but having fun and learning in the process.

    I’ve worked with AVR’s a bit, and I can recommend getting yourself an AVR dragon, if you plan to do a lot of work with the chip. They’re a little pricey at around $50, but worth it, isp, JTAG, and “High voltage” programming. A lot of people on AVRFreaks complain that they’re fragile, but I baby mine, and I’ve never had a problem.

    I’ve got a few posts about my efforts:


    Anyways, great blog, I like you’re writing style, it’s got a quality and level of polish that I strive for (but usually miss). The Bread Head is awesome.


    1. Thanks for the great feedback! I like what you said about pursuing a solution without a problem. That’s been part of my lack of motivation on this sub-project. I started it on the premise that “an AVR on my wifi network seems like a useful widget to have”, but with no larger goal in mind. A genuine vision of the goal is helpful when things get tough and you have to dig deep to make progress on a problem. I’ll take another look at the Dragon when my hacking budget allows. I had also heard stories about fragility, but real on-chip debugging is too useful to ignore. Is it Windows only though, I wonder? All of Atmel’s stuff seems to be.

  15. I use mine under Windows, but I there are (non Atmel) Linux tools for debugging. I tried the linux tools very briefly, so I can’t comment on how well they work. The AVR Studio software is definitely Windows only, it’s built on the Microsoft Visual Studio shell, so it looks and feels very much like that IDE.

    About it being fragile, I only have one data point. I had a goof last year where I connected the wrong side of my voltage regulator to the rail of my breadboard, and for about a minute or so, I was sending 9v to the circuit with the Dragon attached before I realized what had happened. The interesting thing was that it fried my AVR, but only whatever part of the chip that controlled programming the flash. The core of the AVR still functioned with the previous programming, I was just unable to flash anything new. Thankfully, the Dragon came out unscathed. It gave me a chance to play with the “high voltage” programming mode, trying to “unbrick” the fried AVR.

    Hope that helps. I bought mine where I was still pretty new to embedded development, so I didn’t even consider making my own programmer at the time.

  16. So, from what I understand, the main problem was trying to separate the ‘duino code further from the module? apparently a lot of boilerplate arduino code is kept in “WProgram.h” in the arduino source code, where it defines vtables and some other things. This is probably where your code is getting confused. I hope this helps at least a little.

    I’m also following your Veronica project as well, and just purchased a WDC W65C02S CPU to try my hand at duplicating some of your work. 🙂

  17. in your dead bug picture isnt the module wired backwards? I am doing the same thing myself but I have it wired the other way round. I looked on yr site and got worried that I did it wrong. But your bigger breadboard picture shows it wired correctly (I think)..

    I mean that when viewed from the back pin 1 is the one closest to the (c) symbol and pin 36 is the one next to ‘FCC’

    great blog BTW

    1. You might be right! Honestly, I don’t remember. I take these photos as I go along, then try to string together the narrative from memory afterwards. It’s very possible that I wired it backwards, then realized (and corrected) the error, but forgot I had taken a photo of the incorrect one. Great eye!

    1. That’s quite a cool looking device. Let us know if you try it out! I wonder if that’s new? I did quite a lot of searching for something like that when I started this, and couldn’t find much.

Comments are closed.

Mini Cart 0

Your cart is empty.