AVR-Software-USB mit Bascom          


von Ralf Beesner    
Elektronik-Labor   Projekte   AVR 



Es gibt nur wenige AVR-Chips mit USB-Hardware. Wenn es gilt, die übrigen 8-bit-AVRs USB-fähig zu machen, ist die VUSB-Software (www.obdev.at/products/vusb/projects-de.html) die etablierte Lösung.

Schon seit einigen Jahren ist Rick Richards aktiv, um eine vergleichbare Lösung unter BASCOM zu entwickeln. Er tritt im BASCOM- Forum unter dem Nick "ollopa" auf; dort wird der Fortschritt seiner Lösung begleitet und diskutiert:

http://www.mcselec.com/index2.php?option=com_forum&Itemid=59&page=viewtopic&t=7537

Offenbar hat er wenig Zeit, um sich dem Projekt zu widmen, aber inzwischen ist seine Software recht brauchbar. Der Kern, die Library swusb.lbx und ein include-File sowie (derzeit) zwei Beispielimplementationen (ein Gamepad und ein Keyboard) finden sich unter www.sloservers.com/swusb.

Die Library steht unter einer restriktiven Lizenz, ist aber in der kompilierten Version (*.lbx) derzeit frei für nichtkommerziellen Einsatz (vielleicht wird sie mal ein kostenpflichtiges Addon für BASCOM).

Sie ist nicht ganz so universell wie VUSB, z.B. wird der PLL-Takt der AtTinies 25/45/85 nicht unterstützt, sondern es ist stets ein Quarz erforderlich (12 oder 15 MHz). Außerdem ist der Code so umfangreich, dass mindestens 4 kB Flashspeicher erforderlich sind, also mindestens ein AtTiny 45 oder AtTiny 44 eingesetzt werden muss.

Mit SWUSB kann man jedoch in der vertrauten BASCOM-Umgebung ähnlich "coole" Sachen machen wie mit VUSB, ohne sich mit der sperrigen Programmiersprache "C" herumzuplagen.

Ich habe mit ollopas Keyboard-Beispielimplementation einige VUSB- Projekte in BASCOM nachgebaut:

 - Serial Keyboard
 - Slideshow Presenter
 - Tremor Mouse


Hardware

Die USB- Anschaltung an den Mikrocontroller ist die gleiche wie bei den VUSB- Projekten. Da die SWUSB- Implementation stets einen Quarz benötigt, hat man bei Verwendung eines AtTiny 45 nur einen Pin frei.


Serial Keyboard

Dies reicht so gerade eben für das Serial Keyboard (es nimmt ein Signal über seine serielle Schnittstelle auf und leitet es über den USB als Tastatureingaben weiter, so dass man z.B. Messwerte direkt in eine Tabellenkalkulation eintragen kann).



PB1 wird als Software-UART verwendet und benötigt keinen Pegelwandler, da die Polaritätsumwandlung durch den Software-UART erfolgt. Es ist lediglich ein Vorwiderstand zur Strombegrenzung (in Verbindung mit den internen Schutzdioden des AtTiny) erforderlich.

Für den Slideshow Presenter werden jedoch zwei freie Pins benötigt (der Slideshow Presenter hat zwei Tasten (Bild rauf / Bild runter), mit denen man durch eine Powerpoint- Präsentation oder ein PDF blättert).



Slideshow Presenter

Zunächst wollte ich den Reset- Pin opfern und zu einem normalen digitalen Eingang umfusen, aber ich erinnerte mich an Schaltungsbeiträge von Hermann Nieder, die den Reset- Pin über einen Trick nutzbar machen: Wenn der Reset-Pin aktiv ist, kann man ihn zwar nicht als digitalen Eingang verwenden, aber als ADC- Eingang (ADC0). Man muss nur Sorge tragen, dass das angelegte Signal stets größer als 1/2 VCC ist, damit kein Reset ausgelöst wird.



Die beiden Tasten werden daher über einen Spannungsteiler an PB5/ADC0 angeschlossen. Die Widerstände sind so bemessen, dass die Spannung stets höher als 1/2 VCC ist. Der ADC gibt bei offenen Tastern 1023 aus; wird der Taster an PAD2 betätigt, sinkt der Wert knapp unter 950, wird der Taster an PAD3 betätigt, sinkt er knapp unter 900.



Software des Serial Keyboard

Ollopas Keyboard-Beispiel-Implementation habe ich nur ansatzweise verstanden, aber glücklicherweise erkennt man leicht die Stellen, an denen man sich in seinen Code mit seiner eigenen Task einhängen kann.

Der USB ist mit seinem relativ hohen Takt (1,5 MHz) sehr zeitkritisch, so dass die Anwendung eigener Interrupts nicht möglich ist. Man kann also in der eigenen Task Eingangspins, ADC- oder Timer- Register und dergleichen nur zyklisch abfragen (pollen).

