HWSUART

Download hwsuart v1.0

Overview

Hardware-assisted software uart (hwsuart)

This module is an interrupt-driven 'software' uart that uses hardware peripheral functions to assist in the generation of the serial data timing and detection of the RX start bit. In addition, it provides an input RX buffer to hold incoming data allowing the module to be used for asyncronous reception (which is something most software uarts cannot do).

There are three implementations provided:

  • hwsuart_T0CKI - Simplest, and supports most devices.
                  Uses TMR0 T0CKI pin for RX input. 
                  Half-duplex (cannot transmit and receive simultaneuosly)
  • hwsuart_HD - Uses INT, TMR, and CCP peripherals.
               RX input can be INT0, INT1, INT2, or IOCB4.
               Half-duplex (cannot transmit and receive simultaneuosly)
  • hwsuart_FD - Uses INT, and two TMR/CCP peripherals for independant RX/TX timing.
               RX input can be INT0, INT1, INT2, or IOCB4.
               Full-duplex (simultaneous transmit/receive supported)

The RX input must be connected to a pin that can generate an interrupt on a high- to-low transition in order to detect the beginning of the incoming START bit. The TX output can be assigned to any pin.

For all implementations the max allowable baudrate depends on the cpu clock frequency, but in general the half-duplex methods support up to 115K baud while full-duplex is limited to 57600.

Example 1 - simple echo test

program hwsuart_test

// define device and clock
device = 18F25K22
clock = 64

include "intosc.bas"
include "setdigitalio.bas"

// select library to use (T0CKI tmr0, HD half-duplex, or FD full-duplex)
#option LIB_SEL = T0CKI

#option HWSUART_DEBUG_PIN = PORTC.3     // debug output pin used for scope timing
#if (LIB_SEL = T0CKI)
#warning "using hwsuart_T0CKI tmr0 library"
// RXD must be the T0CKI pin (on K22 T0CKI=RA4)
#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_RXD_PIN = PORTA.4   // RXD pin (MUST be T0CKI)
#option HWSUART_RX_BUFFER_SIZE = 4
#option HWSUART_TX_PACING = 10      // in usecs
include "hwsuart_T0CKI.bas"

#elseif (LIB_SEL = HD)
#warning "using hwsuart_HD half-duplex library"
#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_INT_SELECT = 0      // RXD pin 0=INT0, 1=INT1, 2=INT2, 4=IOCB4
#option HWSUART_TMR_SELECT = 1      // TMR
#option HWSUART_CCP_SELECT = 1      // CCP
#option HWSUART_RX_BUFFER_SIZE = 4
include "hwsuart_HD.bas"

#elseif (LIB_SEL = FD)
#warning "using hwsuart_FD full-duplex library"
#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_INT_SELECT = 0      // RXD pin 0=INT0, 1=INT1, 2=INT2, 4=IOCB4
#option HWSUART_TMR_SELECT = 1      // TMR
#option HWSUART_CCP_SELECT = 1      // CCP
#option HWSUART_TX_TMR_SELECT = 3   // TX TMR
#option HWSUART_TX_CCP_SELECT = 2   // TX CCP
#option HWSUART_RX_BUFFER_SIZE = 64
include "hwsuart_FD.bas"
#endif

const
    CR = 13,
    LF = 10

public dim rxc as byte          // last received char
public dim baud as longword     // baud rate

main:
setalldigital()
delayms(10)

// init the hwsuart
baud = 115200
hwsuart.initialize(baud)

// test tx timing (infinite loop sending 'U' $55)
#if (false)
hwsuart.check_bit_timing()
#endif

// show we're alive
hwsuart.write_byte(">")

// echo test
while (true)
    // check to see if there's a byte available to read
    if (hwsuart.data_available()) then
        rxc = hwsuart.read_byte()   // get byte from the rx buffer
        hwsuart.write_byte(rxc)     // and echo it
        if (rxc = CR) then          // if CR then send a LF to go along with it
            hwsuart.write_byte(LF)
        endif
    endif
end while

end program

Example 2 - RX input buffer test

program hwsuart_test

// define device and clock
device = 18F25K22
clock = 64

include "intosc.bas"
include "setdigitalio.bas"

// select library to use (T0CKI tmr0, HD half-duplex, or FD full-duplex)
#option LIB_SEL = T0CKI

