' Copyright Brad Campbell, 2007 - All rights reserved ' ' Thanks to Dick Dick Streefland and Igor Cesko (Not quite sure the unicode translates) ' for inspiration ' http://www.xs4all.nl/~dicks/avr/usbtiny/index.html ' http://cesko.host.sk/IgorPlugUSB/IgorPlug-USB%20(AVR)_eng.htm ' ' Thanks to Michael Hetherington for the lyrics ' http://www.sxlist.com/techref/scenix/lib/io/dev/keys/usbdemo-mh.htm ' The MAC COG relies on the CRC table defined in here, don't replace it without altering ' the MAC COG code! ' Hacky non-compliant CDC-ACM FullDuplexSerial_001 compliant USB serial port ' This uses bulk endpoints which are a gross no-no using low-speed USB ' Works under Windows, MacOS and Linux (with 2 minor kernel alterations) ' Work is in progress to develop a standard linux driver for this implementation which will then ' work for all the AVR based CDC units also. ' Mac OSX is a great testing environment as it seems to have the strictest enumeration requirements CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 BSIZ = $1F ' Buffer performs nicely about here. Can go as low as $F and as high as $FF ' *Must* be a clean all ones bitmask VAR ' each endpoint in/out has 4 longs ' IN endpoints 1st ' byte counter ' bytes [0-3] ' bytes [4-7] ' spare ' OUT endpoints ' byte counter ' bytes [0-3] ' bytes [4-7] ' bytes [8-9] justified to LSB long endpoints[32] long packetcount long packetbad long txhead, txtail, rxhead, rxtail byte txbuf[BSIZ+1] byte rxbuf[BSIZ+1] OBJ ll : "USB_lowlevel_005" PUB start : OK | i Pcrcmask := @crc16table Dtable := @dd_table Word[Dtable][0] := 0 Word[Dtable][1] := @dd_device Word[Dtable][2] := @dd_config Word[Dtable][3] := @dd_status Word[Dtable][4] := 0 Word[Dtable][5] := @dd_langid Word[Dtable][6] := @dd_vendor Word[Dtable][7] := @dd_product Word[Dtable][8] := 0 Word[Dtable][9] := 0 Word[Dtable][10] := @dd_defmode Word[Dtable][11] := @dd_dcd_status rxh := @rxhead txh := @txhead rxt := @rxtail txt := @txtail rxb := @rxbuf txb := @txbuf rxhead := 0 rxtail := 0 txhead := 0 txtail := 0 i := @endpoints epb := i longfill(i, 0, 32) OK := ll.start(@crc16table, epb) OK := cognew(@USB_APP, 0) PUB rxflush '' Flush receive buffer repeat while rxcheck => 0 PUB rxtime(ms) : rxbyte | ti '' Wait ms milliseconds for a byte to be received '' returns -1 if no byte received, $00..$FF if byte ti := cnt repeat until (rxbyte := rxcheck) => 0 or (cnt - ti) / (clkfreq / 1000) > ms PUB rx : rxbyte '' Receive byte (may wait for byte) '' returns $00..$FF repeat while (rxbyte := rxcheck) < 0 PUB rxcheck : rxbyte | i '' Check if byte received (never waits) '' returns -1 if no byte received, $00..$FF if byte rxbyte-- if rxtail <> rxhead rxbyte := rxbuf[rxtail] rxtail := (rxtail + 1) & BSIZ PUB tx(txbyte) | i '' Send byte (may wait for room in buffer) repeat until (txtail <> (txhead + 1) & BSIZ) txbuf[txhead] := txbyte txhead := (txhead + 1) & BSIZ PUB str(stringptr) '' Send string repeat strsize(stringptr) tx(byte[stringptr++]) PUB dec(value) | i '' Print a decimal number if value < 0 -value tx("-") i := 1_000_000_000 repeat 10 if value => i tx(value / i + "0") value //= i result~~ elseif result or i == 1 tx("0") i /= 10 PUB hex(value, digits) '' Print a hexadecimal number value <<= (8 - digits) << 2 repeat digits tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F")) PUB bin(value, digits) '' Print a binary number value <<= 32 - digits repeat digits tx((value <-= 1) & 1 + "0") DAT USB_APP org wrlong zero, epb ' Spark up MAC thread now we are running usboot mov lflags, #0 mov out, #0 :loop mov pointer, epb rdlong da, pointer wz IF_NZ call #process_0i add pointer, #16 rdlong da, pointer wz IF_NZ call #process_1i add pointer, #16 rdlong da, pointer wz IF_NZ call #process_2i add pointer, #16 rdlong da, pointer wz IF_NZ call #process_3i add pointer, #16 test out, #1 wz IF_Z jmp #:loop1 rdlong da, pointer wz IF_Z call #process_0o :loop1 add pointer, #16 test out, #2 wz IF_Z jmp #:loop2 rdlong da, pointer wz IF_Z call #process_1o :loop2 add pointer, #16 test out, #4 wz IF_Z jmp #:loop3 rdlong da, pointer wz IF_Z call #process_2o :loop3 add pointer, #16 test out, #8 wz IF_Z jmp #:loop4 rdlong da, pointer wz IF_Z call #process_3o :loop4 jmp #:loop reset mov bytecnt, #0 mov flags, #0 mov epoint, #0 call #rdbuf add epoint, #1 call #rdbuf add epoint, #1 call #rdbuf add epoint, #1 call #rdbuf jmp #usboot process_0i test da, flag_reset wz IF_NZ jmp #reset ' Lower level has requested a system reset mov epoint, #0 call #rdbuf test buf0, flag_setup wz ' Are we a setup packet? IF_Z jmp #:end mov dc, buf1 shr dc, #8 and dc, #$FF ' Request Number mov flags, #0 mov bytecnt, buf0 and bytecnt, #$FF cmp bytecnt, #8 wz ' Number of bytes IF_NZ jmp #:end ' All setup packets are 8 bytes test buf0, flag_class wz ' Are we a class packet? IF_NZ jmp #:class cmp dc, #6 wz ' GET_DESCRIPTOR IF_NZ jmp #:setup3 mov dd, buf1 shr dd, #24 and dd, #$FF mov da, #0 cmp dd, #1 wz ' Device Descriptor IF_Z mov da, #1 cmp dd, #2 wz ' Config Descriptor IF_Z mov da, #2 cmp dd, #3 wz ' String Descriptor (see below) IF_Z jmp #:ss_string_d tjz da, #:send_empty ' Have we anything to send? jmp #:queue ' These string ID's are tied to the ID's specified in the Config Descriptor ' Make it easy and use 1/2/3. 0 is Mandatory if you have 1, 2 or 3. :ss_string_d mov dd, buf1 shr dd, #16 and dd, #$FF cmp dd, #0 wz IF_Z mov da, #5 ' Language ID cmp dd, #1 wz IF_Z mov da, #6 ' Vendor String cmp dd, #2 wz IF_Z mov da, #7 ' Device Name String cmp dd, #3 wz IF_Z mov da, #8 ' Serial Number String tjz da, #:send_empty ' Have we anything to send? :queue mov ep0bl, buf2 shr ep0bl, #16 and ep0bl, #$1FF ' Max length call #queue_string test db, allones wz IF_Z jmp #:send_empty ' No string to queue mov ep0len, db mov ep0buf, da or lflags, flag_debug or out, #1 ' Endpoint 0 jmp #:end :setup3 cmp dc, #5 wz ' Address change packet IF_NZ jmp #:setup4 or flags, flag_address ' Address change is handled in the low level driver jmp #:send_empty :setup4 cmp dc, #0 wz ' Status request (need this if we are not picked up by a driver - lsusb causes a reboot) IF_NZ jmp #:setup5 mov da, #3 jmp #:queue :setup5 cmp dc, #9 wz ' Set Config IF_NZ jmp #:send_empty or out, #2 ' Whee we are go! Enable the Data port :send_empty or flags, flag_empty1 ' STATUS requests requires a DATA1 call #wrbuf jmp #:end :class cmp dc, #$20 wz ' SET_LINE_CODING IF_Z jmp #:send_empty cmp dc, #$21 wz ' GET_LINE_CODING IF_Z jmp #:clinecode cmp dc, #$22 wz ' SET_CONTROL_LINE_STATE IF_Z jmp #:clinestate test b_1, #$80 wz ' Host direction bit IF_NZ jmp #:end ' No need for empty packet jmp #:send_empty :clinecode mov da, #10 jmp #:queue :clinestate call #send_serial_state ' or flags, flag_rtoggle ' SET_C_LINE_STATE resets input toggle bit ?!? jmp #:send_empty :end mov da, epb wrlong zero, epb process_0i_ret ret ' da is table index, dc is maximum length ' returns length in db and address in da ' if db is 0 there is no string queue_string shl da, #1 add da, Dtable rdword db, da wz IF_Z jmp #queue_string_ret ' Descriptor is empty mov da, db rdbyte db, da ' Queue up the descriptor and send it off add da, #1 queue_string_ret ret ' Sends a serial port mask update on endpoint 3 send_serial_state mov ep3bl, #$1F ' Max send length mov da, #11 ' If this returns garbage we can kiss our backsides goodbye! call #queue_string test db, allones wz IF_Z jmp #send_serial_state_ret mov ep3len, db mov ep3buf, da or out, #4 ' endpoint #3 send_serial_state_ret ret ' This queues up the endpoint for action process_1i ' This buffer space calculation code is _very_ fragile ' it's been tested with buffer sizes of $f, $1F, $3F & $FF and proven to work ' It could, however, be more efficient I'm sure and da, #$FF ' Length pointer mov bytecnt, da rdlong db, rxh ' Head rdlong dc, rxt ' Tail ' mov buf0, db ' mov buf1, dc cmp dc, db wc, wz IF_C jmp #:p12 ' Head < Tail IF_Z jmp #:p14 ' Head == Tail sub dc, db ' Space = Tail - Head jmp #:p13 :p12 mov dd, #BSIZ ' Top space = Buffer size - head sub dd, db add dc, dd ' Total space = Top space + tail :p13 mov dd, bytecnt sub dc, #1 ' Always leave at least 1 byte spare in the buffer ' mov buf2, dc ' IF_C jmp #process_1i_ret ' dc is less than 0, not good. cmp dc, dd wc IF_C jmp #process_1i_ret ' Not enough room in the buffer :p14 mov epoint, #1 mov dd, da call #rdbuf mov data, #$C0 movs :p1i, #b_1 call #unpack_buffer :p1i mov dc, 0-0 add :p1i, #1 mov dd, rxb add dd, db wrbyte dc, dd add db, #1 and db, #BSIZ djnz bytecnt, #:p1i wrlong db, rxh process_1i_ret ret process_2i mov epoint, #2 call #rdbuf process_2i_ret ret process_3i mov epoint, #3 call #rdbuf process_3i_ret ret process_0o mov epoint, #0 mov epbuf, ep0buf mov eplen, ep0len mov epbl, ep0bl call #ep_string_out mov ep0bl, epbl mov ep0buf, epbuf mov ep0len, eplen process_0o_ret ret process_1o rdlong da, txh rdlong db, txt cmp da, db wz IF_Z jmp #process_1o_ret ' Nothing to send call #bufstart mov dc, #8 :loop mov dd, txb add dd, db rdbyte data, dd call #pushbuf add db, #1 and db, #BSIZ cmp db, da wz ' Buffer is empty IF_Z jmp #:done ' Sent all we have djnz dc, #:loop ' Done 8 bytes yet? :done wrlong db, txt call #pushcrc call #packem mov epoint, #1 mov flags, #0 call #wrbuf process_1o_ret ret process_2o mov epoint, #2 mov flags, #0 mov bytecnt, #0 call #wrbuf process_2o_ret ret process_3o mov epoint, #3 mov epbuf, ep3buf mov eplen, ep3len mov epbl, ep3bl call #ep_string_out mov ep3bl, epbl mov ep3buf, epbuf mov ep3len, eplen process_3o_ret ret rdbuf mov da, epoint shl da, #4 add da, epb rdlong buf0, da add da, #4 rdlong buf1, da add da, #4 rdlong buf2, da sub da, #8 wrlong zero, da ' Clear the buffer, we have it now rdbuf_ret ret wrbuf mov da, epoint shl da, #4 add da, epb add da, #68 ' 64 + skip 1st long wrlong buf0, da add da, #4 mov db, flags ' Interleaved wrlong buf1, da add da, #4 or db, bytecnt ' Interleaved wrlong buf2, da mov flags, #0 sub da, #12 wrlong db, da ' Byte Counter / Flags wrbuf_ret ret packem ' 25 Instructions mov buf0, b_1 mov da, b_2 shl da, #8 or buf0, da mov da, b_3 shl da, #16 or buf0, da mov da, b_4 shl da, #24 or buf0, da mov buf1, b_5 mov da, b_6 shl da, #8 or buf1, da mov da, b_7 shl da, #16 or buf1, da mov da, b_8 shl da, #24 or buf1, da mov buf2, b_9 mov da, b_10 shl da, #8 or buf2, da packem_ret ret unpack_buffer mov da, buf1 mov b_1, da shr da, #8 mov b_2, da shr da, #8 mov b_3, da shr da, #8 mov b_4, da mov da, buf2 mov b_5, da shr da, #8 mov b_6, da shr da, #8 mov b_7, da shr da, #8 mov b_8, da unpack_buffer_ret ret pushbuf movd :pindr, bptr add bptr, #1 :pindr mov 0-0, data call #crc16byte add bytecnt, #1 pushbuf_ret ret ' 13 Instructions pushcrc movd :pcrc1, bptr add bptr, #1 movd :pcrc2, bptr xor crc, allones mov data, crc and data, #$FF :pcrc1 mov 0-0, data mov data, crc shr data, #8 and data, #$FF :pcrc2 mov 0-0, data add bytecnt, #2 pushcrc_ret ret bufstart mov bptr, #b_1 mov crc, crcmask mov bytecnt, #0 bufstart_ret ret ' 30 Instructions not including subroutines ep_string_out test eplen, allones wz IF_Z jmp #:send_empty call #bufstart mov da, #8 :send_loop rdbyte data, epbuf add epbuf, #1 call #pushbuf sub da, #1 sub eplen, #1 sub epbl, #1 tjz da, #:send_next tjz epbl, #:send_next tjnz eplen, #:send_loop :send_next call #pushcrc call #packem jmp #:next :send_empty mov bytecnt, #0 mov flags, flag_empty :next call #wrbuf mov data, #$20 add data, epoint test epbl, allones wz ' If we are at the end of the buffer or request length we don't IF_Z jmp #:here ' send an empty packet cmp bytecnt, #$0A wz IF_Z jmp #ep_string_out_ret :here mov da, #1 shl da, epoint andn out, da andn lflags, flag_debug ep_string_out_ret ret crcmask long $0000FFFF crc16byte ' data has the byte to add to the crc. it's not touched mov t, data xor t, crc and t, #$FF shl t, #1 add t, Pcrcmask rdword t1, t shr crc, #8 xor crc, t1 crc16byte_ret ret allones long $FFFFFFFF zero long 0 aring long 0 Pcrcmask long 0 epb long 0 out long 0 debug long $7FF0 ep0len long 0 ep0buf long 0 ep0bl long 0 ep1len long 0 ep1buf long 0 ep1bl long 0 ep2len long 0 ep2buf long 0 ep2bl long 0 ep3len long 0 ep3buf long 0 ep3bl long 0 eplen long 0 epbuf long 0 epbl long 0 bptr long 0 buf0 long 0 buf1 long 0 buf2 long 0 b_1 long 0 b_2 long 0 b_3 long 0 b_4 long 0 b_5 long 0 b_6 long 0 b_7 long 0 b_8 long 0 b_9 long 0 b_10 long 0 rxh long 0 rxt long 0 txh long 0 txt long 0 rxb long 0 txb long 0 flag_reset long 1 << 22 flag_rtoggle long 1 << 23 flag_empty1 long 1 << 24 ' Send an empty DATA1 packet flag_debug long 1 << 25 ' Debugging used in the app level flag_class long 1 << 26 ' We recieved a CLASS request flag_address long 1 << 27 ' Inbound setup packet is an address request flag_empty long 1 << 28 ' Send next logical DATAX token in an empty packet flag_setup long 1 << 29 ' Inbound packet is a setup request flag_serr long 1 << 30 ' Bit stuffing error detected flag_crc long 1 << 31 ' CRC error detected Dtable long 0 flags res 1 lflags res 1 pointer res 1 epoint res 1 ' inbound endpoint t res 1 t1 res 1 data res 1 da res 1 db res 1 dc res 1 dd res 1 crc res 1 bytecnt res 1 fit $1F0 crc16table word $0000, $c0c1, $c181, $0140, $c301, $03c0, $0280, $c241, $c601, $06c0, $0780 word $c741, $0500, $c5c1, $c481, $0440, $cc01, $0cc0, $0d80, $cd41, $0f00, $cfc1 word $ce81, $0e40, $0a00, $cac1, $cb81, $0b40, $c901, $09c0, $0880, $c841, $d801 word $18c0, $1980, $d941, $1b00, $dbc1, $da81, $1a40, $1e00, $dec1, $df81, $1f40 word $dd01, $1dc0, $1c80, $dc41, $1400, $d4c1, $d581, $1540, $d701, $17c0, $1680 word $d641, $d201, $12c0, $1380, $d341, $1100, $d1c1, $d081, $1040, $f001, $30c0 word $3180, $f141, $3300, $f3c1, $f281, $3240, $3600, $f6c1, $f781, $3740, $f501 word $35c0, $3480, $f441, $3c00, $fcc1, $fd81, $3d40, $ff01, $3fc0, $3e80, $fe41 word $fa01, $3ac0, $3b80, $fb41, $3900, $f9c1, $f881, $3840, $2800, $e8c1, $e981 word $2940, $eb01, $2bc0, $2a80, $ea41, $ee01, $2ec0, $2f80, $ef41, $2d00, $edc1 word $ec81, $2c40, $e401, $24c0, $2580, $e541, $2700, $e7c1, $e681, $2640, $2200 word $e2c1, $e381, $2340, $e101, $21c0, $2080, $e041, $a001, $60c0, $6180, $a141 word $6300, $a3c1, $a281, $6240, $6600, $a6c1, $a781, $6740, $a501, $65c0, $6480 word $a441, $6c00, $acc1, $ad81, $6d40, $af01, $6fc0, $6e80, $ae41, $aa01, $6ac0 word $6b80, $ab41, $6900, $a9c1, $a881, $6840, $7800, $b8c1, $b981, $7940, $bb01 word $7bc0, $7a80, $ba41, $be01, $7ec0, $7f80, $bf41, $7d00, $bdc1, $bc81, $7c40 word $b401, $74c0, $7580, $b541, $7700, $b7c1, $b681, $7640, $7200, $b2c1, $b381 word $7340, $b101, $71c0, $7080, $b041, $5000, $90c1, $9181, $5140, $9301, $53c0 word $5280, $9241, $9601, $56c0, $5780, $9741, $5500, $95c1, $9481, $5440, $9c01 word $5cc0, $5d80, $9d41, $5f00, $9fc1, $9e81, $5e40, $5a00, $9ac1, $9b81, $5b40 word $9901, $59c0, $5880, $9841, $8801, $48c0, $4980, $8941, $4b00, $8bc1, $8a81 word $4a40, $4e00, $8ec1, $8f81, $4f40, $8d01, $4dc0, $4c80, $8c41, $4400, $84c1 word $8581, $4540, $8701, $47c0, $4680, $8641, $8201, $42c0, $4380, $8341, $4100 word $81c1, $8081, $4040 crc16tableend dd_table word 0 word @dd_device ' 1 word @dd_config ' 2 word @dd_status ' 3 word 0 ' 4 word @dd_langid ' 5 word @dd_vendor ' 6 word @dd_product ' 7 word 0 ' 8 word 0 ' 9 word @dd_defmode ' 10 word @dd_dcd_status ' 11 word 0 ' 12 word 0 ' 13 dd_device byte @dd_device_end-(@dd_device+1) byte @dd_device_end-(@dd_device+1) ' Descriptor length byte 1 ' Descriptor type (Device) byte 1, 1 ' USB Version byte 2 ' USB Device Class (CDC) byte 0 ' Device Subclass byte 0 ' Device Protocol byte 8 ' Max packet size byte $C0, $16 ' Vendor ID byte $E1, $05 ' Device PID byte $00, $01 ' Device Version byte 1 ' Vendor string index byte 2 ' Product string index byte 0 ' Serial number index (none) byte 1 ' Number of configurations dd_device_end dd_config byte @dd_inter_acm_e-(@dd_config+1) ' Total length to return to the application level byte @dd_config_end-(@dd_config+1) ' Descriptor length byte 2 ' Descriptor type (Config) byte @dd_inter_acm_e-(@dd_config+1),0 ' Total descriptor length returned byte 2 ' Number of interfaces byte 1 ' Configuration index byte 0 ' Configuration name string index (none) byte $40 ' Device is self powered byte 0 ' Consumes no bus power dd_config_end dd_inter byte @dd_inter_e-@dd_inter byte 4 ' Descriptor type (Interface) byte 0 ' Index of this interface byte 0 ' Alternate interface (None) byte 1 ' Number of endpoints for this interface byte 2 ' Interface Class (CDC) byte 2 ' Interface Subclass (Modem) byte 1 ' Interface Protocol (AT Commands) byte 0 ' Interface name string index (none) dd_inter_e dd_class byte 5 ' Descriptor Length byte $24 ' Descriptor Type (CS_INTERFACE) byte 0 ' Descriptor sybtype byte $10, $01 ' CDC Specification release number byte 4 ' Descriptor Length byte $24 ' Descriptor Type (CS_INTERFACE) byte 2 ' Descriptor subtype (ACM Function) byte 2 ' Function capabilities (Set_Line_Coding,Set_Control_Line_State,Get_Line_Coding,Serial_State) byte 5 ' Descriptor Length byte $24 ' Descriptor Type (CS_INTERFACE) byte 6 ' Descriptor subtype (Union functional descriptor) byte 0 ' Master interface (Endpoint 0) byte 1 ' Slave interface (Endpoint 1) byte 5 ' Descriptor Length byte $24 ' Descriptor Type (CS_INTERFACE) byte 1 ' Descriptor subtype (Call management functional descriptor) byte 3 ' Function capabilities (Device handles call management, Device can send call management over data interface) byte 1 ' bDataInterface (Endpoint 1) - Interface to pass call management data over dd_class_e dd_endpoint byte @dd_endpoint_e-@dd_endpoint ' Descriptor length byte 5 ' Descriptor type (Endpoint) byte $83 ' Endpoint (In / 3) byte 3 ' Interrupt endpoint byte 8, 0 ' Maximum packet size byte $64 ' Polling interval (in mS) dd_endpoint_e dd_inter_acm byte 9 ' Descriptor Length byte 4 ' Descriptor type (interface) byte 1 ' Interface index byte 0 ' Alternate settings byte 2 ' Number of endpoint descriptors to follow byte $A ' Data interface class byte 0 ' Interface subclass byte 0 ' Interface protocol byte 0 ' Interface name string index byte 7 ' Descriptor Length byte 5 ' Descriptor type (Endpoint) byte 1 ' Endpoint number (Out / 1) byte 2 ' Bulk endpoint (Illegal and invalid for a low speed device) byte 8, 0 ' Max packet size byte 0 ' Polling interval byte 7 ' Descriptor Length byte 5 ' Descriptor type (Endpoint) byte $81 ' Endpoint number (In / 1) byte 2 ' Bulk endpoint (Illegal and invalid for a low speed device) byte 8, 0 ' Max packet size byte 0 ' Polling interval dd_inter_acm_e dd_vendor byte @dd_vendor_e-(@dd_vendor+1) byte @dd_vendor_e-(@dd_vendor+1) byte 3 byte "B",0,"r",0,"a",0,"d",0,"s",0,"-",0,"P",0,"r",0,"o",0,"p",0,"e",0,"l",0,"l",0,"e",0,"r",0 dd_vendor_e dd_product byte @dd_product_e-(@dd_product+1) byte @dd_product_e-(@dd_product+1) byte 3 byte "C",0,"D",0,"C",0,"-",0,"A",0,"C",0,"M",0,"-",0,"H",0,"A",0,"C",0,"K",0 dd_product_e dd_langid byte 4 byte 4, 3, $04, $09 dd_langid_e dd_dcd_status byte @dd_dcd_status_e-(@dd_dcd_status+1) byte $a1, $20, 0, 0, 0, 0, 2, 0, 3, 0 dd_dcd_status_e dd_defmode byte @dd_defmode_e-(@dd_defmode+1) byte $80, $25, 0, 0, 0, 0, 8 dd_defmode_e dd_status byte 2 byte 0, 0 dd_status_e