In Ollopas Beispielimplementation ist vorgesehen, dass die Software einen gleichbleibenden Buchstaben über den USB ausgibt, sobald man einen Pin auf Low zieht. Ich habe einfach mal versucht, an dieser Stelle auf den Befehl "Inkey" zu verzweigen, vor dem eigentlich immer gewarnt wird, weil er den Prozessor blockiert, wenn er kein Signal empfängt. Die Gefahr besteht hier jedoch nicht, weil das "Low", bei dem auf "Inkey" verzweigt wird, der Beginn eines Startbits ist.

Der Software-UART ist wesentlich weniger leistungsfähig als der Hardware UART, weil er sich um jedes Bit einzeln kümmern muss, statt ein komplettes Byte bei der Hardware "abholen" zu können. Dennoch funktioniert die Lösung besser als erwartet.

Man darf allerdings nicht mehrere Bytes auf einmal an den Software-UART senden, dann werden Zeichern verstümmelt oder verschluckt, sondern man muss kleine Pausen zwischen den Einzelzeichen lassen, wie es z.B. der Fall ist, wenn das serielle Signal aus "händisch eingegebenen" Zeichen besteht (serielles Terminal).

Stammen die Zeichen aus einem zweiten Mikrocontroller, muss man gezielt kleine Pausen zwischen den Zeichen einlegen. Die Bitrate beträgt 1200 bit/s (maximal sind 4800 bit/s möglich), und man kann etwa 20 - 30 Bytes pro Sekunde übermitteln. Das klingt nach wenig, dürfte aber für viele Fälle reichen (z.B. die automatische Eintragung von Messwerten in eine Excel Tabelle).

Eine Eigenheit der Ollopa-Software ist, dass man erst "lossenden" darf, wenn der AtTiny und der PC die USB-Verbindung vollständig ausgehandelt haben, sonst wird das Serial Keyboard nicht erkannt.

Wie beim VUSB Serial Keyboard reicht es nicht, die empfangenen ASCII-Zeichen unmodifiziert weiterzugeben, da der USB-Keyboard-Standard auf der US-Keyboard-Belegung beruht; der Lookup-Befehl und eine Tabelle sorgen für die Umcodierung, und für einige Zeichen müssen auch Modifier geändert werden.


Software des Slideshow Presenter

Hier besteht die eigene Task aus der Abfrage des ADC; sinkt der ADC- Wert unter 950 bzw. unter 900, wird der HID- Key 75 (dezimal) bzw. 78 (dezimal) gesendet.


Tremor Mouse

Die virtuelle Maus ist aus Ollopas Gameport- Beispiel abgeleitet. Der "usb hid report descriptor" (ab Zeile 352) muss abgeändert werden; nach einigen vergeblichen Versuchen, ihn selbst zu schreiben, habe ich irgendwo einen korrekten Descriptor für eine Maus mit 3 Tasten - aber ohne Scrollrad - gefunden und "geklaut".

Zu beachten ist, dass immer auch der Wert in Zeile 351 und Zeile 332 angepasst werden muss, wenn sich die Länge des Descriptors ändert.

Die Bytes für Tastendrücke, X- und Y- Koordinaten werden ab Zeile 260 aufbereitet. Damit der Mauszeiger wild zappelt, wird der Zufallsgenerator ein paar mal herangezogen.


Kompilieren des Codes

Wegen der restriktiven Lizenz für swusb.lbx habe ich dem Sourcecode lediglich das File swusb-includes.bas beigefügt - swusb.lbx ist auf der oben angegebenen Homepage des Projekts herunterzuladen und in den BASCOM-LIB-Ordner zu verschieben.


Fusen des AtTiny

Ein fabrikfrischer Attiny muss für Quarzbetrieb ohne 1:8- Vorteiler gefust werden; Watchdog und Browout sind  nicht aktiviert:
Low Fuse = 0xFF   High Fuse = 0xDF


Update

Die Software lässt sich mit neueren BASCOM-Versionen (vermutlich ab Version 2.0.7.3) nicht mehr kompilieren, weil der SWUSB-Autor ein paar Nachlässigkeiten bei der Verwendung von Arrays begangen hat, die von der strikteren Syntaxprüfung der neueren Bascom-Versionen als Fehler gemeldet werden.

Übergangsweise lässt sich das alte Verhalten mit dem Befehl
Config Error = Ignore , 380 = Ignore
wieder herstellen, so dass der Code wieder kompiliert wird.

Die Datei swusb.lbx ist nun beigefügt (der Autor hat inzwischen im Bascom-Forum klargestellt, dass die Datei weiterverteilt werden darf. Sie muss in den LIB-Ordner der Bascom-Installation kopiert werden).

Die im obigen Abschnitt über die Tremor Mouse erwähnten Zeilennummern haben sich im Update um 15 erhöht.



Update 2