#option HWSUART_DEBUG_PIN = PORTC.3     // debug output pin used for scope timing
#if (LIB_SEL = T0CKI)
#warning "using hwsuart_T0CKI tmr0 library"
// RXD must be the T0CKI pin (on K22 T0CKI=RA4)
#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_RXD_PIN = PORTA.4   // RXD pin (MUST be T0CKI)
#option HWSUART_RX_BUFFER_SIZE = 4
#option HWSUART_TX_PACING = 10      // in usecs
include "hwsuart_T0CKI.bas"

#elseif (LIB_SEL = HD)
#warning "using hwsuart_HD half-duplex library"
#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_INT_SELECT = 0      // RXD pin 0=INT0, 1=INT1, 2=INT2, 4=IOCB4
#option HWSUART_TMR_SELECT = 1      // TMR
#option HWSUART_CCP_SELECT = 1      // CCP
#option HWSUART_RX_BUFFER_SIZE = 4
include "hwsuart_HD.bas"

#elseif (LIB_SEL = FD)
#warning "using hwsuart_FD full-duplex library"
#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_INT_SELECT = 0      // RXD pin 0=INT0, 1=INT1, 2=INT2, 4=IOCB4
#option HWSUART_TMR_SELECT = 1      // TMR
#option HWSUART_CCP_SELECT = 1      // CCP
#option HWSUART_TX_TMR_SELECT = 3   // TX TMR
#option HWSUART_TX_CCP_SELECT = 2   // TX CCP
#option HWSUART_RX_BUFFER_SIZE = 64
include "hwsuart_FD.bas"
#endif

const
    CR = 13,
    LF = 10

public dim rxc as byte          // last received char
public dim baud as longword     // baud rate
public dim numchars as byte     // number of chars in the input buffer
public dim maxchars as byte     // max number of chars seen in the buffer
public dim txchars as word      // number of chars we've transmitted

main:
setalldigital()
delayms(10)

// init statistics
maxchars = 0
txchars = 0

// init the hwsuart
baud = 115200
hwsuart.initialize(baud)

// set addtl pacing delay of one bit time (ie two stop bits)
hwsuart.tx_pacing = hwsuart.bit_time()

// high-level functions
hwsuart.write("hwsuart echo test...", CR, LF)

// low-level functions (non-blocking)
while (true)
    // check to see if there's a byte available to read
    if (hwsuart.data_available()) then
        rxc = hwsuart.read_byte()   // get byte from the rx buffer
        hwsuart.write_byte(rxc)     // and echo it
        if (rxc = CR) then          // if CR then send a LF to go along with it
            hwsuart.write_byte(LF)
        endif
        txchars = txchars + 1       // count of chars we've transmitted    
    endif

    // test rx buffering
    // baudrate and RX_BUFFER_SIZE determine how long we can wait here
    delayms(1)          // delay while chars come in
    // see if we can keep up with the transmitter sending chars...
    // record max number of chars in rx buffer
    numchars = hwsuart.buffer_count()
    if (numchars > maxchars) then
        maxchars = numchars
    endif

    if (hwsuart.is_full()) then         // buffer is full...
        while (hwsuart.data_available())    // empty (and echo) it
            rxc = hwsuart.read_byte()
            hwsuart.write_byte(rxc)
        end while
        hwsuart.write_byte(CR)
        hwsuart.write_byte(LF)
    endif
    if (hwsuart.overrun()) then
        hwsuart.flush_input()
    endif
end while

end program

Example 3 - high-level function examples

program hwsuart_test

// define device and clock
device = 18F25K22
clock = 64

include "intosc.bas"
include "setdigitalio.bas"

// select library to use (T0CKI tmr0, HD half-duplex, or FD full-duplex)
#option LIB_SEL = FD

#option HWSUART_DEBUG_PIN = PORTC.3     // debug output pin used for scope timing
#if (LIB_SEL = T0CKI)
#warning "using hwsuart_T0CKI tmr0 library"
// RXD must be the T0CKI pin (on K22 T0CKI=RA4)
#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_RXD_PIN = PORTA.4   // RXD pin (MUST be T0CKI)
#option HWSUART_RX_BUFFER_SIZE = 4
#option HWSUART_TX_CHAR_PACING = 10     // in usecs, 0-255
include "hwsuart_T0CKI.bas"

#elseif (LIB_SEL = HD)
#warning "using hwsuart_HD half-duplex library"
#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_INT_SELECT = 0      // RXD pin 0=INT0, 1=INT1, 2=INT2, 4=IOCB4
#option HWSUART_TMR_SELECT = 1      // TMR
#option HWSUART_CCP_SELECT = 1      // CCP
#option HWSUART_RX_BUFFER_SIZE = 4
include "hwsuart_HD.bas"

