Shop OBEX P1 Docs P2 Docs Learn Events
Spin school - can serial tx be done this simple? — Parallax Forums

Spin school - can serial tx be done this simple?

ErlendErlend Posts: 612
edited 2014-02-27 11:57 in General Discussion
For some serial communications I only need to shoot off a few bytes, then go on executing the main code. For this I want to call a very very simple serial tranmission method - without starting a new cog for it. My question is; can it be thus simple? -and is the code correct?
PRI Tx96(txByte,txPin) | t, bitDur
    bitDur := clkfreq / 9600  
    txByte := (txByte | $100) << 2                      ' add stop bit
    outa[txPin] :=  1                                  ' idle state
    dira[txPin]~~                                       ' could instead do this outside this method - once and for all
    t := cnt                                            ' sync
    repeat 10                                           ' start + eight data bits + stop
      waitcnt(t += bitDur)                              ' wait bit duration, first wait keeps 'idle state' for one bit duration
      outa[txPin] := (txByte >>= 1) & 1                 ' output lsb, then shift right to move next bit in place for tx

I have pinched and stolen parts of this code, and honestly I do not understand why the byte needs << 2 to give room for 1 stop bit, and does it really need to be | $100?
I could do with some mentoring, Erlend

Comments

  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2014-02-26 08:03
    Erlend wrote: »
    For some serial communications I only need to shoot off a few bytes, then go on executing the main code. For this I want to call a very very simple serial tranmission method - without starting a new cog for it. My question is; can it be thus simple? -and is the code correct?
    PRI Tx96(txByte,txPin) | t, bitDur
        bitDur := clkfreq / 9600  
        txByte := (txByte | $100) << 2                      ' add stop bit
        outa[txPin] :=  1                                  ' idle state
        dira[txPin]~~                                       ' could instead do this outside this method - once and for all
        t := cnt                                            ' sync
        repeat 10                                           ' start + eight data bits + stop
          waitcnt(t += bitDur)                              ' wait bit duration, first wait keeps 'idle state' for one bit duration
          outa[txPin] := (txByte >>= 1) & 1                 ' output lsb, then shift right to move next bit in place for tx
    

    I have pinched and stolen parts of this code, and honestly I do not understand why the byte needs << 2 to give room for 1 stop bit, and does it really need to be | $100?
    I could do with some mentoring, Erlend

    You can calculate the bitdur as a constant rather than caclulating each time. Since asynch transmits lsb first the << 2 just makes room for a start bit (a zero) plus an extra bit because of the way it outputs each bit it wastes the very first bit. The $100 that is OR'd before it is shifted left is to make sure that the 10th bit (9th<<2-1) that is shifted out is a 1 or stop bit. Looking at the bits just before it starts to send them you have from lsb to msb --> DUMMY=0 + START=0 + DATA(0)..(DATA(7) + STOP=1
  • ErlendErlend Posts: 612
    edited 2014-02-26 23:39
    Thanks, Peter
    I didn't first get that the shift-right was done prior to tranmitting. So, if instead of
    outa[txPin] := (txByte >>= 1) & 1
    
    if I do a tx first and then shift-right, the first bit will not be wasted - which means the initial <<2 can be changed to <<1. That would - at least for me - make the code more logical and understandable, like this:
    PRI Tx96(txByte,txPin, BitDur) | t
        txByte := (txByte | $100) << 1                      ' add stop bit [COLOR=#008080]EDIT: add start bit and OR in a stop bit[/COLOR]
        outa[txPin] :=  1                                  ' idle state
        dira[txPin]~~                                       ' 
        t := cnt                                            ' sync
        repeat 10                                           ' start + eight data bits + stop
          waitcnt(t += BitDur)                              ' wait bit duration, first wait keeps 'idle state' for one bit duration
          outa[txPin] := txByte & 1                 ' output lsb, 
          txByte >>= 1                                   ' then shift right to move next bit in place for tx
    

    Does this look OK? Question: what about the duration of the stop bit at the end? -there is no waitcnt to control it's duration, but as long as there is no dira[txPin]~ (tidy up) before the method ends, the stop bit will just stay high until next call - but what happens if that call comes really quick and cuts off the stop bit too early? Maybe the stop bit duration isn't critical.

    Erlend



    Erlend
  • Cluso99Cluso99 Posts: 18,069
    edited 2014-02-27 01:03
    The reason it is first shifted left is to add a start bit ("0") to the bottom bit. So you will be transmitting 10 bits, LSB first.
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2014-02-27 01:07
    Erlend wrote: »
    Thanks, Peter
    I didn't first get that the shift-right was done prior to tranmitting. So, if instead of
    outa[txPin] := (txByte >>= 1) & 1
    
    if I do a tx first and then shift-right, the first bit will not be wasted - which means the initial <<2 can be changed to <<1. That would - at least for me - make the code more logical and understandable, like this:
    PRI Tx96(txByte,txPin, BitDur) | t
        txByte := (txByte | $100) << 1                      ' add stop bit
        outa[txPin] :=  1                                  ' idle state
        dira[txPin]~~                                       '
        t := cnt                                            ' sync
        repeat 10                                           ' start + eight data bits + stop
          waitcnt(t += BitDur)                              ' wait bit duration, first wait keeps 'idle state' for one bit duration
          outa[txPin] := txByte & 1                 ' output lsb,
          txByte >>= 1                                   ' then shift right to move next bit in place for tx
    

    Does this look OK? Question: what about the duration of the stop bit at the end? -there is no waitcnt to control it's duration, but as long as there is no dira[txPin]~ (tidy up) before the method ends, the stop bit will just stay high until next call - but what happens if that call comes really quick and cuts off the stop bit too early? Maybe the stop bit duration isn't critical.

    The <<2 part I think was just to try and keep it efficient, at least for Spin when it did the (txbyte >>= 1) & 1 in a single operation, the compiler generates:
    9                            outa[txPin] := (txByte >>= 1) & 1
    Addr : 003A:             36  : Constant 2 $00000001
    Addr : 003B:          66 C2  : Variable Operation Local Offset - 1 Assign ByteMathop >> Push
    Addr : 003D:             36  : Constant 2 $00000001
    Addr : 003E:             E8  : Math Op &
    Addr : 003F:             68  : Variable Operation Local Offset - 2 Read
    Addr : 0040:          3D B4  : Register [Bit] op OUTA Write
    
    vs your version
    8                            outa[txPin] := txByte & 1                 ' output lsb,
    Addr : 0032:             64  : Variable Operation Local Offset - 1 Read
    Addr : 0033:             36  : Constant 2 $00000001
    Addr : 0034:             E8  : Math Op &     
    Addr : 0035:             68  : Variable Operation Local Offset - 2 Read
    Addr : 0036:          3D B4  : Register [Bit] op OUTA Write
    9                            txByte >>= 1                                   ' then shift right to move next bit in place for tx
    Addr : 0038:             36  : Constant 2 $00000001
    Addr : 0039:          66 42  : Variable Operation Local Offset - 1 Assign ByteMathop >>
    
    So for the sake of an extra bit shift before the loop it becomes more efficient during the loop.
    This isn't a problem at low baud rates but makes a difference at high baud rates.
    The stop bit should be included in loop too with a waitcnt but these routines don't normally run that fast and nor does Spin.

    By way of contrast a high level loop like this in my Tachyon Forth runs up to 250k baud vs the 19.2k baud or so of Spin (which takes 38 bytes).
    [FONT=courier new]\ Code size: 18 bytes
    \ Tested to 250K baud
    pub SEROUT ( dataByte pin -- \ send data byte to pin at rate set with SERBAUD ) 
        MASK DUP OUTSET                \ ensure pin is an output (very first time perhaps)
        SWAP 2*                        \ Include start bit (0)
        baudcnt @ DELTA                \ setup delay and wait
          9 FOR WAITCNT SHROUT NEXT    \ data bits
        DROP WAITCNT OUTSET            \ final stop bit
        ;[/FONT]
    
  • ErlendErlend Posts: 612
    edited 2014-02-27 05:40
    Thanks, and thanks for a taste of Forth in this application - extremely compact!. I did learn some Forth 30 years ago - out of curiosity - but my mind seems to be wired for the 'verbal' high level languages born from Pascal etc., so I guess that makes me more of a Spin person.
    I didn't realise that code penalty was so high for my suggested change in the code. It doesn't matter in practice, but from a 'coding estetics' point of view I guess it hurts. Same goes for the half-hearted coding of the stop bit.

    Erlend
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2014-02-27 06:12
    Erlend wrote: »
    Thanks, and thanks for a taste of Forth in this application - extremely compact!. I did learn some Forth 30 years ago - out of curiosity - but my mind seems to be wired for the 'verbal' high level languages born from Pascal etc., so I guess that makes me more of a Spin person.
    I didn't realise that code penalty was so high for my suggested change in the code. It doesn't matter in practice, but from a 'coding estetics' point of view I guess it hurts. Same goes for the half-hearted coding of the stop bit.

    Erlend
    Actually I missed a line in the first listing, just added it back, it's this one here:
    Addr : 0040: 3D B4 : Register [Bit] op OUTA Write

    So the compact form didn't really save anything at all, just one byte!
    As for Forth you are missing all the fun of interacting with the Prop itself. I can't say about the "wiring" thing because I don't have a problem with "verbal" (do you swear at it? :) ) languages but they don't live on the Prop or interact with it, only on the PC.
    Look at the loop for the start and data bits (9 bits)
    9 FOR WAITCNT SHROUT NEXT
    How simple is that?, and only 6 bytes.
  • ErlendErlend Posts: 612
    edited 2014-02-27 06:32
    ...
    Look at the loop for the start and data bits (9 bits)
    9 FOR WAITCNT SHROUT NEXT
    How simple is that?, and only 6 bytes.

    Yes, Peter I did see that and thought: Wow, I am sorry I am not part of this joy. Well, I may have a long life ahead of me, so you never know if I will some day go Forth and enlighten myself : )

    Erlend
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2014-02-27 09:40
    The &1 is not necessary. The output pin is one bit, and it will pick up only the lsbit of the txbyte.

    outa[txPin] := (txByte >>= 1) & 1
  • ErlendErlend Posts: 612
    edited 2014-02-27 11:57
    Thanks Tracy, I should have known, I've learned that before, but obviously not all knowledge sticks the first time.
    Erlend
Sign In or Register to comment.