µ-Wire – USB on an ATtiny 10

Atmels AVR ATtiny10 are surprisingly powerful devices that come in an extremely tiny SOT23 package with only 6 pins. The have 1kb of flash, 32 bytes of SRAM and use the reduced AVR core which only supports 16 instead of 32 register. It seems like Atmels idea of these devices is to use them as an advanced blinker, and to replace tiny logic circuits. But other people have shown that much more is possible. For example the noiseplug (video), a chiptune player, and a Simon Says game.

I previously used the ATtiny10 in the TinyTouchbutton, a touchbutton controlled light with WS2812 LEDs. This time I aimed higher: Is it possible to turn the ATtiny10 into a USB compatible device? My goal was to implement a subset of the little-wire functionality, to control a WS2812 LED by USB. This takes 3 I/O lines, which is exactly the number of free pins on the ATtiny10.

Littlewire supports several functions to control WS2812 LEDs on arbitrary I/O ports. I simplified this to only supporting a single LED on a specific pin, however still retained protocol compatibility. This means that all the little-wire host-programs still work. The finished device can, for example, be used as an RGB indicator LED similar to the Blink(1).

My test setup is shown below. The ATtiny10 is almost the smallest part of the circuit. There are some discrete components on the rear-side of all PCBs, so do not be surprised about missing decoupling capacitors, zener diodes and resistors.

u-wire-test

Implementation

The V-USB USB interface requires more than 1.5kb of flash in its smallest implementation and uses more than 50 bytes of ram, including stack usage. Additional code is required to implement the littlewire interface and to write to the WS2812. The ATtiny10 has 1kb of flash, 32 bytes of SRAM and only 16 instead of 32 registers. So this ended up as a nice exercise in cutting down V-USB to its bare essentials – and further.

A main step towards reducing the memory footprint was to switch to an interrupt-free implementation of V-USB, as developed for micronucleus. This allowed to reduce the SRAM footprint by omitting register saving on the stack and avoiding double buffering. In turn, this also allowed reducing the program size. The image below shows a timing diagram of an incoming USB packet, instructing to set the LED color, and the access to the WS2812 LED. The controller “steals” the time to write out to the WS2812 by ignoring the following USB data packet and catching up after the host resends it. This strict in-order processing allows to avoid the need of moving the data around in memory.

USB-Timing

AVR-GCC issues

One major annoyance during this project was AVR-GCC. Even though I used the newest versions of Atmels toolchain (4.8.1), the ATtiny10 support is still quite buggy. The ATtiny 4/5/9/10 come with a “reduced” AVR CPU core. One change is the introduction of different memory access instructions. Instead of 2 word LDS/STS, a 1 word LDS/STS is supported, which allows single cycle access to the SRAM. Unfortunately, AVR-GCC simply does not generate these instructions. Instead, indirect memory access via the Z-Register is used.

What I want:

  uint8_t   value0 = rq->wValue.bytes[0];
                      lds    r22,0x54

What I get:

   uint8_t   value0 = rq->wValue.bytes[0];
344:    e4 e5           ldi    r30, 0x54    ; 84
346:    f0 e0           ldi    r31, 0x00    ; 0
348:    60 81           ld     r22, Z

AVR-GCC effectively wastes 4 bytes for every variable access. I submitted a support ticket to Atmel around 3 months ago, but this seems to be a low priority issue to them. A pity they do not value support of their smallest microcontrollers higher, but maybe people are really only using them for very low complexity tasks.

I worked around this issue by using defines (see also this thread).

#define STS(mem,in) \
    asm volatile ("sts %0,%1"  : : "i" (&mem), "r" (in) :  "memory" );
#define LDS(out,mem) \
    asm volatile ("lds %0,%1"  : "=d" (out) : "i" (&mem));

Example:

uint8_t   value0 = rq->wValue.bytes[0];              // 6 bytes
uint8_t value0;     LDS(value0,rq->wValue.bytes[0]); // 2 bytes

Further measures to reduce code size:

  • I clocked the controller at 12 MHz using the internal RC oscillator and calibrated from the USB bus timing. The 12 MHz V-USB implementation is much smaller than the recommended 12.8 MHz version, but it does not come with an internal phase locked loop to cope with the more inaccurate RC-oscillator timing. Surprisingly I did not observe any timing errors.
  • Since the reduced core AVR only support 16 registers, I had to manually remap numerous registers in V-USB to avoid collisions with GCC.
  • I removed all handling of the reset signal on the USB-Bus. This means the device will not properly re-enumerate when a bus reset is issued. But this is not a problem if you plug it in after the PC was turned on.
  • V-USB was completely gutted and integrated into the main loop. It only support SETUP requests to a single endpoint now. All additional functions have been removed.  This also reduces stack usage as fewer subroutine calls are required.
  • I removed the code to support replies from the SRAM. Data can only be sent from flash. This is possible since only single byte-replies are required to implement the protocol, apart from the fixed USB configuration replies which are stored in the flash.

Final Stats

  • Flash: 988 of 1024 bytes used (96.4%)
  • SRAM: 28 (variables)+2 (stack) of 32 bytes used (93.8%)

This is most likely the most complex firmware ever loaded into an ATtiny10!

You can find all the code in the Github repository.

Bonus

Bonus below, it also works with the new 8mm WS2812 LEDs.

test-with-WS2812D

About these ads

7 thoughts on “µ-Wire – USB on an ATtiny 10

  1. […] Atmel’s ATtiny10 is their smallest microcontroller in terms of physical size – it’s an SOT-23-6 package, or about the same size as surface mount transistors. The hardware inside this extremely bare-bones; three I/O lines, 1kB of Flash, 32 bytes of RAM, and a reduced AVR core with 16 registers instead of 32. With such a minimal feature set, you would think the only thing this micro would be good for is blinking a LED. You’d be right, but [cpldcpu] can blink a LED with the ‘tiny10 over USB. […]

    • Niftyled looks great, but maybe it is a bit of overkill for a single LED.

      Controlling two LEDs may work but any more than that and I run into trouble with the USB timing.

  2. Do you use or recommend any usb anaylzing tool to see what’s going across the usb? (USBlyzer, Eltima USB Analyzer, USBTrace, etc)

    I’m not very familiar with usb, but I have a windows driver that sends an ink reset command to my printer.. except it’s a pain to switch to windows just to do this.. I’m sure others would also love to do a epson reset using linux!

    • The Saleae logic analyzer as able to analyze low speed USB 1.1. That’s what I used.

      However, you problem rather sounds as if you need some kind of software based USB sniffer on your host computer. I know there are some out there, but I have never used them.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s