#elseif (LIB_SEL = FD)
#warning "using hwsuart_FD full-duplex library"
#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_INT_SELECT = 0      // RXD pin 0=INT0, 1=INT1, 2=INT2, 4=IOCB4
#option HWSUART_TMR_SELECT = 1      // TMR
#option HWSUART_CCP_SELECT = 1      // CCP
#option HWSUART_TX_TMR_SELECT = 3   // TX TMR
#option HWSUART_TX_CCP_SELECT = 2   // TX CCP
#option HWSUART_RX_BUFFER_SIZE = 64
include "hwsuart_FD.bas"
#endif

const
    CR = 13,
    LF = 10

public dim baud as longword     // baud rate
public dim rxc as byte          // last received char
public dim s as string

// define a string/buffer union
structure SBUF_T
    buf(24) as byte union
    s       as string union
end structure
public dim sb as SBUF_T

main:
setalldigital()
delayms(10)

// init the hwsuart
baud = 115200
hwsuart.initialize(baud)

// high-level functions
hwsuart.write(CR, LF)
hwsuart.write("hwsuart function test...", CR, LF)

// read and echo string
// default length of a string is 23 chars + null
hwsuart.ReadTerminator = CR
hwsuart.write("read/write string (23 chars max + CR): ")
hwsuart.read(s)
hwsuart.write(s, CR, LF)

// change read terminator to ':' char
hwsuart.ReadTerminator = ":"
hwsuart.write("read string ':' term: ")
hwsuart.read(s)
hwsuart.write(s, CR, LF)

// check data available with timeout
hwsuart.flush_input()
hwsuart.write("data available timeout (5 sec): ")
if (DataAvailableTimeout(5000)) then
    hwsuart.write("ok")
else
    hwsuart.write("**timeout**")
endif
hwsuart.write(CR, LF)

// wait up to 5 secs for "1"
// discards all chars
hwsuart.flush_input()
hwsuart.write("waitfor '1' timeout (5 sec): ")
if (WaitForTimeout(byte("1"), 5000)) then
    hwsuart.write("ok")
else
    hwsuart.write("**timeout**")
endif
hwsuart.write(CR, LF)

// waitfor 10 bytes
hwsuart.flush_input()
hwsuart.write("waitfor 10 chars: ")
WaitForCount(sb.buf, 10)
hwsuart.write("ok ")
sb.buf(10) = 0
hwsuart.write(sb.s, CR, LF)

hwsuart.write("echo test: ")
while(true)
    // check to see if there's a byte available to read
    if (hwsuart.data_available()) then
        rxc = hwsuart.read_byte()   // get byte from the rx buffer
        hwsuart.write_byte(rxc)     // and echo it
        if (rxc = CR) then          // if CR then send a LF to go along with it
            hwsuart.write_byte(LF)
        endif
    endif
end while

end program

Example 4 - Using PPS to assign RX input (18FxxK40)

program hwsuart_test

// define device and clock
device = 18F27K40
clock = 64

include "intosc.bas"
include "setdigitalio.bas"

// for the K40 we're going to use PPS to assign T0CKI RXD pin to PORTB.0
include "pps.bas"

#option HWSUART_TXD_PIN = PORTC.6   // TXD pin...  use RC6_TX
#option HWSUART_RXD_PIN = PORTB.0   // RXD pin (MUST be T0CKI, default is PORTA.4)
#option HWSUART_RX_BUFFER_SIZE = 4
#option HWSUART_TX_CHAR_PACING = 10     // in usecs, 0-255
include "hwsuart_T0CKI.bas"

const
    CR = 13,
    LF = 10

public dim rxc as byte          // last received char
public dim baud as longword     // baud rate

main:
setalldigital()

// assign T0CKI input to RB0
pps.assign_input(T0CKIPPS, PPS_PORTB, 0)

delayms(10)

// init the hwsuart
baud = 115200
hwsuart.initialize(baud)

// high-level functions
hwsuart.write("hwsuart echo test...", CR, LF)

// low-level functions (non-blocking)
while (true)
    // check to see if there's a byte available to read
    if (hwsuart.data_available()) then
        rxc = hwsuart.read_byte()   // get byte from the rx buffer
        hwsuart.write_byte(rxc)     // and echo it
        if (rxc = CR) then          // if CR then send a LF to go along with it
            hwsuart.write_byte(LF)
        endif
    endif
end while

end program