User Tools

Site Tools


en:pfw:usb_cdc_driver_for_rp2040

**This is an old revision of the document!**

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
...

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
en/pfw/usb_cdc_driver_for_rp2040.1766843193.txt.gz · Last modified: 2025-12-27 14:46 by willem