====== USB CDC driver for RP2040 ====== ===== The idea ===== This is a compact and universal MSP430 USB CDC driver. Every effort has been made to make it portable. ===== What we need to do in short ===== * Initialising the USB hardware on the RP2040. * Receiving and responding to setup packets from the host (PC). This determines which driver the OS will load. * If the host is satisfied with the setup, then sending and receiving data packets may start. ===== Pseudo code ===== hex Function: DEVICE-DECRIPTOR \ 18 bytes The USB CDC device descriptor Function: CONFIGURATION-DESCRIPTOR \ 9 or 75 bytes The complex USB CDC device descriptor Function: USB-STATE ( -- a ) Hold current USB state in first half word: 3 = ready In second half word are memory for the 900/880 requests The English/US language ID string CDC Line data: 115k2, Stop bits, Parity, Data bits Function: START-USB ( -- ) Initialise the RP2040 USB hardware, restart the USB hardware, erase the USB DPRAM, enable USB controller, set USB address to zero. Setup used endpoints and used interrupts & enable full speed USB. Function: PAD ( -- a ) Reserve 64 bytes RAM as scratchpad area Function: EP0 ( -- a ) Function: EP1 ( -- a ) Function: EP2 ( -- a ) Function: EP3 ( -- a ) Seven cells data structures to control endpoint-0, 1, 2 & 3 The first four cells a variable data The three cells thereafter contain USB pointers & register addresses Function: @VAL ( -- +n ) Read the wValue from any eight bytes setup packet Function: @LEN ( -- +n ) Read the wLength from any eight bytes setup packet Function: >UNI ( a1 u -- a2 ) Store the ASCII string to a unicode string tailored for USB Function: >CNT \ Offset 00 Function: >ORG \ Offset 04 Function: >PKT \ Offset 08 Function: >PID \ Offset 12 Function: >ICTRL \ Offset 16 Function: >OCTRL \ Offset 20 Function: >BUF \ Offset 24 Seven functions that calculate the address of each field in the endpoint data structures Function: EP-IN ( ep -- org buf pkt ) Calculate data to send for 'ep' x Function: PREPARE ( a u ep -- ) Prepare data packet from address 'a' with the length 'u' to send tp 'ep' x Function: >NEXT ( ep -- pkt ) Get next packet size for 'ep' x, when 'pkt' is zero there is no more data to send Function: !PKT ( pkt ep -- ictrl ) Store next packet to send for 'ep' x Function: PREP-RCV ( ep -- ) Enable endpoint 'ep' x to receive a data packet Function: GONE? ( ep -- f ) Leave true when the data in 'ep' x was sent Function: USB? ( -- +n ) Leave three when the host & device are connected Function: USB-SEND ( ep -- ) Transmit the prepared data packet to 'ep' x Function: USB-RCV ( pid ep -- ) Enable the receiving of a data packet on 'ep' x starting with the 'pid' from the stack Function: BUS=RESET ( -- ) Handle a USB bus reset, clearing the device address to zero, the USB state to zero, reinitialise the receiving & transmitting endpoints Function: SETUP> ( a u -- ) Handle the answer data packet 'a' 'u' for all setup packages When done wait for a ZLP from the host to signal a correct answer Function: XTABLE Define an execution table entry for '+n' request actions Define: ( +n "name" -- ) Create a table with "name" for '+n' actions Action: ( request -- ) Execute the action for 'request' or generate a stall when the 'request' is not available Function: ZLP> ( -- ) Send a zero length package through endpoint zero. This is used to signal the handling of a request that sends no data back Define four action in front that leave their execution tokens on the stack 0302 action xt 0300 action xt 0200 action xt 0100 action xt Function: HANDLE-SETUP ( req0 .. req3 4 -- ) 2221 action xt 21A1 action xt 2021 action xt 0900 action xt 0880 action xt 0680 action xt 0500 action xt Function: HANDLE-REQ) ( req0 .. req6 7 -- ) Function: HANDLE-REQ ( -- ) Reset setup request interrupt, read bmrequest type and do HANDLE-REQ) Function: #L ( -- +n ) Ring buffer size, must be a power of two Function: #R ( -- +n ) this is equal to #L - 1 Function: #RX ( -- +n ) Leave number of characters in receive buffer Function: #TX ( -- +n ) Leave number of characters in transmit buffer Function: >RX ( c -- ) Store character 'c' in RX ring buffer Function: >TX ( c -- ) Store character 'c' in TX ring buffer Function: RX> ( -- c ) Read character 'c' from RX ring buffer Function: TX> ( -- c ) Read character 'c' from TX ring buffer Function: IFLAG Reserve one cell space for receive interrupt flag Function: ENDPOINTS ( -- ) Handle EP1 & EP3 endpoints, save received data interrupt flag in IFLAG Release all interrupt flags Function: REQUESTS ( -- ) Handle all USB interrupts, that are the USB bus reset, setup requests & endpoints Function: USB-HANDLER ( -- ) Handle the receiving of the RX data using iFLAG and use the low level USB ACK/NAK handshake to move data from an endpoint to the RX ring buffer in a controlled way. Handle the transmitting of TX data, but only when a connection is made. Before filling the TX endpoint check if the previous data packet was sent too. Function: USB-KEY? ( -- flag ) Leave true flag when there is data in the RX ring buffer, otherwise false Call the USB-HANDLER once too Function: USB-KEY ( -- c ) Leave character 'c' when there is data in the RX ring buffer Call the USB-HANDLER waiting for space in the RX ring buffer Function: USB-EMIT ( c -- ) Store character 'c' when there is space in the TX ring buffer Call the USB-HANDLER waiting for space in the TX ring buffer Function: USB-ON Initialise the USB DPRAM, call START-USB, clear USB-STATE & IFLAG finally initialse the KEY?, KEY and EMIT vectors of the used system. ===== Generic Forth implementation example ===== \ This program assumes 32-bit cells. \ Non standard words used are: \ : C@+ ( a1 -- a2 b ) count ; \ : H@ ( a -- h ) count swap c@ 8 lshift or ; \ : H! ( h a -- ) >r dup r@ c! 8 rshift r> 1+ c! ; \ : @+ ( a1 -- a2 x ) >r r@ cell+ r> @ ; \ : **BIS ( msk a -- ) >r r@ @ or r> ! ; \ : **BIC ( msk a -- ) >r invert r@ @ and r> ! ; \ : **BIX ( msk a -- ) >r r@ @ xor r> ! ; \ : BIT** ( msk a -- b ) @ and ; hex \ USB device data structures create DEV-DESCR \ Device descriptor (18 bytes) 12 c, 01 c, 10 c, 01 c, ( 1.10 ) EF c, 02 c, 01 c, 40 c, 66 c, 66 c, ( 6666 ) 10 c, 66 c, ( 6610 ) 00 c, 01 c, ( vsn 1.00 ) 00 c, 02 c, 00 c, 01 c, align create CONFIG-DESCR \ Configuration descriptor (9 or 75 bytes) 9 c, 2 c, 4B c, 0 c, 2 c, 1 c, 0 c, 80 c, FA c, \ Maximum power = 500mA 8 c, 0B c, 0 c, 2 c, 2 c, 2 c, 1 c, 0 c, \ Interface Association Descriptor - CDC 0 9 c, 4 c, 0 c, 0 c, 1 c, 2 c, 2 c, 1 c, 0 c, \ Interface 1: Control - 1 - 0 - 5 c, 24 c, 0 c, 10 c, 1 c, ( CDC vsn 1.10 ) \ CDC Header functional 5 c, 24 c, 1 c, 0 c, 1 c, \ CDC Call management functional 4 c, 24 c, 2 c, 2 c, \ CDC ACM functional ( two commands ) 5 c, 24 c, 6 c, 0 c, 1 c, \ CDC Union functional ( one interface ) 7 c, 5 c, 81 c, 3 c, 8 c, 0 c, 10 c, \ Endpoint 1 IN descriptor 9 c, 4 c, 1 c, 0 c, 2 c, 0A c, 0 c, 0 c, 0 c, \ Interface 2: DATA 7 c, 5 c, 82 c, 2 c, 40 c, 0 c, 0 c, \ Endpoint 2 IN descriptor 7 c, 5 c, 3 c, 2 c, 40 c, 0 c, 0 c, align \ Endpoint 3 OUT descriptor create PAD ( -- a ) 40 allot \ Scratchpad memory decimal create USB-STATE 0 , \ Hold current USB state: 3 = ready \ Second half word for 900/880 requests 4 c, 3 c, 9 c, 4 c, \ English/US = language ID 115200 , 0 c, 0 c, 8 c, \ Line data: 115k2, Stop bits, Parity, Data bits align hex : START-USB ( -- ) 1000000 4000C000 \ Bit-24 mask & Reset register 2dup **bis 2dup **bic \ Restart USB begin 2dup 8 + bit** until \ Wait until USB is ready 2drop 50100000 1000 false fill \ Erase USB ram 00000009 50110074 ! \ USB_USB_MUXING Softcon, to PHY 0000000C 50110078 ! \ USB_USB_PWR VBUS overide & detect enable 00000001 50110040 ! \ USB_MAIN_CTRl Enable controller 20000000 5011004C ! \ USB_SIE_CONTROL Enable End Point 0 interrupt false 50110000 ! \ Respond to address 0 on initial setup 00011010 50110090 ! \ USB_INTE Enable 3 interrupts AC000180 50100008 ! \ init COMM endpoint in buffer 1 A8000200 50100010 ! \ init SEND endpoint in buffer 2 A8000280 5010001C ! \ init RECV endpoint out buffer 3 00010000 5011204C ! ; \ USB_SIE_CONTROL Enable pull up \ Endpoint data structures, 4 variable cells & 3 constant cells \ Name: CNT ORG PKT PID INBUF-CTRL OUTBUF-CTRL EPxBUF \ Offsets: 00 04 08 0C 10 14 18 create EP0 4 cells allot 50100080 dup , 4 + , 50100100 , create EP1 4 cells allot 50100088 dup , 4 + , 50100180 , create EP2 4 cells allot 50100090 dup , 4 + , 50100200 , create EP3 4 cells allot 50100098 dup , 4 + , 50100280 , : @VAL ( -- +n ) 50100002 h@ ; \ wValue : @LEN ( -- +n ) 50100006 h@ ; \ wLength : >UNI ( a1 u -- a2 ) \ Convert string to unicode format for USB dup 2* 302 + pad h! 0 \ String length & notifier ?do c@+ pad i 2* + 2 + h! loop drop pad ; \ Addresssing tools for the endpoint data structures : >CNT ; immediate \ 00 - ep0 >cnt ! - Bytes left te send : >ORG cell+ ; \ 04 - ep1 >org @ - Address of next packet : >PKT 2 cells + ; \ 08 - Packet size : >PID 3 cells + ; \ 0C - Actual PID : >ICTRL 4 cells + ; \ 10 - USB in control register : >OCTRL 5 cells + ; \ 14 - USB out control register : >BUF 6 cells + ; \ 18 - EPx buffer address \ Primitive endpoint functionality : EP-IN ( ep -- org buf pkt ) \ Calculate data to send for EPx >r r@ >org @ r@ >buf @ r> >pkt @ ; : PREPARE ( a u ep -- ) \ Prepare data to send for EPx >r dup r@ >cnt ! 40 min r@ >pkt ! r> >org ! ; : >NEXT ( ep -- pkt ) \ Get next packet size for EPx >r r@ >pkt @ r@ >org @ + r@ >org ! r@ >pkt @ r@ @ over - dup r@ ! 40 min r> >pkt ! ; : !PKT ( pkt ep -- ictrl ) \ pid + pkt + mask - Store next packet to send for EPx >r r@ >pid @ or 8000 or r> >ictrl @ tuck ! ; : PREP-RCV ( ep -- ) \ Prepare to receive a packet on EPx >r r@ >octrl @ r@ >pid @ 40 or over ! 2000 r> >pid **bix 400 swap **bis ; : GONE? ( ep -- f ) >ictrl @ @ 400 and 0= ; \ Leave true when Epx packet was sent : USB? ( -- +n ) usb-state h@ 3 = ; \ True when host & device are connected \ Basic receive & transmit packet handlers : USB-SEND ( ep -- ) >r r@ >next r@ !pkt 2000 r> >pid **bix 400 swap **bix ; : USB-RCV ( pid ep -- ) tuck >pid ! prep-rcv ; : BUS-RESET ( -- ) 80000 50113050 ! \ SIE_STATUS - BUS_RESET (Clear bit) false 50110000 ! false usb-state ! \ USB-address=0 & USB-state=0, u-config = 0 false ep3 usb-rcv false ep2 >pid ! ; \ Allow receiving EP3 & init. transmit EP2 : SETUP> ( a u -- ) \ Handle the answer for all setup packages 2000 ep0 >pid ! ep0 prepare \ Start with DATA1, setup packet data begin ep0 ep-in move ep0 usb-send \ Store & send packet 1 50110058 \ Packet gone? begin 2dup bit** until **bis ep0 @ 0= until 2000 ep0 usb-rcv ; \ Handle ZLP : XTABLE ( +n "name" -- ) \ Define custom execution table create , does> @+ ( req -- ) >r r@ for 2dup @ = if \ Token found? nip rdrop r> cells + \ Yes, calc. cell with XT @ execute exit \ Fetch & execute XT then cell+ next rdrop 2drop \ No token found 1 5011,0068 ! 800 5010,0080 ! ; \ Send EP0 stall : ZLP> ( -- ) here false setup> ; \ Send EP0 ZLP :noname me count 1D min >uni dup c@ @len min setup> ; \ 0302 Device name string :noname usb-state cell+ 4 setup> ; \ 0300 Lang-ID string :noname config-descr @len 4B min setup> ; \ 0200 Config descriptor :noname dev-descr @len 12 min setup> ; \ 0100 Device descriptor 4 xtable HANDLE-SETUP ( req -- ) 0100 , 0200 , 0300 , 0302 , ( dev ) , ( conf ) , ( 300 ) , ( 302 ) , :noname @val usb-state c! zlp> ; \ 2221 Set control line state :noname usb-state cell+ cell+ 7 setup> ; \ 21A1 Get line coding :noname 2000 ep0 usb-rcv zlp> ; \ 2021 Set line coding :noname zlp> @val usb-state 2 + h! ; \ 0900 Set USB configuration :noname usb-state 2 + 2 setup> ; \ 0880 Get USB configuration :noname @val handle-setup ; \ 0680 Basic usb SETUP handler :noname zlp> @val 5011,0000 ! ; \ 0500 Set device address 7 xtable HANDLE-REQ) ( -- ) 0500 , 0680 , 0880 , 0900 , 2021 , 21A1 , 2221 , , , , , , , , : HANDLE-REQ ( -- ) 20000 5011,3050 ! 5010,0000 h@ handle-req) ; \ Ring buffer structure for serial I/O \ \ 0 = Number of used storage units (CNT) \ 1 = In pointer (IN) \ 2 = Out pointer (OUT) \ 3 = Start of ring buffer 100 constant #L \ Buffer size #L 1- constant #R \ Buffer mask 50100300 constant BUFFER1 \ Usess 2 * #L + 24 bytes = 536 bytes buffer1 #l + 3 cells + constant BUFFER2 \ Start of out buffer : #RX ( -- +n ) buffer1 @ ; \ +n characters in receive buffer : #TX ( -- +n ) buffer2 @ ; \ +n characters in send buffer : >RX ( ch -- ) buffer1 cell+ >r r@ @ r@ 2 cells + + c! \ Yes, add char to buffer r@ @ 1+ #r and r@ ! \ Increase & protect write pointer 1 r> cell- +! ; \ Increase used space : RX> ( -- ch ) buffer1 2 cells + >r r@ @ r@ cell+ + c@ \ Read char. from buffer r@ @ 1+ #r and r@ ! \ Increase & protect read pointer true r> 2 cells - +! ; \ Decrease used space : >TX ( ch -- ) buffer2 cell+ >r r@ @ r@ 2 cells + + c! \ Yes, add char to buffer r@ @ 1+ #r and r@ ! \ Increase & protect write pointer 1 r> cell- +! ; \ Increase used space : TX> ( -- ch ) buffer2 2 cells + >r r@ @ r@ cell+ + c@ \ Read char. from buffer r@ @ 1+ #r and r@ ! \ Increase & protect read pointer true r> 2 cells - +! ; \ Decrease used space 0 value IFLAG : ENDPOINTS ( -- ) \ Handle used endpoints 50110058 @ >r r@ 04 and if zlp> then \ EP1 active? iflag r@ 80 and or to iflag \ EP3 active, remember r> 50110058 ! ; : REQUESTS ( -- ) \ Handle all USB requests 50110098 @ >r ( ints ) r@ 10 and if endpoints then \ 10 = #04 bitmask r@ 1000 and if bus-reset then \ 1000 = #12 bitmask r> 10000 and if handle-req then ; \ 10000 = #16 bitmask : USB-HANDLER ( -- ) \ Handle USB setup & character I/O requests iflag if \ Next RX packet wanted? #L #tx - 40 > \ Yes, enough space in TX buffer? #L #rx - 40 > and if \ And next RX packet fits too? ep3 >buf @ 5010009C c@ 0 ?do c@+ >rx loop \ Yes, fill RX buffer drop false to iflag \ Done ep3 prep-rcv \ And allow next RX packet then then usb? if \ Still connected? #tx if \ EP2 Any chars to send? ep2 gone? if \ Yes, previous packet gone? ep2 >buf @ #tx 40 umin \ Packet place & size 2dup ep2 prepare bounds \ Init. transmit pointers ?do tx> i c! loop \ Place data in EP2-buffer ep2 usb-send \ Send to host then then then ; : USB-KEY? ( -- f ) usb-handler #rx ; \ KEY & EMIT with ringbuffer : USB-KEY ( -- c ) begin usb-handler #rx until rx> ; : USB-EMIT ( c -- ) begin usb-handler #L #tx - until >tx ; : USB-ON ( -- ) 50100300 [ #L 2* 6 cells + ] literal false fill \ Init. buffer space to zero start-usb false usb-state ! false to iflag ['] usb-key? to 'key? \ Install USB CDC char. I/O vectors (noForth specific!) ['] usb-key to 'key ['] usb-emit to 'emit ; usb-on ===== Implementations ===== [[https://github.com/WillemOuwerkerk/noForth-T-hardware-examples-RP2040/tree/main/USB-CDC|Different noForth implementations for the USB-CDC driver]]