User Tools

Site Tools


en:pfw:usb_cdc_driver_for_rp2040

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

en/pfw/usb_cdc_driver_for_rp2040.txt · Last modified: 2026-01-13 18:16 by willem