Is Hserin really this slow?

BillBWann

Member
I’ve been trying to use Hserin in a project where quite long sequences of characters are being received and it would be difficult to process the characters and not miss any arriving characters - but I’ve been very disappointed with Hserin.

To demonstrate my problem, I’ve set up this test situation. I have an HC-12 connected to pin b.2 of an 18M2 and I’m sending it the letters “ABCDEFGHIJKLMNOP” from another HC-12. The HC-12’s are setup in FU3 mode at 4800 baud.

When I use this program running on the 18M2 at 16MHz, I get the 16 letters printed correctly.

Code:
#picaxe 18m2
#no_data

setfreq m16

do
    bptr=28
    serin [5],b.2,T4800_16,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc

    if bptr>28 then
        b0=bptr-1
        for bptr=28 to b0
            sertxd (@bptr)
        next bptr
    endif
loop

However when I use this program (largely copied from the manual) which utilizes the hardware serial and I run the 18M2 at 32MHz, I largely get garbage.

Code:
#picaxe 18m2
#no_data

setfreq m32

hsersetup B4800_32, %1000 ;  baud 4800 at 32MHz, no hserout

do
    w1 = $FFFF ; set up a non-valid value
    hserin w1 ; receive 1 byte into w1
    if w1 <> $FFFF then ; if a byte was received
        sertxd (w1)
    endif