Die Demo-Version von Bascom ist immer noch auf dem Versionsstand 2.0.7.5, mit der zwar das Übersetzen klappt, aber kein funktionierendes HEX-File erzeugt wird. Bei Version 2.0.7.5 muss man zusätzlich den Befehl
$noframeprotect
an den Anfang des Sourcecode eintragen.

Download aller Beispielprogramme und Hex-Files: swusb-software.zip


' USB Keyboard Emulator Example
' Author: ollopa 2009-2012
'
' This project emulates a standard 105 key USB keyboard.

' Using swusb-includes.bas and swusb.lbx (the latter copied to the bascom plugins folder)
' swusb.lbx is copyrighted by Rick Richard (ollopa) and may only be used only for noncommercial purposes


' modifications by R.B.:

' ----------------------------------------------
'
' Serial Keyboard; Ascii bytes received on PB.1 are converted into keybord characters
' Rs232 input, no MAX232 necessary, only a resistance to prevent Port B.1 from high voltages,
' TxD signal is inverted by software (may be changed in the source code)
'
' Problem: the Ascii characters must be sent with a little waiting between characters, as the
' serial input is polled (with swusb-lib, own IRQs are forbidden)
'
' - controller changed to Attiny 45
' - omitted LED status, as there are not enough Ports
' - pin usbd_plus and usb_dminus changed to PB.2 and PB.0
' - only one port (PB1) left; used as Input for a serial signal with 1200 bit/s
' - remapped for german keyboard driver
' - German Umlaute (Windows codepage)

' my modifications of the original code are marked by this separator:
' ----------------------------------------------

' Fuse bytes: low fuse = FF high fuse = DF


'Save about 38 bytes of code size
$noramclear

$hwstack = 30
$swstack = 30
$framesize = 40 '24 bytes reserved


$regfile = "Attiny45.dat"

$crystal = 12000000


$eepromhex 'for STK500 programmer


'Include the software USB library
$lib "swusb.lbx"
$external _swusb
$external Crcusb

Declare Sub Usb_reset()
Declare Sub Usb_processsetup(txstate As Byte)
Declare Sub Usb_send(txstate As Byte , Byval Count As Byte)
Declare Sub Usb_senddescriptor(txstate As Byte , Maxlen As Byte)
Declare Sub Typekey(key As Byte)
Declare Function Ascii2usage(ascii As Byte) As Word
Declare Function Crcusb(buffer() As Byte , Count As Byte) As Word

'*******************************************************************************
'*************************** Begin USB Configuration ***************************
'
'Set the following parameters to match your hardware configuration and USB
'device parameters.

'******************************* USB Connections *******************************

'Define the AVR port that the two USB pins are connected to
_usb_port Alias Portb ' changed by R.B.
_usb_pin Alias Pinb ' changed by R.B.
_usb_ddr Alias Ddrb ' changed by R.B.


'Define the D+ and D- pins. (put D+ on an interrupt pin)
Const _usb_dplus = 2
Const _usb_dminus = 0 ' changed by R.B.

'Configure the pins as inputs
Config Pinb.2 = Input ' changed by R.B.
Config Pinb.0 = Input ' changed by R.B.

'disable pullups
_usb_port._usb_dplus = 0
_usb_port._usb_dminus = 0

'*******************************************************************************
'************************* USB Configuration Constants *************************

'Use EEPROM or FLASH to store USB descriptors
'1 = EEPROM, 0 = FLASH. Storing to EEPROM will reduce code size slightly.
Const _usb_use_eeprom = 0

'Don't wait for sent packets to be ACK'd by the host before marking the
'transmission as complete. This option breaks the USB spec but improves
'throughput with faster polling speeds.
'This may cause reliability issues. Should leave set to 0 to be safe.
Const _usb_assume_ack = 0

' *************************** Device Descriptor *****************************

'USB Vendor ID and Product ID (Assigned by USB-IF)
Const _usb_vid = &HAAAA
Const _usb_pid = &HEF03

'USB Device Release Number (BCD)
Const _usb_devrel = &H0001

'USB Release Spec (BCD)
Const _usb_spec = &H0110

'USB Device Class, subclass, and protocol (assigned by USB-IF).
'&h00 = Class defined by interface. (HID is defined in the interface)
'&hFF = Vendor-defined class (You must write your own PC driver)
'See http://www.usb.org/developers/defined_class for more information
Const _usb_devclass = 0
Const _usb_devsubclass = 0
Const _usb_devprot = 0

'These are _indexes_ to UNICODE string descriptors for the manufacturer,
'product name, and serial number. 0 means there is no descriptor.
Const _usb_imanufacturer = 1
Const _usb_iproduct = 2
Const _usb_iserial = 0

'Number of configurations for this device. Don't change this unless
'you know what you are doing. Ordinarily it should just be 1.
Const _usb_numconfigs = 1

' *************************** Config Descriptor *****************************

'The number of interfaces for this device (Typically 1)
Const _usb_numifaces = 1

