en:pfw:usb_cdc_driver_for_rp2040
Table of Contents
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