Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Finding serial START bit by bit banging

Status
Not open for further replies.

Oznog

Active Member
I've seen a number of routines that do bit banging on a serial port. But I don't get how they handle framing problems. There's an assumption that the code can start looking for the start bit at the start of the loop. If the code starts the loop in the middle of a byte though this won't work. First 0 in the bus it'll assume is a START bit. There's a good chance a 1 won't happen in the window it will look for the STOP bit in so it may realize there's a framing error.

How does the bit banger figure out what the start of the byte's framing is?
 
Oznog said:
I've seen a number of routines that do bit banging on a serial port. But I don't get how they handle framing problems. There's an assumption that the code can start looking for the start bit at the start of the loop. If the code starts the loop in the middle of a byte though this won't work. First 0 in the bus it'll assume is a START bit. There's a good chance a 1 won't happen in the window it will look for the STOP bit in so it may realize there's a framing error.

How does the bit banger figure out what the start of the byte's framing is?

The whole point of asyncronous comms is that you MUST detect a clear start bit - if not the whole thing fails, this isn't a 'bit banging' problem, it occurs with hardware UART's as well. There are a number of simple steps you can take to help reject incorrect bytes, for a start test the STOP bit, if this is wrong, then the byte hasn't been read correctly. Secondly, use the parity bit, this will help detect some further errors.

Generally micro-controller code does neither of these, it's not often required, and modern RS232 is reliable enough without.

If you're trying to do wireless comms?, then RS232 is completely the wrong choice, wireless schemes (such as Manchester coding) add special start sequences to allow syncronisation of the receiver.
 
If you check out the various bit-banged serial I/O examples on PICList (and elsewhere) you'll find they basically detect the start bit leading edge, delay approximately 1/2 bit time, then clock in the serial data in the middle of each data bit... There are examples for interrupt driven and non-interrupt serial I/O code...

The interrupt driven half-duplex 12F683 demo on PICList uses IOC (interrupt on change) to detect a start bit leading edge, then turns off IOC, advances the TMR2 interrupt source by 1/2 bit time, clocks in the start/data/stop bits, and finally switches IOC back on after clocking in the stop bit...

The interrupt driven full-duplex 16F819 demo on PICList uses TMR2 generated interrupts at 3X the bit rate and a state machine in the ISR (interrupt service routine) to detect a start bit within 00%-33% of the start bit period then clocks in the data bits during the 33%-66% portion of each bit period...

Regards, Mike[/url]
 
Nigel Goodwin said:
The whole point of asyncronous comms is that you MUST detect a clear start bit - if not the whole thing fails, this isn't a 'bit banging' problem, it occurs with hardware UART's as well. There are a number of simple steps you can take to help reject incorrect bytes, for a start test the STOP bit, if this is wrong, then the byte hasn't been read correctly. Secondly, use the parity bit, this will help detect some further errors.

If you're trying to do wireless comms?, then RS232 is completely the wrong choice, wireless schemes (such as Manchester coding) add special start sequences to allow syncronisation of the receiver.

I have a setup where the USART is already being used for something else and I need to read NMEA-0183 from a GPS, which is 4800 baud serial data with no parity, so I want to see how bad the resource hit is when bit-banging. No wireless here.

I know failure to detect the START bit will make the scheme fail. How do we fix it? I imagine if there's a break between characters the code can wait for a long period of 1 on the bus and assume the next 0 is the start bit. But nowhere does the NMEA-183 protocol specification make any promises about such gaps and this needs to work with any GPS. Thus making a scheme which works with one GPS based on features not specified by the protocol proves little since another GPS may not do this.

Or do we just kick the bit-time state counter over one every time it detects a bad STOP bit, until it starts consistently finding a 1 in the STOP bit time? That does seem like the way to do it.
 
Oznog said:
Or do we just kick the bit-time state counter over one every time it detects a bad STOP bit, until it starts consistently finding a 1 in the STOP bit time? That does seem like the way to do it.

