Wow, has it really been three months since I last blogged? Hard to believe. I have been so busy hacking on cool stuff. Previously my blog posts have been to demonstrate something, which is all well and good when one has something to demonstrate. I think I might do more blog posts which are stream-of-consciousness.
In particular, the USB Doodad project is not dead. I have been working on it constantly, just not writing about it.
Anyway, expect to see a rash of new articles soon.
Thursday, May 19, 2011
Thursday, February 10, 2011
USB Doodad 6: Bootloader
Now that we know USB is working, it's time to get the bootloader working. The bootloader is called "bootloadHID", and can be found here:
http://www.obdev.at/products/vusb/bootloadhid.html
I downloaded bootloadHID and extracted it:
I have made some changes to the bootloader source and config:
"BOOTLOADER_ADDRESS = 7800": This is the start address of the bootloader. There's 2k (0x800) bytes of program space allocated for the bootloader and the '328 has 32k (0x8000) bytes of program flash. So the start of the bootloader is at 0x8000 - 0x800 = 0x7800.
"FUSEH = 0xda": For the blinkenled and the mouse demo, the high fuse was set to the '328's default of 0xd9. With this fuse setting, execution will start at address 0. In order to jump to the bootloader, we want to enable (=0) the BOOTRST fuse, and set the size of the bootloader to be 2048 bytes = 1024 words, and the start address to be 0x3C00 words = 0x7800 bytes. (This use of words and bytes is quite confusing. In the AVR and gcc world, some values are given in bytes and some are given in words. I've had several puzzling moments getting all this straightened out...)
Once again I looked up the fuse calculator:
http://frank.circleofcurrent.com/fusecalc/
Making these changes gives a high fuse value of 0xda.
Ok, back to the patch:
Then I compiled the software:
The warnings don't seem to matter, and I have ignored them.
Now I can program the Doodad:
Let me explain my strategy regarding the LEDs. My changes mean that the bootloader lights up the last LED, and the mouse demo lights up the second last LED. Both programs also run a binary counter on the next four LEDs.
If the last LED is on, it means that the bootloader has run. If the second last LED is on, it means the mouse demo has run. After programming the Doodad as above, the bootloader LED is on, but the mouse demo LED isn't. That makes sense because programming the bootloader erases all memory. Note also the four LEDs before the mouse demo LED are all on. That tells me the bootloader tried to jump to the mouse demo.
Also, when I run the lsusb command, I can't see any USB devices corresponding to the Doodad.
Next, pull out the Doodad and insert it with the button held down. The bootloader LED is on, the mouse demo LED is on, and the next four LEDs are running a binary counter. That tells me the bootloader is in program mode. lsusb also shows this:
Later on I'll show how to program an application such as the mouse demo over USB, but for the moment I want to prove that I can load the mouse demo and the bootloader into memory at the same time, and that the bootloader will fall through to the mouse demo if the bootloader button isn't pressed when the Doodad first starts.
So far, whenever avrdude has programmed the '328 program flash, that memory has first been erased. The problem with this is that it erases any existing program.
In order to get both the bootloader and the mouse demo in flash at the same time, we need to program the bootloader with a flash erase, then program the mouse demo without erasing the flash. (Note, the only time you can load a program with the programmer without doing an erase is if you're loading into memory which has already been erased).
+++ vusb-20100715/examples/hid-mouse/firmware/Makefile
@@ -11,7 +11,7 @@
F_CPU = 16000000 # in Hz
FUSE_L = 0xff# see below for fuse values for particular devices
FUSE_H = 0xda
-AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE) # edit this line for your programmer
+AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE) -D # edit this line for your programmer
CFLAGS = -Iusbdrv -I. -DDEBUG_LEVEL=0
OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
And if that wasn't impressive enough, After I plugged it into my computer's USB port, the mouse cursor started moving around in a large circle, and the LEDs are counting!
Here's what lsusb shows:
The mouse is the "VOTI" entry. The USB vendor ID and product ID can be set in the software.
Now to see the details:
I'm not sure why it says the report descriptors are unavailable. But at least it works.
Now I'll start working on the bootloader. If I can get the bootloader working, I can take the three pins we're currently using for ISP, and make them LED outputs.
To be covered in the next article:
http://www.obdev.at/products/vusb/bootloadhid.html
I downloaded bootloadHID and extracted it:
[mjd@onza src]$ wget -q http://www.obdev.at/downloads/vusb/bootloadHID.2010-07-29.tar.gz
[mjd@onza src]$ tar -xzf bootloadHID.2010-07-29.tar.gz
[mjd@onza src]$
[mjd@onza src]$ tar -xzf bootloadHID.2010-07-29.tar.gz
[mjd@onza src]$
I have made some changes to the bootloader source and config:
- We're using different pins for the USB.
- I want the LEDs to blink to show something's happening. The blinking is different depending on whether the bootloader is in program mode, or has handed control to the application.
- Our bootloader program mode switch is connected to an analog input rather than a digital input. Therefore I had to add code to read an analog value.
- Once the bootloader is in program mode, I want it to stay in program mode after the button is released. (In the stock code, you have to hold down the button constantly in order to program the MCU).
- I've changed the MCU type, the fuses, the execution start address and the right command to run avrdude for my USB programmer:
--- bootloadHID.2010-07-29.orig/firmware/bootloaderconfig.h
+++ bootloadHID.2010-07-29/firmware/bootloaderconfig.h
@@ -43,7 +43,7 @@
/* This is the port where the USB bus is connected. When you configure it to
* "B", the registers PORTB, PINB and DDRB will be used.
*/
-#define USB_CFG_DMINUS_BIT 0
+#define USB_CFG_DMINUS_BIT 3
/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
* This may be any bit in the port.
*/
@@ -106,6 +106,7 @@
#ifndef __ASSEMBLER__ /* assembler cannot parse function definitions */
#include <util/delay.h>
+#if 0
static inline void bootLoaderInit(void)
{
PORTD = 1 << 3; /* activate pull-up for key */
@@ -113,6 +114,7 @@
}
#define bootLoaderCondition() ((PIND & (1 << 3)) == 0) /* True if jumper is set */
+#endif
#endif
--- bootloadHID.2010-07-29.orig/firmware/main.c
+++ bootloadHID.2010-07-29/firmware/main.c
@@ -81,8 +81,15 @@
static void (*nullVector)(void) __attribute__((__noreturn__));
+static inline void setLed(const int i)
+{
+ PORTC &= ~(_BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3));
+ PORTC |= i & (_BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3));
+}
+
static void leaveBootloader()
{
+ setLed(15);
DBG1(0x01, 0, 0);
cli();
boot_rww_enable();
@@ -215,9 +222,68 @@
sei();
}
+#define ADC_CHANNEL 6
+#define ADC_ENABLED (1 << ADEN)
+#define ADC_SINGLE_CONVERSION (0 << ADATE)
+#define ADC_CONV_COMPLETE_INTR_OFF (0 << ADIE)
+#define ADC_PRESCALE_128 ((1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0))
+#define ADC_REFERENCE_AVCC (0 << REFS1 | 1 << REFS0)
+#define ADC_RIGHT_ADJUSTED (0 << ADLAR)
+#define ADC_START_CONV (1 << ADSC)
+#define ADC_CONV_IN_PROGRESS (1 << ADSC)
+#define ADC_CONV_INTFLAG (1 << ADIF)
+
+static inline void ADC_Init()
+{
+ // Write 0 to power up ADC, 1 to power down
+ PRR &= ~PRADC;
+
+ ADCSRA = ADC_ENABLED | ADC_SINGLE_CONVERSION | ADC_CONV_COMPLETE_INTR_OFF | ADC_PRESCALE_128;
+
+ ADMUX = ADC_REFERENCE_AVCC | ADC_RIGHT_ADJUSTED | ADC_CHANNEL;
+
+ _delay_us(10); /* wait for levels to stabilize */
+}
+
+static inline uint16_t ADC_Read()
+{
+ // TODO: Start conversion with ADSC=1
+ ADCSRA |= ADC_START_CONV;
+
+ // Wait for the conversion to complete
+ while (ADCSRA & ADC_CONV_IN_PROGRESS)
+ ;
+
+ ADCSRA |= ADC_CONV_INTFLAG;
+
+ return ADC;
+}
+
+static inline void ADC_StopReading()
+{
+ // ADEN in ADSRA enables ADC
+ ADCSRA &= ~ADEN;
+}
+
+static inline void bootLoaderInit(void)
+{
+ ADC_Init();
+}
+
+static inline char bootLoaderCondition()
+{
+ uint16_t v = ADC_Read();
+
+ ADC_StopReading();
+
+ return v < 512;
+}
+
int __attribute__((noreturn)) main(void)
{
/* initialize hardware */
+ DDRC |= _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3) | _BV(PC4) | _BV(PC5);
+ PORTC = _BV(5);
bootLoaderInit();
odDebugInit();
DBG1(0x00, 0, 0);
@@ -229,7 +295,14 @@
GICR = (1 << IVSEL); /* move interrupts to boot flash section */
#endif
initForUsbConnectivity();
+ unsigned int a = 0;
+ unsigned int b = 0;
do{ /* main event loop */
+ if (++a == 32)
+ {
+ a = 0;
+ setLed(++b >> 11);
+ }
wdt_reset();
usbPoll();
#if BOOTLOADER_CAN_EXIT
@@ -243,7 +316,7 @@
}
}
#endif
- }while(bootLoaderCondition());
+ } while(1);
}
leaveBootloader();
}
--- bootloadHID.2010-07-29.orig/firmware/Makefile
+++ bootloadHID.2010-07-29/firmware/Makefile
@@ -14,11 +14,11 @@
# make flash # to load the boot loader into flash
# make lock # to protect the boot loader from overwriting
-DEVICE = atmega8
-BOOTLOADER_ADDRESS = 1800
-F_CPU = 12000000
-FUSEH = 0xc0
-FUSEL = 0x9f
+DEVICE = atmega328p
+BOOTLOADER_ADDRESS = 7800
+F_CPU = 16000000
+FUSEH = 0xda
+FUSEL = 0xff
# Fuse high byte:
# 0xc0 = 1 1 0 0 0 0 0 0 <-- BOOTRST (boot reset vector at 0x1800)
# ^ ^ ^ ^ ^ ^ ^------ BOOTSZ0
@@ -38,7 +38,7 @@
###############################################################################
-AVRDUDE = avrdude -c stk500v2 -P avrdoper -p $(DEVICE)
+AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE)
LDFLAGS += -Wl,--relax,--gc-sections -Wl,--section-start=.text=$(BOOTLOADER_ADDRESS)
A note about the changes to the Makefile:+++ bootloadHID.2010-07-29/firmware/bootloaderconfig.h
@@ -43,7 +43,7 @@
/* This is the port where the USB bus is connected. When you configure it to
* "B", the registers PORTB, PINB and DDRB will be used.
*/
-#define USB_CFG_DMINUS_BIT 0
+#define USB_CFG_DMINUS_BIT 3
/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
* This may be any bit in the port.
*/
@@ -106,6 +106,7 @@
#ifndef __ASSEMBLER__ /* assembler cannot parse function definitions */
#include <util/delay.h>
+#if 0
static inline void bootLoaderInit(void)
{
PORTD = 1 << 3; /* activate pull-up for key */
@@ -113,6 +114,7 @@
}
#define bootLoaderCondition() ((PIND & (1 << 3)) == 0) /* True if jumper is set */
+#endif
#endif
--- bootloadHID.2010-07-29.orig/firmware/main.c
+++ bootloadHID.2010-07-29/firmware/main.c
@@ -81,8 +81,15 @@
static void (*nullVector)(void) __attribute__((__noreturn__));
+static inline void setLed(const int i)
+{
+ PORTC &= ~(_BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3));
+ PORTC |= i & (_BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3));
+}
+
static void leaveBootloader()
{
+ setLed(15);
DBG1(0x01, 0, 0);
cli();
boot_rww_enable();
@@ -215,9 +222,68 @@
sei();
}
+#define ADC_CHANNEL 6
+#define ADC_ENABLED (1 << ADEN)
+#define ADC_SINGLE_CONVERSION (0 << ADATE)
+#define ADC_CONV_COMPLETE_INTR_OFF (0 << ADIE)
+#define ADC_PRESCALE_128 ((1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0))
+#define ADC_REFERENCE_AVCC (0 << REFS1 | 1 << REFS0)
+#define ADC_RIGHT_ADJUSTED (0 << ADLAR)
+#define ADC_START_CONV (1 << ADSC)
+#define ADC_CONV_IN_PROGRESS (1 << ADSC)
+#define ADC_CONV_INTFLAG (1 << ADIF)
+
+static inline void ADC_Init()
+{
+ // Write 0 to power up ADC, 1 to power down
+ PRR &= ~PRADC;
+
+ ADCSRA = ADC_ENABLED | ADC_SINGLE_CONVERSION | ADC_CONV_COMPLETE_INTR_OFF | ADC_PRESCALE_128;
+
+ ADMUX = ADC_REFERENCE_AVCC | ADC_RIGHT_ADJUSTED | ADC_CHANNEL;
+
+ _delay_us(10); /* wait for levels to stabilize */
+}
+
+static inline uint16_t ADC_Read()
+{
+ // TODO: Start conversion with ADSC=1
+ ADCSRA |= ADC_START_CONV;
+
+ // Wait for the conversion to complete
+ while (ADCSRA & ADC_CONV_IN_PROGRESS)
+ ;
+
+ ADCSRA |= ADC_CONV_INTFLAG;
+
+ return ADC;
+}
+
+static inline void ADC_StopReading()
+{
+ // ADEN in ADSRA enables ADC
+ ADCSRA &= ~ADEN;
+}
+
+static inline void bootLoaderInit(void)
+{
+ ADC_Init();
+}
+
+static inline char bootLoaderCondition()
+{
+ uint16_t v = ADC_Read();
+
+ ADC_StopReading();
+
+ return v < 512;
+}
+
int __attribute__((noreturn)) main(void)
{
/* initialize hardware */
+ DDRC |= _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3) | _BV(PC4) | _BV(PC5);
+ PORTC = _BV(5);
bootLoaderInit();
odDebugInit();
DBG1(0x00, 0, 0);
@@ -229,7 +295,14 @@
GICR = (1 << IVSEL); /* move interrupts to boot flash section */
#endif
initForUsbConnectivity();
+ unsigned int a = 0;
+ unsigned int b = 0;
do{ /* main event loop */
+ if (++a == 32)
+ {
+ a = 0;
+ setLed(++b >> 11);
+ }
wdt_reset();
usbPoll();
#if BOOTLOADER_CAN_EXIT
@@ -243,7 +316,7 @@
}
}
#endif
- }while(bootLoaderCondition());
+ } while(1);
}
leaveBootloader();
}
--- bootloadHID.2010-07-29.orig/firmware/Makefile
+++ bootloadHID.2010-07-29/firmware/Makefile
@@ -14,11 +14,11 @@
# make flash # to load the boot loader into flash
# make lock # to protect the boot loader from overwriting
-DEVICE = atmega8
-BOOTLOADER_ADDRESS = 1800
-F_CPU = 12000000
-FUSEH = 0xc0
-FUSEL = 0x9f
+DEVICE = atmega328p
+BOOTLOADER_ADDRESS = 7800
+F_CPU = 16000000
+FUSEH = 0xda
+FUSEL = 0xff
# Fuse high byte:
# 0xc0 = 1 1 0 0 0 0 0 0 <-- BOOTRST (boot reset vector at 0x1800)
# ^ ^ ^ ^ ^ ^ ^------ BOOTSZ0
@@ -38,7 +38,7 @@
###############################################################################
-AVRDUDE = avrdude -c stk500v2 -P avrdoper -p $(DEVICE)
+AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE)
LDFLAGS += -Wl,--relax,--gc-sections -Wl,--section-start=.text=$(BOOTLOADER_ADDRESS)
"BOOTLOADER_ADDRESS = 7800": This is the start address of the bootloader. There's 2k (0x800) bytes of program space allocated for the bootloader and the '328 has 32k (0x8000) bytes of program flash. So the start of the bootloader is at 0x8000 - 0x800 = 0x7800.
"FUSEH = 0xda": For the blinkenled and the mouse demo, the high fuse was set to the '328's default of 0xd9. With this fuse setting, execution will start at address 0. In order to jump to the bootloader, we want to enable (=0) the BOOTRST fuse, and set the size of the bootloader to be 2048 bytes = 1024 words, and the start address to be 0x3C00 words = 0x7800 bytes. (This use of words and bytes is quite confusing. In the AVR and gcc world, some values are given in bytes and some are given in words. I've had several puzzling moments getting all this straightened out...)
Once again I looked up the fuse calculator:
http://frank.circleofcurrent.com/fusecalc/
Making these changes gives a high fuse value of 0xda.
Ok, back to the patch:
[mjd@onza src]$ cd bootloadHID.2010-07-29
[mjd@onza bootloadHID.2010-07-29]$ patch -p1 < ../bootloadHID.2010-07-29.diff
patching file firmware/bootloaderconfig.h
patching file firmware/main.c
patching file firmware/Makefile
[mjd@onza bootloadHID.2010-07-29]$
[mjd@onza bootloadHID.2010-07-29]$ patch -p1 < ../bootloadHID.2010-07-29.diff
patching file firmware/bootloaderconfig.h
patching file firmware/main.c
patching file firmware/Makefile
[mjd@onza bootloadHID.2010-07-29]$
Then I compiled the software:
[mjd@onza bootloadHID.2010-07-29]$ cd firmware
[mjd@onza firmware]$ make
avr-gcc -Wall -Os -fno-move-loop-invariants -fno-tree-scev-cprop -fno-inline-small-functions -Iusbdrv -I. -mmcu=atmega328p -DF_CPU=16000000 -DDEBUG_LEVEL=0 -x assembler-with-cpp -c usbdrv/usbdrvasm.S -o usbdrv/usbdrvasm.o
avr-gcc -Wall -Os -fno-move-loop-invariants -fno-tree-scev-cprop -fno-inline-small-functions -Iusbdrv -I. -mmcu=atmega328p -DF_CPU=16000000 -DDEBUG_LEVEL=0 -c usbdrv/oddebug.c -o usbdrv/oddebug.o
avr-gcc -Wall -Os -fno-move-loop-invariants -fno-tree-scev-cprop -fno-inline-small-functions -Iusbdrv -I. -mmcu=atmega328p -DF_CPU=16000000 -DDEBUG_LEVEL=0 -c main.c -o main.o
usbdrv/usbdrv.h:213: warning: 'usbFunctionDescriptor' used but never defined
usbdrv/usbdrv.h:220: warning: 'usbSetInterrupt' declared 'static' but never defined
avr-gcc -Wall -Os -fno-move-loop-invariants -fno-tree-scev-cprop -fno-inline-small-functions -Iusbdrv -I. -mmcu=atmega328p -DF_CPU=16000000 -DDEBUG_LEVEL=0 -o main.bin usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o -Wl,--relax,--gc-sections -Wl,--section-start=.text=7800
rm -f main.hex main.eep.hex
avr-objcopy -j .text -j .data -O ihex main.bin main.hex
avr-size main.hex
text data bss dec hex filename
0 1926 0 1926 786 main.hex
[mjd@onza firmware]$
[mjd@onza firmware]$ make
avr-gcc -Wall -Os -fno-move-loop-invariants -fno-tree-scev-cprop -fno-inline-small-functions -Iusbdrv -I. -mmcu=atmega328p -DF_CPU=16000000 -DDEBUG_LEVEL=0 -x assembler-with-cpp -c usbdrv/usbdrvasm.S -o usbdrv/usbdrvasm.o
avr-gcc -Wall -Os -fno-move-loop-invariants -fno-tree-scev-cprop -fno-inline-small-functions -Iusbdrv -I. -mmcu=atmega328p -DF_CPU=16000000 -DDEBUG_LEVEL=0 -c usbdrv/oddebug.c -o usbdrv/oddebug.o
avr-gcc -Wall -Os -fno-move-loop-invariants -fno-tree-scev-cprop -fno-inline-small-functions -Iusbdrv -I. -mmcu=atmega328p -DF_CPU=16000000 -DDEBUG_LEVEL=0 -c main.c -o main.o
usbdrv/usbdrv.h:213: warning: 'usbFunctionDescriptor' used but never defined
usbdrv/usbdrv.h:220: warning: 'usbSetInterrupt' declared 'static' but never defined
avr-gcc -Wall -Os -fno-move-loop-invariants -fno-tree-scev-cprop -fno-inline-small-functions -Iusbdrv -I. -mmcu=atmega328p -DF_CPU=16000000 -DDEBUG_LEVEL=0 -o main.bin usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o -Wl,--relax,--gc-sections -Wl,--section-start=.text=7800
rm -f main.hex main.eep.hex
avr-objcopy -j .text -j .data -O ihex main.bin main.hex
avr-size main.hex
text data bss dec hex filename
0 1926 0 1926 786 main.hex
[mjd@onza firmware]$
The warnings don't seem to matter, and I have ignored them.
Now I can program the Doodad:
[mjd@onza firmware]$ su
Password:
[root@onza firmware]# make flash
avrdude -c avrisp2 -P usb -p atmega328p -U flash:w:main.hex:i
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e950f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (32646 bytes):
Writing | ################################################## | 100% 0.60s
avrdude: 32646 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 32646 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 8.67s
avrdude: verifying ...
avrdude: 32646 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]# make fuse
avrdude -c avrisp2 -P usb -p atmega328p -U hfuse:w:0xda:m -U lfuse:w:0xff:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e950f
avrdude: reading input file "0xda"
avrdude: writing hfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of hfuse written
avrdude: verifying hfuse memory against 0xda:
avrdude: load data hfuse data from input file 0xda:
avrdude: input file 0xda contains 1 bytes
avrdude: reading on-chip hfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of hfuse verified
avrdude: reading input file "0xff"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xff:
avrdude: load data lfuse data from input file 0xff:
avrdude: input file 0xff contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]#
Password:
[root@onza firmware]# make flash
avrdude -c avrisp2 -P usb -p atmega328p -U flash:w:main.hex:i
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e950f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (32646 bytes):
Writing | ################################################## | 100% 0.60s
avrdude: 32646 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 32646 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 8.67s
avrdude: verifying ...
avrdude: 32646 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]# make fuse
avrdude -c avrisp2 -P usb -p atmega328p -U hfuse:w:0xda:m -U lfuse:w:0xff:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e950f
avrdude: reading input file "0xda"
avrdude: writing hfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of hfuse written
avrdude: verifying hfuse memory against 0xda:
avrdude: load data hfuse data from input file 0xda:
avrdude: input file 0xda contains 1 bytes
avrdude: reading on-chip hfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of hfuse verified
avrdude: reading input file "0xff"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xff:
avrdude: load data lfuse data from input file 0xff:
avrdude: input file 0xff contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]#
Let me explain my strategy regarding the LEDs. My changes mean that the bootloader lights up the last LED, and the mouse demo lights up the second last LED. Both programs also run a binary counter on the next four LEDs.
If the last LED is on, it means that the bootloader has run. If the second last LED is on, it means the mouse demo has run. After programming the Doodad as above, the bootloader LED is on, but the mouse demo LED isn't. That makes sense because programming the bootloader erases all memory. Note also the four LEDs before the mouse demo LED are all on. That tells me the bootloader tried to jump to the mouse demo.
Also, when I run the lsusb command, I can't see any USB devices corresponding to the Doodad.
Next, pull out the Doodad and insert it with the button held down. The bootloader LED is on, the mouse demo LED is on, and the next four LEDs are running a binary counter. That tells me the bootloader is in program mode. lsusb also shows this:
⋮
Bus 003 Device 010: ID 16c0:05df VOTI
⋮
Bus 003 Device 010: ID 16c0:05df VOTI
⋮
Later on I'll show how to program an application such as the mouse demo over USB, but for the moment I want to prove that I can load the mouse demo and the bootloader into memory at the same time, and that the bootloader will fall through to the mouse demo if the bootloader button isn't pressed when the Doodad first starts.
So far, whenever avrdude has programmed the '328 program flash, that memory has first been erased. The problem with this is that it erases any existing program.
In order to get both the bootloader and the mouse demo in flash at the same time, we need to program the bootloader with a flash erase, then program the mouse demo without erasing the flash. (Note, the only time you can load a program with the programmer without doing an erase is if you're loading into memory which has already been erased).
- Erase the flash
+++ vusb-20100715/examples/hid-mouse/firmware/Makefile
@@ -11,7 +11,7 @@
F_CPU = 16000000 # in Hz
FUSE_L = 0xff# see below for fuse values for particular devices
FUSE_H = 0xda
-AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE) # edit this line for your programmer
+AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE) -D # edit this line for your programmer
CFLAGS = -Iusbdrv -I. -DDEBUG_LEVEL=0
OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
And if that wasn't impressive enough, After I plugged it into my computer's USB port, the mouse cursor started moving around in a large circle, and the LEDs are counting!
Here's what lsusb shows:
[root@onza mjd]# lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 1a40:0101 TERMINUS TECHNOLOGY INC.
Bus 001 Device 003: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 004: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 005: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 1a40:0101 TERMINUS TECHNOLOGY INC.
Bus 001 Device 003: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 004: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 005: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
The mouse is the "VOTI" entry. The USB vendor ID and product ID can be set in the software.
Now to see the details:
[root@onza mjd]# lsusb -v
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x16c0 VOTI
idProduct 0x03e8 free for internal lab use 1000
bcdDevice 1.00
iManufacturer 1 obdev.at
iProduct 2 Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 400mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.01
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 52
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 100
Device Status: 0x0000
(Bus Powered)
⋮
[root@onza mjd]#
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x16c0 VOTI
idProduct 0x03e8 free for internal lab use 1000
bcdDevice 1.00
iManufacturer 1 obdev.at
iProduct 2 Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 400mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.01
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 52
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 100
Device Status: 0x0000
(Bus Powered)
⋮
[root@onza mjd]#
I'm not sure why it says the report descriptors are unavailable. But at least it works.
Now I'll start working on the bootloader. If I can get the bootloader working, I can take the three pins we're currently using for ISP, and make them LED outputs.
To be covered in the next article:
- How to use the bootloader to program new apps to the Doodad without using an ISP.
- How to use fuses to protect the bootloader.
Tuesday, November 23, 2010
USB Doodad 5: Working USB
Now that I know the processor on the Doodad is working, I want to try out USB. As we'll be using a software USB library, it's important that we test this out as soon as possible, in case we need to make circuit changes, or it's unreliable, or it just doesn't work at all.
The software USB stack we want to use is called V-USB:
http://www.obdev.at/products/vusb
I downloaded V-USB and extracted it:
V-USB comes with several demos. I chose the mouse demo because it doesn't require any extra hardware such as switches or chips.
I have made some changes to the mouse demo source to reflect that we're using different pins for the USB, and also to make the LEDs blink. I've also changed the MCU type, the fuses, and the right command to run avrdude for my USB programmer:
I applied this patch:
Then compiled the software:
And now I can program the Doodad:
As a prelude to trying it I also filed away the end of the board so it can fit into a USB port.
Well after that, I power cycled the Doodad, and the second last LED is lit. And if that wasn't impressive enough, After I plugged it into my computer's USB port, the mouse cursor started moving around in a large circle, and the LEDs are counting!
Here's what lsusb shows:
The mouse is the "VOTI" entry. The USB vendor ID and product ID can be set in the software.
Now to see the details:
I'm not sure why it says the report descriptors are unavailable. But at least it works.
Now I'll start working on the bootloader. If I can get the bootloader working, I can take the three pins we're currently using for ISP, and make them LED outputs.
The success continues!
The software USB stack we want to use is called V-USB:
http://www.obdev.at/products/vusb
I downloaded V-USB and extracted it:
[mjd@onza src]$ wget http://www.obdev.at/downloads/vusb/vusb-20100715.tar.gz
--2010-11-23 18:17:39-- http://www.obdev.at/downloads/vusb/vusb-20100715.tar.gz
Resolving www.obdev.at... 78.46.114.187
Connecting to www.obdev.at|78.46.114.187|:80... connected.
HTTP request sent, awaiting response... 302 Found
Location: http://www.obdev.at/ftp/pub/Products/vusb/vusb-20100715.tar.gz [following]
--2010-11-23 18:17:40-- http://www.obdev.at/ftp/pub/Products/vusb/vusb-20100715.tar.gz
Reusing existing connection to www.obdev.at:80.
HTTP request sent, awaiting response... 200 OK
Length: 417848 (408K) [application/x-gzip]
Saving to: “vusb-20100715.tar.gz”
100%[======================================>] 417,848 94.8K/s in 4.3s
2010-11-23 18:17:45 (94.8 KB/s) - “vusb-20100715.tar.gz” saved [417848/417848]
[mjd@onza src]$ tar -xzf vusb-20100715.tar.gz
[mjd@onza src]$
--2010-11-23 18:17:39-- http://www.obdev.at/downloads/vusb/vusb-20100715.tar.gz
Resolving www.obdev.at... 78.46.114.187
Connecting to www.obdev.at|78.46.114.187|:80... connected.
HTTP request sent, awaiting response... 302 Found
Location: http://www.obdev.at/ftp/pub/Products/vusb/vusb-20100715.tar.gz [following]
--2010-11-23 18:17:40-- http://www.obdev.at/ftp/pub/Products/vusb/vusb-20100715.tar.gz
Reusing existing connection to www.obdev.at:80.
HTTP request sent, awaiting response... 200 OK
Length: 417848 (408K) [application/x-gzip]
Saving to: “vusb-20100715.tar.gz”
100%[======================================>] 417,848 94.8K/s in 4.3s
2010-11-23 18:17:45 (94.8 KB/s) - “vusb-20100715.tar.gz” saved [417848/417848]
[mjd@onza src]$ tar -xzf vusb-20100715.tar.gz
[mjd@onza src]$
V-USB comes with several demos. I chose the mouse demo because it doesn't require any extra hardware such as switches or chips.
I have made some changes to the mouse demo source to reflect that we're using different pins for the USB, and also to make the LEDs blink. I've also changed the MCU type, the fuses, and the right command to run avrdude for my USB programmer:
--- vusb-20100715-orig/examples/hid-mouse/firmware/main.c
+++ vusb-20100715/examples/hid-mouse/firmware/main.c
@@ -124,9 +124,19 @@
/* ------------------------------------------------------------------------- */
+static const unsigned char portCMask = _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3);
+
+static void setLED(const unsigned char v)
+{
+ PORTC &= ~portCMask;
+ PORTC |= (v & portCMask);
+}
+
int __attribute__((noreturn)) main(void)
{
-uchar i;
+ uchar i;
+ DDRC |= _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3) | _BV(PC4) | _BV(PC5);
+ PORTC |= _BV(4);
wdt_enable(WDTO_1S);
/* Even if you don't use the watchdog, turn it off here. On newer devices,
@@ -148,12 +158,16 @@
usbDeviceConnect();
sei();
DBG1(0x01, 0, 0); /* debug output: main loop starts */
+ unsigned char a = 0;
for(;;){ /* main event loop */
DBG1(0x02, 0, 0); /* debug output: main loop iterates */
wdt_reset();
usbPoll();
if(usbInterruptIsReady()){
/* called after every poll of the interrupt endpoint */
+ ++a;
+ a &= 0b111111;
+ setLED(a >> 2);
advanceCircleByFixedAngle();
DBG1(0x03, 0, 0); /* debug output: interrupt report prepared */
usbSetInterrupt((void *)&reportBuffer, sizeof(reportBuffer));
--- vusb-20100715-orig/examples/hid-mouse/firmware/Makefile+++ vusb-20100715/examples/hid-mouse/firmware/Makefile
@@ -7,11 +7,11 @@
# License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
# This Revision: $Id: Makefile 692 2008-11-07 15:07:40Z cs $
-DEVICE = atmega168
-F_CPU = 16000000 # in Hz
-FUSE_L = # see below for fuse values for particular devices
-FUSE_H =
-AVRDUDE = avrdude -c usbasp -p $(DEVICE) # edit this line for your programmer
+DEVICE = atmega328p
+F_CPU = 16000000 # in Hz
+FUSE_L = 0xff# see below for fuse values for particular devices
+FUSE_H = 0xd9
+AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE) # edit this line for your programmer
CFLAGS = -Iusbdrv -I. -DDEBUG_LEVEL=0
OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
@@ -125,6 +125,8 @@
clean:
rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.elf *.o usbdrv/*.o main.s usbdrv/oddebug.s usbdrv/usbdrv.s
+main.o: main.c usbconfig.h
+
# Generic rule for compiling C files:
.c.o:
$(COMPILE) -c $< -o $@
diff -x usbdrv -X lufa-dontdiff.txt -u -r vusb-20100715-orig/examples/hid-mouse/firmware/usbconfig.h vusb-20100715/examples/hid-mouse/firmware/usbconfig.h
--- vusb-20100715-orig/examples/hid-mouse/firmware/usbconfig.h 2010-07-16 02:43:47.000000000 +1000
+++ vusb-20100715/examples/hid-mouse/firmware/usbconfig.h 2010-11-22 09:07:33.000000000 +1100
@@ -27,7 +27,7 @@
/* This is the port where the USB bus is connected. When you configure it to
* "B", the registers PORTB, PINB and DDRB will be used.
*/
-#define USB_CFG_DMINUS_BIT 4
+#define USB_CFG_DMINUS_BIT 3
/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
* This may be any bit in the port.
*/
@@ -116,7 +116,7 @@
/* Define this to 1 if the device has its own power supply. Set it to 0 if the
* device is powered from the USB bus.
*/
-#define USB_CFG_MAX_BUS_POWER 20
+#define USB_CFG_MAX_BUS_POWER 400
/* Set this variable to the maximum USB bus power consumption of your device.
* The value is in milliamperes. [It will be divided by two since USB
* communicates power requirements in units of 2 mA.]
+++ vusb-20100715/examples/hid-mouse/firmware/main.c
@@ -124,9 +124,19 @@
/* ------------------------------------------------------------------------- */
+static const unsigned char portCMask = _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3);
+
+static void setLED(const unsigned char v)
+{
+ PORTC &= ~portCMask;
+ PORTC |= (v & portCMask);
+}
+
int __attribute__((noreturn)) main(void)
{
-uchar i;
+ uchar i;
+ DDRC |= _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3) | _BV(PC4) | _BV(PC5);
+ PORTC |= _BV(4);
wdt_enable(WDTO_1S);
/* Even if you don't use the watchdog, turn it off here. On newer devices,
@@ -148,12 +158,16 @@
usbDeviceConnect();
sei();
DBG1(0x01, 0, 0); /* debug output: main loop starts */
+ unsigned char a = 0;
for(;;){ /* main event loop */
DBG1(0x02, 0, 0); /* debug output: main loop iterates */
wdt_reset();
usbPoll();
if(usbInterruptIsReady()){
/* called after every poll of the interrupt endpoint */
+ ++a;
+ a &= 0b111111;
+ setLED(a >> 2);
advanceCircleByFixedAngle();
DBG1(0x03, 0, 0); /* debug output: interrupt report prepared */
usbSetInterrupt((void *)&reportBuffer, sizeof(reportBuffer));
--- vusb-20100715-orig/examples/hid-mouse/firmware/Makefile+++ vusb-20100715/examples/hid-mouse/firmware/Makefile
@@ -7,11 +7,11 @@
# License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
# This Revision: $Id: Makefile 692 2008-11-07 15:07:40Z cs $
-DEVICE = atmega168
-F_CPU = 16000000 # in Hz
-FUSE_L = # see below for fuse values for particular devices
-FUSE_H =
-AVRDUDE = avrdude -c usbasp -p $(DEVICE) # edit this line for your programmer
+DEVICE = atmega328p
+F_CPU = 16000000 # in Hz
+FUSE_L = 0xff# see below for fuse values for particular devices
+FUSE_H = 0xd9
+AVRDUDE = avrdude -c avrisp2 -P usb -p $(DEVICE) # edit this line for your programmer
CFLAGS = -Iusbdrv -I. -DDEBUG_LEVEL=0
OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
@@ -125,6 +125,8 @@
clean:
rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.elf *.o usbdrv/*.o main.s usbdrv/oddebug.s usbdrv/usbdrv.s
+main.o: main.c usbconfig.h
+
# Generic rule for compiling C files:
.c.o:
$(COMPILE) -c $< -o $@
diff -x usbdrv -X lufa-dontdiff.txt -u -r vusb-20100715-orig/examples/hid-mouse/firmware/usbconfig.h vusb-20100715/examples/hid-mouse/firmware/usbconfig.h
--- vusb-20100715-orig/examples/hid-mouse/firmware/usbconfig.h 2010-07-16 02:43:47.000000000 +1000
+++ vusb-20100715/examples/hid-mouse/firmware/usbconfig.h 2010-11-22 09:07:33.000000000 +1100
@@ -27,7 +27,7 @@
/* This is the port where the USB bus is connected. When you configure it to
* "B", the registers PORTB, PINB and DDRB will be used.
*/
-#define USB_CFG_DMINUS_BIT 4
+#define USB_CFG_DMINUS_BIT 3
/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
* This may be any bit in the port.
*/
@@ -116,7 +116,7 @@
/* Define this to 1 if the device has its own power supply. Set it to 0 if the
* device is powered from the USB bus.
*/
-#define USB_CFG_MAX_BUS_POWER 20
+#define USB_CFG_MAX_BUS_POWER 400
/* Set this variable to the maximum USB bus power consumption of your device.
* The value is in milliamperes. [It will be divided by two since USB
* communicates power requirements in units of 2 mA.]
I applied this patch:
[mjd@onza src]$ cd vusb-20100715
[mjd@onza vusb-20100715]$ patch -p1 < ../vusb-20100715-mjd-1.diff
patching file examples/hid-mouse/firmware/main.c
patching file examples/hid-mouse/firmware/Makefile
patching file examples/hid-mouse/firmware/usbconfig.h
[mjd@onza vusb-20100715]$
[mjd@onza vusb-20100715]$ patch -p1 < ../vusb-20100715-mjd-1.diff
patching file examples/hid-mouse/firmware/main.c
patching file examples/hid-mouse/firmware/Makefile
patching file examples/hid-mouse/firmware/usbconfig.h
[mjd@onza vusb-20100715]$
Then compiled the software:
[mjd@onza vusb-20100715]$ cd examples/hid-mouse/firmware
[mjd@onza firmware]$ make hex
cp -r ../../../usbdrv .
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c usbdrv/usbdrv.c -o usbdrv/usbdrv.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -x assembler-with-cpp -c usbdrv/usbdrvasm.S -o usbdrv/usbdrvasm.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c usbdrv/oddebug.c -o usbdrv/oddebug.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c main.c -o main.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -o main.elf usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
rm -f main.hex main.eep.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avr-size main.hex
text data bss dec hex filename
0 1872 0 1872 750 main.hex
[mjd@onza firmware]$ make hex
cp -r ../../../usbdrv .
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c usbdrv/usbdrv.c -o usbdrv/usbdrv.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -x assembler-with-cpp -c usbdrv/usbdrvasm.S -o usbdrv/usbdrvasm.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c usbdrv/oddebug.c -o usbdrv/oddebug.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -c main.c -o main.o
avr-gcc -Wall -Os -DF_CPU=16000000 -Iusbdrv -I. -DDEBUG_LEVEL=0 -mmcu=atmega328p -o main.elf usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
rm -f main.hex main.eep.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avr-size main.hex
text data bss dec hex filename
0 1872 0 1872 750 main.hex
And now I can program the Doodad:
[mjd@onza firmware]$ su
Password:
[root@onza firmware]# make flash
avrdude -c avrisp2 -P usb -p atmega328p -U flash:w:main.hex:i
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (1872 bytes):
Writing | ################################################## | 100% 0.65s
avrdude: 1872 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 1872 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.52s
avrdude: verifying ...
avrdude: 1872 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]# make fuse
avrdude -c avrisp2 -P usb -p atmega328p -U hfuse:w:0xd9:m -U lfuse:w:0xff:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: reading input file "0xd9"
avrdude: writing hfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of hfuse written
avrdude: verifying hfuse memory against 0xd9:
avrdude: load data hfuse data from input file 0xd9:
avrdude: input file 0xd9 contains 1 bytes
avrdude: reading on-chip hfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of hfuse verified
avrdude: reading input file "0xff"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xff:
avrdude: load data lfuse data from input file 0xff:
avrdude: input file 0xff contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]#
Password:
[root@onza firmware]# make flash
avrdude -c avrisp2 -P usb -p atmega328p -U flash:w:main.hex:i
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (1872 bytes):
Writing | ################################################## | 100% 0.65s
avrdude: 1872 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 1872 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.52s
avrdude: verifying ...
avrdude: 1872 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]# make fuse
avrdude -c avrisp2 -P usb -p atmega328p -U hfuse:w:0xd9:m -U lfuse:w:0xff:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: reading input file "0xd9"
avrdude: writing hfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of hfuse written
avrdude: verifying hfuse memory against 0xd9:
avrdude: load data hfuse data from input file 0xd9:
avrdude: input file 0xd9 contains 1 bytes
avrdude: reading on-chip hfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of hfuse verified
avrdude: reading input file "0xff"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xff:
avrdude: load data lfuse data from input file 0xff:
avrdude: input file 0xff contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza firmware]#
As a prelude to trying it I also filed away the end of the board so it can fit into a USB port.
Well after that, I power cycled the Doodad, and the second last LED is lit. And if that wasn't impressive enough, After I plugged it into my computer's USB port, the mouse cursor started moving around in a large circle, and the LEDs are counting!
Here's what lsusb shows:
[root@onza mjd]# lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 1a40:0101 TERMINUS TECHNOLOGY INC.
Bus 001 Device 003: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 004: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 005: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 1a40:0101 TERMINUS TECHNOLOGY INC.
Bus 001 Device 003: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 004: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 001 Device 005: ID 0951:1607 Kingston Technology Data Traveler 2.0
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
The mouse is the "VOTI" entry. The USB vendor ID and product ID can be set in the software.
Now to see the details:
[root@onza mjd]# lsusb -v
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x16c0 VOTI
idProduct 0x03e8 free for internal lab use 1000
bcdDevice 1.00
iManufacturer 1 obdev.at
iProduct 2 Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 400mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.01
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 52
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 100
Device Status: 0x0000
(Bus Powered)
⋮
[root@onza mjd]#
Bus 003 Device 002: ID 16c0:03e8 VOTI free for internal lab use 1000
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x16c0 VOTI
idProduct 0x03e8 free for internal lab use 1000
bcdDevice 1.00
iManufacturer 1 obdev.at
iProduct 2 Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 400mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.01
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 52
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 100
Device Status: 0x0000
(Bus Powered)
⋮
[root@onza mjd]#
I'm not sure why it says the report descriptors are unavailable. But at least it works.
Now I'll start working on the bootloader. If I can get the bootloader working, I can take the three pins we're currently using for ISP, and make them LED outputs.
The success continues!
Thursday, November 4, 2010
USB Doodad 4: Blinkenleds
Progress on the USB Doodad has been going well with several successes.
After soldering on the processor, I added the reset pullup resistor, and rigged up a 6-pin programming header on the end of the board.
There's no permanent position for a programming header on the board. This is because once the board has a bootloader, loading software on the Doodad will be done over USB. Our plan is to use pogo pins to make a "bed of nails" that we can temporarily sit the Doodad board on to get the bootloader into it.
I plugged the Doodad into my programmer, and plugged the programmer into my PC. There was no smoke (!), and the avrdude program could recognise the '328! That's always a good sign.
I then soldered a LED and a dropper resistor onto the pin for bit 5 of port C, searched for a simple "blinkenled" program on the 'net, and compiled it for the '328:
I then programmed it into the '328:
Yay, the LED blinks! But there's a slight problem: The LED should blink once a second, but I found it was actually blinking once every eight seconds. This is because by default, there's a fuse in the '328 which divides the clock by 8.
AVR chips have a few bytes of persistent memory called "fuses". The bits in these fuses control things such as what source the chip uses for clock, and what areas of memory are protected. The name for the fuse that's dividing the clock by 8 is called CKDIV8.
Here's how I retrieved the fuse values:
I then looked up an AVR fuse calculator:
http://frank.circleofcurrent.com/fusecalc/
Turning off the CKDIV8 fuse means the "low fuse" value changes from 0x62 to 0xE2. The fuse calculator also helpfully shows the avrdude parameters to make this change:
I can then check that the fuse programming has worked:
After doing this, the LED flashes once per second.
At this point, the '328's clock is being provided from an internal oscillator. The internals oscillator is convenient if you want to keep the number of parts on the board to a minimum, but the frequency can drift with temperature, so it's not a great choice for a USB device. So my next step was to solder on a quartz crystal and the two associated capacitors.
Back in the fuse calculator, I used the pull-down menu in the "low fuse presets" to choose "Ext. Crystal Osc.; Frequency 8.0- MHz; Start-up time PWRDWN/RESET: 16K CK/14 CK + 65 ms; [CKSEL=1111 SUT=11]". That makes the low fuse value 0xFF. So I programmed that in using avrdude as above:
[root@onza 328hello]# /usr/bin/avrdude -c avrisp2 -P usb -p m328p -U lfuse:w:0xff:m
⋮
⋮
⋮
avrdude done. Thank you.
The LED is now flashing, but it's twice as fast as it should be. This is because when I compiled the code, I said the clock frequency was 8MHz, to match the internal oscillator. That's not going to work very well with a 16MHz external crystal. So I recompiled the code:
I won't show the linking and .hex file creation steps as they're the same as above, but after reprogramming the '328, the LED now blinks at 1Hz.
So, I have a board on which I can run software, and the means to program it. Winnage!
After soldering on the processor, I added the reset pullup resistor, and rigged up a 6-pin programming header on the end of the board.
![]() |
| (Note this is a prototype at a later stage) |
There's no permanent position for a programming header on the board. This is because once the board has a bootloader, loading software on the Doodad will be done over USB. Our plan is to use pogo pins to make a "bed of nails" that we can temporarily sit the Doodad board on to get the bootloader into it.
I plugged the Doodad into my programmer, and plugged the programmer into my PC. There was no smoke (!), and the avrdude program could recognise the '328! That's always a good sign.
[root@onza mjd]# /usr/bin/avrdude -c avrisp2 -P usb -p m328p -q
avrdude: AVR device initialized and ready to accept instructions
avrdude: Device signature = 0x1e950f
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza mjd]#
avrdude: AVR device initialized and ready to accept instructions
avrdude: Device signature = 0x1e950f
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza mjd]#
I then soldered a LED and a dropper resistor onto the pin for bit 5 of port C, searched for a simple "blinkenled" program on the 'net, and compiled it for the '328:
[mjd@onza 328hello]$ cat hello.c
#include <avr/io.h>
#include <util/delay.h>
int main()
{
// Set bit 5 of port C to be an output
DDRC |= _BV(PC5);
while (1)
{
// Invert the value of pin 5 on port C
PORTC ^= _BV(PC5);
_delay_ms(500);
}
}
[mjd@onza 328hello]$ avr-gcc -Os -DF_CPU=8000000 -mmcu=atmega328p -o hello.o -c hello.c
[mjd@onza 328hello]$ avr-gcc -Os -DF_CPU=8000000 -mmcu=atmega328p -o hello.elf hello.o
[mjd@onza 328hello]$ avr-objcopy -O ihex hello.elf hello.hex
[mjd@onza 328hello]$ ls -l hello.hex
-rw-rw-r--. 1 mjd mjd 582 2010-11-21 14:52 hello.hex
[mjd@onza 328hello]$
#include <avr/io.h>
#include <util/delay.h>
int main()
{
// Set bit 5 of port C to be an output
DDRC |= _BV(PC5);
while (1)
{
// Invert the value of pin 5 on port C
PORTC ^= _BV(PC5);
_delay_ms(500);
}
}
[mjd@onza 328hello]$ avr-gcc -Os -DF_CPU=8000000 -mmcu=atmega328p -o hello.o -c hello.c
[mjd@onza 328hello]$ avr-gcc -Os -DF_CPU=8000000 -mmcu=atmega328p -o hello.elf hello.o
[mjd@onza 328hello]$ avr-objcopy -O ihex hello.elf hello.hex
[mjd@onza 328hello]$ ls -l hello.hex
-rw-rw-r--. 1 mjd mjd 582 2010-11-21 14:52 hello.hex
[mjd@onza 328hello]$
I then programmed it into the '328:
[root@onza 328hello]# /usr/bin/avrdude -c avrisp2 -P usb -p m328p -U flash:w:hello.hex
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "hello.hex"
avrdude: input file hello.hex auto detected as Intel Hex
avrdude: writing flash (200 bytes):
Writing | ################################################## | 100% 0.08s
avrdude: 200 bytes of flash written
avrdude: verifying flash memory against hello.hex:
avrdude: load data flash data from input file hello.hex:
avrdude: input file hello.hex auto detected as Intel Hex
avrdude: input file hello.hex contains 200 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.06s
avrdude: verifying ...
avrdude: 200 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza 328hello]#
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "hello.hex"
avrdude: input file hello.hex auto detected as Intel Hex
avrdude: writing flash (200 bytes):
Writing | ################################################## | 100% 0.08s
avrdude: 200 bytes of flash written
avrdude: verifying flash memory against hello.hex:
avrdude: load data flash data from input file hello.hex:
avrdude: input file hello.hex auto detected as Intel Hex
avrdude: input file hello.hex contains 200 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.06s
avrdude: verifying ...
avrdude: 200 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza 328hello]#
Yay, the LED blinks! But there's a slight problem: The LED should blink once a second, but I found it was actually blinking once every eight seconds. This is because by default, there's a fuse in the '328 which divides the clock by 8.
AVR chips have a few bytes of persistent memory called "fuses". The bits in these fuses control things such as what source the chip uses for clock, and what areas of memory are protected. The name for the fuse that's dividing the clock by 8 is called CKDIV8.
Here's how I retrieved the fuse values:
[root@onza 328hello]# /usr/bin/avrdude -c avrisp2 -P usb -p m328p -t
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude> dump lfuse
0000 62 |. |
avrdude> dump hfuse
0000 d9 |. |
avrdude> dump efuse
0000 07 |. |
avrdude> quit
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza 328hello]#
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude> dump lfuse
0000 62 |. |
avrdude> dump hfuse
0000 d9 |. |
avrdude> dump efuse
0000 07 |. |
avrdude> quit
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza 328hello]#
I then looked up an AVR fuse calculator:
http://frank.circleofcurrent.com/fusecalc/
Turning off the CKDIV8 fuse means the "low fuse" value changes from 0x62 to 0xE2. The fuse calculator also helpfully shows the avrdude parameters to make this change:
[root@onza 328hello]# /usr/bin/avrdude -c avrisp2 -P usb -p m328p -U lfuse:w:0xE2:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: reading input file "0xE2"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.02s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xE2:
avrdude: load data lfuse data from input file 0xE2:
avrdude: input file 0xE2 contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude: reading input file "0xE2"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.02s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xE2:
avrdude: load data lfuse data from input file 0xE2:
avrdude: input file 0xE2 contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
I can then check that the fuse programming has worked:
[root@onza 328hello]# /usr/bin/avrdude -c avrisp2 -P usb -p m328p -t
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude> dump lfuse
0000 e2 |. |
avrdude> quit
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza 328hello]#
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e950f
avrdude> dump lfuse
0000 e2 |. |
avrdude> quit
avrdude: safemode: Fuses OK
avrdude done. Thank you.
[root@onza 328hello]#
After doing this, the LED flashes once per second.
At this point, the '328's clock is being provided from an internal oscillator. The internals oscillator is convenient if you want to keep the number of parts on the board to a minimum, but the frequency can drift with temperature, so it's not a great choice for a USB device. So my next step was to solder on a quartz crystal and the two associated capacitors.
Back in the fuse calculator, I used the pull-down menu in the "low fuse presets" to choose "Ext. Crystal Osc.; Frequency 8.0- MHz; Start-up time PWRDWN/RESET: 16K CK/14 CK + 65 ms; [CKSEL=1111 SUT=11]". That makes the low fuse value 0xFF. So I programmed that in using avrdude as above:
[root@onza 328hello]# /usr/bin/avrdude -c avrisp2 -P usb -p m328p -U lfuse:w:0xff:m
⋮
⋮
⋮
avrdude done. Thank you.
The LED is now flashing, but it's twice as fast as it should be. This is because when I compiled the code, I said the clock frequency was 8MHz, to match the internal oscillator. That's not going to work very well with a 16MHz external crystal. So I recompiled the code:
[mjd@onza 328hello]$ avr-gcc -Os -DF_CPU=16000000 -mmcu=atmega328p -o hello.o -c hello.c
I won't show the linking and .hex file creation steps as they're the same as above, but after reprogramming the '328, the LED now blinks at 1Hz.
So, I have a board on which I can run software, and the means to program it. Winnage!
Wednesday, November 3, 2010
USB Doodad 3: Hot-air soldering of TQFP-32 chips
The processor we're using in the USB Doodad is an AVR ATmega328P. This very capable processor is at the heart of the popular Arduino educational microcontroller platform.
The two most common packages for the ATmega328P are the PDIP-28 and the TQFP-32.
<Good place to insert picture of PDIP vs TQFP>
Because the whole purpose of the USB Doodad is to use surface mount components, we want to use the TQFP-32 package.
Armed with a USB Doodad prototype board that Ross etched, I thought it was time to try soldering this chip.
There are several ways to solder SMT ICs. A good guide is in this SparkFun SMT tutorial. The method I used was using solder paste and hot air.
In order to do hot air soldering, I use a "hot air rework station" which I bought for about $80 in China. I found another one at Ameritronics which looks very nice, nicer than mine.
Ideally, I'd use a syringe of solder paste, but I don't have one. Instead I used the paste I covered previously.
I applied my solder paste with a jeweller's screwdriver. Not a very precise way of doing it, but it did the job. I applied one thin line of solder paste along the line of IC pads, then squished the chip pins into the paste. Then I ran some liquid flux along the pins on all four sides of the chip.
Next, I used the hot air to melt the solder on each corner of the chip. That was to fix the chip into place. Finally, I ran the hot air along each side of the chip in turn. The combination of pins, solder paste and flux means that when the solder paste melted, it blobbed around each pin. I didn't get any bridges between pins, but if I had, I'm confident I could have fixed it with some desoldering braid.
My success makes me think that beginners could solder it too, if they had the right equipment (a hot air rework station). And if we had a board with a solder mask (as we'll surely have for Doodad a little further down the track), I think beginners could do it with a regular iron too, if they had a video to watch showing how it's done.
Well, that's how I soldered a TQFN-32!
The two most common packages for the ATmega328P are the PDIP-28 and the TQFP-32.
<Good place to insert picture of PDIP vs TQFP>
Because the whole purpose of the USB Doodad is to use surface mount components, we want to use the TQFP-32 package.
| Image courtesy of SparkFun. |
Armed with a USB Doodad prototype board that Ross etched, I thought it was time to try soldering this chip.
There are several ways to solder SMT ICs. A good guide is in this SparkFun SMT tutorial. The method I used was using solder paste and hot air.
In order to do hot air soldering, I use a "hot air rework station" which I bought for about $80 in China. I found another one at Ameritronics which looks very nice, nicer than mine.
Ideally, I'd use a syringe of solder paste, but I don't have one. Instead I used the paste I covered previously.
I applied my solder paste with a jeweller's screwdriver. Not a very precise way of doing it, but it did the job. I applied one thin line of solder paste along the line of IC pads, then squished the chip pins into the paste. Then I ran some liquid flux along the pins on all four sides of the chip.
Next, I used the hot air to melt the solder on each corner of the chip. That was to fix the chip into place. Finally, I ran the hot air along each side of the chip in turn. The combination of pins, solder paste and flux means that when the solder paste melted, it blobbed around each pin. I didn't get any bridges between pins, but if I had, I'm confident I could have fixed it with some desoldering braid.
My success makes me think that beginners could solder it too, if they had the right equipment (a hot air rework station). And if we had a board with a solder mask (as we'll surely have for Doodad a little further down the track), I think beginners could do it with a regular iron too, if they had a video to watch showing how it's done.
Well, that's how I soldered a TQFN-32!
Tuesday, November 2, 2010
USB Doodad 2: Board design and layout
Some time ago, Ross and I discussed and agreed on what we'd like this board to do (and perhaps just as importantly, what we don't need it to do). How do we get from this stage to something people can assemble? One way is by making a prototype.
A prototype lets us:
Using that project as proof of what is possible, Ross designed the circuit to our requirements, and did a PCB layout. Because we may use the underside of the board for other purposes later, Ross faced the additional constraint of having to make the board single-sided. He and I discussed and improved the circuit and the layout several times, and Ross reworked the circuit and layout to match.
In particular, if we use the same pins as the Elektor project, we'd only have 14 pins free for driving LEDs. Ideally however, we'd like 16 LEDs. We think by reassigning some pins, doing away with some features (for example, a bootloader exit button, and a reset button), and being creative with some circuit ideas (for example, using a resistor network to run a number of switches off some otherwise-unused ADC pins), we believe we can recover enough pins to give us 16 LEDs. We'd also like to be able to use the SDA and SCK pins on the expansion port so we can easily attach I2C devices such as sensors and storage ICs.
In order to do this pin experimentation, the board currently has a number of solder pads we can link to reroute various signals. Here's the current layout:
On the left is the expansion port (+, - and two data pins). On the right is the USB finger port. Along the bottom is where the LEDs and dropper resistors will go. In the top right are the USB analog components. In the middle is the microcontroller, and to each side are the power supply and clock components. The switches go in the top left.
While this board may not do everything we want it to, it will let us answer a lot of the questions we need to answer before we can produce a final board ready for sending to a manufacturing house.
I want to say that Ross has done a top job: The single-sided layout constraint is pretty fierce, and the quality of the prototype board is easily as good as a bought one. I am learning a lot working with him.
A prototype lets us:
- Try out the circuit and different variations,
- Try out the PCB layout and variations,
- Try out the software and variations, and
- Try out different assembly ideas.
Using that project as proof of what is possible, Ross designed the circuit to our requirements, and did a PCB layout. Because we may use the underside of the board for other purposes later, Ross faced the additional constraint of having to make the board single-sided. He and I discussed and improved the circuit and the layout several times, and Ross reworked the circuit and layout to match.
In particular, if we use the same pins as the Elektor project, we'd only have 14 pins free for driving LEDs. Ideally however, we'd like 16 LEDs. We think by reassigning some pins, doing away with some features (for example, a bootloader exit button, and a reset button), and being creative with some circuit ideas (for example, using a resistor network to run a number of switches off some otherwise-unused ADC pins), we believe we can recover enough pins to give us 16 LEDs. We'd also like to be able to use the SDA and SCK pins on the expansion port so we can easily attach I2C devices such as sensors and storage ICs.
In order to do this pin experimentation, the board currently has a number of solder pads we can link to reroute various signals. Here's the current layout:
![]() |
| USB Doodad PCB artwork (v15) |
While this board may not do everything we want it to, it will let us answer a lot of the questions we need to answer before we can produce a final board ready for sending to a manufacturing house.
I want to say that Ross has done a top job: The single-sided layout constraint is pretty fierce, and the quality of the prototype board is easily as good as a bought one. I am learning a lot working with him.
Monday, November 1, 2010
USB Doodad: An SMT exercise project
Surface mount soldering doesn't have to be scary!
Way back in the distant past when I learned electronics, components were "through hole". That means that components such as resistors had a wire out each end, and these wires were bent and passed through holes in a circuit board, then soldered.
These days, through hole technology is on the decline. It's becoming harder to get the newer integrated circuits in through-hole form. In its place, more and more people are using "surface mount" technology to build circuits. With SMT, components are often in the form of a tiny block, which is placed on a circuit board, directly in contact with the circuit board tracks, then soldered into place.
Many people I've spoken to think that using SMT is harder than through-hole. Sure, many SMT components are very tiny, but it's quite easy to buy larger sized SMT components.
As a project at our hackerspace, Connected Community Hackerspace, Ross McKenzie and I have been working on a project which shows people that doing SMT assembly is not as hard as people think. We have deliberately used the largest size of SMT components to make assembly as easy as possible.
The board has a USB connector at one end, a small expansion connector at the other, and a row of 16 software controlled LEDs in the middle.
The primary purpose of the board is to act as a practical exercise in surface mount assembly. The secondary purpose is to show how to use the V-USB software USB stack to make a USB device. The third purpose is to give the Doodad some post-tutorial value, in a number of intentionally frivolous ways:
We plan to release the design for this project under an open licence, possibly the TAPR OHL, which means that others can copy this design.
The project has been designed to use a single sided PCB. Although this makes the job of designing and routing the board traces much more difficult, being single sided means people can make this board at home.
For applications which require the doodad to operate independently of a PC, we plan to add a small Li-Ion battery and management chip to the reverse of the board.
More information about the project can be found in the USB Doodad Google doc.
Ross and I are making rapid progress, and I'll post more project updates here soon.
Way back in the distant past when I learned electronics, components were "through hole". That means that components such as resistors had a wire out each end, and these wires were bent and passed through holes in a circuit board, then soldered.
| An example through-hole board. |
These days, through hole technology is on the decline. It's becoming harder to get the newer integrated circuits in through-hole form. In its place, more and more people are using "surface mount" technology to build circuits. With SMT, components are often in the form of a tiny block, which is placed on a circuit board, directly in contact with the circuit board tracks, then soldered into place.
| An example SMT board. |
As a project at our hackerspace, Connected Community Hackerspace, Ross McKenzie and I have been working on a project which shows people that doing SMT assembly is not as hard as people think. We have deliberately used the largest size of SMT components to make assembly as easy as possible.
The board has a USB connector at one end, a small expansion connector at the other, and a row of 16 software controlled LEDs in the middle.
The primary purpose of the board is to act as a practical exercise in surface mount assembly. The secondary purpose is to show how to use the V-USB software USB stack to make a USB device. The third purpose is to give the Doodad some post-tutorial value, in a number of intentionally frivolous ways:
- Persistence-of-vision toy. Wave it around or put it on the wheels of your bike. Enjoy making groovy flowing messages in the dark.
- Secure password keystore.
- Log-data-to-serial-flash data logger, where samples could be retrieved over USB at some later time.
- “Messages waiting” / “processor load” / “build progress” indicator.
- Multimedia keys (if your keyboard doesn’t have them).
- Novelty breathalyser.
- Countdown timer for car parking reminder.
We plan to release the design for this project under an open licence, possibly the TAPR OHL, which means that others can copy this design.
The project has been designed to use a single sided PCB. Although this makes the job of designing and routing the board traces much more difficult, being single sided means people can make this board at home.
For applications which require the doodad to operate independently of a PC, we plan to add a small Li-Ion battery and management chip to the reverse of the board.
More information about the project can be found in the USB Doodad Google doc.
Ross and I are making rapid progress, and I'll post more project updates here soon.
Subscribe to:
Posts (Atom)

