Trouble with serial receive code
David Betz
Posts: 14,516
Can someone spot the bug in this simple serial receive function? What I'm wondering about is if I have to wait half a bit time after sampling the stop bit.
' receive a byte ' output: ' rxdata is the byte received rxbyte waitpne rxmask, rxmask ' wait for a start bit mov rxcnt, bitticks ' wait until half way through the bit time shr rxcnt, #1 add rxcnt, bitticks add rxcnt, cnt mov rxbits, #9 ' receive 8 data bits and the stop bit :next waitcnt rxcnt, bitticks ' wait until the center of the next bit test rxmask, ina wc ' sample the rx pin rcr rxdata, #1 ' add to the byte being received djnz rxbits, #:next shr rxdata, #32-9 ' shift the received data to the low bits and rxdata, #$ff ' mask off the stop bit rxbyte_ret ret
Comments
What your code is actually doing is waiting 1.5 clock times after cnt+16. The +16 is the time the instructions execute after the waitpne.
It would be better if you sampled the cnt immediately after the waitpne. Alternately adjust the rxcnt accordingly.
BTW This only applies for high speeds. At lower speeds it is irrelevant.
Also, you can sample and compile the last bit (8th bit) and then check that the stop bit is present at the next bit sample time.
As for the bug, well you are adding rxcnt which is 1.5 bit times each time, rather than 1 bit time (at the :next label), so you sample is sliding into the next bit.
Here is code I wrote for one of the P2 versions. Its unravelled code because it is being run under an LMM interpreter, but the basics the same.
You really need to precalculate the half-bit timing so that you are ready to apply it after detecting the start bit otherwise you will have timing errors at higher speeds.
Here is the high-speed code I use in Tachyon.
There shouldn't be a problem as far as I can see but it's what you do after you receive the data that I'm not sure about. Will it be ready for the next character?
David,
The code looks perfect to me, although I have not tested it. That last sample, number nine, falls in the middle of the stop bit, and that gives you up to half a bit period to deal with the received byte before you need to start searching for the start bit of the following character.
Should work fine up to a Megabit, possibly two. Do some cycle counting to get the exact maximum.
Cheers,
Peter (pjv)
It's easy enough to get these simple loops working for a single character at these much higher baud rates. The trick is to get them to buffer it and be ready for the next. This is where this simple loop might have a problem even at 115.2k but we don't know what it has to do when it does a return.
Usually the Stop bit is sampled Mid bit (tho that can be nudged left, if time to deal with the byte is tight)
Ideally, you start looking for the START bit at the MID Stop bit, as that gives the highest theoretical baud tolerance
However, if you begin testing for Start edge at 0.75 of Stop bit, you still have 0.25 bit time of tolerance, in 10 bits or 2.5% (budget total for both ends)
If you delay till 90% of stop bit, you have 0.1 bit time or 1.0%, so you can see the effect
Here's another simple serial loop that works like the others and may help you -- I tend to write code in the simplest, most obvious fashion that I can. To be honest, unrolling it was inspired by Peter's code.
Peter,
See my code snippets in the "extra stop bit" thread. It dumps to hub at 5 Mega bits per second.... all day long.
I'll try to find a bit of time to link it with a Spin control program in the next while for a demonstration.
Cheers,
Peter (pjv)
This was more of a comment on your comment about how David's code should work fine at megabit speeds. So the point was it all depends on whether it's ready for the next character.
I have 10Mbit code too that transmits and receives continually but like the 5M code it is locked to that rate (and CLKFREQ) so it's not really a general-purpose receive routine.
Start of Receive
It's important to not start the Rx process if the Rx wire isn't continuously high for 10 bit times. Failing to do this can cause the receiver to lock onto the middle of a character and be out of sync with the incoming bit stream for a very long time. This need only be done when the driver is started ...
End of Receive
There's no reason to wait until the middle of the stop bit unless you want to detect and report framing errors. Since none of the OBEX code detects framing errors, the best implementation is to sample the eighth bit and then simply wait for the Rx wire to go high. If the eighth bit was a one, you can leave in the middle of that bit time. If it was a zero, you need to wait only until the leading edge of the stop bit arrives.
Doing this, you buy back either 1/2 bit time or 1 1/2 bit times for the rest of your implementation to handle the received byte and get back to looking for a start bit. Turns out that's enough time to do things like maintain a CRC16 without any added latency, etc.
Transmit
On the transmit side, the implementation sends two stop bits, which decreases throughput by 10%. There's no real rationale for this - the receiver can either keep up or it can't.
pkt.zip
On a system where TxMIT is master, 2 stop bits can buy some more system margin in streaming data cases.
However, 2 Stops bits on a slave transmit seems a very bad idea where incoming master Rx is 1 stop bit.
There is not enough time to reply to every byte, and designs that expect duplex replies would fail.
If the remote unit needs (mostly) 2 stop bits, then send with 2 stop bits, and TX replies will usually pace off the Rx with 1 added stop bit. If the baud rates are not exactly matched, there will be rare 2+1/-1 stop bits to keep the average data rates matched.
You might have some protocol built on top of what's provided, but if your protocol has real-time limitations, you can't implemented it safely on these drivers as you can't really determine what's going on with the wire in a meaningful, reliable way. Your only choice is to modify the driver. If modifying it by adding a 2nd stop bit works for you, then have at it.
But the simple fact remains that the OBEX transmit implementations waste 10% of the available bandwidth at any baud rate because there's an 11 where there ought to be a 10.
The problems arise when the baud rates are not exactly the same, and sustained high throughputs stress the details of the implementation.
As Micros, and PCs get faster, the chances of hitting sustained high throughput goes up.
I think #16 talks about removing stop bits to fix his issues, not adding :
" I got rid of the second stop bit on transmit."
If you are getting picky with the obex then join the queue but then again the fact that we have an obex should be appreciated as a lot of people have contributed their time and resources. Far better to contribute a better object and make it a better obex.
However some serial objects don't seem to include false start bit rejection just as UARTs do, nor do they detect framing errors which requires that half-time sample into the stop bit. I also use framing errors to detect break sequences as every time I have a framing error and data of zero I count down until I accept it as a break sequence and in the case of a lot of my systems this will reset the Prop which is ideal for forcing a reboot remotely. There's also the timing errors at higher speeds and the inability of many objects to cope with full throughput (back-to-back, no extra stop bits) at those higher speeds.
But for the most part what's in the obex is what most people mostly need. Some other features that are useful are things like gap detection (for MODBUS etc), parity, 9-bit multidrop mode, runtime selectable buffers and sizes, RS485 support etc. The fact is that I have written drivers with a lot of these features and have not contributed to the obex which is something I have been intending to correct. It just seems that everybody picks FDS each and everytime, if I type in "full duplex serial" into the obex search there it is at the top, and there are no reviews or star ratings to give the unwary any feedback. Besides, a lot of these drivers are loaded up with conversion utilities which IMO should be common to all stream I/O. In Tachyon I just simply select the stream input/output device whether it be an LCD or VGA or printer or LED matrix etc and all the formatting operations and control sequences (rather than explicit methods) are common so it still works the same if I change the output device.
LCD CLS PRINT" CLOCK FREQUENCY IS " CLKFREQ PRINT CR
LEDS CLS PRINT" CLOCK FREQUENCY IS " CLKFREQ PRINT CR
CON CLS PRINT" CLOCK FREQUENCY IS " CLKFREQ PRINT CR
I think the use of explicit methods such as dec hex str newline etc etc in each and every Spin driver is ugly.
You might want to elaborate, so others reading this thread in the future get information that may help their case.
I've been running some numbers on higher Baud rates, where the Prop talks to something other than another Prop.
One common MCU Internal Osc clock is 24.5MHz, and another is 48MHz. 3~6MBd look practical.
A better fit for 24.5MHz is 3.076923MBd, which gives 0.460% error. ( 26 Prop SysClks per bit )
For those cases where a fractional Cycle is ideal, it may be possible to have a compile-time baud ?
(no runtime param), and use waitcnt,#immedA ..waitcnt,#immedB where immedA, immedB vary by 1 but average to the better fit - sample points should .all be within 1 sysclk of ideal.