The answer is simple (as far your questions here are concerned), you simply make sure you don't miss the start bit!. Presumably though this is related to your request for multiple simultaneous serial inputs?, that makes it 'difficult'.
 
Nigel Goodwin said:
The answer is simple (as far your questions here are concerned), you simply make sure you don't miss the start bit!. Presumably though this is related to your request for multiple simultaneous serial inputs?, that makes it 'difficult'.

Well not being able to recover the framing is a really unreliable port. Either the device was already sending before the device turned on/woke up, transmitter got reset, there was a clock jitter issue, noise on the cable, rare software/interrupt issues made it lose a bit-time, doesn't matter. These things happen.

How does the hardware USART figure it out? I haven't seen an explanation. If there's a gap between characters the bus will remain high, it'll proceed through its bit times and wait for the next 1->0 transition which will always mean it'll always recognize the START bit after any gap longer than a normal char's transmission time. But is there a good way to do it when there's no gaps between characters?
 
Oznog said:
How does the hardware USART figure it out? I haven't seen an explanation.

The hardware USART doesn't figure it out, it's no different than a software UART.

You need to think more generally, and not just on the receive side - it's the transmit side which makes it easily possible.

1) Don't send continuous data, a gap of more than one word should allow syncronisation to occur.

2) Use handshaking.
 
I took a look at a summary of the nmea-0183 standard. A sentence is framed by a '$' character in the beginning and a <CR><LF> sequence at the end. Use this to check if you have a valid sentence in case the async port loses bit synchronism. Eventually, the receiver will recover synchronism and you should be able to get a properly framed sentence.
 
Nigel Goodwin said:
You need to think more generally, and not just on the receive side - it's the transmit side which makes it easily possible.

1) Don't send continuous data, a gap of more than one word should allow syncronisation to occur.

2) Use handshaking.

Remember this is for a GPS interface so it is impossible to do anything to the transmitted data.

It will also read data from a PC. It will use the same code so the protocol details must be the same. If I dump a data file with a hyperterminal program, are there any implied gaps here or are all the bytes going to end up back to back?

motion said:
A sentence is framed by a '$' character in the beginning and a <CR><LF> sequence at the end. Use this to check if you have a valid sentence in case the async port loses bit synchronism. Eventually, the receiver will recover synchronism and you should be able to get a properly framed sentence.

Detecting a framing error would probably not need to go to that level. First the STOP bit will be wrong more or less half the time, if you somehow don't see this then you can detect some nonsense ASCII codes (not alphanumeric, punctuation, space, <CR>, or <LF>), if you somehow don't see that then the sentence format will be wrong too.
 
Multiple channel RS232 doable.

I think you need to track all the input channels simultaneously. This should be doable with a 20MHz 16F628.

At 4800 baud you would need to interrupt at 3 times the bit rate. Your interrupts need to be at 5,000,000/4800/3 = 347 clock cycles. I just checked my interupt driven bit banger and the worst case path is only 17 cycles long. So, you could easily check 8 channels simultaneously. This would take 17*8 + the overhead of the interrupt, say 20, gives 156 cycles. Less than 50% of the processor time.

I could modify my code to handle multiple channels if you like and post it.

Mike.
 
Detecting a framing error would probably not need to go to that level. First the STOP bit will be wrong more or less half the time, if you somehow don't see this then you can detect some nonsense ASCII codes (not alphanumeric, punctuation, space, <CR>, or <LF>), if you somehow
don't see that then the sentence format will be wrong too.
If you feel you can do a simpler job than checking for a '$' and a <CR>,<LF> sequence, I don't understand what's all the fuss about the start bit.
 
I wouldn't worry about framing errors and detecting stop bits etc. The only way to get into sync is to assume each bit is a start bit. The data will gradually sync up as more 1 bits are sent. Consider the case of sending 0xff, the only thing you would see with a scope would be the start bit.

This diagram from Nigels tutorial shows it well.

**broken link removed**

