V-USB with ATtiny45 / ATtiny85 without a crystal

One guy at Hack a Day remarked on the long wire runs in my V-USB tutorial breadboard setup. So I thought I’d build upon the part 4 of the tutorial but modify the setup a bit to run the AVR at 5 volts and use zener diodes to drop D+/D- voltage, thus eliminating the need for a regulator. And why not stop there. ATtiny45 and ATtiny85 are smaller than ATtiny2313 and have an internal oscillator that can be calibrated to provide 16.5 MHz clock, accurate enough for V-USB to do its magic. I challenge anyone to drastically shorten these wire runs!

In the photo, I used a 4-pin header to show the place of the USB cable so the zener diodes would not get obstructed. Note that due to the angle it can seem like the 0.1 uF tantalum cap (light brown one) is wired to PB4 when it really is going to GND pin! Here’s the schematic, heavily borrowed from V-USB’s EasyLogger reference implementation:

Note: 2.2 kohm pullup for D- was missing in the schematic, it has now been corrected.

Main code changes needed are the change of pins to PORTB. Also, the oscillator calibration routine needs a hook added to the config. Here are the changes I made to usbconfig.h:

#define USB_CFG_IOPORTNAME      B
#define USB_CFG_DMINUS_BIT      1
#define USB_CFG_DPLUS_BIT       2
#define USB_CFG_CLOCK_KHZ       (F_CPU/1000)
#define USB_CFG_HAVE_MEASURE_FRAME_LENGTH   1

// The following needs to be uncommented
#define USB_RESET_HOOK(resetStarts)     if(!resetStarts){hadUsbReset();}

// Add the following after USB_RESET_HOOK
#ifndef __ASSEMBLER__
extern void hadUsbReset(void); // define the function for usbdrv.c
#endif

Note that USB_CFG_CLOCK_KHZ now relies on F_CPU constant. Furthermore, F_CPU can be set in the Makefile by passing -DF_CPU=16500000 as a command-line argument to gcc, so defining it can also be omitted from main.c. Another thing we need to change in the makefile is to set mcu/part code to attiny85 for both avrdude and gcc.

In main.c, we only need to remove definition of F_CPU, change the LED pin from PB0 to PB3, and add the calibration routine. The calibration routine in EasyLogger has a slightly mind-bending binary search which, while short, does not take into account that ATtiny45 and ATtiny85 have OSCCAL which works in two overlapping frequency regions, 0..127 and 128..255. So I made my own which is as short but does search the two spaces separately for the optimum value (on the other hand, my routine does not descend to 0 or 128, which should not be an issue).

#define abs(x) ((x) > 0 ? (x) : (-x))

// Called by V-USB after device reset
void hadUsbReset() {
    int frameLength, targetLength = (unsigned)(1499 * (double)F_CPU / 10.5e6 + 0.5);
    int bestDeviation = 9999;
    uchar trialCal, bestCal, step, region;

    // do a binary search in regions 0-127 and 128-255 to get optimum OSCCAL
    for(region = 0; region <= 1; region++) {
        frameLength = 0;
        trialCal = (region == 0) ? 0 : 128;
        
        for(step = 64; step > 0; step >>= 1) { 
            if(frameLength < targetLength) // true for initial iteration
                trialCal += step; // frequency too low
            else
                trialCal -= step; // frequency too high
                
            OSCCAL = trialCal;
            frameLength = usbMeasureFrameLength();
            
            if(abs(frameLength-targetLength) < bestDeviation) {
                bestCal = trialCal; // new optimum found
                bestDeviation = abs(frameLength -targetLength);
            }
        }
    }

    OSCCAL = bestCal;
}

That’s it. Again, the full main.c and usbconfig.h along with modified makefile, updated schematic and rest of the needed stuff (driver, libusb, usbdrv folders) are available as one zip file released under GPL. Interface to the new device is unchanged, “usbtest on”, “usbtest out” etc. should work from command-line like they did with the ATtiny2313-powered device.

Fuse bits