'Configuration Number (do not edit)
Const _usb_confignum = 1

'Index of UNICODE string descriptor that describes this config (0 = None)
Const _usb_iconfig = 2

'&H80 = device powered from USB bus.
'&HC0 = self-powered (has a power supply)
Const _usb_powered = &HC0

'Required current in 2mA increments (500mA max)
Const _usb_maxpower = 25 '25 * 2mA = 50mA

' ************************** Interface Descriptor ***************************

'Number of interfaces for this device (1 or 2)
Const _usb_ifaces = 1

'Interface number
Const _usb_ifaceaddr = 0

'Alternate index
Const _usb_alternate = 0

'Number of endpoints for this interface (excluding endp 0)
Const _usb_ifaceendpoints = 1

'USB Interface Class, subclass, and protocol (assigned by USB-IF).
'&h00 = RESERVED
'&hFF = Vendor-defined class (You must write your own PC driver)
' Other values are USB interface device class. (such as HID)
'See http://www.usb.org/developers/defined_class for more information
Const _usb_ifclass = 3
Const _usb_ifsubclass = 1 'Boot interface subclass
Const _usb_ifprotocol = 1 'Keyboard

'Index to UNICODE string descriptor for this interface (0 = None)
Const _usb_iiface = 0

' ************************* Optional HID Descriptor *************************
'HID class devices are things like keyboard, mouse, joystick.
'See http://www.usb.org/developers/hidpage/ for the specification,
'tools, and resources.

'Note that for a HID device, the device class, subclass, and protocol
'must be 0. The interface class must be 3 (HID).
'Interface subclass and protocol must be 0 unless you are making a
'keyboard or a mouse that supports the predefined boot protocol.
'See pages 8 and 9 of the HID 1.11 specification PDF.

'Number of HID descriptors (EXCLUDING report and physical)
'If you are not making a HID device, then set this constant to 0
Const _usb_hids = 1

'BCD HID releasenumber. Current spec is 1.11
Const _usb_hid_release = &H0111