Note that the stop bit doesn't exist, it is just a gap to allow things to synchronize. Also note if all bits were 1 then the data would be a flat line.

Mike.
 
Pommie said:
Note that the stop bit doesn't exist, it is just a gap to allow things to synchronize. Also note if all bits were 1 then the data would be a flat line.

Except for the start bit of course!.

motion said:
I don't understand what's all the fuss about the start bit.

The fuss is the requirement to identify it!. Assuming the transmitter is sending continuous data, and you turn the receiver ON - there's no way it can identify the start bit in a continuous data stream, all it can do is indenify ANY zero bit. However, once you have a gap (all ones) longer than a single word, then it can syncronise on the next start bit.
 
Re: Multiple channel RS232 doable.

Pommie said:
I just checked my interupt driven bit banger and the worst case path is only 17 cycles long. ~~~~ I could modify my code to handle multiple channels if you like and post it.

Mike.

Mike,

Is that 17 instruction cycles for 3X bit rate interrupt driven RX only code? If so, that's incredible... I'd very much like to see your code Sir...

Regards, Mike
 
Re: Multiple channel RS232 doable.

Mike said:
Is that 17 instruction cycles for 3X bit rate interrupt driven RX only code? If so, that's incredible... I'd very much like to see your code Sir...

I counted wrong, it's 18 cycles. However, the modified code is only 16. Here's the original code.

Code:
RecSkips        equ     20h
InByte          equ     21h
Cache           equ     22h
TransferFlags   equ     23h


                #define RS232In PORTB,2


                decfsz  RecSkips,F
                goto    DoneRS232
                btfss   TransferFlags,b_receiving
                goto    get_start_bit
                bsf     STATUS,C
                btfss   b_RS232In
                bcf     STATUS,C
                rrf     InByte,F
                movlw   3
                movwf   RecSkips
                btfss   STATUS,C
                goto    DoneRS232
                movf    InByte,W
                movwf   Cache
                bsf     TransferFlags,b_byte_available
                bcf     TransferFlags,b_receiving
                goto    DoneRS232
get_start_bit	incf    RecSkips,F;	set to 1
                btfsc   b_RS232In
                goto    DoneRS232
                movlw   4
                movwf   RecSkips
                bsf     TransferFlags,b_receiving
                movlw   80h
                movwf   InByte
DoneRS232

It does away with the need to keep a count of bits received by setting the received byte to 80h. When the last bit is shifted into InByte then the carry will be set. This makes it a lot quicker.


To make it multi channel, I found it easier to write a macro for each channel. To make sure that the timing is accurate, PORTB is read at the beginning of the interrupt and stored in a temporary variable.

Here's the macro
Code:
ReceiveChannel  Macro   chan
                rrf     PortTemp,F
                decfsz  RecSkips+chan,F
                goto    DoneRS232#v(chan)
                btfss   b_receiving,chan
                goto    g_strt#v(chan)
                rrf     InByte+chan,F
                movlw   3
                movwf   RecSkips+chan
                btfss   STATUS,C
                goto    DoneRS232#v(chan)
                movf    InByte+chan,W
                movwf   cache+chan
                bsf     b_byte_avail,chan
                bcf     b_receiving,chan
                goto    DoneRS232#v(chan)
g_strt#v(chan)  incf    RecSkips+chan,F;	set to 1
                btfsc   STATUS,C
                goto    DoneRS232#v(chan)
                movlw   4
                movwf   RecSkips+chan
                bsf     b_receiving,chan
                movlw   80h
                movwf   InByte+chan
DoneRS232#v(chan)
                endm

And this is the code that would go in the interrupt routine.

Code:
                bcf     PIR1,CCP1IF;	reset special event trigger interupt

                movfw   PORTB;		read all channels at once
                movwf   PortTemp

                ReceiveChannel  0
                rrf     PortTemp,F;	skip bit 1
                rrf     PortTemp,F;	skip bit 2
                ReceiveChannel  1
                ReceiveChannel  2
                ReceiveChannel  3
                ReceiveChannel  4
                ReceiveChannel  5