Oh and one last thing – the fuse bits need of course be changed. Remove the 8x clock divider and switch to High Frequency PLL clock (CKSEL=0001) with slowly rising power (SUT=10). For ATtiny45/85 this should result in low fuse byte of 0xE1. You might also consider 2.7 V brown-out detection, making the high byte 0xDD:

avrdude -c usbtiny -p attiny85 -U lfuse:w:0xe1:m -U hfuse:w:0xdd:m

Published by

Joonas Pihlajamaa

Coding since 1990 in Basic, C/C++, Perl, Java, PHP, Ruby and Python, to name a few. Also interested in math, movies, anime, and the occasional slashdot now and then. Oh, and I also have a real life, but lets not talk about it!

105 thoughts on “V-USB with ATtiny45 / ATtiny85 without a crystal”

    1. I don’t know exactly myself, but because the clock is calibrated by measuring USB frame length, I’d wager that it’s something like “USB frame is 1499 clock cycles with rate of 10.5 MHz”, so with F_CPU double that, it would be 1499 * 2 CPU cycles (F_CPU / 10.5e6 = 2), and similarly for other values of F_CPU.

      The 0.5 is for rounding towards nearest integer, not flooring like it usually does ((int)4.9 = 4, but (int)(4.9+0.5)=5).

      1. thanks for reply,i found the word below
        /* This function MUST be called IMMEDIATELY AFTER USB reset and measures 1/7 of
        * the number of CPU cycles during one USB frame minus one low speed bit
        * length. In other words: return value = 1499 * (F_CPU / 10.5 MHz)
        and the usb1.1 speed is 1.5mbps,is that possiblely 10.5=7*1.5?

  1. Just in case anyone gets this in the future. When I tried the make file I got:


    usbdrv/usbdrv.h:455:6: error: variable 'usbDescriptorDevice' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.h:461:6: error: variable 'usbDescriptorConfiguration' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.h:467:6: error: variable 'usbDescriptorHidReport' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.h:473:6: error: variable 'usbDescriptorString0' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.h:479:5: error: variable 'usbDescriptorStringVendor' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.h:485:5: error: variable 'usbDescriptorStringDevice' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.h:491:5: error: variable 'usbDescriptorStringSerialNumber' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.c:70:14: error: variable 'usbDescriptorString0' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.c:80:14: error: variable 'usbDescriptorStringVendor' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.c:89:14: error: variable 'usbDescriptorStringDevice' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.c:111:14: error: variable 'usbDescriptorDevice' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    usbdrv/usbdrv.c:142:14: error: variable 'usbDescriptorConfiguration' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
    .

    To reason for it is the new avr-gcc needs progmem to be const. You just need to put const in front of every line that begins with PROGMEM . Sed fixes this nicely with sed -i 's/^PROGMEM/const PROGMEM/g' usbdrv/*.

  2. I failed and failed to get this working.
    I first tried a year ago. I finally came back to this.
    Still failed. Then, while reading your 2313 logic analyser post, I came across nerdralph’s reply-which had NOTHING to do with v-usb. I went to nerdralph’s blog and the answer was staring me in the face.

    http://nerdralph.blogspot.com USB interfacing for AVR microcontrollers
    too LOW value of D- pullup resistor.
    I was using 1k5. I changed to 2k5 and windows likes it.
    thanks joonas and thanks ralph

    1. Don’t know, they are from local electronics shop, not from the big mail vendors. I don’t think there are big differences, as long as they are small enough (some big ones require more current to start the cascade) — you can see the pictures. :)

  3. Quick question about your absolute value macro:
    #define abs(x) ((x) > 0 ? (x) : (-x))

    When you call it with
    abs(frameLength-targetLength)
    won’t it substitute to
    ((frameLength-targetLength) > 0 ? (frameLength-targetLength) : (-frameLength-targetLength))
    ?

    Ideally, your macro would be defined
    #define abs(x) ((x) > 0 ? (x) : -(x))
    with the minus sign outside the parentheses, right?

Leave a Reply

Your email address will not be published. Required fields are marked *