Shop OBEX P1 Docs P2 Docs Learn Events
P2 USB Host driver (re-)development thread - Page 14 — Parallax Forums

P2 USB Host driver (re-)development thread

1910111214

Comments

  • Wuerfel_21Wuerfel_21 Posts: 4,515
    edited 2023-10-12 10:44

    Really just three things, I think:
    - Hub needs the preamble when doing something lowspeed.
    - The hub has some report you can poll to check the status of its ports. This is how you know when a device connects or disconnects.
    - The hub needs you to set a port to enabled before you can talk to it. You need to do this one at a time so you can set a different address for each device (if you enabled all ports at once, they'd all respond to the first SetAddress).

  • cgraceycgracey Posts: 14,133

    @Wuerfel_21 said:
    Really just three things, I think:
    - Hub needs the preamble when doing something lowspeed.
    - The hub has some report you can poll to check the status of its ports. This is how you know when a device connects or disconnects.
    - The hub needs you to set a port to enabled before you can talk to it. You need to do this one at a time so you can set a different address for each device (if you enabled all ports at once, they'd all respond to the first SetAddress).

    Ok. Sounds good.

    I've got all my output-signalling code done, complete with CRC stuff. It generates all ten messages. It's only 68 longs of PASM code. I will post it in a little bit. Just going over it.

  • I had intentions at one time to dig into this, I ran out of time, but if my Teledyne LeCroy USB analyzer would be helpful to this effort, I could loan/trade/give it away... Honestly, who here could put it to use?

  • cgraceycgracey Posts: 14,133

    @RichardB said:
    I had intentions at one time to dig into this, I ran out of time, but if my Teledyne LeCroy USB analyzer would be helpful to this effort, I could loan/trade/give it away... Honestly, who here could put it to use?

    I've been putting this off for 25 years. The USB v1.1 spec I'm reading is from 1998.

  • cgraceycgracey Posts: 14,133
    edited 2023-10-12 14:45

    Here is my code that generates all the USB signals - 64 longs, total, for the routines that stay in the cog registers. Next, I need to write the code that inputs the USB signals. After that, I can start doing things with real devices.

    CON 
    
      _clkfreq = 320_000_000
    
      'Packet IDs
    
      PID_PRE       = %0011_1100                    'preamble
      PID_ACK       = %0100_1011                    'handshakes
      PID_NAK       = %0101_1010
      PID_STALL     = %0111_1000
      PID_OUT       = %1000_0111                    'tokens
      PID_IN        = %1001_0110
      PID_SOF       = %1010_0101
      PID_SETUP     = %1011_0100
      PID_DATA0     = %1100_0011                    'data
      PID_DATA1     = %1101_0010
    
      'Miscellaneous
    
      PRE_PAT       = %1011111011010101             'SYNC + PID_PRE pattern (K=1, J=0)
    
      IDLE          = 0                             'IDLE state
      SEO           = 1                             'SEO state
      K             = 2                             'K state
      J             = 3                             'J state
      EOP           = 4                             'EOP sequence (SE0,SE0,J,IDLE)
      SYNC          = $80                           'SYNC sequence (KJKJKJKK)
    
      CODE_ORG      = $80                           'starting register for PASM code and variables
    
      CRC5_POLY     = $05 rev 4                     'CRC polynomials
      CRC16_POLY    = $8005 rev 15
    
    
    PUB go()
    
      start(0)
    
    
    PUB start(BasePin)
    
      regload(@CogCode)                             'load PASM code into registers
    
      reg[PinA] := BasePin                          'enter settings into register variable
      reg[PinB] := BasePin + 1
      reg[PinAB] := BasePin addpins 1
    
      reg[LowSpeed] := $8000 | (1_500_000 frac clkfreq) >> 16       'low-speed host mode
      reg[FullSpeed] := $C000 | (12_000_000 frac clkfreq) >> 16     'high-speed host mode
    
                    org
    
                    fltl    PinAB                   'configure USB pins
                    wrpin   #P_OE+P_USB_PAIR,PinAB
                    wxpin   FullSpeed,PinA
                    drvl    PinAB
    
                    wypin   #IDLE, PinA             'send IDLE to initially raise IN
    
                    call    #SendPRE                'do PRE
    
                    mov     addr,#$18               'do IN
                    mov     endp,#1
                    call    #SendIN
    
                    debug   (uhex_byte(CRC))        'show CRC5
    
                    callpb  #8,#IdlePeriods         'idle
    
                    call    #SendDATAx              'do DATA0/DATA1
                    callpa  #$05,#SendByteCRC
                    callpa  #$0C,#SendByteCRC
                    callpa  #$09,#SendByteCRC
                    callpa  #$01,#SendByteCRC
                    callpa  #$A1,#SendByteCRC
                    callpa  #$01,#SendByteCRC
                    callpa  #$19,#SendByteCRC
                    callpa  #$00,#SendByteCRC
                    call    #EndDATAx
    
                    not     CRC
                    debug   (uhex_word(CRC))        'show CRC16
    
    
                    callpb  #8,#IdlePeriods
    
                    call    #SendACK                'do ACK
    
                    jmp     #$
    
                    end
    
    
    DAT
    
    CogCode         word    CODE_ORG, CodeEnd-CodeStart-1
    
                    org     CODE_ORG                'code and variables that occupy upper register space
    CodeStart
    
    SendPRE         mov     pb,##PRE_PAT            'send PRE (assumes full-speed)
    .bit            shr     pb,#1           wc
            if_c    callpa  #K,#SendByte            'send J/K stream, instead of bytes, to avoid EOP
            if_nc   callpa  #J,#SendByte
                    tjnz    pb,#.bit                'ends on K state
                    callpb  #2,#IdlePeriods         'IDLE twice to float pins and get past K state
                    wxpin   LowSpeed,PinA           'switch to low-speed, J/K swap polarity
                    callpb  #4,#IdlePeriods         'wait four periods before low-speed command
                    ret
    
    SendACK         callpa  #PID_ACK,#SendShake     'send ACK
    SendNAK         callpa  #PID_NAK,#SendShake     'send NAK
    SendSTALL       callpa  #PID_STALL,#SendShake   'send STALL
    
    SendOUT         callpa  #PID_OUT,#SendToken     'send OUT
    SendIN          callpa  #PID_IN,#SendToken      'send IN
    SendSOF         callpa  FrameCount,#SendSOF2    'send SOF
    SendSETUP       callpa  #PID_SETUP,#SendToken   'send SETUP
    
    SendDATAx       callpa  #SYNC,#SendByte         'send DATA0/DATA1 according to c
            if_nc   callpa  #PID_DATA0,#SendByte
            if_c    callpa  #PID_DATA1,#SendByte
            _ret_   bmask   CRC,#15                 'init CRC16
    
    EndDATAx        not     pa,CRC                  'end DATA0/DATA1 with CRC16 bytes
                    callpb  #2,#SendByteEOP
    
    
    SendToken       shl     pa,#24                  'get PID into bits 31..24 and clear bits 23..0
                    or      pa,Addr                 'get 7-bit address into bits 6..0
                    ror     pa,#7
                    or      pa,Endp                 'get 4-bit endpoint into bits 10..7
                    rol     pa,#7
                    jmp     #SendToken2
    
    SendSOF2        setbyte pa,#PID_SOF,#3          'get PID into bits 31..24, frame count is in bits 10..0
                    add     FrameCount,#1           'increment and trim frame count
                    zerox   FrameCount,#10
    
    SendToken2      callpb  pa,#TokenCRC            'get CRC5 of bits 10..0
                    ror     pa,#11                  'arrange CRC5, 11 bits, and PID into bits 23..0
                    or      pa,CRC
                    rol     pa,#11+8
                    mov     pb,#4                   'send SYNC + PID + 11 bits + CRC5 + EOP
                    jmp     #SendSyncByteEOP
    
    SendShake       mov     pb,#2                   'send SYNC + PID + EOP
    
    SendSyncByteEOP rolbyte pa,#SYNC,#0             'rotate SYNC byte into bytes to send
    
    SendByteEOP     call    #SendByte               'send bytes, followed by automatic EOP
                    shr     pa,#8
                    djnz    pb,#SendByteEOP
    
                    pop     pa                      'pop return address generated by prior CALLPA/CALLPB
    
    WaitEOP         rdpin   pa,PinA                 'wait for EOP to signal last byte done (needs timeout)
                    and     pa,#%00100000
            _ret_   tjz     pa,#WaitEOP
    
    
    IdlePeriods     callpa  #IDLE,#SendByte         'IDLE for pb bit periods
            _ret_   djnz    pb,#IdlePeriods
    
    SendByte        testp   PinB            wz      'send state/sequence/byte to USB smart pin
            if_nz   jmp     #SendByte               'wait for USB buffer not full
                    akpin   PinB                    'acknowledge USB buffer not full
            _ret_   wypin   pa,PinA                 'enter byte into USB buffer
    
    SendByteCRC     call    #SendByte               'send byte then feed it into CRC16
    ByteCRC         rev     pa                      'feed byte into CRC16, reverse bits into top of long
                    setq    pa                      'get reversed bits into Q (SETQ/CRCNIB inhibit interrupts)
                    crcnib  CRC,.poly               'feed bits 0..3 into CRC16
            _ret_   crcnib  CRC,.poly               'feed bits 4..7 into CRC16
    .poly           long    CRC16_POLY
    
    TokenCRC        mov     CRC,#$1F                'compute CRC5 for 11 LSBs of pb, init CRC5
                    rep     #2,#11                  'feed bits 0..11 into CRC5
                    shr     pb,#1           wc
                    crcbit  CRC,#CRC5_POLY
            _ret_   xor     CRC,#$1F                'invert CRC5 to complete
    
    CodeEnd
    
    
    PinA            res     1                       'register variables
    PinB            res     1
    PinAB           res     1
    
    LowSpeed        res     1
    FullSpeed       res     1
    
    CRC             res     1
    
    FrameCount      res     1                       '11 bits
    Addr            res     1                       '7 bits
    Endp            res     1                       '4 bits
    
  • cgraceycgracey Posts: 14,133
    edited 2023-10-13 08:21

    I found a few minor errors in the USB smart pin documentation. Here is the update I just made:

    At any time, a RDPIN/RQPIN can be executed on the lower pin to read the current 16-bit status of the receiver, with the error flag going into C. The lower pin's IN will be raised whenever a change occurs in the receiver's status. This will necessitate a WRPIN/WXPIN/WYPIN/RDPIN/AKPIN before IN can be raised again, to alert of the next change in status. The receiver's status bits are as follows:

    [31:16] - $0000
    [15:8] byte - last byte received
    [7] byte toggle - cleared on SOP, toggled on each byte received
    [6] error - cleared on SOP, set on bit-unstuff error or EOP SE0 > 2 bit periods or SE1
    [5] EOP in - cleared on SOP or 8 bit periods of J or K, set on EOP
    [4] SOP in - cleared on EOP or 8 bit periods of J or K, set on SOP
    [3] steady state - cleared on DP/DM state change, set on 8 bit periods of no change
    [2] SE0 in - high when DP/DM state is SE0
    [1] K in - high when DP/DM state is K
    [0] J in - high when DP/DM state is J

    Before, the docs said that bits 0 and 1 needed 7+ bit periods to clear, which was wrong. Also, everything that talked about 7+ bit periods is really 8 bit periods. Last of all, the error bit (bit 6) will be set when end-of-packet SE0 state is longer than 2 bit periods, not 3. I wonder if these errors caused any trouble for Garry and others.

    Something else I noticed while running my USB test code was that you can't feed J and K states on every bit period. These state commands take effect only EVERY OTHER bit period, so spoofing the PRE preamble by outputting a stream of J and K states would require doubling the bit rate from 12MHz to 24MHz. It turns out if you just have fast-enough code, you CAN update output states at every bit period, afterall.

    Lastly, I wish I had made the IN output on the lower pin of the USB pair only go high when bits 4/5/6/7 changed. At least, bits 0 and 1 are just noise to software.

  • @cgracey Chip, will your model for USB host control be able to transmit out the 1ms sync frames? Do you ultimately plan to support timer based ISRs in your code somehow?

  • cgraceycgracey Posts: 14,133

    @rogloh said:
    @cgracey Chip, will your model for USB host control be able to transmit out the 1ms sync frames? Do you ultimately plan to support timer based ISRs in your code somehow?

    Yes, I will use a timer interrupt to generate the SOF packets (start of frame).

  • @cgracey said:

    @rogloh said:
    @cgracey Chip, will your model for USB host control be able to transmit out the 1ms sync frames? Do you ultimately plan to support timer based ISRs in your code somehow?

    Yes, I will use a timer interrupt to generate the SOF packets (start of frame).

    Cool. In some ways you sort of want things slaved off that ISR to run a scheduler that determines what goes out onto the bus rather than have it directly demand driven by the client.

  • cgraceycgracey Posts: 14,133
    edited 2023-10-13 08:20

    @rogloh said:

    @cgracey said:

    @rogloh said:
    @cgracey Chip, will your model for USB host control be able to transmit out the 1ms sync frames? Do you ultimately plan to support timer based ISRs in your code somehow?

    Yes, I will use a timer interrupt to generate the SOF packets (start of frame).

    Cool. In some ways you sort of want things slaved off that ISR to run a scheduler that determines what goes out onto the bus rather than have it directly demand driven by the client.

    Yes, I am starting to get the idea.

    I found a way to output single-bit-period states at 12Mbps to realize the PRE command. It works from 170MHz on up:

                    fltl    PinAB                   'configure USB pins
                    wrpin   #P_OE+P_USB_PAIR,PinAB
                    wxpin   FullSpeed,PinA
                    drvl    PinAB
    
                    mov     pa,PinB                 'set SE1 for PinB rise
                    or      pa,#%001_000000
                    setse1  pa
    
    .test           rep     @.r,#16                 'ready for 16 bits
                    ror     .pat,#2         wc      'get next bit into c
                    waitse1                         'wait for PinB to rise
            if_nc   wypin   #J,PinA                 'output J or K, by c
            if_c    wypin   #K,PinA
                    akpin   pinB                    'acknowledge PinB
    .r
                    waitx   #400
                    jmp     #.test
    
    .pat            long    %%2022222022020202
    
  • cgraceycgracey Posts: 14,133

    Here's how you could output anything as fast possible:

                    fltl    PinAB                   'configure USB pins
                    wrpin   #P_OE+P_USB_PAIR,PinAB
                    wxpin   FullSpeed,PinA
                    drvl    PinAB
    
                    mov     pa,PinB                 'set SE1 for PinB rise
                    or      pa,#%001_000000
                    setse1  pa
    
                    rdfast  #0,statelist            'ready to read states
                    rep     @.r,statecount          'ready for state stream
                    rfbyte  pa                      'get next state
                    waitse1                         'wait for PinB to rise
                    wypin   pa,PinA                 'set state
                    akpin   pinB                    'acknowledge PinB
    .r
    
  • Tried to once again look into the mystery of the unreadable 8bitdo SN30 controller. Looked at a packet capture from plugging it into a computer. The only difference appears to be that the PC tries reading some strings, though a lot of that actually fails, so idk. Everything else appears to be the same: SetConfiguration, OUT to endpoint 2 to set LEDs, start polling endpoint 1. But we only get NAK back.

    Though maybe there's something I'm missing, the software capture is missing a lot of low-level details.

  • Wuerfel_21Wuerfel_21 Posts: 4,515
    edited 2023-10-13 20:47

    Okay, the string request after the LED set is significant (WTF??????). Adding that makes it report data. Ok, very cool. It might actually work with any string request (possibly before the LED OUT), might have to look into that. Also, when not connected through a hub, it keeps resetting unless you press X to force XInput mode. I guess the same string request trick might make the HID mode work, too, but I'd rather not do strange things in that codepath.

  • Well, shrug, here's to usbnew V1.1.1 that (partially) fixes the SN30pro USB through unknown machinations.

  • After, I wanted to investigate the issue wherein the Logitech F710 RF receiver only worked in a hub. Turns out the receiver has rusted or something in the meantime, it is now very finnicky to get it working. As-in an actual physical issue that prevents it from being detected at all if it's plugged all the way in. Attempt to fix this made it worse. But I might have an idea now as to how to fix the root port connection issue maybe.

  • Well, that was easy: just don't reset everything when the root device disconnects. That fixes devices that do this fake software disconnect thing (both the logitech and the 8bitdo). We'll see if it causes any problems... :pensive:

    usbnew v1.1.2

  • pik33pik33 Posts: 2,350

    A slightly offtopic: what clkfreq variable (without _) really does in Flexprop?

    The background: I changed _clkfreq in a Basic interpreter and USB stopped working.

    The bug was in my video driver that assigned something to the variable named clkfreq. It then was supposed to do hubset to this frequency, but it did not as I commented the hubset out while experimenting with the driver.
    The result: 340500000 assigned to clkfreq, 338688000 set as _clkfreq, USB driver doesn't work.

  • _clkfreq is a constant that has special meaning to the compiler, but is otherwise just a constant. clkfreq is a special variable stored in a fixed location that holds the current clock speed. The compiler will generate code to set this (based on the actual computed clock mode, not necessarily exactly the _clkfreq requested). If you change the clock mode without updating clkfreq or vice versa, everything depending on that to compute timings will be too fast or too slow. (See above where the guy didn't account for his 19.2 MHz oscillator)

  • @pik33 said:
    A slightly offtopic: what clkfreq variable (without _) really does in Flexprop?

    The background: I changed _clkfreq in a Basic interpreter and USB stopped working.

    clkfreq is a global variable that records the last frequency value set by the clkset function. It is not read-only, but changing it is (as you discovered) a bad idea: it probably should be marked as const but it isn't really constant either since clkset will change it. You shouldn't ever have to manually change it (unless you change the frequency directly with hubset).

  • evanhevanh Posts: 15,198
    edited 2023-10-15 02:24

    On a deviation to an off-topic question ... The best and only use of the compile time constant _clkfreq is for telling the compiler of desired initial sysclock frequency. It also only exists in top level object and only when explicitly declared, btw.

    clkfreq_ however is usefully useable in code, along with its companion clkmode_. These always exist in all objects and tells of the compiler's calculated initial sysclock mode and frequency. As a pair, they can be used to derive the board's crystal mode and frequency - Assuming the board installer used the right compile settings that matches the physical board.

    Once the crystal is known, then recomputing a new clock mode for clkset() is a lot less error prone.

    Here's my take on Chip's PLL calculation code. Notably I've added a three-way if structure at the start to pick out the crystal mode and frequency from the compiled settings:

    pub  pllset( targetfreq ) : xinfreq | mode, mult, divd, divp, post, Fpfd, Fvco, error, besterror, vcolimit
    
        mode := clkmode_ & %11_11 | %11         ' keep %CC, force %SS, ditch the rest
    
        if clkmode_ >> 24 & 1                   ' compiled with PLL on
            divd := clkmode_ >> 18 & $3f + 1
            mult := clkmode_ >> 8 & $3ff + 1
            divp := (clkmode_ >> 4 + 1) & $f
            divp := divp ? divp * 2 : 1
            xinfreq := muldiv65( divp * divd, clkfreq_, mult )
    
        elseif clkmode_ >> 2 & 3                ' compiled with PLL off
            xinfreq := clkfreq_             ' clock pass-through
    
        else                                    ' unknown build mode
            xinfreq := 20_000_000           ' default to 20 MHz crystal
            mode := %10_11                  ' default to 15 pF loading
    
        besterror := div33( targetfreq, 100 )           ' _errfreq at 1.0% of targetfreq
        vcolimit := targetfreq + besterror
        vcolimit := vcolimit < 201_000_000 ? 201_000_000 : vcolimit
    
        repeat post from 0 to 15
            divp := post ? post * 2 : 1
    
            repeat divd from 64 to 1
                Fpfd := div33( xinfreq, divd )
                mult := muldiv65( divp * divd, targetfreq, xinfreq )
                Fvco := muldiv65( xinfreq, mult, divd )
    
                if Fpfd >= 250_000 and mult <= 1024 and Fvco > 99_000_000 and Fvco <= vcolimit
                    error := div33( Fvco, divp ) - targetfreq
    
                    if abs( error ) <= abs( besterror )     ' the last iteration at equality gets priority
                        besterror := error
                        mode := (mode&%11_11) | 1<<24 | (divd-1)<<18 | (mult-1)<<8 | ((post-1)&15)<<4
    
        if mode.[24]                                    ' PLL-ON bit set when calculation is valid
            clkset( mode, targetfreq + besterror )  ' make the frequency change, also sets debug port baud as of Pnut v36
        else
            xinfreq := -1                           ' failed, no change
    
  • RaymanRayman Posts: 13,905

    Just a note... Testing the driver with a new mouse...
    Scroll wheel may not work, but pushing in scroll wheel as a button does work.
    Also, the two side buttons work.

  • @Rayman said:
    Just a note... Testing the driver with a new mouse...
    Scroll wheel may not work, but pushing in scroll wheel as a button does work.
    Also, the two side buttons work.

    Yes, the usual "boot protocol is insufficient for most mice" issue. Really should look into finishing work on running mice in full HID mode.

  • RaymanRayman Posts: 13,905

    It's pretty sufficient for what I need to do... But, if I really did need the full report, pretty easy to capture PC--mouse traffic and then pretend to be PC. As long as nothing goes wrong anyway...

  • Well, I just made a new version anyways. You can now set MOUSE_FULL_PROTOCOL to enable full HID parsing for mice. Works with all mice I tested and can possibly read extra buttons and the scroll wheel. Though it can't read all the extra buttons on my G502...

  • If you check on github, I also added a dumb hack to make numlock default to off on those Pi keyboards where the numblock is unhelpfully superimposed upon the regular layout (who thought that was an acceptable idea?)

  • And here's to version 1.1.4 that actually works properly if you don't have gamepads enabled.

    Also, just realized that hpad_translate ends up getting called twice for xinput/ps3 devices (because they jump into hpad_translate, but it's also pushed on the return stack). Not sure how that flew under the radar for so long.

  • cgraceycgracey Posts: 14,133
    edited 2023-10-22 11:47

    I've got the USB packet send/receive driver code down to 108 longs of PASM code. It lives in a cog's registers, so that Spin2 can be used for high-level decision making. It is bullet-proof. It needs at least 170 MHz to operate.

    Here it is in a file that demonstrates it.

    CON 
    
      _clkfreq = 170_000_000
    
      BasePin       = 0
    
      'Packet IDs
    
      PID_PRE       = %0011_1100    'host                   preamble
      PID_ACK       = %0100_1011    'host + device          handshakes
      PID_NAK       = %0101_1010    '       device
      PID_STALL     = %0111_1000    '       device
      PID_OUT       = %1000_0111    'host                   tokens
      PID_IN        = %1001_0110    'host
      PID_SOF       = %1010_0101    'host
      PID_SETUP     = %1011_0100    'host
      PID_DATA0     = %1100_0011    'host + device          data
      PID_DATA1     = %1101_0010    'host + device
    
      'Miscellaneous
    
      IDLE          = 0             'IDLE state
      SEO           = 1             'SEO state
      K             = 2             'K state
      J             = 3             'J state
      EOP           = 4             'EOP sequence (SE0,SE0,J,IDLE)
      SYNC          = $80           'SYNC sequence (KJKJKJKK)
    
      CODE_ORG      = $80           'starting register for PASM code and variables
    
    
    VAR
    
      stack1[100]
      stack2[100]
      byte Data[100]
    
    
    PUB go() | toggle
    
      cogspin(newcog, sniffer(), @stack1)
      cogspin(newcog, getter(), @stack2)
    
      setup()
    
      repeat
                    org
    
    
                    fltl    PinAB                   'configure USB pin pair
                    wrpin   #P_OE+P_USB_PAIR,PinAB
                    call    #SetFullSpeed
                    drvl    PinAB
                    wypin   #IDLE,PinA              'write IDLE to initially raise PinB IN
    
    .loop           getrnd                  wc      'send DATA0/DATA1 packet with 8 data bytes
                    call    #SendDATA
                    callpa  #$05,#ByteDATA
                    callpa  #$0C,#ByteDATA
                    callpa  #$09,#ByteDATA
                    callpa  #$01,#ByteDATA
                    callpa  #$A1,#ByteDATA
                    callpa  #$01,#ByteDATA
                    callpa  #$19,#ByteDATA
                    callpa  #$00,#ByteDATA
                    call    #EndDATA
    
                    callpb  #50,#IdlePeriods
    
                    getrnd                  wc      'send DATA0/DATA1 packet with 0 data bytes
                    call    #SendDATA
                    call    #EndDATA
    
                    callpb  #50,#IdlePeriods
    
                    call    #SendACK                'send ACK packet
    
                    callpb  #50,#IdlePeriods
    
                    jmp     #.loop
    
                    end
    
    
    PUB sniffer()
    
                    org
    
                    bmask   dirb,#15                'USB sniffer program reports status to pins 47..32
    
                    rep     #2,#0
                    rqpin   pa,PinA
                    setword outb,pa,#0
    
                    end
    
    
    PUB getter() | i
    
      setup()
    
      repeat
    
        call(#GetPacket)
    
        case reg[PID]
          0:                'ignore error packets due to sender/receiever misalignment
          PID_ACK:
            debug("ACK")
          PID_DATA0:
            debug("DATA0 - ", uhex(reg[DataBytes]), uhex_byte_array(@Data, reg[DataBytes] <# 10))
          PID_DATA1:
            debug("DATA1 - ", uhex(reg[DataBytes]), uhex_byte_array(@Data, reg[DataBytes] <# 10))
    
        waitus(getrnd() >> 24)
    
    
    PRI setup()
    
      regload(@CogCode)                             'load PASM code into registers
    
      reg[PinA] := BasePin                          'enter settings into register variable
      reg[PinB] := BasePin + 1
      reg[PinAB] := BasePin addpins 1
    
      reg[LowSpeed] := $8000 | (1_500_000 frac clkfreq) >> 16       'low-speed host mode
      reg[FullSpeed] := $C000 | (12_000_000 frac clkfreq) >> 16     'high-speed host mode
    
      reg[TimeoutLow] := clkfreq / (1_500_000 / 18)                 'set low-speed timout
      reg[TimeoutFull] := clkfreq / (12_000_000 / 18)               'set full-speed timout
      reg[Timeout] := reg[TimeoutFull]
    
      reg[Frame] := 0
      reg[addr] := 0
      reg[endp] := 0
    
      reg[DataStart] := @Data
    
    
    DAT
    
    ' This is the PASM driver that signals and receives USB packets.
    ' It lives in registers $80..$11F and can be called from Spin2 and inline PASM.
    
    CogCode         word    CODE_ORG, CodeEnd-CodeStart-1
    
                    org     CODE_ORG                'code and variables that occupy upper register space
    CodeStart
    
    GetPacket       call    #SetTimeout             'wait for SOP
    .sop            rqpin   pb,PinA                 'read status word from PinA
                    and     pb,#%00010000   wc      'get SOP in c, also clears pb lsb for .getbyte
            if_nc   jnct1   #.sop                   'loop until SOP (c=1) or timeout (c=0)
    
            if_c    call    #.getbyte               'if SOP, receive PID byte
            if_nc   jmp     #.error                 'if SOP timeout or EOP before PID byte, error
                    mov     PID,pa                  'save PID byte
    
                    cmp     PID,#PID_ACK    wz      'ACK?
            if_nz   cmp     PID,#PID_NAK    wz      'NAK?
            if_nz   cmp     PID,#PID_STALL  wz      'STALL?
            if_z    jmp     #.handshake             'if so, handshake
    
                    cmp     PID,#PID_DATA0  wz      'DATA0?
            if_nz   cmp     PID,#PID_DATA1  wz      'DATA1?
            if_nz   jmp     #.error                 'if unknown PID, error
    
    
                    wrfast  #0,DataStart            'DATA0/DATA1, start fast hub write
                    bmask   CRC,#15                 'initialize CRC
    
                    mov     DataBytes,#64+2         'receive up to 64 data bytes plus CRC word
    .byte           call    #.getbyte               'receive byte and/or EOP
            if_c    djf     DataBytes,#.error       'if byte overrun, error
            if_c    wfbyte  pa                      'write byte to hub
            if_c    rolbyte CRCword,pa,#0           'save possible CRC byte into CRCword
            if_c    rolword CRCpast,CRC,#0          'save current CRC into CRCpast
            if_c    call    #ByteCRC                'feed byte into CRC
            if_nz   jmp     #.byte                  'loop until EOP
    
                    subr    DataBytes,#64           'got EOP, get number of data bytes
            if_c    jmp     #.error                 'if missing CRC byte(s), error
    
                    movbyts CRCpast,#%%0023         'get CRC value prior to last two bytes
                    xor     CRCpast,CRCword         'xor against last two bytes which were CRC
                    signx   CRCpast,#15             'sign extend word, should become $FFFFFFFF
            _ret_   tjnf    CRCpast,#.error         'if $FFFFFFFF then done, else error
    
    
    .handshake      call    #.getbyte               'ACK/NAK/STALL, receive EOP
            if_c    jmp     #.error                 'if byte before EOP, error
                    ret                             'got EOP, done
    
    
    .getbyte        call    #SetTimeout             'wait for byte and/or EOP
    .wait           rqpin   pa,PinA                 'read status word
                    testb   pa,#5           wz      'get EOP in z
                    shr     pa,#8           wc      'get byte in pa and byte-toggle in c
                    rcl     pb,#1                   'compare new and old byte-toggles
                    test    pb,#%11         wc      'get new-byte flag in c
      if_nc_and_nz  jnct1   #.wait                  'loop until byte and/or EOP or timeout
      if_c_or_z     ret                             'if byte and/or EOP, return
    
                    pop     pa                      'timeout, pop return address
    .error  _ret_   mov     PID,#0                  'error, return 0
    
    
    SendPRE         mov     pb,#18                  'send PRE (assumes full-speed)
    .state          ror     PREStates,#1    wc      'get next J/K state
    .wait           testp   PinB            wz      'wait for FIFO not full on PinB
            if_nz   jmp     #.wait
            if_nc   wypin   #J,PinAB                'send J and acknowledge PinB?
            if_c    wypin   #K,PinAB                'send k and acknowledge PinB?
                    djnz    pb,#.state              'loop until done
                    call    #SetLowSpeed            'switch to low-speed, J and K swap polarity
                    callpb  #4,#IdlePeriods         'wait four periods before low-speed command
            _ret_   rol     PREStates,#18           'restore PRE pattern
    
    SendACK         mov     pb,#2                   'send ACK
                    callpa  #PID_ACK,#SendSOPByteEOP
    
    SendSOF         add     Frame,#1                'Send SOF
                    zerox   Frame,#10
                    setbyte Frame,#PID_SOF,#3
                    callpa  Frame,#SendToken2
    
    SendSETUP       callpa  #PID_SETUP,#SendToken   'send SETUP
    SendOUT         callpa  #PID_OUT,#SendToken     'send OUT
    SendIN          callpa  #PID_IN,#SendToken      'send IN
    
    SendDATA        callpa  #SYNC,#SendByte         'send DATA0/DATA1 according to c
            if_nc   callpa  #PID_DATA0,#SendByte
            if_c    callpa  #PID_DATA1,#SendByte
            _ret_   bmask   CRC,#15                 'init CRC16
    
    EndDATA         not     pa,CRC                  'end DATA0/DATA1 with CRC16 bytes
                    callpb  #2,#SendByteEOP
    
    SendToken       shl     pa,#24                  'get PID into bits 31..24 and clear bits 23..0
                    or      pa,Addr                 'get 7-bit address into bits 6..0
                    ror     pa,#7
                    or      pa,Endp                 'get 4-bit endpoint into bits 10..7
                    rol     pa,#7
    SendToken2      mov     CRC,#$1F                'compute CRC5 of bits 10..0
                    rep     #2,#11                  'feed bits 0..10 into CRC5
                    ror     pa,#1           wc
                    crcbit  CRC,#$05 rev 4          'CRC5 polynomial
                    xor     CRC,#$1F
                    or      pa,CRC                  'install CRC5 into bits 15..11
                    rol     pa,#11+8
                    mov     pb,#4                   'send SYNC + PID + 11 bits + CRC5 + EOP
    
    SendSOPByteEOP  rolbyte pa,#SYNC,#0             'rotate SYNC byte into bytes to send
    
    SendByteEOP     call    #SendByte               'send bytes, followed by automatic EOP
                    shr     pa,#8
                    djnz    pb,#SendByteEOP
    
                    pop     pa                      'pop return address generated by prior CALLPA/CALLPB
    
                    call    #SetTimeout             'wait for SE0 to signal FIFO empty
    .SE0            rdpin   pa,PinA                 'read status word from PinA
                    testb   pa,#2           wz      'get SE0 in z
            if_nz   jnct1   #.SE0                   'loop until SE0 or timeout, then do SetTimeout _ret_
    
    SetTimeout      getct   pa                      'set timeout period
            _ret_   addct1  pa,Timeout
    
    IdlePeriods     callpa  #IDLE,#SendByte         'output IDLE for pb bit periods
            _ret_   djnz    pb,#IdlePeriods
    
    SendByte        testp   PinB            wz      'wait for FIFO not full on PinB
            if_nz   jmp     #SendByte
            _ret_   wypin   pa,PinAB                'write pa byte into FIFO on PinA and acknowledge PinB
    
    ByteDATA        call    #SendByte               'send pa byte and feed it into CRC16 for DATA0/DATA1
    
    ByteCRC         rev     pa                      'feed pa byte into CRC16
                    setq    pa                      '(SETQ/CRCNIB inhibit interrupts)
                    crcnib  CRC,CRC16Poly           'feed bits 0..3 into CRC16
            _ret_   crcnib  CRC,CRC16Poly           'feed bits 4..7 into CRC16
    
    SetFullSpeed    wxpin   FullSpeed,PinA          'set full-speed mode
            _ret_   mov     Timeout,TimeoutFull
    
    SetLowSpeed     wxpin   LowSpeed,PinA           'set low-speed mode
            _ret_   mov     Timeout,TimeoutLow
    
    PREStates       long    %11_1011111011010101    'J/K pattern for PRE+K+K
    CRC16Poly       long    $8005 rev 15            'CRC16 polynomial
    
    CodeEnd
    
    
    PinA            res     1                       'register variables
    PinB            res     1
    PinAB           res     1
    
    LowSpeed        res     1
    FullSpeed       res     1
    
    TimeoutLow      res     1
    TimeoutFull     res     1
    Timeout         res     1
    
    PID             res     1
    DataStart       res     1
    DataBytes       res     1
    CRCpast         res     1
    CRCword         res     1
    
    CRC             res     1
    
    Frame           res     1                       '11 bits
    Addr            res     1                       '7 bits
    Endp            res     1                       '4 bits
    
                    fit     $120                    'make sure below Spin2 interpreter
    
  • roglohrogloh Posts: 5,172
    edited 2023-10-22 22:49

    @cgracey 108 Longs sounds pretty cool Chip :smile: Is the model intended to support a single USB COG that can execute logic while all primitives remain inside it and can be triggered by some (future) ISR? I wasn't sure that SPIN2 supported ISRs but I might not have been keeping up with current events.

    It would be rather nice to be able to control the USB stuff from SPIN2 assuming it is fast enough to do what is needed for a host to function with suitable servicing latency for its downstream devices. I expect that is still to be determined if hubs are used and the polled device count starts increasing.

  • Hi folks, is this the latest thread for USB Host code? Can you please post the git url for the latest code? TIA

Sign In or Register to comment.