With the above code, if a byte is received on channel 3 (or any of 0 to 5), bit 3 of b_byte_avail will be set and the byte will be in cache+3.

Mike.
 
Last edited:
Hey Mike,

Nice code... Thanks... I always enjoy looking at good code from a fellow "cycle counter" (grin)... I came up with a similar routine while waiting for your response and I was just starting to think about how to implement multiple channels...

I've been using 3X bit rate interrupts for full duplex 9600 baud bit banged serial I/O awhile now so I appreciate your small fast algorithm... My 12F683 and 16F819 full duplex 9600 baud serial I/O demos with 16-byte circular RX and TX buffers use 34/35 instruction cycles average of the 69 instruction cycles between interrupts (using the 8-MHz INTOSC clock) but now I think I'll take a stab at trying to reduce that 50% processing 'overhead'...

Take care... Happy Holidays... Regards, Mike
 
Mike said:
I've been using 3X bit rate interrupts for full duplex 9600 baud bit banged serial I/O awhile now so I appreciate your small fast algorithm... My 12F683 and 16F819 full duplex 9600 baud serial I/O demos with 16-byte circular RX and TX buffers use 34/35 instruction cycles average of the 69 instruction cycles between interrupts (using the 8-MHz INTOSC clock) but now I think I'll take a stab at trying to reduce that 50% processing 'overhead'...

Take care... Happy Holidays... Regards, Mike

That's wierd, the code I posted was from a project that was on a 16F819, used the internal 8MHz clock and had two 16 byte circular fifo buffers. I only had it running at 4800 baud as my background code was processor intensive. If your interested, I posted the fifo code In this thread .

Happy Holidays

Mike.
 
Pommie said:
Mike said:
I've been using 3X bit rate interrupts for full duplex 9600 baud bit banged serial I/O awhile now so I appreciate your small fast algorithm... My 12F683 and 16F819 full duplex 9600 baud serial I/O demos with 16-byte circular RX and TX buffers use 34/35 instruction cycles average of the 69 instruction cycles between interrupts (using the 8-MHz INTOSC clock) but now I think I'll take a stab at trying to reduce that 50% processing 'overhead'...

Take care... Happy Holidays... Regards, Mike

That's wierd, the code I posted was from a project that was on a 16F819, used the internal 8MHz clock and had two 16 byte circular fifo buffers. I only had it running at 4800 baud as my background code was processor intensive. If your interested, I posted the fifo code In this thread .

Happy Holidays

Mike.

Yeah, that is wierd (grin)... Here's my version (below)... Your code is cleaner...

Take care... Regards, Mike

Code:
;******************************************************************
;*                                                                *
;*  Companion Put232 and Get232 subroutines                       *
;*                                                                *
;******************************************************************
;
;  Put232 - enter with character to be sent in W
;         - performs TXBUFF 'load buffer' operation
;
Put232  movwf   TXCHAR          ; save character                  |B0
Pwait   incf    TX_WPTR,W       ; W = WPTR + 1                    |B0
        andlw   h'0F'           ; keep it in range 00..0F         |B0
        addlw   TXBUFF          ; make it in range B0..BF         |B0
        movwf   PTRTMP          ; save here temporarily           |B0
        xorwf   TX_RPTR,W       ; buffer full (WPTR+1=RPTR)?      |B0
        bz      Pwait           ; yes, branch, wait               |B0
        movf    FSR,W           ; get FSR                         |B0
        movwf   FSRTMP          ; save it                         |B0
        movf    TX_WPTR,W       ; get TX buffer Wr ptr (B0..BF)   |B0
        movwf   FSR             ; setup indirect address          |B0
        movf    TXCHAR,W        ; get character                   |B0
        movwf   INDF            ; place it in TX buffer           |B0
        movf    FSRTMP,W        ;                                 |B0
        movwf   FSR             ; restore FSR                     |B0
        movf    PTRTMP,W        ; get saved TX_WPTR+1 value       |B0
        movwf   TX_WPTR         ; update TX_WPTR                  |B0
        movf    TXCHAR,W        ; restore W entry data            |B0
        return                  ;                                 |B0