'Country code from page 23 of the HID 1.11 specifications.
'Usually the country code is 0 unless you are making a keyboard.
'''Const _usb_hid_country = 0
Const _usb_hid_country = 049

'The number of report and physical descriptors for this HID
'Must be at least 1! All HID devices have at least 1 report descriptor.
Const _usb_hid_numdescriptors = 1

'Use a software tool to create the report descriptor and $INCLUDE it.


' ************************* Endpoint Descriptor(s) **************************

'Endpoint 0 is not included here. These are only for optional
'endpoints.
'Note: HID devices require 1 interrupt IN endpoint

'Address of optional endpoints (Must be > 0. comment-out to not use)
Const _usb_endp2addr = 1
'Const _usb_Endp3Addr = 2

'Valid types are 0 for control or 3 for interrupt
Const _usb_endp2type = 3
Const _usb_endp3type = 0

'Directions are: 0=Out, 1=In. Ignored by control endpoints
Const _usb_endp2direction = 1
Const _usb_endp3direction = 0

'Polling interval (ms) for interrupt endpoints. Ignored by control endpoints
' (Must be at least 10)
Const _usb_endp2interval = 20
Const _usb_endp3interval = 20

'*******************************************************************************
'The includes need to go right here--between the configuration constants above
'and the start of the program below. The includes will automatically calculate
'constants based on the above configuration, dimension required variables, and
'allocate transmit and receive buffers. Nothing inside the includes file needs
'to be modified.
$include "swusb-includes.bas"
'*******************************************************************************

'**************************** USB Interrupt And Init ***************************
'Set all the variables, flags, and sync bits to their initial states
Call Usb_reset()

Const _usb_intf = Intf0
Config Int0 = Rising
On Int0 Usb_isr Nosave
Enable Int0
Enable Interrupts

'*******************************************************************************
'*************************** End Of USB Configuration **************************



' -------------------------------------------------------

Open "comb.1:1200,8,n,1,INVERTED" For Input As #1 ' RS232 input; no inverter (e.g. Max232) necessary

' Open "comb.1:1200,8,n,1" For Input As #1 ' TTL input, this is for TxD from another microcontroller


Ddrb.1 = 0
Portb.1 = 0 ' external pulldown resistance necessary

' --------------------------------------------------------

Dim Resetcounter As Word
Dim Idlemode As Byte

Dim Keybd_leds As Byte 'keyboard status LEDs (numlock, capslock, etc.)
Dim Message As String * 10

Dim Akey As Byte



Do
Resetcounter = 0

'Check for reset here
While _usb_pin._usb_dminus = 0
Incr Resetcounter
If Resetcounter = 1000 Then
Call Usb_reset()
End If
Wend

'Check for received data
If _usb_status._usb_rxc = 1 Then

If _usb_status._usb_setup = 1 Then
'Process a setup packet/Control message
Call Usb_processsetup(_usb_tx_status)
'else

End If

'Reset the RXC bit and set the RTR bit (ready to receive a new packet)
_usb_status._usb_rxc = 0
_usb_status._usb_rtr = 1

End If

' ----------------------------------------------

' Changed by R.B. for Attiny45 serial KB

If Pinb.1 = 1 Then
Akey = Inkey(#1)
Call Typekey(akey)
End If

' -----------------------------------------------

Loop


Sub Typekey(key As Byte)
Local Usage As Word
Usage = Ascii2usage(key)

Do : Loop Until _usb_tx_status2._usb_txc = 1
' Key down
_usb_tx_buffer2(2) = High(usage) 'Modifier keys (shift, ctl, alt, etc)
_usb_tx_buffer2(3) = 0 'Reserved. Always 0
_usb_tx_buffer2(4) = Low(usage) 'key1
_usb_tx_buffer2(5) = 0 'key2
_usb_tx_buffer2(6) = 0 'key3
_usb_tx_buffer2(7) = 0 'key4
_usb_tx_buffer2(8) = 0 'key5
_usb_tx_buffer2(9) = 0 'key6
Call Usb_send(_usb_tx_status2 , 8)

' Key up
Do : Loop Until _usb_tx_status2._usb_txc = 1
_usb_tx_buffer2(2) = 0 'Modifier keys (shift, ctl, alt, etc)
_usb_tx_buffer2(3) = 0 'Reserved. Always 0
_usb_tx_buffer2(4) = 0 'key1
_usb_tx_buffer2(5) = 0 'key2
_usb_tx_buffer2(6) = 0 'key3
_usb_tx_buffer2(7) = 0 'key4
_usb_tx_buffer2(8) = 0 'key5
_usb_tx_buffer2(9) = 0 'key6
Call Usb_send(_usb_tx_status2 , 8)
End Sub

End

'*******************************************************************************
'******************** Descriptors stored in EEPROM or FLASH ********************
' Do not change the order of the descriptors!
'
#if _usb_use_eeprom = 1
$eeprom
#else
$data
#endif

'Device Descriptor
_usb_devicedescriptor:
Data 18 , 18 , _usb_desc_device , _usb_specl , _usb_spech , _usb_devclass
Data _usb_devsubclass , _usb_devprot , 8 , _usb_vidl , _usb_vidh , _usb_pidl
Data _usb_pidh , _usb_devrell , _usb_devrelh , _usb_imanufacturer
Data _usb_iproduct , _usb_iserial , _usb_numconfigs


'Retrieving the configuration descriptor also gets all the interface and
'endpoint descriptors for that configuration. It is not possible to retrieve
'only an interface or only an endpoint descriptor. Consequently, this is a
'large transaction of variable size.
_usb_configdescriptor:
Data _usb_descr_total , 9 , _usb_desc_config , _usb_descr_totall
Data _usb_descr_totalh , _usb_numifaces , _usb_confignum , _usb_iconfig
Data _usb_powered , _usb_maxpower

'_usb_IFaceDescriptor
Data 9 , _usb_desc_iface , _usb_ifaceaddr , _usb_alternate
Data _usb_ifaceendpoints , _usb_ifclass , _usb_ifsubclass , _usb_ifprotocol
Data _usb_iiface

#if _usb_hids > 0
'_usb_HIDDescriptor
Data _usb_hid_descr_len , _usb_desc_hid , _usb_hid_releasel , _usb_hid_releaseh
Data _usb_hid_country , _usb_hid_numdescriptors

'Next follows a list of bType and wLength bytes/words for each report and
'physical descriptor. There must be at least 1 report descriptor. In practice,
'There are usually 0 physical descriptors and only 1 report descriptor.
Data _usb_desc_report
Data 63 , 0
'End of report/physical descriptor list
#endif

#if _usb_endpoints > 1
'_usb_EndpointDescriptor
Data 7 , _usb_desc_endpoint , _usb_endp2attr , _usb_endp2type , 8 , 0
Data _usb_endp2interval
#endif

#if _usb_endpoints > 2
'_usb_EndpointDescriptor
Data 7 , _usb_desc_endpoint , _usb_endp3attr , _usb_endp3type , 8 , 0
Data _usb_endp3interval
#endif

#if _usb_hids > 0
_usb_hid_reportdescriptor:
Data 63
Data &H05 , &H01 ' USAGE_PAGE (Generic Desktop)
Data &H09 , &H06 ' USAGE (Keyboard)
Data &HA1 , &H01 ' COLLECTION (Application)
Data &H05 , &H07 ' USAGE_PAGE (Keyboard)
Data &H19 , &HE0 ' USAGE_MINIMUM (Keyboard LeftControl)
Data &H29 , &HE7 ' USAGE_MAXIMUM (Keyboard Right GUI)
Data &H15 , &H00 ' LOGICAL_MINIMUM (0)
Data &H25 , &H01 ' LOGICAL_MAXIMUM (1)
Data &H75 , &H01 ' REPORT_SIZE (1)
Data &H95 , &H08 ' REPORT_COUNT (8)
Data &H81 , &H02 ' INPUT (Data,Var,Abs)
Data &H95 , &H01 ' REPORT_COUNT (1)
Data &H75 , &H08 ' REPORT_SIZE (8)
Data &H81 , &H03 ' INPUT (Cnst,Var,Abs)
Data &H95 , &H05 ' REPORT_COUNT (5)
Data &H75 , &H01 ' REPORT_SIZE (1)
Data &H05 , &H08 ' USAGE_PAGE (LEDs)
Data &H19 , &H01 ' USAGE_MINIMUM (Num Lock)
Data &H29 , &H05 ' USAGE_MAXIMUM (Kana)
Data &H91 , &H02 ' OUTPUT (Data,Var,Abs)
Data &H95 , &H01 ' REPORT_COUNT (1)
Data &H75 , &H03 ' REPORT_SIZE (3)
Data &H91 , &H03 ' OUTPUT (Cnst,Var,Abs)
Data &H95 , &H06 ' REPORT_COUNT (6)
Data &H75 , &H08 ' REPORT_SIZE (8)
Data &H15 , &H00 ' LOGICAL_MINIMUM (0)
Data &H25 , &H65 ' LOGICAL_MAXIMUM (101)
Data &H05 , &H07 ' USAGE_PAGE (Keyboard)
Data &H19 , &H00 ' USAGE_MINIMUM (Reserved (no event indicated))
Data &H29 , &H65 ' USAGE_MAXIMUM (Keyboard Application)
Data &H81 , &H00 ' INPUT (Data,Ary,Abs)
Data &HC0 ' END_COLLECTION
#endif

'*****************************String descriptors********************************
'Yes, they MUST be written like "t","e","s","t". Doing so pads them with
'0's. If you write it like "test," I promise you it won't work.

'Default language descriptor (index 0)
_usb_langdescriptor:
Data 4 , 4 , _usb_desc_string , 09 , 04 '&h0409 = English

'Manufacturer Descriptor (unicode)
_usb_mandescriptor:
Data 14 , 14 , _usb_desc_string
Data "o" , "l" , "l" , "o" , "p" , "a"

'Product Descriptor (unicode)
_usb_proddescriptor:
Data 46 , 46 , _usb_desc_string
Data "o" , "l" , "l" , "o" , "p" , "a" , "'" , "s" , " " , "k" , "e" , "y" , "b" , "o" , "a" , "r" , "d" , " "
Data "v" , "1" , "." , "0"



'*******************************************************************************



'*******************************************************************************
'******************************** Subroutines **********************************
'*******************************************************************************

Sub Usb_processsetup(txstate As Byte)
Senddescriptor = 0
'Control transfers reset the sync bits like so
Txstate = _usb_setup_sync

'These are the standard device, interface, and endpoint requests that the
'USB spec requires that we support.
Select Case _usb_rx_buffer(2)
'Standard Device Requests
Case &B10000000:
Select Case _usb_rx_buffer(3)
' CASE _usb_REQ_GET_STATUS:
Case _usb_req_get_descriptor:
Select Case _usb_rx_buffer(5)
Case _usb_desc_device:
'Send the device descriptor
#if _usb_use_eeprom = 1
Readeeprom _usb_eepromaddrl , _usb_devicedescriptor
#else
Restore _usb_devicedescriptor
#endif
Senddescriptor = 1
Case _usb_desc_config:
'Send the configuration descriptor
#if _usb_use_eeprom = 1
Readeeprom _usb_eepromaddrl , _usb_configdescriptor
#else
Restore _usb_configdescriptor
#endif
Senddescriptor = 1
Case _usb_desc_string:
Select Case _usb_rx_buffer(4)
Case 0:
'Send the language descriptor
#if _usb_use_eeprom = 1
Readeeprom _usb_eepromaddrl , _usb_langdescriptor
#else
Restore _usb_langdescriptor
#endif
Senddescriptor = 1
Case 1:
'Send the manufacturer descriptor
#if _usb_use_eeprom = 1
Readeeprom _usb_eepromaddrl , _usb_mandescriptor
#else
Restore _usb_mandescriptor
#endif
Senddescriptor = 1
Case 2:
'Send the product descriptor
#if _usb_use_eeprom = 1
Readeeprom _usb_eepromaddrl , _usb_proddescriptor
#else
Restore _usb_proddescriptor
#endif
Senddescriptor = 1
End Select
End Select
' CASE _usb_REQ_GET_CONFIG:
End Select
Case &B00000000:
Select Case _usb_rx_buffer(3)
' CASE _usb_REQ_CLEAR_FEATURE:
' CASE _usb_REQ_SET_FEATURE:
Case _usb_req_set_address:
'USB status reporting for control writes
Call Usb_send(txstate , 0)
While Txstate._usb_txc = 0 : Wend
'We are now addressed.
_usb_deviceid = _usb_rx_buffer(4)
' CASE _usb_REQ_SET_DESCRIPTOR:
Case _usb_req_set_config:
'Have to do status reporting
Call Usb_send(txstate , 0)
End Select
'Standard Interface Requests
Case &B10000001:
Select Case _usb_rx_buffer(3)
' CASE _usb_REQ_GET_STATUS:
' CASE _usb_REQ_GET_IFACE:
Case _usb_req_get_descriptor
'_usb_rx_buffer(4) is the descriptor index and (5) is the type
Select Case _usb_rx_buffer(5)
Case _usb_desc_report:
#if _usb_use_eeprom = 1
Readeeprom _usb_eepromaddrl , _usb_hid_reportdescriptor
#else
Restore _usb_hid_reportdescriptor
#endif
Senddescriptor = 1
' CASE _usb_DESC_PHYSICAL

' CASE _USB_DESC_HID

End Select
End Select
'CASE &B00000001:
'SELECT CASE _usb_rx_buffer(3)
' CASE _usb_REQ_CLEAR_FEATURE:

' CASE _usb_REQ_SET_FEATURE:

' CASE _usb_REQ_SET_IFACE:

'END SELECT
'Standard Endpoint Requests
'CASE &B10000010:
'SELECT CASE _usb_rx_buffer(3)
' CASE _usb_REQ_GET_STATUS:

'END SELECT
'CASE &B00000010:
'SELECT CASE _usb_rx_buffer(3)
' CASE _usb_REQ_CLEAR_FEATURE:

' CASE _usb_REQ_SET_FEATURE:

'END SELECT

'Class specific requests (useful for HID)
Case &B10100001:
'Class specific GET requests
Select Case _usb_rx_buffer(3)
Case _usb_req_get_report:
'CASE _usb_REQ_GET_IDLE:
'CASE _usb_REQ_GET_PROTOCOL:
End Select
Case &B00100001:
'Class specific SET requests
Select Case _usb_rx_buffer(3)
Case _usb_req_set_report:
_usb_status._usb_rxc = 0
_usb_status._usb_rtr = 1
_usb_status2._usb_ignore = 0
'Do status reporting
Call Usb_send(txstate , 0)

'We need to get the second data packet
'Reset the RXC bit and set the RTR bit (ready to receive a new packet)
Do
Loop Until _usb_status._usb_rxc = 1

Keybd_leds = _usb_rx_buffer(2)


'The output report for a keyboard containts a bitmap representing
'the status of the LEDs:
'BIT Description
'0 NUM LOCK
'1 CAPS LOCK
'2 SCROLL LOCK
'3 COMPOSE
'4 KANA
'5-7 CONSTANT/RESERVED
Toggle Keybd_leds
' Portb = Keybd_leds

Case _usb_req_set_idle:
Idlemode = 1
'Do status reporting
Call Usb_send(txstate , 0)
'CASE _usb_REQ_SET_PROTOCOL:
End Select
End Select

If Senddescriptor = 1 Then
Call Usb_senddescriptor(txstate , _usb_rx_buffer(8))
End If

End Sub


Dim Sd_size As Byte
Dim Sd_i As Byte
Dim Sd_j As Byte
Dim Sd_timeout As Word

Sub Usb_senddescriptor(txstate As Byte , Maxlen As Byte)
'Break the descriptor into packets and send to TxState
#if _usb_use_eeprom = 1
'EEPROM access is a little funky. The size has already been fetched
'and stored in _usb_EEPROMADDRL, and the address of the descriptor
'is now in the EEAR register pair.

Sd_size = _usb_eepromaddrl

'Fetch the location of the descriptor and use it as an address pointer
push R24
in R24, EEARL
sts {_USB_EEPROMADDRL}, R24
in R24, eearH
sts {_USB_EEPROMADDRH}, R24
pop R24

#else
Read Sd_size
#endif

If Maxlen < Sd_size Then Sd_size = Maxlen

Sd_i = 2
For Sd_j = 1 To Sd_size
Incr Sd_i
#if _usb_use_eeprom = 1
Incr _usb_eepromaddr
Readeeprom Txstate(sd_i) , _usb_eepromaddr
#else
Read Txstate(sd_i)
#endif

If Sd_i = 10 Or Sd_j = Sd_size Then
Sd_i = Sd_i - 2
Call Usb_send(txstate , Sd_i)
While Txstate._usb_txc = 0
Sd_timeout = 0
'To prevent an infinite loop, check for reset here
While _usb_pin._usb_dminus = 0
Incr Sd_timeout
If Sd_timeout = 1000 Then
Call Usb_reset()
Exit Sub
End If
Wend
Wend
Sd_i = 2
End If
Next
End Sub

Sub Usb_send(txstate As Byte , Byval Count As Byte)

'Calculates and adds the CRC16,adds the DATAx PID,
'and signals to the ISR that the data is ready to be sent.
'
'"Count" is the DATA payload size. Range is 0 to 8. Do not exceed 8!

'Reset all the flags except TxSync and RxSync
Txstate = Txstate And _usb_syncmask

'Calculate the 16-bit CRC
_usb_crc = Crcusb(txstate(3) , Count)

'Bytes to transmit will be PID + DATA payload + CRC16
Count = Count + 3
Txstate = Txstate + Count

Txstate(count) = Low(_usb_crc)
Incr Count
Txstate(count) = High(_usb_crc)


'Add the appropriate DATAx PID
Txstate(2) = _usb_pid_data1
If Txstate._usb_txsync = 0 Then
Txstate(2) = _usb_pid_data0
End If

'The last step is to signal that the packet is Ready To Transmit
Txstate._usb_rtt = 1
Txstate._usb_txc = 0
End Sub

Sub Usb_reset()
'Reset the receive flags
_usb_status._usb_rtr = 1
_usb_status._usb_rxc = 0

'Reset the transmit flags
_usb_tx_status = _usb_endp_init
#if Varexist( "_usb_Endp2Addr")
_usb_tx_status2 = _usb_endp_init
#endif
#if Varexist( "_usb_Endp3Addr")
_usb_tx_status3 = _usb_endp_init
#endif

'Reset the device ID to 0
_usb_deviceid = 0

Idlemode = 0
End Sub




Function Ascii2usage(ascii As Byte) As Word
Local Result As Byte


'Maps common (mostly printable) ASCII characters to USB Keyboard usage codes
'Returns two bytes: keyboard modifier flags and key code

'Modifier bits:
'0 LEFT CTRL
'1 LEFT SHIFT
'2 LEFT ALT
'3 LEFT GUI
'4 RIGHT CTRL
'5 RIGHT SHIFT
'6 RIGHT ALT
'7 RIGHT GUI

' -------------------------------------------------------


' The USB key codes are valid for US keyboards. As the german keyboard driver remaps some of the keys,
' we habe to remap them back using the Data Table "Tabelle:"
' Ascii values < 30 are mapped to ctrl-A to ctrl-Z
' the Ascii Table is 7 bit, but some 8 bit values (German Umlaute) are supported, too

Result = Lookup(ascii , Tabelle )
Ascii2usage = Result


' Modifiers:


If Ascii > 90 And Ascii < 94 Then ' this are characters [ | ]
Ascii2usage = Result Or 16384 ' 16384 is modifier bit 6 (Right ALT = Alt Gr)
End If

If Ascii > 122 And Ascii < 126 Then ' this are characters { \ }
Ascii2usage = Result Or 16384
End If

If Result > 128 Then ' this are capital letters and shifted keys
Ascii2usage = Ascii2usage + 384 ' 384 is modifier bit 1 (512, but we have to subtract 128)
End If

Select Case Ascii ' German Umlaute äöüÄÖÜß
Case 129 : Ascii2usage = 47 ' ü
Case 132 : Ascii2usage = 52 ' ä
Case 142 : Ascii2usage = 52 + 512 ' Ä
Case 148 : Ascii2usage = 51 ' ö
Case 153 : Ascii2usage = 51 + 512 ' Ö
Case 154 : Ascii2usage = 47 + 512 ' Ü
Case 225 : Ascii2usage = 45 ' ß
End Select

End Function




Tabelle:

' A value of 128 is added for Capital letters and shifted keys:

' 0 - 15 8 -> 42: backspace 9 -> 43: tab 011 -> 43: tab back 013 -> 40: return
Data 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 42 , 43 , 0 , 171 , 0 , 40 , 0 , 0,


' 16 - 31
Data 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0

' 32 - 47
Data 44 , 158 , 159 , 49 , 161 , 162 , 163 , 177 , 165 , 166 , 176 , 48 , 54 , 56 , 55 , 164

' 48 - 63 starts with 0 - 9
Data 39 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 183 , 182 , 100 , 167 , 228 , 173

' 64 - 79 @ - O
Data 206 , 132 , 133 , 134 , 135 , 136 , 137 , 138 , 139 , 140 , 141 , 142 , 143 , 144 , 145 , 146

' 80 - 95 starts with P - Z
Data 147 , 148 , 149 , 150 , 151 , 152 , 153 , 154 , 155 , 157 , 156 , 37 , 45 , 38 , 53 , 184


' 96 - 111 ? a - o
Data 174 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18,


' 112 - 127 starts with p - z

Data 19 , 20 , 21 , 22 , 23 , 24 , 25 , 26 , 27 , 29 , 28 , 36 , 100 , 39 , 0 , 0

' -------------------------------------------------------


Elektronik-Labor   Projekte   AVR