Mit V-USB von Objective Development ist es ohne weitere Hardware möglich, mit einem AVR Mikrocontroller ein USB-Gerät aufzubauen.
Inhaltsverzeichnis
Hardware Vorraussetzungen des AVRs
Spannungspegel-Problem
Da der AVR mit anderen Spannungen als die des USB-Standarts arbeitet, müssen diese Angepasst werden. Der Computer und der AVR-Mikrocontroller haben beide bestimmte Anforderungen an die Spannung für einen High- bzw. Low-Pegel. Da wir die Computerseite nicht verändern können, müssen die Anpassungen am AVR statfinden. Vom PC wird ein Low-Pegel als 0V, ein High-Pegel als 3,3V übertragen. Jedoch ist die kein Problem, da die AVR-Mikrcontroller selbst bei 5V diesen Pegel sicher als High erkennen. Die andere Richtung ist ein größreres Problem. Der Computer erwartet als Low-Pegel eine Spannung von 0V - 0,8V und 2V - 3,6V als High. Um diese Vorraussetzungen zu erfüllen, muss entweder der AVR mit einer Spannung von 2V - 3,6V betreiben werden oder die Pegel müssen an D+ und D- des AVRs angepasst werden.
Lösung A: Verringern der Betreibsspannug des AVRs
Für diese Lösung wird die Betriebsspannung des AVRs auf 3,3V - 3,6V heruntergesetzt. Dies ist mit einem 3,3V-Spannungsregler (LE33CZ) möglich. Schaltplan
Vorteile
- Saubere Lösung. Schnelle Übergänge auf D+ und D-
- Gute Störfestigkeit am Signaleingang
Nachteile
- 3,3V-Spannungsregler sind oft teuer und schwer zu beschaffen
- Viele AVRs funktionieren nicht sicher bei den Angeforderten Taktraten
- Hohe Stromaufnahme durch den Spannungsregler (überschreiten maximale Stromaufnahme von 500mA)
Ebenso ist es möglich, die Spannung mit Glechrichterdioden zu reduzieren. Hierfür werden einfach zwei oder drei Gleichrichterdioden in Reihe vor VCC des AVRs geschaltet. Schaltplan
Zusätzliche Vorteile
- Niedrige kosten / Bauteile sind leicht zu bekommen.
- kein Ruhestrom --> USB-Konformer Standby-Modus
Zusätzliche Nachteile
- Keine Spannungsregelung --> Spannung kann bei hoher Last einbrechen
- Die unregulierte Spannung ist problemtisch für Analoge Schaltungen (ADC,...)
Lösung B: Anpassen der Sapnnung an D+ und D-
Ebenso kann man die Spannung an den Datenleitungen (D+ und D-) mit Zenerdioden verringern. Es werden 3,6V Low-Power Zernerdioden (wie 1N4148, 500mW oder weniger) empfohlen, da diese eine geringe Kapazität haben und somit weniger Störungen auf den Datenleitungen verursachen. Schaltplan
Wenn diese Lösung verwendet wird, stellen sie vor benutzung sicher, dass die Spannungspegel mit den geforderten Pegeln(LOW:0V-0,8V HIGH:2V-3,6V) überinstimmen.
Vorteile
- Niedrige kosten
- Leicht zu bekommen
- Ganze Schaltung kann mit 5V betrieben werden --> Hohe Taktraten möglich
Nachteile
- Keine saubere Lösung: Es muss ein Kompromiss zwischen allen Möglichkeiten gefunden werden.
- Zener-Dioden kommen mit einem breiten Spektrum an Merkmalen. Deshalb könnten die Ergebnisse nicht reproduzierbar sein.
- Hohe Ströme beim Senden von High-Leveln
Allgemeine Anmerkungen zur Hardware
Es wird bei jeder der oben genannten Schaltungen ein PullUp-Widerstand (1,5k - 10k) von D- nach Vcc benötigt. Ebenso muss zwischen AVR und Die Datenleitungen ein Widerstand von je 68 Ohm.
Welche Taktraten können verwendet werden?
Momentan unterstützt V-USB Taktraten von 12 MHz, 12.8 MHz, 15 MHz, 16 MHz, 16.5 MHz, 18 MHz und 20 MHz. Diese Taktraten sind prezise, dies bedeutet, dass ein Quarz mit 11.9 MHz nicht funktionieren würde. Nur bei den 16.5 und 12.8 MHz Varianten ist eine Toleranz von 1%.16.5 Mhz kann z.B. mit dem internen RC Oszillator von AVRs wie dem ATTiny25/45/85 oder dem ATTiny26 erreicht werden. Entscheidungshilfe
- Möchten sie den internen OC Oszillator benutzen? Dann nehmen sie die 16.5MHz-Variante.
- Ist sehr wenig Speicher vorhanden? Verwenden sie am besten die 16MHz oder 20Mhz Variante.
- Wird der AVR mit einer niedrigen Spannung betrieben? Dann verwenden sie die 12MHz-Variante.
- Wollen sie CRC-Prüfsummen verwenden? Benutzen sie die 18Mhz-Variante.
Anschluss an den AVR
Die Datenleitung D+ muss über den 68 Ohm-Widerstand mit dem Port INT0 des AVRs verbunden werden. D- kann an einen beliebigen Port. Diese Ports müssen dann in der "usbconfig.h" angepasst werden.
Software des AVR: Die Firmware
Als erstes muss man sich die neueste Version von V-USB von folgender Seite herunterladen: [1] Nachdem sie das Archiv heruntergeladen haben, entpacken sie es und kopieren sie den Ordner "usbdrv" in ihr Projektverzeichnis. Kopieren bzw. erstellen das Makefile und passen darin die Taktrate und den verwendeten Controller an. Ebenso wird eine "usbconfig.h" benötigt, diese kann z.B. aus dem tests-Ordner kopiert werden. In dieser müssen nun die Punkte USB_CFG_IOPORTNAME, USB_CFG_DMINUS_BIT und USB_CFG_DPLUS_BIT angepasst werden.
In der main.c müssen immer als erstes folgende Dateien importiert werden:
#include <avr/io.h> //IO-Zugriff: Braucht man immer #include <avr/interrupt.h> // V-USB braucht interrupts #include <avr/pgmspace.h> #include <avr/wdt.h> //Watchdog: Sollte immer aktiv sein. #include "usbdrv.h" //Der USB-Treiber #include <util/delay.h> //Wird später für Timing etc. benötigt
Gerät mit selbst geschriebenem PC-Treiber (kein HID)
Der Code für ein eigenes Gerät wird hier am Beispiel eines Gerätes veranschaulicht, bei dem sich über USB der PWM-Kanal steuern lässt. Damit kann man zum Beispiel eine LED dimmen.
Nach den Includes komm nun die Funktion zum Verarbeiten eingehender Daten. Das erste Empfangene Byte, also der Befehl wird mit rq->bRequest abgefragt. In diesem Fall ist dieser entweder 0 oder 1. 0 bedeutet den PWM-Wert zu setzen, 1 bedeutet den Status (PWM-Wert) zurückzusenden. Die einzelnen Parameter des Aufrufs können mit rq->wValue.bytes[id] abgefragt werden. Bei Befehl 0 wird in rq->wValue.bytes[0] der neue PWM-Wert übertragen. Durch setzen von replyBuf kann festgelegt werden, welche Daten zurückgesendet werden sollen. Dabei muss immer replyBuf[id] verwedet werden. Bei Befehl 1 wird mit replyBuf[0] = OCR1A der aktuelle PWM-Wert zurückgesendet. Mit return 2 wird dann noch mitgeteilt, dass überhaupt was zurückgesendet werden soll. Mit return 0 wird nichts zurückgesendet. Das ist in diesem Fall der Fall, wenn ein ungültiger Befehl übermittelt wurde.
USB_PUBLIC uchar usbFunctionSetup(uchar data[8]) { usbRequest_t *rq = (void *)data; static uchar replyBuf[2]; usbMsgPtr = replyBuf; if(rq->bRequest == 0){ replyBuf[0] = rq->wValue.bytes[0]; replyBuf[1] = 0x0; OCR1A=rq->wValue.bytes[0]; eeprom_write_byte(0,OCR1A); return 2; } else if (rq->bRequest == 1) { replyBuf[0] = OCR1A; replyBuf[1] = 0x0; return 2; } return 0; }
Als nächstes komm void main:
Hier schalten wir als erstes den Watchdog-Timer ein. Danach setzen wir alle Ports und DDR so wie wir sie brauchen. Es ist nur darauf zu achten, dass INT0 ein Eingang ist. Bei einem Atmega8 ist dies PORTD.2. Für unser Beispiel setzen wir TCCR1A als 8_bit PWM-Timer. Danach laden wir den letzten PWM-Wert aus dem EEPROM (Wird immer beim Ändern gespeichert). Nun trennen wir die Verbindung für > 500ms, dies wird benötigt, um nach einen Neustart des Mikrocontrollers (z.B. durch Watchdog) dem PC mitzuteilen, dass er neugestartet ist. Danach Verbinden wir uns wieder mit usbDeviceConnect(). Nun muss die Verbindung mit usbInit() initialisiert werden ud die Interrupts mit sei() eingeschaltet werden. Nun muss nur noch im Mainloop der Watchdog zurückgesetzt werden und usbPoll() aufgerufen werden.
int main(void) { uchar i; wdt_enable(WDTO_1S); DDRD = ~(1 << 2); /* all outputs except PD2 = INT0 */ DDRB = 0xff; /* output pins setzen */ PORTD = 0; /* USB-Verbindung */ PORTB = 0; /* output pins aus*/ TCCR1A = (1<<WGM10)|(1<<COM1A1); // PWM, phase correct, 8 bit. OCR1A=eeprom_read_byte(0); /* We fake an USB disconnect by pulling D+ and D- to 0 during reset. This is * necessary if we had a watchdog reset or brownout reset to notify the host * that it should re-enumerate the device. Otherwise the host's and device's * concept of the device-ID would be out of sync. */ usbDeviceDisconnect(); /* enforce re-enumeration, do this while interrupts are disabled! */ i = 0; while(--i){ /* fake USB disconnect for > 500 ms */ wdt_reset(); _delay_ms(2); } usbDeviceConnect(); usbInit(); sei(); for(;;){ /* main event loop */ wdt_reset(); usbPoll(); } return 0; }
Damit ist die Firmware fertig. Hier noch ein Makefile für avr-gcc: (Chip: Mega8, Clock: 12MHz, Programmer: Ponyser)
# Name: Makefile # Project: edited PowerSwitch # Author: # Creation Date: # Tabsize: 4 # Copyright: (c) 2005 by OBJECTIVE DEVELOPMENT Software GmbH # License: GNU GPL v2 (see License.txt) or proprietary (CommercialLicense.txt) # This Revision: $Id: Makefile 277 2007-03-20 10:53:33Z cs $ DEVICE = atmega8 AVRDUDE = avrdude -c ponyser -P /dev/ttyS0 -p $(DEVICE) # Choose your favorite programmer and interface above. COMPILE = avr-gcc -Wall -Os -Iusbdrv -I. -mmcu=$(DEVICE) #-DDEBUG_LEVEL=2 # NEVER compile the final product with debugging! Any debug output will # distort timing so that the specs can't be met. OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o # symbolic targets: all: main.hex .c.o: $(COMPILE) -c $< -o $@ .S.o: $(COMPILE) -x assembler-with-cpp -c $< -o $@ # "-x assembler-with-cpp" should not be necessary since this is the default # file type for the .S (with capital S) extension. However, upper case # characters are not always preserved on Windows. To ensure WinAVR # compatibility define the file type manually. .c.s: $(COMPILE) -S $< -o $@ flash: all $(AVRDUDE) -U flash:w:main.hex:i # Fuse low byte: # 0xef = 1 1 1 0 1 1 1 1 # ^ ^ \+/ \--+--/ # | | | +------- CKSEL 3..0 (clock selection -> crystal @ 12 MHz) # | | +--------------- SUT 1..0 (BOD enabled, fast rising power) # | +------------------ CKOUT (clock output on CKOUT pin -> disabled) # +-------------------- CKDIV8 (divide clock by 8 -> don't divide) # # Fuse high byte: # 0xdb = 1 1 0 1 1 0 1 1 # ^ ^ ^ ^ \-+-/ ^ # | | | | | +---- RSTDISBL (disable external reset -> enabled) # | | | | +-------- BODLEVEL 2..0 (brownout trigger level -> 2.7V) # | | | +-------------- WDTON (watchdog timer always on -> disable) # | | +---------------- SPIEN (enable serial programming -> enabled) # | +------------------ EESAVE (preserve EEPROM on Chip Erase -> not preserved) # +-------------------- DWEN (debug wire enable) #fuse_tiny2313: # only needed for attiny2313 # $(AVRDUDE) -U hfuse:w:0xdb:m -U lfuse:w:0xef:m clean: rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.bin *.o usbdrv/*.o main.s usbdrv/oddebug.s usbdrv/usbdrv.s # file targets: main.bin: $(OBJECTS) $(COMPILE) -o main.bin $(OBJECTS) main.hex: main.bin rm -f main.hex main.eep.hex avr-objcopy -j .text -j .data -O ihex main.bin main.hex ./checksize main.bin # do the checksize script as our last action to allow successful compilation # on Windows with WinAVR where the Unix commands will fail. disasm: main.bin avr-objdump -d main.bin cpp: $(COMPILE) -E main.c
D+ ist in diesem Beispiel mit PD2 verbunden, D- mit PD0. Die LED hängt an PB1. (Schaltplan noch in Arbeit.)
Die PC-Software zu diesem Beispiel wird bald verfügbar sein.
HID-Gerät (Tastatur/Maus/...)
[noch in Arbeit]
PC-Software bei nicht HID-Projekten (Treiber)
[noch in Arbeit]