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).
@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?
@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.
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
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?
@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).
@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.
@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
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.
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.
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...
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).
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
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.
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.
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
@cgracey 108 Longs sounds pretty cool Chip 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.
Comments
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?
I've been putting this off for 25 years. The USB v1.1 spec I'm reading is from 1998.
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.
I found a few minor errors in the USB smart pin documentation. Here is the update I just made:
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?
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:
Here's how you could output anything as fast possible:
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.
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...
usbnew v1.1.2
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 updatingclkfreq
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)clkfreq
is a global variable that records the last frequency value set by theclkset
function. It is not read-only, but changing it is (as you discovered) a bad idea: it probably should be marked asconst
but it isn't really constant either sinceclkset
will change it. You shouldn't ever have to manually change it (unless you change the frequency directly withhubset
).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 companionclkmode_
. 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: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.
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.
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.
@cgracey 108 Longs sounds pretty cool Chip 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
https://github.com/Wuerfel21/usbnew