How to do unpredictable USB input
Pages: 1 2 3 4 5 6 7 8 9 10 11 12
Dave Higton (1515) 3526 posts |
For some unknown reason, I recently took inspiration to try to write a driver module for a USB serial dongle. I’ve had this going in an application, but I’d like to get the input done by a process that can’t be blocked by a foreground task and thus lose incoming information. The same consideration applies to USB MIDI. I can hang some code on TickerV; but would that be a sensible thing to do? The input code could possibly be held up for milliseconds at least – probably more, in my experience of USB. So should I hang stuff directly on TickerV, or should I get TickerV to set a callback; or what should I do? This is an area of which I have no experience, so I would appreciate all help. There’s another specific question: how do I open streams to and from USB bulk endpoints in assembly language? The BASIC functions are OPENOUT and OPENIN. Do I use OS_Find and specify existing files? It looks sensible for input (though I haven’t tried it yet), but there aren’t OS_Find codes with write-only access (which is what the name OPENOUT would imply); you can have read-only or read/write access. |
Tank (53) 375 posts |
A while ago I started to look at this, and got a USB/serial cable detected and device streams opened in assembler. If you send me your email address to webmaster@tankstage.co uk I’ll send you what I have… If your interested. |
Jeff Doggett (257) 234 posts |
Dave, Fat32fs uses callbacks from TickerV when it is flushing the write behind buffers five seconds after the last write. I’m not saying that it’s the correct thing to do, but…….. |
Dave Higton (1515) 3526 posts |
I had a glance at callbacks yesterday. Have I understood this correctly: they operate at user level, therefore they can be blocked by a user task that blocks? In which case a driver can still lose unexpected input? |
Colin (478) 2433 posts |
OPENIN = OS_Find 0×4X open read only The X nibble determines how errors are reported CLOSE = OS_Find 0×0 use OS_GBPB to read data As OS_CallEvery routines should be short and is entered with interrupts disabled. This probably means that Filing systems don’t work – not quite sure about that maybe someone else can confirm it. I’d probably use OS_AddCallBack in OS_CallEvery – with a flag to stop adding callbacks until the last one has been processed. Alternatively your OS_AddCallBack could call OS_CallAfter If you intend to have a foreground process ultimately you may lose data if you can’t use it quicker than it arrives whatever you do. You could read the usb in the callback and fill a buffer to smooth out when your wimp process gets delayed. Your wimp program could then read it when it gains control. I’d possibly go for a double buffer so you fill one from the callback while the other is being read. Once all the buffer is read by the foreground it is swapped with the buffer the callback is filling. This way you are not filling the same buffer as is being read by the foreground task. If the buffer being read isn’t read before the buffer being filled is full you lose data. Effectively you are just making the usb buffer bigger. |
Dave Higton (1515) 3526 posts |
@Colin: Yes, thanks, I’ve been able to open files to/from the device with OS_Find &4F (input) and OS_Find &CF (output), in that I receive sensible handles and no error from OS_Find, but I haven’t yet had time to try sending or receiving data. Also I’ve verified that OS_Find 0 closes the files, so the calls to that are in the module’s finalisation. (And no, it doesn’t call them if the handles are 0.) There are different considerations for USB serial and USB MIDI. In theory at least, serial input can be halted by handshaking, be it in hardware or software. However, the USB serial dongle isn’t autonomous in that regard, so sending XOFF/XON or altering the state of handshake lines needs to be done in real time by the driver, so it can’t be deferred indefinitely. MIDI cannot be halted, and every input needs to be time-stamped (if used to record) or acted on immediately (if used to generate sounds). So every input has to be handled in real time. Despite the different considerations, it still comes down to handling USB input in real time. Or very near real time – a delay of a few milliseconds would be tolerable – if the difference is worth considering. |
Peter van der Vos (95) 115 posts |
Hi Dave, what I did some time ago was make a big serial input buffer. The normal buffer is only 256 bytes long. On a timer tick I read all bytes from the serial input and placed it in a much bigger buffer (1 MB). Now I could block the desktop (by pressing F12) and when I started it again after a few seconds not a single byte was missing. If you don’t block the desktop the responds will be near real time. |
Colin (478) 2433 posts |
Millisecond accuracy hmmm. I presume MIDI sends data in bytes at keyboard playing speeds and the timing is determined by the 31250 baud rate. This works out to around 4000 bytes per sec and if I remember correctly usb only polls a device every msec so even if you used the same timer as usb you could only read 4 bytes (1 msec) at a time. The ticker event is a csec timer. If you record the time/play from your OS_CallEvery you’d have a csec granularity to your timings instead of 1/4000 sec of the midi data. usb would need to read 40 bytes in that time so you shouldn’t miss anything. You could get a multibyte sequence straddling 2 csec reads. Interesting. |
Dave Higton (1515) 3526 posts |
You don’t need millisecond accuracy or resolution with MIDI. Centisecond is good enough. The other useful feature is that multibyte sequences cannot be interleaved, and even SysEx data can only have the top bit clear, so you know where every sequence begins and ends. |
Peter van der Vos (95) 115 posts |
For MIDI you probably don’t need it but if you really need a higher accuracy then csec you could always use an other timer. |
Rick Murray (539) 13840 posts |
I would round up times of MIDI events to the nearest cs. For the MIDI capabilities that we have, 1/100th sec is plenty good enough! Just read until there’s no more data. It is easier with MIDI on USB as it always arrives in packets of four bytes (though I’m not sure if the RPi byte stuffing is equally well behaved, that might break the idea of groups-of-four). Might be simpler to read “as much as is there” and parse that down to pure MIDI in a separate buffer that the front end can read as it wants. Don’t get too hung up on the possibility of missing data. It was the same with Acorn’s original MIDI module – it buffered incoming data and if the foreground application didn’t read it in time, data would be lost. You just need to design your software to have a reasonably large buffer and your front end to be capable of asking how much data there is available and reading multiple events at one time (ie don’t wimp poll between reads if there is outstanding data). You can’t prevent data loss, but you can try to make it less likely. Of course, if you have the user pressing F12…… |
Tim Rowledge (1742) 170 posts |
If you want to get called every millisecond (or configurable other intervals) try the Millisecond timer module by Rik Griffin at http://squeakysoftware.org/haltimer.html – I’ve been using it for years with considerable success. |
Dave Higton (1515) 3526 posts |
Just briefly checking in again with y’all… Over the last few days, I’ve got a relocatable module and serial blockdriver on their way. Still some way away from any definition of completion, of course. Yesterday I discovered the libftdi sources, and got control of the RTS and DTR outputs. I will probably be able to get two-way communication with a serial terminal on the BeagleBoard if I wire up the handshake lines so as to satisfy the BB. |
Steve Pampling (1551) 8170 posts |
?? |
Dave Higton (1515) 3526 posts |
Steve, you raise an interesting point. I no longer understand why the simple serial terminal programme I run there ignores incoming characters and refuses to send characters. I thought I’d seen it working there before; perhaps not. Edit: I wonder if I have failed to change the driver port number to pick up the BB’s serial port. |
Dave Higton (1515) 3526 posts |
I did indeed fail to change the driver port number. Now at least I have serial coming out of the USB serial dongle. I haven’t looked at the other direction yet. |
Dave Higton (1515) 3526 posts |
Well, several steps forward and one step back. I’ve got a relocatable module and companion serial blockdriver going – not complete, but enough to be useful. I have got receive working without padding or blocking – many thanks to Thomas Milius for that. I am trying to diagnose why my LPC1114 app sometimes malfunctions. I’ve discovered one of the causes. The FTDI chip offers USB input by default 16 milliseconds after the last input transmission or when 64 bytes are available, whichever occurs first. The first two bytes are flags (typically 0×01 0×60, although I’ve not investigated how fixed that is). OK, so I get input; I discard the first 2, and consider any remaining ones to be wanted data. So far, mostly so good. Occasionally I receive two transmissions before I collect the data. Now in my buffer I have 0×01 0×60 0×01 0×60. Of course the third and fourth bytes are considered as data. Rats. I wish, how I wish, there were a way to know the message boundaries. |
Colin (478) 2433 posts |
I have a usb – ttl uart device which uses a silicon labs cp2102. It has a control descriptor, described in the virtual com interface application note, which can read the number of bytes in the uart input buffer. Does your device have this facility? If it did you could read the number of bytes available before fetching. |
Dave Higton (1515) 3526 posts |
@Colin: the FTDI devices appear to have no such facility, sadly. It’s a pity we can’t get an interrupt to occur after each transfer and put the number of bytes transferred into a queue. That would be enough information to sort out the content of each transfer. But the queue would have to be uniquely associated with a device endpoint – there would be no point in queuing all transfers in one queue, that would be chaos. Or maybe we can, but I simply don’t know how? |
Dave Higton (1515) 3526 posts |
Something else to add: my stuff seems to work pretty much the same on the Iyonix and the BBxM, but it stiffs the Raspberry Pi. Curious symptoms: the mouse cursor freezes, the NumLock LED goes on and off in response to the NumLock key, but Alt-Break doesn’t work. I haven’t yet done more investigation to narrow down where it’s failing. Having learned key stuff recently about unpredictable USB input, it would be useful to go back to my PTP application and see if I can effect an improvement in that. |
Colin (478) 2433 posts |
I’ve been looking around for some information on ftdi devices and can find virtually no information on the usb commands available. The only one I found was http://yosemitefoothills.com/Electronics/FTDI_Chip_Commands.html but I don’t think this is complete. If you look at ft_read in the d2xx programmers guide – http://www.ftdichip.com/Support/Documents/ProgramGuides/D2XX_Programmer’s_Guide(FT_000071).pdf – (should have .pdf on the end but it disappears) it says you need to read the rx queue size before calling ft_read as ft_read will block – and gives the appropriate commands. Another snippet of interest I found was http://hackage.haskell.org/packages/archive/ftdi/0.1/doc/html/System-FTDI.html#9 which says ‘The modem status is send as a header for each read access. In the absence of data the FTDI chip will generate the status every 40 ms.’ which would explain your 2 bytes at the beginning of a read. |
Dave Higton (1515) 3526 posts |
@Colin: thanks for your efforts. I am aware that the first two bytes are flags. The problem seems to be that I can’t tell the difference between two reads of two bytes each, and one read of four bytes. What triggers a read from a bulk IN endpoint? If I call OS_GBPB once to read data, then wait for several times 16 milliseconds, then call OS_GBPB again, how many times will RISC OS attempt to read data from the bulk IN endpoint? If it’s a one-call-causes-exactly-one-read relationship, I can cope with it. The read will either return one transfer’s worth of data, or no data at all in which case I set a flag to say the transfer is pending and keep reading EXT#pipe until some data arrive. Those data must again be exactly one transfer’s worth. If it isn’t a one-call-causes-exactly-one-read relationship, I’m sunk. As for reading the Rx queue length: I think that must be the OS’s internal queue. I don’t think there is any way to read the length of the device’s internal queue. |
Colin (478) 2433 posts |
As I understand it any USB device sends nothing to the computer unless specifically asked for – similar to i2c in that respect. A read from bulk input will not return until the number of bytes asked for has been delivered or the endpoint buffer has been filled (a large request is delivered in chunks). As the computer can’t know that there is data available a request for 4 bytes will not return until 4 bytes are available to send. I think what is happening to you is that you request 4 bytes with OS_GBPB and if the input is doing nothing the uart rx buffer fills with the status flags and when 4 bytes are available the request is fulfilled. If it didn’t put these in the buffer just fetching bulk in bytes would block until they were available. As it is it is probably blocking for up to 80ms at a time – the time for 2 status byte reads – this may be causing timing problems on the pi. There must be a way to read the uarts rx buffer. Buffering on your computer doesn’t matter as at some stage the computer has to read data from the uart and has to know that it is available if it is to avoid blocking. |
Dave Higton (1515) 3526 posts |
Interesting. The situation may be a little clouded in the case of non-blocking inputs. Since I have no idea how many bytes are on offer, I have to attempt a read of 64 (the max. packet size). There is no information embedded in the data about how many bytes are in the message. The only thing that can possibly tell me that a transfer is complete, and (most importantly) that the next two bytes will be flags and not data, is the end of the single transfer. Is each transfer asked for by a single call to OS_GBPB? So, if it’s been a while since the last transfer, and I request a 64 byte non-padded non-blocking transfer, will OS_GBPB return with the content of just a single transfer? Probably. It’s worth some experimentation, anyway. Edit: It’s only going to work if I can read bytes from the stream without triggering another read. All I can think of is OS_BGet.
I don’t think there is any such thing as “there must be” for the device itself. There would be no need. A call to see how many bytes are in the OS’s Rx buffer would be another thing entirely. Much easier to achieve – and the driver has already done the required processing to remove the flag bytes. If you read the number from the device, it would in any case only mean “at least this many are available”. |
Colin (478) 2433 posts |
OS_BGet is just OS_GBPB reading 1 byte It appears that the padding is a bodge to stop OS_GBPB blocking. Theory no. 321 :-) What I think is happening is when the buffer is empty and you request a byte the buffer will call a fill routine to fill the buffer. This routine will ask the device for buffersize bytes regardless of how many bytes you ask for. The device returns with say 2 bytes – which is less than requested – the fill routine sets the buffer pointers indicating 2 bytes in it and returns. Now if your OS_GBPB routine asked for 4 bytes the buffer will empty and the fill routine will ask for another bufferfull of bytes. If the device has no more to give OS_GBPB just waits. So the bodge appears to be to fill the buffer to make bytes available for OS_GBPB to return – presumably assuming that this is more than you asked for anyway and you won’t be asking for any more. In finite sized files as opposed to streams you get an EOF condition which makes OS_GBPB return early. The bytes you want will only be at the beginning of the OS_GBPB read block if your program fetches a buffersize of bytes. That way you will get a new fetch on every read – otherwise you just read padding bytes in the buffer. If the padding is 0’s and your data doesn’t include 0 then I suppose you could scan the bufferfull you’ve read until a 0 is found. I can’t see it being fixed without removal of the bodge and non blocking file reads and writes or a non blocking DeviceFS_CallDevice block read Its easier to buy a serial port with a chip where you can read the uart buffer size :-) |
Pages: 1 2 3 4 5 6 7 8 9 10 11 12