loop
This is an example of the garbage I receive.
Code:
A¨(TGHI©Š5NOý
The first letter always gets through and you can see some of the later characters have also got through but its different every time. I’ve tried running the 18M2 at slower frequencies but they all seem to perform similarly and only ever reliably receive the first character.

I also tried setting the sending serial port to 2 stop bits so there was a slightly bigger gap between characters but it didn’t seem to make any difference.

If I send the 16 characters from a Picaxe terminal, all the letters get through presumably because that terminal inserts sizable gaps between the sent characters.

I would have expected that Hserin would perform better than Serin and I’d hoped to be able to do a couple of other things between arriving characters.

At a baud rate of 4800, characters arrive about every 2 msecs so with Hserin having a 2 byte buffer and the 18M2 running at 32 MHz, I expected to be able to run a number of lines of code and not miss any characters.

Is Hserin really this hopeless or am I doing something silly?
 

BillBWann

Member
I just realized that the serin code I posted just now has an error in it and the serin statement should have a timeout of 5 msecs which at M16 should have been ...

Code:
serin [20],b.2,T4800_16,@bptrinc,.............
 

Aries

New Member
I'm not sure that sertxd(w1) would show the characters correctly anyway.
Code:
b2 = "A"
b3 = "B"
sertxd(#w1,".")
sertxd(w1,".")
gives
Code:
16961..
- i.e. w1 has "AB" but prints nothing because it is not a printable character.
You could try sertxd(b2,b3) instead, but unless you know you have had exactly 2 characters and the next one hasn't started, you may well get corruption of w1 while you are processing it.
 

westaust55

Moderator
In your Hserin example change to the use of a byte variable instead of a word variable.

Hserin is intended to work with bytes and sertxd is also intended to work with bytes.
 

hippy

Technical Support
Staff member
The main difference in your code is that your SERIN grabs all the characters received then shows what they were after they have all been received.

Your HSERIN loop reports each character as it is received.

I would guess this is causing some characters to be missed or corrupted.

You could optimise your HSERIN code, something like below but you'll want some sort of terminating condition ...
Code:
Do
  b1 = 1
  Do
    HSerIn w0
  Loop Until b1 = 0
  @bPtrInc = b0
Loop
The ideal solution would be to use a PICAXE-X2 and utilise its background HSERIN capability. That way one is never polling and nothing should be missed.
 

AllyCat

Senior Member
Hi,

A PICaxe can't do very much processing in 2 ms, even at a 32 MHz clock frequency. You might find that changing from Word to Byte variables will help a little by reducing the number of interpreter "Tokens" to be executed.

However, in a similar (but more complex) situation with an 08M2, I did find that directly PEEKSFRing the Hardware Serial Buffer was significantly more reliable than the HSERIN command (because the latter appears to be slower). The full thread starts here but you might find posts #4 to #8 are particularly relevant.

Cheers, Alan.
 

BillBWann

Member
Thanks to everyone who offered their thoughts on this. I’ll try to address them all.

Yes, I agree that the use of bytes rather than words seems more logical and I used them in my original program but when that didn’t work, I used the example given in the manual. I assumed that that was the “official” way to do it but I couldn’t see any difference in performance between the two in that neither of them worked unless the characters were sent from a picaxe terminal.

However, the problem doesn’t just seem to be that the program can’t keep up because I’d noted that the program was looping a number of times while waiting for the next character to arrive even when the characters were sent without gaps.

Yes Hippy, I take your point that sertxding the characters takes time but the sertxd rate is very high compared to the Hserin receive rate and I’d also observed that even so, the program was still looping while waiting for the next character. For the record though, I ran your suggested code but it didn’t fare any better.

AllyCat, I’m indebted to you for your reference to your earlier work on this. For me, your approach has saved the day. As an aside, I’ve also been very grateful on many occasions for the work you did on readinternaltemp which I’ve used a lot over the years.

For anyone who wants to practically use Hserin, I certainly recommend you read Alan’s paper from #6. Much of the code below is extracted from Alan's more complicated program. It is the equivalent of my Hserin example in #1 – the difference being that this one works.


Code:
#picaxe 18m2
#no_data
symbol RCREG    = $79    ; Serial receive register
symbol PIR1     = $11    ; RCIF = 32 = Received byte NOT yet read, 16 = TXIF = NO byte in buffer
setfreq m32
hsersetup B4800_32, %1000 ;  baud 4800 at 32MHz, no hserout
do
    do
        peeksfr PIR1,b0                    ; Read the Hardware Serial Status byte
    loop while bit5 <> 1                    ; There is an Unread byte in the receive buffer
    peeksfr RCREG,b0                ; Read it
    sertxd (b0)
loop
Alan’s method allowed me to implement what I had originally set out to do with Hserin and that was to use an 18M2 to receive a file from a utility such as Tera Term and save it directly to a 24LC512 EEPROM. The following code does that. It waits indefinitely to receive the first byte and then continues to receive and save all subsequent bytes until the transmission stops. It then sertxd out from the 24LC512 all the characters that its received. The last few lines of code would be best remmed out if you were sending a binary file with lots of unprintable characters.

Code:
#picaxe 18m2
#no_data

symbol EepromStart=0    ; Start address for writing to 24LC512 EEPROM
symbol Tmeout=8        ; Maximum waiting period for next character (TeraTerm>=8, Picaxe terminal>=120)

symbol RCREG     = $79    ; Serial receive register
symbol PIR1     = $11    ; RCIF = 32 = Received byte NOT yet read, 16 = TXIF = NO byte in buffer
symbol lpcntr=b1        ; No Hserin data loop counter
symbol addr=w1            ; External EEPROM address
symbol endaddr=w2        ; External EEPROM end address
symbol tempbptr=w2    ; Temporary value of bptr
symbol flag=b6            ; Flag for start of data flow

Hi2CSETUP i2cmaster,%10100000,i2cslow,i2cword    'To communicate with on-board 65k byte EEPROM
pause 100

setfreq m32

hsersetup B4800_32, %1000 ;  baud 4800 at 32MHz, no hserout

addr=EepromStart    ; Set 24LC512 eeprom start address

Start:
flag=0    ; Wait indefinitely while flag=0 for first byte of data to be received

do
    bptr=28    ; Set above picaxe variables
    do            ; receive up to 8 bytes of data or timeout
        lpcntr=0    ; Initialize timeout loop counter
        do
            peeksfr PIR1,b0
            if flag>0 then
                inc lpcntr
            endif
        loop while bit5 <> 1 and lpcntr<Tmeout                    ; Wait a while for another byte of data
    
        if lpcntr<Tmeout then    ; if hasn't timed out then must be a new character
            peeksfr RCREG,@bptr                ; Read it
            bptr=bptr+1        ; note that bptrinc incremented by 2 for some reason so not used
            flag=1    ; timeout will apply from now on
        endif
    loop while bptr<36 and lpcntr<Tmeout    ; collect up to 8 characters
    tempbptr=bptr-1    ; remember how many bytes in buffer in case its less than 8
    bptr=28
    If lpcntr<Tmeout then    ; presumably there are 8 characters as didn't time out
        Hi2cout addr,(@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc)    'Save 8 bytes to EEPROM
        addr=addr+8
    else
        for bptr=28 to tempbptr    ; save the bytes that did arrive
            Hi2cout addr,(@bptr)
            addr=addr+1
            pause 100    ; required here as writes are done in quick succession
        next bptr
    endif

loop while lpcntr<Tmeout    ; continue to receive characters until there is a gap in transmission
endaddr=addr-1
pause 100

for addr=EepromStart to endaddr    ; This sertxd's all the received characters (rem out if non-ascii file being sent)
    Hi2cin addr,(b0)
    sertxd (b0)
next addr
sertxd (cr,lf,"End EEPROM address=",#endaddr,cr,lf)

Goto start    ; This could be remmed out if don't want to store two or more contiguous files in 24LC512
An interesting point that came out from this for me was just how big a gap that the picaxe terminal puts between characters. This program has a settable constant (Tmeout) for the maximum number of times around the loop without terminating while waiting for the next character. When Tera Term is used to send the file, that constant can be set down at 8 but for the picaxe terminal sending multiple characters, it needs to be 120 or more.

Anyway, again thanks to everyone.
 

BillBWann

Member
I just realized that there is a potential problem with the code included in the previous post. I modified the code to allow a variable starting address in the 24LC512 just before I posted and I now realize that because of paging limitations when writing to the EEPROM, this starting address would need to be a multiple of 8. Similarly, if you were sending more than 1 file at a time and expecting each one to be appended to the previous file, then each file would have to have a length which was a multiple of 8.

I hope this makes sense.
 

inglewoodpete

Senior Member
One of the problems with logging (with SerTxd) data received via hSerIn is that some of the benefits of using the hardware UART in the PIC/PICAXE get overridden by using the SerTxd command. The reason is that SerTxd's output is bit-banged, requiring critical timing for the serial bits to be generated. To enable the strict timing for bit-banging, internal interrupts used for managing the hardware UART have to be disabled. This creates the very real situation where hardware serial data reception events are ignored and incoming characters get dropped.

I learned the hard way and now use background serial data reception exclusively. Logging should be treated as a lower priority and can only be done when it is safe to do so. I have found that background hSerIn works reliably at up to 76,800 baud.
 

AllyCat

Senior Member
Hi,

Alternatively, in place of SERTXD you might output single characters using HSEROUT (or even POKESFR to the buffer), which AFAIK does not disable the internal interrupts. Personally, I like the "challenge" of using the 08M2 (in particular), which is rather a different beast to any of the X2 family. Usefully, the HSEROUT on 08 and 14M2 is the same pin as for the SERTXD (SerOut) pin.
An interesting point that came out from this for me was just how big a gap that the picaxe terminal puts between characters.
Yes, IIRC the "nominal" default gap was quoted as 5 ms, but at least with PE5 on my ancient Dell it was nearer to 20 ms. That seems quite consistent with up to 120 passes of your test loop running at m32.
Much of the code below is extracted from Alan's more complicated program. It is the equivalent of my Hserin example in #1 – the difference being that this one works.

As an aside, I’ve also been very grateful on many occasions for the work you did on readinternaltemp which I’ve used a lot over the years.
It's interesting that you also found that PEEKSFRing the flag and the buffer registers appears faster than the resident HSERIN command. A clue might be as Technical said in post #8 of my linked thread:
"One of the limitations of the M2 command is that there is no 'error reporting' and so the firmware simply tries to silently reset itself if, for instance, the buffer overruns and generates an error flag. As the only way to correct a buffer overrun in the PIC is to toggle the UART enable bit, and if this enable toggle then also occurs 'mid next byte' you could also get a corrupt byte received,...".
Since we each appear to have found that the SFR method may need to "wait" for the next character to be received, perhaps there's time to also test the 'overrun' flag?

However, I'm reluctant to suggest a "bug" in HSERIN because of my experience with READINTERNALTEMP (as you happened to mention it) which IMHO proved rather a "disappointment". It did have a bug that I reported and which was duly "patched" in PE6. Sadly, that raised two issues: firstly it's now necessary to write two different versions of code, depending on which Editor is to be used, and secondly the patch uses a surprisingly large amount of Program memory. So I generally "gave up" on READINTERNALTEMP and created a new function/algorithm CHIPTEMP (post #3) which can be used entirely with SFR commands, thus is fully compatible with PE5 and PE6.

Cheers, Alan.
 

hippy

Technical Support
Staff member
Yes, IIRC the "nominal" default gap was quoted as 5 ms, but at least with PE5 on my ancient Dell it was nearer to 20 ms.
I think what happens on older Windows versions, when one asks to be woken up in 5ms, Windows put the program to sleep for a minimum 'clock tick' and that used to be something like 18ms.

These days it's not entirely clear how long a 'clock tick' is and it does seem to vary depending on OS version and configuration.

Since we each appear to have found that the SFR method may need to "wait" for the next character to be received, perhaps there's time to also test the 'overrun' flag?
Possibly, but you are starting to move to doing all the checking the firmware does for HSERIN into the PICAXE program which will be slower so it may be introducing the same issues, and in some cases perhaps making things worse.

I suspect the reason it works is an overrun situation; PEEKSFR simply ignores that and carries on regardless where the firmware would try to rectify things as Technical indicates.
 
Last edited:

AllyCat

Senior Member
Hi,
I suspect the reason it works is an overrun situation; PEEKSFR simply ignores that and carries on regardless ...
Not quite, because IF the OverRun flag gets set, then the UART hardware locks up and no further characters are received until it is released (as described by Technical). I don't recall all the details now, but I believe that using HSERIN was the easiest way to release the lockup, although presumably it could be done with a few POKESFR commands.

IF the OverRun flag does become set, then at least one byte will presumably have been "lost", so I think we all agree that that needs to be avoided. However, it appears that directly reading the serial buffer is a "better" method of doing that than HSERIN. ;)

I've just looked at a "base" PIC data sheet and see that the OverRun flag is in a different SFR to the "Character Received" flag,, so the additional check(s) would indeed add a significant delay. It will depend on each specific users' application: (1) Whether an OverRun error will ever occur and if so, (2) Whether (and when) the error needs to be detected and (3) What strategy is available to recover from that error.

Cheers, Alan.
 

BillBWann

Member
Inglewoodpete, your experience with Hserin differs from mine and I suspect that the Hserin problem is caused by something else than sertxd disabling internal interrupts. I think this firstly because the demo program that I included in #7 doesn’t suffer from that problem and it includes a sertxd statement. To test it out, I used Tera Term to send a picaxe bas file and then copied what was received in the terminal window into a new program window. Doing a successful syntax check on it confirmed that it had been received error free. And I did this a few times and it always worked flawlessly.

The other reason I believe that Hserin can’t be fixed by removing sertxd’s is the program below. I wrote it using Hippy’s suggestion in #5 and it doesn’t include a sertxd until all the characters have arrived. Unfortunately, it still only reliably receives the first character sent unless the characters are sent slowly via the picaxe terminal.

Code:
#picaxe 18m2
#no_data
symbol RCREG     = $79    ; Serial receive register
symbol PIR1     = $11    ; RCIF = 32 = Received byte NOT yet read, 16 = TXIF = NO byte in buffer

setfreq m32
hsersetup B4800_32, %1000 ;  baud 4800 at 32MHz, no hserout
bptr=28
Do
  b1 = 1
  Do
    HSerIn w0
  Loop Until b1 = 0
  @bPtrInc = b0
Loop while bptr<36
bptr=28
sertxd (@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,cr,lf)
Actually, I usually had to send it more than 8 characters to get it to display the received characters because it invariably misses at least one character completely.

As regards the checking of error flags, I think it’s much more practical to ignore them completely. Firstly, that because I’ve found Alan’s method to be incredibly reliable and I’ve sent many 10 of kilobytes and never received a single error. The other reason is that, as Alan says, what can you do if you detect it? You certainly can’t correct it and if the transfer continues you’ll end up with a file which might superficially look OK but will contain errors which will be hard to find. With no error checking, it will be very obvious that something has gone wrong and where it occurred because the hardware will have locked up at that point - but so far that has never happened to me. If confirmation of an error free transmission is required, then I’d suggest using a checksum.

My file transfers have used a pair of HC-12s. If it was a noisier link, then some error checking might well be required but you’d need to have two way communications so that the sending program could resend the missed data and things would then get very complicated – probably better to simply get a more reliable link.

Finally, this is a bit OT but did anyone notice my comment in one of the lines of code in the second program of #7?

Code:
            peeksfr RCREG,@bptr                ; Read it
            bptr=bptr+1        ; note that bptrinc incremented by 2 for some reason so not used
Why is that occurring?
 

hippy

Technical Support
Staff member
There is something odd going on if HSERIN is missing characters at 4800 baud when run at 32MHz.

At 4800 baud a byte will be received every 2ms or so and the HSERIN loop will almost certainly have executed within that time at 32MHz.

Even a SERTXD of a byte at 38400 baud at 32MHz ( 4800 at 4MHz ) should take less than 300us.

There is PICAXE execution overhead in all the commands but, given the double-buffering of serial in, nothing should be missed until a check on HSERIN became more than 4ms apart, and it is hard to see how that could be happening.

I really cannot see how the code in Post #13 wouldn't work, that accumulating the 8 bytes sent doesn't work, how bytes would be missed.
 

hippy

Technical Support
Staff member
If a PICAXE is sending I would recommend testing with just one character sent with a one second gap between them and see how that goes. Perhaps with a modified SERTXD -

SerTxd("Got",TAB, #bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,TAB,#b0,TAB,b0,CR,LF)

Then start to increase the number of bytes sent, see what happens.
 

hippy

Technical Support
Staff member
Finally, this is a bit OT but did anyone notice my comment in one of the lines of code in the second program of #7?
Code:
peeksfr RCREG,@bptr                ; Read it
bptr=bptr+1        ; note that bptrinc incremented by 2 for some reason so not used
Why is that occurring?
The only thing I can think is that with "PeekSfr <adr>, @bPtrInc", the firmware is seeing the destination as @bPtrInc, is applying an increment, is reading the SFR, storing it where bPtr now points to and, seeing it is @bPtrInc, is applying another increment.

I don't have an 18M2 to hand but you could try this ...
Code:
#Picaxe 18M2
#Terminal 4800
#No_Data

Symbol ADRESL = $3B ; $09B

Do
  SerTxd( CR, LF, "=====================", CR, LF )
  For bPtr = 0 To 10
    @bPtr = 0
  Next
  For bPtr = 0 To 10
    SerTxd( #@bPtr, " " )
  Next
  SerTxd( CR, LF )
  bPtr = 0
  For b27 = 1 To 5
    PokeSfr ADRESL, b27
    PeekSfr ADRESL, @bPtrInc
  Next
  For bPtr = 0 To 10
    SerTxd( #@bPtr, " " )
  Next
  Pause 1000
Loop
 

inglewoodpete

Senior Member
Inglewoodpete, your experience with Hserin differs from mine and I suspect that the Hserin problem is caused by something else than sertxd disabling internal interrupts. I think this firstly because the demo program that I included in #7 doesn’t suffer from that problem and it includes a sertxd statement. To test it out, I used Tera Term to send a picaxe bas file and then copied what was received in the terminal window into a new program window. Doing a successful syntax check on it confirmed that it had been received error free. And I did this a few times and it always worked flawlessly.
I suspect that we are talking about different subjects. I have never used the foreground hSerIn because of the advantages I have found using background serial receive into the scratchpad (at up to 76800 baud). Also I rarely use asynch serial on M2 chips, preferring X2s due to their scratchpad feature.
 

AllyCat

Senior Member
Hi,

I didn't test HSERIN as comprehensively as Bill has, but I do recall from my testing over 4 years ago that the difference between the HSERIN and PEEKSFR RCREG methods (in my case at 4800 baud, 16 MHz) was like chalk and cheese, with the behaviour of HSERIN (in M2 devices) appearing rather "strange" and inexplicable.

Recently I've been re-reading the EUSART section in the Microchip data sheet(s), particularly concerning the Framing and OverRun flags, and made some interesting observations. The Framing bit is a (non fatal) error flag attached to each received byte, indicating that the Stop bit was not at the correct level (in some ways similar to a parity bit), but I think it's worth quoting the OverRun description almost in full (with my emphasis):

"The receive FIFO buffer can hold two characters. An overrun error will be generated if a third character, in its entirety, is received before the FIFO is accessed. When this happens the OERR bit of the RCSTA register is set. The characters already in the FIFO buffer can be read but no additional characters will be received until the error is cleared. The error must be cleared by either clearing the CREN bit of the RCSTA register or by setting the EUSART by clearing the SPEN bit of the RCSTA register" {and re-enabling it].

So we actually have almost three character periods after the start bit (or two after the character-received flag is set) to read a byte from the buffer, before an OverRun occurs. Of course on average we must still read the bytes at the rate they are transmitted, but it gives some margin for interrupt latency, etc. The "critical path" in the program for receiving characters is typicaly: (1) Check the character-received flag is set, (2) Read the byte, (3) Store the byte (somewhere), (4) Advance the buffer pointer (if not automatic) and (5) Check / correct for a buffer wraparound if using a circular buffer (optional), (6) Juimp back to (1). If well-coded, this seems quite plausible at 4800 baud, 16 MHz, particularly as a circular buffer won't wrap on consecutive bytes.

But if we are receiving bytes then the OverRun flag cannot be set, so it is not necessary to read it and add to the critical path ! However, if there is no byte in the buffer then we normally have to "kill time" (for one to arrive), so we can easily read the OverRun flag which takes only two or three instructions (depending whether using a bit-addressable variable). What to do if the flag is set is another matter: Time is no longer critical (because at least one byte is almost certainly lost) and in the case of a file transfer, for example, we probably just abort and start again.. For other situations we might just log (or count) the error and/or report it via SERTXD. Or we might try to "recover" operation as cleanly as possible; clearing CREN might be less disruptive than re-enabling or issuing an HSERIN.:

However, if the characters / bytes are totally concatonated (continuous) then we have an almost random bit sequence, so it's not possible to assure immediate re-synchronisation. We could at least read the Framing error flags, rejecting those bytes and perhaps any added Parity bits (as we are in the SFR domain), but there is always the possibility that a "lucky sequence" (or is it unlucky) of random bits will appear as a valid ASCII character byte.

Finally, if one is using an interrupt-driven structure (as in my original thread linked at #6), it would be wise to read the OverRun flag immediately before returning from the interrupt, and probably also poll it in the "main" program to prevent the risk of a total lock-up (or lock-out). Perhaps I'll revisit my original code sometime soon, but for me one of the greatest "inconveniences" of HSERIN is that the Hardware can be used only with idle-high signals (so not directly with the PE / terminal).

Cheers, Alan. .
...
 

BillBWann

Member
Hippy, I ran your program from #16 and got ....

Code:
=====================
0 0 0 0 0 0 0 0 0 0 0
0 1 0 2 0 3 0 4 0 5 0
Which seems to confirm that it double increments. Does this mean that this firmware glitch would apply to all similar statement and for all chips?

I also added your sertxd statement to detail the bytes that Hserin did produce. The output is different every time and the only obvious consistency is that the first byte is correct. Below are 3 responses when the sending string was “ABCDEFGH”.

Code:
Got  01000001    65    A
Got  01000010    66    B
Got  00101000    40    (
Got  01010100    84    T
Got  10010100    148    ”
Got  11010100    212    Ô
Got  00010100    20     
Got  11111010    250    ú

Got  01000001    65    A
Got  01000010    66    B
Got  01000011    67    C
Got  01000100    68    D
Got  01010001    81    Q
Got  11010100    212    Ô
Got  00010100    20     
Got  11111010    250    ú

Got  01000001    65    A
Got  10101000    168    ¨
Got  00101000    40    (
Got  01010100    84    T
Got  10010100    148    ”
Got  11010100    212    Ô
Got  00010100    20     
Got  11111010    250    ú
As you can see, the garbage characters received aren't totally random but I'm not sure that I can draw any useful conclusion from that.

I’m away from home at the moment so accurately measuring the gap between characters sent from a picaxe to the 18M2 would be difficult. Mind you, I think knowing the actual send rate that Hserin will accept is becoming pretty academic. We know that it’s significantly slower than Alan’s SFR method and I really can’t see any situation where I’d use Hserin rather than Alan's. Certainly, Hserin doesn’t lock up but I’d prefer to have control over what happens when an overrun or a framing error occurs rather than simply ignoring it and pressing on regardless as Hserin appears to do.

Thanks Alan for that further info. That’s interesting that you may have nearly 3 character periods when the program could be elsewhere and still not cause an overrun. I’m glad that this exercise to transfer data to the 24LC512 when I didn't have an X2 available, has made me take a detailed look at Hserin and consequentially at your findings because it seems that the hardware serial on the M2 chips opens up a number of possibilities that I would previously have assumed required an X2.

I look forward to reading about whatever you discover when you revisit your interrupt driven application of Hserin.
 

hippy

Technical Support
Staff member
Hippy, I ran your program from #16 and got .... Which seems to confirm that it double increments. Does this mean that this firmware glitch would apply to all similar statement and for all chips?
Possibly but perhaps not. Firmware differs between commands, versions, chips and families so, that it's an issue in one case, doesn't necessarily mean it will be an issue in others.

Apart from checking the revision history for the particular chip the only way to tell if there is an issue is to try it.

But, as "PeekSfr <adr>,@bPtr : bPtr=bPtr+1" is a substitute for "PeekSfr <adr>,@bPtrInc" it's annoying but not the end of the world.

I think knowing the actual send rate that Hserin will accept is becoming pretty academic. We know that it’s significantly slower than Alan’s SFR method and I really can’t see any situation where I’d use Hserin rather than Alan's.
My guess would be, if blaming HSERIN, that it receives a byte, reads it, resets the on-chip hardware status, which is causing the hardware to lose track of the next byte which is already being transmitted.

Just peeking the received register doesn't reset the hardware so the next byte can then be received unimpeded.

So, assuming the back-to-back nature of data is simply pushing HSERIN too hard, this should work for handling the data using PEEKSFR, based on Alan's code -
Code:
#Picaxe 18M2
#Terminal 38400
#No_Data

Symbol RCREG       = $79  ; $199

Symbol PIR1        = $11  ; $011

Symbol TMR1IF_BIT  = bit0
Symbol TMR2IF_BIT  = bit1
Symbol CCP1IF_BIT  = bit2
Symbol SSPIF_BIT   = bit3
Symbol TXIF_BIT    = bit4
Symbol RCIF_BIT    = bit5
Symbol ADIF_BIT    = bit6
Symbol TMR1GIF_BIT = bit7

SetFreq M32
HSerSetup B4800_32, %1000
Do
  For bPtr = 20 To 27
    Do
      PeekSfr PIR1, b0
    Loop Until RCIF_BIT = 1
    PeekSfr RCREG, @bPtr
  Next
  For bPtr = 20 To 27
    b0 = @bPtr
    SerTxd( "Got", TAB, #bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0, TAB, #b0, TAB, b0, CR, LF )
  Next
  SerTxd( CR, LF )
Loop
That should all work as desired. It should loop fast enough that it doesn't miss anything, and there should be no corruption, no framing errors, no overruns.

The worst case would be that more data was received while the SERTXD's were executing to show what had been received.
 

BillBWann

Member
My guess would be, if blaming HSERIN, that it receives a byte, reads it, resets the on-chip hardware status, which is causing the hardware to lose track of the next byte which is already being transmitted.
Yes, that sounds like a very good explanation on why Hserin is failing. On that basis, I think its use should be avoided and Alan’s used in preference unless you’re expecting to receive individual bytes that are widely separated.

So, assuming the back-to-back nature of data is simply pushing HSERIN too hard, this should work for handling the data using PEEKSFR, based on Alan's code -
Yes, Hippy, that will work and is essentially what I did back in #7 for my EEPROM transfer program although that program is complicated slightly by including provision for a timeout after the first byte has been received. I’ve used that program to transfer many tens of kilobytes of back-to-back bytes of data @ 4800 baud and never had a lockup.

I would think that if you were simply SERTXDing the data to the terminal, then it would be more efficient to do it immediately as I did in my first example in #7 rather than bunch them all up. I only bunched them up for my EEPROM transfer program because that allows more time between consecutive Hi2couts for the 24LC512 EEPROM to accept the data.
 

AllyCat

Senior Member
Hi,

Well finally, I've rebuilt my "08M2 HSERIN Hardware Inverter" (needed because Microchip "forgot" to include an Idle-Low input capability in that base PIC family), so I can now look again at the operation and actual execution times of a PEEKSFR alternative to the HSERIN command. My long-term aim is for an "08M2, Fast, Interrupt-driven, RS232 to I2C Converter", which I plan to document in my own thread(s) in due course. But this thread is a useful "bridge" to discuss other M2 chips and fast operation strategies, etc..

My approach has employed a "First-In First-Out memory" (FIFO), implemented by the PICaxe software as a Circular Buffer with separate Input (interrupt driven) and Output (to the main program) pointers. There is no interrupt flag support in any M2s, and to keep the dedicated I2C hardware pins available on the 08M2, I have chosen to interrupt on pin c.3. This is externally linked to the HSERIN pin c.5 (alternate pin function) which does not directly accept interrupts. Furthermore, it appears that none of the M2 chips accepts an interrupt directly on its HSERIN pin, because the default port for interrupts is Port C, whilst all the HSERIN hardware is on Port B.

In my original thread linked from post #6 above, I joked that the SFR version "almost" worked at 9600 baud with a 16 MHz clock (which was rather optimistic for that code). But by "pulling out all the stops" this speed does appear to be possible, i.e. 2400 baud with a 4 MHz clock, up to 19200 baud at 32 MHz. ;)

Normally I consider that "any code which runs on an 08M2 can easily run on any (M2) PICaxe", but it does appear that the 08M2 executes some instructions slightly faster than the 20M2, which I used for all my original timing measurements. There are probably two reasons; the 14/20M2 PICaxe firmware has to "re-map" its two (B and C) ports from the three internal (A, B and C) ports of the base PIC whilst the 08M2 base PIC has only a "Port A", which is renamed as Port B and/or C for the PICaxe. Secondly, the 08M2 has smaller memory areas to address than other M2s, so some of the code "tokens" may be smaller/faster. I believe the 18M2 doesn't re-map any pins, so its speed may be "in between", but in any case the differences are not large (< 10%) and smaller than any safety margin that I would try to include in a final software design.

The main key to increasing the speed is to use the @bptr{inc} pointer to read the bytes directly from the input SFR into the RAM buffer. Normally I wouldn't use a dedicated register within an interrupt routine, because it precludes its general use in the main program. But here, the benefit is so great that the content of bptr could be saved and restored at the interrupt entry and exit points, if really needed (but better avoided).

Thus the whole HSERIN loop can be written in just four lines of code: The first line reads the status register (into a directly bit-addressable variable) and the second jumps OUT of the loop if there is no byte to read. This allows a faster "fall through" into the line which reads the SFR data directly into the RAM @bptr (with an optional increment). The final line jumps back to the start of the loop, unless the RAM pointer of the circular buffer needs to be reset (via the faster fall-through). There is no need to check the OverRun flag if characters are currently being received, nor for a dedicated counter of the input bytes (even if required) because it can be calculated from the bptr position (and the number of wraps).

So far so good, but now for the "Double-Incrementing Bug" associated with @bptrinc , which Bill reported above. This turns out to be a proverbial "can of worms", rather similar to that in my original thread.
But, as "PeekSfr <adr>,@bPtr : bPtr=bPtr+1" is a substitute for "PeekSfr <adr>,@bPtrInc" it's annoying but not the end of the world.
Partly true, but the "embedded INC" adds literally a zero time overhead, whilst the separate INC (which the PE must convert to bptr = bptr + constant) adds another line to the loop and increases the execution time by almost 20%. That's probably enough to jeopardise the 9600 baud/16 MHz target, so I would prefer to "work around" the Bug:

If the circular buffer is small, then we could just ignore the skipped memory locations, or use them for indirectly-addressed variables, or if another buffer is required then it could be interleaved with our @bptrinc buffer. But my aim is for just one, large buffer, so....

With an emulated circular buffer, we already have code to wrap the pointer back to the physical "start", so that could be modified to "fill in the gaps between" (or interleave) the incoming bytes with those already present. Generally, it's good practice to terminate a loop with a "=>" rather than just "=" (to reduce the risk of long hangs), so having to wrap back from two different locations need have no impact on the basic loop execution time. However, it's also necessary to alternate between two different start addresses, which can be achieved by XORing the current bptr with a "wrapback" constant. That takes only slightly longer than directly loading a start address again, and needs to execute only once for each rollover/wrap.
Code:
interrupt:
    progptr = bptr : bptr = intptr      ; Try to avoid the need for these if possible
getinp:
    peeksfr PIR1,b0                ; Read the serial buffer status (RCIF = bit5)
    if bit5 <> 1 then nochar              ; Jump out if no byte available in the receive buffer
    peeksfr RCREG,@bptrINC              ; Read the byte  ** Double-Increment BUG **
    if bptr <= BUFEND then getinp    ; Loop back unless buffer needs to wrap
    bptr = bptr xor BUFWRAP          ; Back to start of alternate rank
    inc wrapcount                    ; For total character count
    goto getinp          ; Go back for the next character
nochar:      ; Check/clear the OverRun flag, jump back to getinp or RETURN as appropriate
Unfortunately there is another aspect of the Bug: the @bptrinc doesn't write to the "expected" pointer address but inbetween the "before" and "after" addresses. That's annoying because normally, the circular buffer can be detected as "empty" (i.e. no data is available to read) if the input and output pointers are equal, but not in this case. It can't be fixed at source (i.e. in the interrupt routine), because that would defeat the object of minimising the execution time, so it has to be patched when the data is read back. Not too difficult (using both a pre-increment and a post-increment to the read), but it also means that the declared Buffer Start and End addresses are skewed relative to the written range. :(

BTW, there's a "gotcha" if extending the end of the buffer completely to the boundary of the RAM: The bptr is exactly the same size as the RAM (i.e. 7 bits for an 08M2), so an INC at the last byte overflows back to zero and a post-increment test "IF =>" will fail to detect the wraparound. One solution is to DECrement the pointer throughout the buffer, but this adds yet one more potential "complication" to the coding.

So far, the above code ideas have been tested and appear to work well with long strings of continuous characters and a buffer wrap, etc., but my patched test code does occasionally crash. That's probably due to a RETURN being executed just when another character is arriving (and at present I have made no provision to reset an OverRun). PICaxe's RETURN is notoriously slow, so it's vital to check if any more characters have arrived, immediately before leaving the interrupt routine. But the RCIF flag will be set only after a complete character has been received, so my inclination is to check for any (bit) edges directly on the interrupt pin (i.e. in the same way as the interrupt is initially triggered). This can be detected using the "Interrupt on Change" status register/flag, or also on the "Timer1 Gate" hardware in the 08M2.

Sadly, the (polled) interrupt latency of the PICaxe interpreter is largely "unknown", but obviously the "blocking" commands (such as SERIN/OUT, PULSIN/OUT, etc.) must be avoided in the main program. But the other "slow" instructions such as SWAP are constructed from multiple commands by the PE, so at least the interrupt should be polled several times during their execution. However, another risk is that the (longer) buffer-wrap path will need to be executed immediately after receiving only one byte into the interrupt routine and perhaps cause an OverRun on the subsequent byte. A possible solution is to temporarily "shorten" the buffer (i.e. wrap the pointer back immediately) by detecting the imminent situation immediately before RETURNing from the previous interrupt. But that really is beginning to open a can of worms...... ;)

To conclude, just a few "numbers" to indicate the expected timing margins: A single ASCII character (10 bit-periods) of 2400_4 (or equivalent) takes nominally 4,166 PIC instruction cycles (or 3,750 allowing for a 10% safety margin). The normal "getinp" receiving loop uses around 3,400 cycles and the (infrequent) buffer-wrapping loop about 4,900 cycles. A CALL + RETURN (perhaps comparable to the minimum interrupt entry and exit overhead) is around 3,300 cycles, but there is potentially a margin of up to 12,000 instruction cycles between,detecting any activity on the serial input pin and an OverRun error being generated.

Cheers, Alan.
 

tmfkam

Senior Member
Wow! Thanks.

I don't need any serial input at the moment, but I know where to look when I do.

Thanks again, very much appreciated.
 

amdunn

New Member
Just found this thread because I've been having similar problems with hardware serial receive on a 14M2.

What I've discovered from looking at the bit patters of the erroneously-received characters is that they are starting late. Looks like the hardware is missing the start bit, and synchronizing on a later 0 in the transmitted byte.

My protocol involves pairs of characters, transmitted one after the other, from a PC to the 14M2. I'm transmitting very slowly, at 300 bps.

The first character is always received correctly. The second character is sometimes received correctly, but when it is not, it's ALWAYS a shift-along of the bits.

For instance, transmitting 43 8A as the character pair. The 43 is always received correctly.

However, sometimes the 8A is received as either an F1 or an FC. Those are two of the three possibilities that could result if the lead end of the second byte is missed.

8A LSB first = 0 01010001 1 (including start and stop bits)
F1 LSB first = 0 10001111 1
FC LSB first = 0 00111111 1

Note that F1 will result if you miss the start bit and first two bits of the 8A (0 01) and then you have 0 10001 1 and an idling high line 111
Similar for FC if you miss the start bit and first four bits of the 8A (0 0101) and then you have 0 001 1 and an idling high line 11111
I've never seen a C5 which is theoretically the third possibility.

If I insert a long gap between transmitted bytes (at least 150 msec) it works flawlessly. Anything less than that, and start to get at least 10% error rate on the second received character.

I've tried this at 4MHz and 16MHz frequency and there's no difference in results.

Anyone have any idea why the start bit detection/synchronization is so bad with the hardware serial receive and if there's any way around it?

I've looked at all the PEEKSFR examples but they mostly involve a tight poll loop. The goal here is to respond to pairs of bytes arriving at random times.

Could that approach (PEEKSFR) still be done, on a 14M2, in a manner analogous to using HSERIN ?
 
Last edited:

amdunn

New Member
Update: changed my code from an HSERIN to this:

peeksfr PIR1,b0
if bit5 <> 1 then noseree
peeksfr RCREG,b6
gosub gotser
noseree:


and it seems to receive flawlessly with no inter-character time required.
 

amdunn

New Member
Although now of course it is possible to overrun and lock up the serial receive if I send too many characters while the code is busy doing something else.

I guess what I need is a good way to do a reset when I know I've reached the end of a protocol sequence and am not expecting any more received characters for a while. Or can I detect the overrun and do a reset then?
 

amdunn

New Member
Doing the following seems to handle overruns:

; if nothing received, check for FIFO overrun
peeksfr RCSTA,b0 ; look at OERR bit
if bit1 <> 1 then fifook
peeksfr RCREG,b6 ; read the FIFO twice, then...
peeksfr RCREG,b6
hsersetup B300_4, 0 ; redo hsersetup to clear the error
fifook:
 

AllyCat

Senior Member
Hi,

By coincidence, I had been finalising an update to this thread because I have now devised code to reliably receive at 2400 baud with a 4 MHz clock (i.e. up to 19,200 baud at 32 MHz) and the possibility of 4800_4 (i.e. 38,400_32). So my first question has to be why you would want/need to use HSERIN at 300 baud? I thought that might be too slow for the standard SERIN command, but 300_4 is available. Then, 150 and 75 baud could be supported simply by setting SETFREQ M2 or M1, so the first "difficult" baud rate might be 50 baud?

I'm also surprised that you encountered problems with gaps between characters approaching 150 ms, or that you had difficulties with just pairs of characters (using the PEEKSFR method), since the silicon hardware (UART) has a two-byte buffer. Of course you will have to wait around 33 ms for a second byte to arrive, even with concatonated characters).

But to answer your question, it appears that there is a bug in the (M2 PICaxe's) HSERIN command that it "thinks" that an Over-Run has occurred (when it hasn't) and to "clear" that (non-) "fault", it re-initialises the hardware. Since the hardware "byte-available" flag is not even set until (nominally) the middle of the Stop bit, and then resetting the hardware takes a significant time, it is indeed probable that the leading edge of the next start bit will be "lost" (missed).

Unless you're in a great hurry, it may be better to wait for my updated code (because I'd realised that a "substitute" for HSERIN might be useful), but as a "preview", here is the code that I now use (from the Microchip data sheet) to reset the UART when an Over-Run has occurred. :
Code:
     peeksfr RCSTA,b0                ; Check for OverRun error
     if OERR = 1 then                ; Clear overflow error (bit1)
         pokesfr RCSTA,0                ; Clear CREN to clear Buffer OverRun
         pokesfr RCSTA,b0                ; Restore RCSTA and CREN
     endif

Cheers, Alan.
 

AllyCat

Senior Member
Hi,

Following on from post #22 and before; I've been refining the ideas in this thread to work around (or to work with) the two bugs in the M2 firmware that have hindered the fast and reliable reception of serial data. Now, I believe I've devised a substitute for HSERIN, including a "Background Receive" capability, which runs reliably at up to 2400 baud @ 4MHz (and proportionally up to 19200_32) with any PICaxe M2. The full program is Interrupt-Driven, using a Circular Buffer (in the PICaxe's upper RAM), but there is obviously some overhead in maintaining these features, so I will start here with a simpler Linear Buffer structure that, with very careful attention to instruction timings, may be just usable at up to 4800_4 baud (equivalent to 38400_32). The program here is primarily aimed at the 20M2 because this is (marginally) the slowest M2 (so others should work as well), and all 8 bits of Port B can be kept free, for example to drive the 8-bit parallel data bus of some typical OLED and LCD character displays. My longer-term target is to devise code primarily for the 08M2 (which has some rather different characteristics), but that will be for another thread.

The user-interface of the HSERIN command is rather "perverse" in that first the user/programmer must write a "non-valid" byte or word to a variable, then execute the function and test if the variable has been changed (to the received byte), before it is known if a character was actually received. Therefore, IMHO it is not sensible (or practical) to emulate the HSERIN command exactly, but better (and more compatible with future plans) to receive the data at the system's byte pointer (@BPTR), with an associated pointer-increment. Thus, the reception of one (or more) byte(s) can be confirmed if the BPTR has been advanced (e.g. BPTR <> BUFFERSTART) and then any length of input data can be accessed in sequence. Note that BPTR can be set to point to any location in RAM, including the Byte and Word variables (b0, w0, etc.) but only for byte-wide data. For this implementation it is the responsibility of the Main Program to initialise and maintain the BPTR value, to ensure that it stays within suitable bounds (inside the RAM).

To briefly reprise the two "Bugs" discussed earlier in this thread: The base PIC's "HSERIN" Hardware (UART) has only a two byte buffer, so if a third byte is received (before the first is read) then the hardware sets an "Over Run" flag and the hardware (intentionally) blocks the reception of any more bytes. The problem is that the PICaxe M2 HSERIN instruction appears to (try to) clear the Over-Run flag, even when an Over-Run has NOT occurred, which causes an incoming character (or one already in the buffer) to be lost or corrupted. A "work-around" is for the User's Basic Program to directly access the Serial Buffer, via two PEEKSFR commands. This is easy and works well, but for maximum speed a peeksfr RCREG , @bptrINC command is highly desirable because it performs three actions in a single instruction (Read, Write and address INCrement) with almost no additional time overhead. Unfortunately, a Bug in the PICaxe interpreter causes the "automatic address-increment" built into this "compound" instruction to occur twice, as discussed previously in the thread.

I've concluded that the "workarounds" to this latter problem, discussed in post #22 are just too complex or confusing for general use. So the method I have now adopted uses the characteristic that the "Double-Increment Bug" takes the form of a Pre-Increment (before the byte is written) and then a Post-Increment, i.e. the BPTR is (auto-) incremented once before and once after writing the byte to RAM. Thus, a work-around is to alternate instructions using @BPTR and @BPTRINC so that the sequence of events becomes : Write Byte @BPTR : INCrement BPTR + Write Byte @BPTR + INCrement BPTR , where both of the INCrements are caused by the second (i.e. @BPTRINC) instruction. Of course this method fails if only one byte is available to be written, but in this case (i.e. if the buffer has become empty) then there is ample time to handle the address-increment with an explicit INC BPTR instruction.

Particularly at a baud rate of 4800_4 , there is very little time available to enter the (sub-)Routine, so the code may be entered early and begins with a loop that waits for a byte to be received. But if the characters are only expected at a slow(er) repetition rate, then the main program can jump to the routine when the first byte is expected to arrive, similar to using the HSERIN command. The very tight loop can check the Buffer Status flag about every 1700 PIC Instruction Cycles (i.e. less than the minimum serial byte period of around 2000 ICs) but this gives the code no "timeout" capability (i.e. an escape route) if NO character arrives. Therefore, an optional (configurable) timeout will be included later, in the full Demonstration / Test Harness program. Note that this "waiting" loop uses a rather unusual ON {variable} (bit, byte or word) GOTO {label} instruction with just one label address, to create a fast "IF {zero} THEN GOTO ..." (ELSE fall-through) which is (slightly) faster than a conventional IF .. THEN, or any other type of branch instruction.

The core of the routine is a loop which starts with the "knowledge" that a byte is already available in the receive buffer, because it will have been "found" by the entry (waiting) loop, or elsewhere in the routine; It's more efficient (faster) to close the loop by "jumping back" from a test of the "another byte available" flag. Thus, a received byte is immediately copied from the (SFR) Input Buffer to the storage (RAM) Buffer (@BPTR) by a single instruction. Then, the status flag is read again and if another byte is available, it can be copied immediately with an @BPTRINC instruction, which executes an address Pre-Increment (the Bug) and with the normal (intended) Post-Increment. However, if no second byte is available, the program jumps to execute the "Pending" BPTR INCrement and then checks the Over-Run flag, in case it is blocking reception. If an Over-Run is detected, then this code "unlocks" the UART hardware and returns to the core loop, but in some applications a full Error-Reporting or Logging facility may be required.

After the second byte is received in the core loop, the buffer status flag is again tested, leading to either a jump back to the top of the loop or a fall through to the "no character available" section of the program, as just described. Ideally, before exiting (or Returning) from this core routine, a "timeout" is desirable in case the arrival of another character is imminent. It is the Lowest expected baud rate (or more specifically the character repetition period) which determines if (or for how long) an exit delay may be required and in practice the main program may be fast enough to work on a byte-by-byte basis (like the intended M2 usage of HSERIN). At higher baud rates, if the algorithm reaches this section of the program, it may be reasonable to assume that either a block of data has finished, or that the characters are being transmitted with a significantly larger inter-character gap, so an exit may be appropriate. But a basic RETURN and associated CALL consumes over 3000 PIC Instruction Cycles, even without any processing in the Main Program, so it's important not to exit if another byte is imminent. For the higher throughputs, it may be better to use a GOTO in place of a Return, or even embed the routine directly within the Main Program.

So here is a listing of the basic core, in the form of a subroutine.
Code:
getstring:                         ;    \/ Typical PIC Instruction Cycles
  peeksfr PIR1,b0                ; 600    ;  Read the Serial Status byte
  on RCIFb goto getstring    ;1100    ; Waiting loop, polled every ~1700 PIC Instruction Cycles 
  high marker                ; 400    ; For testing
getbyt:                         ; Start Main Loop
  peeksfr RCREG,@bptr        ; 600    ; Read and store the received byte (bptr INCrement Pending)
  peeksfr PIR1,b0                ; 600    ; Read the Hardware Serial Status byte
  on RCIFb goto nodata1    ; 700    ; Jump if there is no Unread byte in the receive buffer
  peeksfr RCREG,@bptrINC    ; 600    ; Read and store the received byte, updating the bptr
xitest:
  peeksfr PIR1,b0                ; 600    ; Read the Serial Status again here to share the jump
  if RCIFb = 1 then getbyt    ;1200    ; Loop back with effective period of 4300 / 2 = 2150 ICs/char
  peeksfr RCSTA,b0            ; 600    ; Check for an OverRun error
  if OERR = 1 then unlock    ; 800    ; Clear Over-Run error
  peeksfr PIR1,b0                ; 600    ; Check buffer again before exiting
  if RCIFb = 1 then getbyt    ; 800    ; Another byte now received
  timeout = 1                    ; 500
xitlp:
  dec timeout                ; 600
  peeksfr PIR1,b0            ; 600    ; Read the Serial Status again
  if RCIFb = 1 then getbyt    ; 800    ; A byte has now been received
  if timeout > 0 then xitlp    ;1200 ; loop = 3200 , or fall through to exit
  low marker                 ; 400    ; For testing
return                     ;2200    ; Return to main program (a GOTO is faster)
nodata1:
  INC bptr                     ; 600    ; Apply the Pending increment
  goto xitest                ; 800
unlock:                    ; Enter with RCSTA in b0
  pokesfr RCSTA,0            ; 600    ; Clear CREN to cancel the Buffer OverRun
  pokesfr RCSTA,b0             ; 600    ; Restore RCSTA
  @bptrinc = "|"                 ; 600 ; Add a "missing character(s)" marker to the data stream
  goto getbyt                    ;1200
Well, that's reached the forum's 10,000 character limit again, so I will have to include the full Demo/Test Harness and other details in my next post.

Cheers, Alan.
 

AllyCat

Senior Member
Hi,

The above program code probably cannot "continuously" receive fully concatonated (adjoining) characters reliably at 4800 baud with a 4 MHz clock. The estimated loop time to receive two characters is about 4300 PIC Instruction Cycles (which each have a duration of 1 us with a 4MHz clock), whilst the nominal duration of two characters at 4800 baud is 4166 us. The two-byte hardware buffer may give some headroom for short bursts of characters, but much of this time is used by the initial entry and polling loop, particularly if it includes the "timeout" facility. However, the routine generally does accurately receive the pre-programmed "Hello, I am your PICAXE .." string from a new (08M2) chip, which has an inter-character gap of about 2 bit periods (450 us).

Also, it may be possible to receive concatonated characters from other hardware (including the PE6 Terminal Emulator), if they are Transmitted with the addition of a Parity and/or Two Stop Bits set (e.g. 12 bit periods = 2.5 ms). PICaxe Basic ignores any "errors" after the 8th data bit into the UART hardware, for example the "Framing" (validity) of the Stop bit, because they are not "fatal", and the language has no strategy to deal with them. So these configuration bits give the opportunity to "optimise" the interface and to investigate the behaviour when an Over-Run does occur. Typically, under "limit conditions" the code may successfully receive up to about 6 - 8 characters, but generally fail after 10 - 16.

When reception does fail, the number of "bad" characters can be considerable, because the PICaxe interpreted code is relatively "slow" compared with the speed of the incoming characters. But also because there is no way to reliably identify the next "Start" bit(s) in a concatonated data stream, from ordinary "0" bits. Therefore, there is little point in attempting to "Repair" the data if an Over-Run error does occur, but it is possible to recover the two bytes still in the UART Buffer, if required. However, the code doesn't do this do this; my main interest is in supporting a Circular Buffer and the code (needing to check for a wrap-around at every byte) becomes unrealistically large.

The Demonstration / Test Harness program below basically receives a burst of characters and then "Echos" them to the SEROUT and/or HSEROUT pins. The core receive loop has a 6 second timeout so after this period a "=" is sent on the SEROUT pin, to confirm that the program is still "alive". After 60 seconds the program terminates to allow a new program to be downloaded, without the need for a Hard Reset. But if the "timeout" feature is omitted, then it's necessary to send (at least) one character after the 60 seconds period, to activate the "exit" procedure. During exit, the program confirms the number of characters received and any detected errors; it was not necessary to implement a dedicated character count, because it can be calculated by the Main Program, from the BPTR value in association with any Resets or Wraparounds of the RAM buffer that occurred.

The full program listing includes a few features which are either a "Legacy" of previous code versions and/or might be useful for "Future Development". For example the HSERIN input is swapped (from pinB.1) to PinC.5 by using the "Alternate Pin Function" SFR (APFCON). This helps to free up Port B so that it may be used as a complete 8-bits wide data bus; also C.5 is an "Interrupt-Capable" input pin, whilst B.1 is not. Another advantage of moving the input is that it's near to the normal SERIN pin, which (to receive from the PE Terminal) requires an inverter, because HSERIN accepts only "True/TTL" (Idle High) levels:

It's easy to connect a simple NPN Transistor/FET inverter (for the signal to the Progamming Input), enabled by an Earth on C.6 (to the Emitter or Source) and driving C.5 (which has its internal Weak Pullup Resistor enabled). Also Symbolised, are the "Interrupt On Change" FLAGS, which are potentially useful for the "exit" decision, detecting if another character is "arriving" (from the leading edge of the Start bit), whilst the Buffer Status Flag can indicate only when a complete Byte has arrived. However, the present version of the code relies on a simple "Exit Delay" (timeout), which may need to be set to suit the baud rate and/or the Main Program execution time.

Again another 10,000 characters forum limit is imminent (even with some cropping), so here is the program: ;)

Code:
; HSERIN EMULATION using 20M2 to give 2400 baud @ 4 MHz (9600 @ 16 MHz, etc.)
; AllyCat, March 2021.  Linear Buffer version possibly usable at 4800_4 
  
#picaxe 20m2    ; And other M2s with some modification(s)
#no_data
#terminal 4800      ; For SETFREQ M4
#DEFINE USETIMES   ; Use timeouts on entry and exit of core routine
; EUSART:SFRs
symbol APFCON = $5D    ; APF: 128=RX on C.5/B.1;4= TX on C.4/C.0
symbol RCREG = $79     ; Serial receive register
symbol PIR1 = $11      ; 32 = Received byte available ; 16 = TX buffer is EMPTY
symbol RCIFb = bit5    ; There is a Received byte in buffer
symbol RCSTA = $7D     ; 128 = Ser Port Enable (RX & TX); 16 = Continuous Receive Enable
symbol CRENb = bit4    ; Continuous Receive Enable (used to reset Buffer OverRun)
symbol OERR = bit1     ; Over-Run Error
symbol FERR = bit2     ; Framing (Stop pulse polarity) Error
symbol IOCAF = $F3     ; Interrupt-On-Change flag(s)
symbol IOCAP = $F1     ; Positive Interrupt-On-Change
symbol IOCAN = $F2     ; Negative Interrupt-On-Change

symbol INTflgs = b0    ; Used by Core Routine to read SFR flags
symbol tempb = b1      ; Temporary (local) variable
symbol timeout = w1    ; Timer to abandon reception (~6 ms units) or delay exit (~3 ms units)
symbol errcnt= w2      ; Error counter for diagnostics
symbol bytecnt = w3    ; Number of bytes received (updated by Core Routine)
symbol ptrout= w4      ; Output RAM Buffer Pointer
symbol BUFST = 64      ; Start address of hserial RAM buffer
symbol MARKER = C.4    ; Spare pin to show when the Core Loop is active
symbol BAUD = N4800_4  ; Max (Concatonated) Serial Baud rate (or 9600_8, 38400_32, etc)
symbol HBAUD = B4800_4  ; Hardware Serial Baud rate (Input and Output)
symbol NENILO = C.7    ; Not ENable Idle LOw at HSERIN pin (for external Inverter)
symbol HSERRX = C.5    ; HSERIN (Alternate Pin Function = C.5/C.1)    [14m2=B.1/C.2]
symbol HSERTX = C.0    ; HSEROUT (Alt Pin Func C.4/C.0)  [14m2=B.0/C.1]
symbol ORUN   = "|"    ; OverRun marker in buffer (Optional)
symbol TIMEUP = 1      ; Additional Time delay before Returning/Exit (Min 1)

    pause 2000        ; Wait for Terminal Emulator to start
init:
    bytecnt = 0
    errcnt = 0
    hsersetup HBAUD,2    ; 16=Disable hserin; 8=Disable hserout; 2=INVERT SEROUT
    pokesfr APFCON,128   ; Move RXD input to C.5 (interruptable), TXD still on C.0
    low NENILO         ; Enable-Low an external hserin inverter (Optional)
    disconnect         ; Disable Program Downloader to allow SerIn pin activity
    pullup $1000       ; Pullup the HSerIn pin (C.5) (for open-collector inverter)
    bptr = BUFST      ; Initialise the RAM buffer
    ptrout = BUFST
main:
do
echo:
    timeout = 1000   ; About 6 seconds
    CALL getstring    ; The Core Loop
    if bptr = ptrout then      ; No character(s) received
        serout A.0,BAUD,("=")  ; Marker to show program is still running
    endif
    do while bptr <> ptrout  ; The RAM buffer is not empty
        peek ptrout,tempb        ; Read the next byte from Linear Buffer
        hserout 0,(tempb)        ; Transmit the byte
        serout A.0,BAUD,(tempb)  ; Serial/Debug/SERTXD Output pin (20M2 only)
        INC ptrout       ; Maybe check for another UART buffer byte here ?
    loop
    bytecnt = bytecnt + bptr - BUFST    ; Update the character counter
    bptr = BUFST             ; Reset the buffer pointers
    ptrout = BUFST
loop until time > 60         ; Then restore the serial downloader
    reconnect                ; Restore Programming input
    hsersetup HBAUD,8        ; 8=DISABLE HSEROUT;  2=Invert serout
    bytecnt = bytecnt - errcnt    ; Number of bytes received
    serout A.0,BAUD,(cr,lf,#bytecnt," bytes - Done")
    serout A.0,BAUD,(" OverRuns= ",#errcnt)
end
getstring:
#IFDEF USETIMES
    peeksfr PIR1,b0      ; Read the Serial Status
    if RCIFb = 1 then begin      ; There is a byte to read in the receive buffer
    dec timeout
    peeksfr PIR1,b0
    if RCIFb = 1 then begin
    if timeout = 0 then done
#ENDIF
    peeksfr PIR1,b0
    on RCIFb goto getstring       ; Average loop 5900/3=1970 ICs, or 1700 (if no Timeout)
begin:
  high marker        ; For testing
getbyt:           ; Start of Core Loop
  peeksfr RCREG,@bptr      ; Read and store the received byte (bptr INCrement Pending)
  peeksfr PIR1,b0
  on RCIFb goto nodata1
  peeksfr RCREG,@bptrINC   ; Read and store the received byte, updating the bptr
xitest:
  peeksfr PIR1,b0
  if RCIFb = 1 then getbyt    ; Loop back with effective period of 4300 / 2 = 2150 ICs/char
  peeksfr RCSTA,b0
  if OERR = 1 then unlock     ; Clear Over-Run error
  peeksfr PIR1,b0
  if RCIFb = 1 then getbyt
#IFDEF USETIMES
  timeout = TIMEUP MIN 1     ; ~3200 ICs per unit (minimum 1)
xit2:
    dec timeout
    peeksfr PIR1,b0
    if RCIFb = 1 then getbyt    ; A byte has now been received
    on timeout goto done
    peeksfr PIR1,b0
    if RCIFb = 0 then xit2      ; Loop = 4500/2 = 2250 average
    goto getbyt
#ENDIF
done:
    low marker        ; For testing
return       ; Return to main program (a GOTO is faster)
nodata1:
    INC bptr              ; Apply the Pending increment
    goto xitest
unlock:                   ; Enter with RCSTA in b0
    pokesfr RCSTA,0       ; Clear CREN to cancel the Buffer OverRun
    pokesfr RCSTA,b0      ; Restore RCSTA
    @bptrinc = ORUN       ; Add a "missing character(s)" marker
    inc errcnt
    goto xitest
Cheers, Alan.
 
Last edited:
Top