;
 
I apologize for hi-jacking the thread Gentlemen

Mike,

I like your scheme of stuffing the buffer first... I modified my routine accordingly, though I'm still saving W and FSR context, and I ending up saving one word of code space and eliminating the use of one GPR var... Cool... Thanks for stimulating these old brain cells (grin)...

Happy Holidays... Mike

Code:
;******************************************************************
;
;  Put232 - enter with character to be sent in W
;         - performs TXBUFF 'load buffer' operation
;
;    vars - TXCHAR, TX_WPTR, TX_RPTR, TXBUFF, FSRTMP
;    SFRs - FSR
;
Put232  movwf   TXCHAR          ; save character                  |B0
        movf    FSR,W           ;                                 |B0
        movwf   FSRTMP          ; save FSR context                |B0
        movf    TX_WPTR,W       ;                                 |B0
        movwf   FSR             ; setup indirect address          |B0
        movf    TXCHAR,W        ; get character                   |B0
        movwf   INDF            ; place it in TXBUFF              |B0
        movf    FSRTMP,W        ;                                 |B0
        movwf   FSR             ; restore FSR context             |B0
Pwait   incf    TX_WPTR,W       ; W = WPTR+1                      |B0
        andlw   h'0F'           ; make it 00..0F                  |B0
        addlw   TXBUFF          ; make it B0..BF                  |B0
        xorwf   TX_RPTR,W       ; buffer full (WPTR+1=RPTR)?      |B0
        bz      Pwait           ; yes, branch, wait               |B0
        xorwf   TX_RPTR,W       ; restore WPTR+1 value in W       |B0
        movwf   TX_WPTR         ; update TXBUFF WR pointer        |B0
        movf    TXCHAR,W        ; restore W entry data            |B0
        return                  ;                                 |B0
;
 
Mike said:
Code:
;******************************************************************
;
;  Put232 - enter with character to be sent in W
;         - performs TXBUFF 'load buffer' operation
;
;    vars - TXCHAR, TX_WPTR, TX_RPTR, TXBUFF, FSRTMP
;    SFRs - FSR
;
Put232  movwf   TXCHAR          ; save character                  |B0
        movf    FSR,W           ;                                 |B0
        movwf   FSRTMP          ; save FSR context                |B0
        movf    TX_WPTR,W       ;                                 |B0
        movwf   FSR             ; setup indirect address          |B0
        movf    TXCHAR,W        ; get character                   |B0
        movwf   INDF            ; place it in TXBUFF              |B0
        movf    FSRTMP,W        ;                                 |B0
        movwf   FSR             ; restore FSR context             |B0
Pwait   incf    TX_WPTR,W       ; W = WPTR+1                      |B0
        andlw   h'0F'           ; make it 00..0F                  |B0
        addlw   TXBUFF          ; make it B0..BF                  |B0
        xorwf   TX_RPTR,W       ; buffer full (WPTR+1=RPTR)?      |B0
        bz      Pwait           ; yes, branch, wait               |B0
[color=red]If an interrupt happens here[/color]
        xorwf   TX_RPTR,W       ; restore WPTR+1 value in W       |B0
        movwf   TX_WPTR         ; update TXBUFF WR pointer        |B0
        movf    TXCHAR,W        ; restore W entry data            |B0
        return                  ;                                 |B0
;

I think you may have introduced a bug in your latest code. If an interrupt happens where I have indicated then, if the buffer is not full but contains data, the xorwf TX_RPTR,W will not restore the value as you intended. This could be recalculated just as efficiently with incf TX_WPTR,F followed by bcf TX_WPTR,4.

Mike.
 
Status
Not open for further replies.

Latest threads

Back
Top