UNIO

This module provides support for serial EEPROMs and the Microchip UNI/O one-wire bus.

UNIO Module

//-----------------------------------------------------------------------------
// name     : unio.bas
// author   : Jerry Messina
// date     : 12/1/2014
// version  : 1.0.0
// notes    : serial EEPROM support for the Microchip UNI/O one-wire protocol
//-----------------------------------------------------------------------------
module unio

//
// adapted from the Microchip app note AN1191
// "Using C18 and a Timer to Interface PIC18 MCUs with UNIO Bus-Compatible Serial EEPROMs"
//
// resources required:
//  - one IO pin with ext 20K pull-up to ensure bus idle during power-up (#option SCIO_PIN) 
//  - one timer and one CCP compare module (hard coded to use TMR1/CCP1)
//  - optional IO for debugging (#option UNIO_TRIGGER/#option TRIGIO_PIN)
//
// note: the UNI/O bus is very timing sensitive, and for reliable operation you
// will need to use these routines with interrupts disabled
//

//-----------------------------------------------------------------------------
// IO pin definition options
//-----------------------------------------------------------------------------
#option SCIO_PIN = PORTB.2
#option SCIO_PIN_TRIS = GetTRIS(SCIO_PIN)

// debug trigger pin enable
// if enabled, this pin is asserted high during the input pin sample point
// this can be used to measure/verify timing
#option UNIO_TRIGGER = false
#option TRIGIO_PIN = PORTB.1
#if (UNIO_TRIGGER)
  #warning "UNIO trigger IO output enabled"
#endif

// IO pin definitions (from #options)
dim
    SCIO as SCIO_PIN.SCIO_PIN@,             // serial clock, input/output pin
    SCIOTRIS as SCIO_PIN_TRIS.SCIO_PIN@     // SCIO direction bit

// debug trigger pin (enabled by #option UNIO_TRIGGER = true)
dim
    TRIGIO as TRIGIO_PIN.TRIGIO_PIN@

//-----------------------------------------------------------------------------
// bus timings
//-----------------------------------------------------------------------------
// if using the IDE Code Explorer to view this module directly, system _clock 
// may not be visible here so the constants will display with different values
// than are actually used when compiling. 
// change the '#define _SYSCLOCK' manually to view them by setting it to the 
// 'clock' value in use, but be sure to change it back to '_clock' when done
#define _SYSCLOCK = _clock      // insert 'clock=' setting here
const SYSCLOCK = _SYSCLOCK
// make sure SYSCLOCK is set correctly to compile
#if (_SYSCLOCK <> _clock)
  #error "set '_SYSCLOCK = _clock' to build"
#endif

// bus freq option
#option UNIO_BUSFREQ = 25000            // set UNIO bus frequency (10KHz-100KHz)
#if (UNIO_BUSFREQ < 10000) or (UNIO_BUSFREQ > 100000)
  #warning "UNIO_BUSFREQ should be 10KHz-100KHz"
#endif
#define _FCY = _SYSCLOCK*1000000/4      // pic18 instruction cycle (in Hz)
// note: the max usable UNIO bus freq is dependant on the system clock
// some examples:
//  clock= 8Mhz: 26000
//  clock=16MHz: 52000
//  clock=32MHz: 100000 (usable to 104KHz)
//  clock=64MHz: 100000 (usable to 200KHz)

// basic Timer period calculations
// calc number of instruction cycles in 1/2 period
#variable _HALF_PERIOD = (_FCY/(2*UNIO_BUSFREQ))
// make sure that _HALF_PERIOD is even so that _QUARTER_PERIOD is accurate
#if ((_HALF_PERIOD and 1) = 1)
  #variable _HALF_PERIOD = _HALF_PERIOD + 1
#endif
#define _QUARTER_PERIOD = (_HALF_PERIOD/2)
// check to see if we need to use full 16-bit timer operation (uses more code)
#if (_HALF_PERIOD > 255)
  #warning "UNIO enabling 16-bit timer code"
  #option UNIO_USE_16BIT_TIMER = true
  type timer_val_t = word
#else
  #option UNIO_USE_16BIT_TIMER = false
  type timer_val_t = byte
#endif 

// create the timer constants from the '#defines'
const HALF_PERIOD as word = _HALF_PERIOD
const QUARTER_PERIOD as word = _QUARTER_PERIOD
const THDR_COUNT as word = 5/(1.0/_SYSCLOCK*4)            // 5us min

// unio bus frequencies range from 10KHz (100us) to 100KHz (10us) per bit
// check if there's enough time to execute the code. currently there's about
// 18 instructions or so in the InputByte/InputBit loop. if _QUARTER_PERIOD
// approaches this, then things start to fail since there's just too few 
// instruction cycles vs the 1/4 bit timing req'd for the InputBit routine
#if (_QUARTER_PERIOD < 20) then
  #error "timer period smaller than code execution cycles. reduce UNIO_BUSFREQ"
#endif

//-----------------------------------------------------------------------------
// UNIO constant definitions
//-----------------------------------------------------------------------------
const 
    STARTHDR = $55,                 // START HEADER sequence
    MAK = 1,                        // master acknowledge (MAK)
    NOMAK = 0,                      // master not acknowledge (NoMAK)
    SAK = 1,                        // slave acknowledge (SAK)
    NOSAK = 0                       // slave not acknowledge (NoSAK)

// UNIO flags
structure flags_t
    b as byte
    sendMAK  as b.bits(0)           // master send 1=MAK, 0=NoMAK
    checkSAK as b.bits(1)           // check slave ack
    SAK1     as b.bits(3)           // SAK sample #1 (at T=1/4 bit)
    SAK2     as b.bits(4)           // SAK sample #2 (at T=3/4 bit)
end structure

dim
    dataOut as byte,                // 8-bit data output buffer
    dataIn as byte,                 // 8-bit data input buffer
    flags as flags_t                // transfer status/control flags

//-----------------------------------------------------------------------------
// eeprom definitions
//-----------------------------------------------------------------------------
// 11XXXX0 device code 0000
// 11XXXX1 device code 0001
public const
    DEV_CODE = %0000
public const
    DEVICE_ADDR = $A0 + DEV_CODE,   // define DEVICE_ADDR (eeprom family code=0xA0)
    PAGESIZE = 16                   // 16-byte page size for 11XXXXX

// 11AA02E48/11AA02E64 devices are 256x8 (2K bit) eeproms with a
// pre-programmed globally unique 48-bit or 64-bit Node Address (EUI48/EUI64)
public const
    EUI48_ADDR = $FA,               // EUI-48 addr location (11AA02E48)
    EUI48_SIZE = 6,                 // EUI-48 size, in bytes
    EUI64_ADDR = $F8,               // EUI-64 addr location (11AA02E64)
    EUI64_SIZE = 8                  // EUI-64 size, in bytes

// eeprom command definitions
const
    WREN  = %10010110,              // WREN command
    WRDI  = %10010001,              // WRDI command
    WRITE = %01101100,              // WRITE command
    READ  = %00000011,              // READ command
    WRSR  = %01101110,              // WRSR command
    RDSR  = %00000101,              // RDSR command
    ERAL  = %01101101,              // ERAL command
    SETAL = %01100111               // SETAL command

// eeprom status reg
structure statusreg_t
    b as byte
    WIP as b.bits(0)
    WEL as b.bits(1)
    BP0 as b.bits(2)
    BP1 as b.bits(3)
end structure

//-----------------------------------------------------------------------------
// timer hardware resources
//-----------------------------------------------------------------------------
// to support the full range of bus freq and sys clock settings, we use a 16-bit 
// timer in special event trigger mode and a CCP compare module to give a 
// programmable period register
//
// the pic18 app note only uses the low byte of the timer and CCP, always setting
// the high byte = 0 ...
//      TMR1H = 0
//      TMR1L += (HALF_PERIOD/2+1)
// this makes for faster updates to the timer, so it produces more accurate results
// since the timer continues to count, the longer the timer read-add-write sequence,
// the more the error. however, this limits the system clock and SCIO BUSFREQ settings.
// UNIO is timing sensitive so the slower the uC, the slower the max usable BUSFREQ
const
    CCP1IF = 2,                         // register bit numbers
    TMR1ON = 0
dim
    TMRCON    as T1CON,
    TIMER_ON  as T1CON.bits(TMR1ON),    // bit 0: TMR1ON
    CCPCON    as CCP1CON,
    PERIOD_IF as PIR1.bits(CCP1IF)      // bit 2: CCP1IF

// timer/CCP setup (NOTE: this varies with device, so be sure to check the bits)
// prescaler 1:1, internal clock (Fosc/4), 16-bit mode enabled (RD16)
const
    TCON_CONFIG as byte = %00000010
// compare mode: trigger special event, reset timer, CCPx match (CCPxIF bit is set) 
const    
    CCPCON_CONFIG as byte = %00001011

// write 16-bit timer with the value 'wval'
macro WRITE_TIMER(wval)
  #if (UNIO_USE_16BIT_TIMER)
    TMR1H = (wval >> 8)         // high byte not actually updated yet
  #else
    TMR1H = 0
  #endif
    TMR1L = byte(wval)          // loads low and high bytes
end macro

// reads 16-bit timer, stores it in 'result'
macro READ_TIMER(result)
  #if (UNIO_USE_16BIT_TIMER)
    result.byte0 = TMR1L        // read low byte (latches the high byte)
    result.byte1 = TMR1H
  #else
    result = TMR1L              // read low byte (latches the high byte)
  #endif
end macro

// add a value to the current timer setting
dim PROD as PRODL.asword
macro ADD_TIMER(wval)
  #if (UNIO_USE_16BIT_TIMER)
    PRODL = TMR1L               // read TMR low byte (latches the high byte)
    PRODH = TMR1H
    PROD = PROD + word(wval)
    TMR1H = PRODH               // high byte not actually updated yet
    TMR1L = PRODL               // loads TMR low and high bytes
  #else
    TMR1H = 0                   // high byte not actually updated yet
    TMR1L = TMR1L + byte(wval)  // loads TMR low and high bytes
  #endif
end macro

// load ccp period register
macro LOAD_PERIOD(w)
  #if (UNIO_USE_16BIT_TIMER)
    CCPR1H = byte(w >> 8)
  #else
    CCPR1H = 0
  #endif
    CCPR1L = byte(w and $FF)
end macro

// wait for ccp period
inline sub WAIT_FOR_PERIOD_IF()
    while (PERIOD_IF = 0)       // wait until CCP1IF flag is set
    end while
    PERIOD_IF = 0               // clear CCP1IF flag
end sub

// debug trigger pin
macro TRIGGER(x)
  #if (UNIO_TRIGGER)
    TRIGIO = x
  #endif
end macro

//--------------------------------------------------------------------
// private sub Tss()
//  Hold SCIO high for Tss start header setup time period (10 us)
//--------------------------------------------------------------------
private sub Tss()
    delayus(10)             // delay for Tss time period
end sub

//--------------------------------------------------------------------
// private sub Standby()
//  Waits until end of current bit period has been reached, ensures 
//  SCIO is high for bus idle, and disables the Timer
//--------------------------------------------------------------------
private sub Standby()
    WAIT_FOR_PERIOD_IF()
    TIMER_ON = 0            // stop Timer1
    SCIO = 1                // ensure SCIO is high for bus idle
    SCIOTRIS = 0            // ensure SCIO is outputting
end sub

//--------------------------------------------------------------------
// private sub InputBit()
//  Inputs and Manchester-decodes a bit of data from SCIO and stores it
//  in the LSb of dataIn. For detecting SAK/NoSAK, the SCIO pin is sampled
//  twice... once at T=1/4 (SAK1) and again at T=3/4 (SAK2). This is done
//  so that we can detect a Machester '1' vs an idle line. For a regular
//  dataIn bit, we just sample SCIO once at T=3/4
//
//  note: the original app note code changes the sampling points by reading
//  and adding values to the timer while its running. writing to the timer 
//  this way means you have to account for the code execution time, 
//  otherwise you get extra cycles and time offsets. instead, here we 
//  leave the timer alone and change the period register to detect the 
//  different sampling points. this is a bit more forgiving, but you still
//  have to make sure that things can execute fast enough to meet the
//  code deadlines
//--------------------------------------------------------------------
private sub InputBit()
    dim t as timer_val_t

    // set CCP to 1/4th period so we're offset by 1/4th bit period
    LOAD_PERIOD(QUARTER_PERIOD)

    // set default SAK bits... they're only valid after an Acknowledge
    flags.SAK1 = 1
    flags.SAK2 = 1

    // wait until flag is set, indicating we are 1/4th into bit period
    WAIT_FOR_PERIOD_IF()

    // sample SCIO pin (we do this twice for Manchester decoding)
    if (SCIO = 0) then
        flags.SAK1 = 0              // if 0, clear 1st SAK bit
    endif

    // wait until flag is set again so we are 1/4 + 1/4 = 1/2 into bit period
    WAIT_FOR_PERIOD_IF()

    // now that we're at the halfway-point, put period reg back to 1/2 period
    LOAD_PERIOD(HALF_PERIOD)

    // wait for timer to indicate we are 3/4th into period so we can read SCIO
    repeat
        READ_TIMER(t)
    until (t >= QUARTER_PERIOD)

    // read and update data
    dataIn.bits(0) = 1              // sssume data bit will be a 1
    TRIGGER(1)                      // assert debug trigger pin (if enabled)
    if (SCIO = 0) then              // sample SCIO pin
        dataIn.bits(0) = 0          // if 0, clear data bit
        flags.SAK2 = 0              // and 2nd SAK bit (for Manchester decode)
    endif
    TRIGGER(0)                      // release debug trigger
end sub

//--------------------------------------------------------------------
// private function InputByte() as byte
//  Inputs and Manchester-decodes from SCIO a byte of data, stores it 
//  in dataIn, and returns the byte
//--------------------------------------------------------------------
private function InputByte() as byte
    dim count as byte

    // loop through byte
    count = 8
    repeat
        WAIT_FOR_PERIOD_IF()        // wait until CCP1IF flag is set
        dataIn = dataIn << 1        // shift dataIn left to prepare for next bit
        InputBit()                  // get input bit
        count = count - 1
    until (count = 0)
    result = dataIn
end function

//--------------------------------------------------------------------
// private sub OutputHalfBit()
//  Waits until Timer 1 rolls over, then outputs the next half of the 
//  current bit period. The value used is specified by the MSb of dataOut
//--------------------------------------------------------------------
private sub OutputHalfBit()
    WAIT_FOR_PERIOD_IF()            // wait until CCP1IF flag is set

    if (dataOut.bits(7) = 1) then   // check if dataOut MSb is 1
        SCIO = 0                    // if 1, set SCIO low
    else
        SCIO = 1                    // if 0, set SCIO high
    endif

    dataOut = not(dataOut)          // invert dataOut for next half bit
end sub

//--------------------------------------------------------------------
// private sub AckSequence()
//  Performs Acknowledge sequence, including MAK/NoMAK as specified by
//  flags.sendMAK, and SAK
//--------------------------------------------------------------------
private sub AckSequence()
    // prepare for sending MAK bit
    dataOut.bits(7) = 1             // assume MAK will be sent
    if (flags.sendMAK = 0) then     // check if MAK is to be sent
        dataOut.bits(7) = 0         // if not sending MAK, clear data bit
    endif

    // send MAK/NoMAK bit
    SCIOTRIS = 0                    // ensure SCIO is outputting
    OutputHalfBit()                 // output first half of bit
    OutputHalfBit()                 // output second half of bit

    // release SCIO at end of bit period
    WAIT_FOR_PERIOD_IF()            // wait until CCP1IF flag is set
    SCIOTRIS = 1                    // set SCIO to be an input

    // detect Acknowledge status (whether it's used or not)
    // this is different from the app note which only checks when 
    // checkSAK = 1, but then it has to adjust timing otherwise.
    // it's simpler to read it and just ignore SAK if you don't care
    InputBit()
end sub

//--------------------------------------------------------------------
// private sub OutputByte(b as byte)
//  Manchester-encodes & outputs the byte value 'b' to SCIO
//--------------------------------------------------------------------
private sub OutputByte(b as byte)
    dim count as byte

    // set output data byte (needed by OutputHalfBit routine)
    dataOut = b

    // loop through byte sending msb to lsb
    count = 8
    SCIOTRIS = 0                    // ensure SCIO is outputting
    repeat
        OutputHalfBit()             // output first half of bit
        OutputHalfBit()             // output second half of bit
        dataOut = dataOut << 1      // shift data left 1 bit
        count = count - 1
    until (count = 0)

    AckSequence()                   // perform Acknowledge Sequence
end sub

//--------------------------------------------------------------------
// private sub StartHeader()
//  Hold SCIO low for Thdr time period (5us min) and output Start Header
//  ('01010101')
//--------------------------------------------------------------------
private sub StartHeader()
    SCIO = 0                        // set SCIO low
    SCIOTRIS = 0                    // ensure SCIO is driving
    PERIOD_IF = 0                   // clear CCP1IF flag

    // load TMR so that the CCP generates a THDR_COUNT time period 
    WRITE_TIMER(HALF_PERIOD-THDR_COUNT)    // load TMR1 to count THDR

    flags.sendMAK = 1               // send MAK bit
    flags.checkSAK = 0              // do not check for SAK bit
    TIMER_ON = 1                    // enable Timer1
    OutputByte(STARTHDR)            // output Start Header value
    flags.checkSAK = 1              // check for SAK bit
end sub

//--------------------------------------------------------------------
// private sub StandbyPulse()
//  Before communicating with a new device, a standby pulse must be 
//  performed. This pulse signals to all slave devices on the bus to 
//  enter Standby mode, awaiting a new command to begin. The standby 
//  pulse can also be used to prematurely terminate a command.
//  The standby pulse consists of holding SCIO high for a minimum of 
//  Tstby (600us). After this has been performed, the slave devices 
//  will be ready to receive a command
//--------------------------------------------------------------------
public sub StandbyPulse()
    SCIO = 1                        // ensure SCIO is high
    SCIOTRIS = 0                    // ensure SCIO is driving
    delayus(600)                    // delay for Tstby time
end sub

//--------------------------------------------------------------------
// private sub SendCmd(b as byte)
//  common initial sequence for sending commands
//--------------------------------------------------------------------
private sub SendCmd(b as byte)
    Tss()                           // observe Tss time
    StartHeader()                   // output Start Header
    OutputByte(DEVICE_ADDR)         // send DEVICE_ADDR
    OutputByte(b)                   // send command byte
end sub

//--------------------------------------------------------------------
// public function DeviceDetect(addr as byte = DEVICE_ADDR) as boolean
//  perform an address poll of device at 'addr'
//  returns true if device acks, false otherwise
//--------------------------------------------------------------------
public function DeviceDetect(addr as byte = DEVICE_ADDR) as boolean
    StandbyPulse()                  // generate a standby pulse
    Tss()                           // observe Tss time
    StartHeader()                   // output Start Header
    flags.sendMAK = NOMAK           // addr byte sent w/NoMAK
    OutputByte(addr)                // send DEVICE_ADDR
    result = false                  // assume slave not present
    if ((flags.SAK1 = 0) and (flags.SAK2 = 1)) then
        result = true               // device ACKed
    endif
    flags.sendMAK = MAK             // put flags back to normal
    // a standby pulse here is a good idea in case of no slave ack
    StandbyPulse()
end function

//
//--------------------------------------------------------------------
// EEPROM specific routines
//--------------------------------------------------------------------
//
//--------------------------------------------------------------------
// public sub WIP_Poll()
//  This function performs WIP polling to determine the end of the current
//  write cycle. It does this by continuously executing a Read Status 
//  Register operation until the WIP bit (bit 0 of the Status Register)
//  is read low
//--------------------------------------------------------------------
public sub WIP_Poll()
    dim stat as statusreg_t

    SendCmd(RDSR)                   // send RDSR command
    stat = InputByte()              // input status byte
    while (stat.WIP = 1)
        AckSequence()               // perform Acknowledge Sequence
        stat = InputByte()          // input status byte
    end while
    flags.sendMAK = 0               // send NoMAK
    AckSequence()                   // perform Acknowledge Sequence
    Standby()                       // gracefully enter Standby
end sub

//--------------------------------------------------------------------
// public sub WriteDisable()
//  This function performs a Write Disable instruction to clear the 
//  WEL bit in the Status Register
//--------------------------------------------------------------------
public sub WriteDisable()
    Tss()                           // observe Tss time
    StartHeader()                   // output Start Header
    OutputByte(DEVICE_ADDR)         // send DEVICE_ADDR
    flags.sendMAK = 0               // send NoMAK
    OutputByte(WRDI)                // send WRDI command
    Standby()                       // gracefully enter Standby
end sub

//--------------------------------------------------------------------
// public sub WriteEnable()
//  This function performs a Write Enable instruction to set the WEL 
//  bit in the Status Register
//--------------------------------------------------------------------
public sub WriteEnable()
    Tss()                           // observe Tss time
    StartHeader()                   // output Start Header
    OutputByte(DEVICE_ADDR)         // send DEVICE_ADDR
    flags.sendMAK = 0               // send NoMAK
    OutputByte(WREN)                // send WREN command
    Standby()                       // gracefully enter Standby
end sub

//--------------------------------------------------------------------
// public function ReadStatusReg() as statusreg_t
//  This function reads and returns the value of the Status Register
//--------------------------------------------------------------------
public function ReadStatusReg() as statusreg_t
    SendCmd(RDSR)                   // send RDSR command
    result = InputByte()            // read input byte
    flags.sendMAK = 0               // send NoMAK
    AckSequence()                   // perform Acknowledge Sequence
    Standby()                       // gracefully enter Standby
end function

//--------------------------------------------------------------------
// public sub WriteStatusReg(status as byte)
//  This function writes the byte value specified by 'status' to the 
//  Status Register on the serial EEPROM
//--------------------------------------------------------------------
public sub WriteStatusReg(status as byte)
    WriteEnable()                   // set Write Enable Latch
    SendCmd(WRSR)                   // send WRSR command
    flags.sendMAK = 0               // send NoMAK
    OutputByte(status)              // output byte
    Standby()                       // gracefully enter Standby

    WIP_Poll()                      // perform WIP Polling
end sub

//--------------------------------------------------------------------
// public sub SeqRead(addr as word, byref buffer() as byte, count as byte)
//  This function reads 'count' bytes from the serial EEPROM starting at 
//  eeprom location 'addr', and stores the bytes into 'buffer()'
//  Parameters: addr    - EEPROM memory location at which to start
//              buffer()- dest buffer address
//              count   - number of bytes to read
//--------------------------------------------------------------------
public sub SeqRead(addr as word, byref buffer() as byte, count as byte)
    SendCmd(READ)                   // send READ command
    OutputByte(addr.bytes(1))       // send address MSB
    OutputByte(addr.bytes(0))       // send address LSB
    FSR2 = addressof(buffer)        // set addr to buffer(0)
    POSTINC2 = InputByte()          // input first byte

    // loop through remaining data to read (first byte already read)
    count = count - 1
    while (count <> 0)
        AckSequence()               // perform Acknowledge Sequence
        POSTINC2 = InputByte()      // input next byte buffer(ix)
        count = count - 1
    end while
    flags.sendMAK = 0               // send NoMAK
    AckSequence()                   // perform Acknowledge Sequence
    Standby()                       // gracefully enter Standby
end sub

//--------------------------------------------------------------------
// public sub PageWrite(addr as word, byref buffer() as byte, count as byte)
//  This function writes 'count' number of bytes from the 'buffer()' 
//  array to the serial EEPROM, starting at the location 'addr'
//  Parameters: addr    - EEPROM memory location at which to start
//              buffer()- src buffer address
//              count   - number of bytes to write (up to PAGESIZE max)
// **NOTE** 
//  This function does NOT test for page boundaries, so it's up to the
//  caller to ensure that page boundaries are not crossed. Otherwise, 
//  the data will wrap back to the beginning of the page
//--------------------------------------------------------------------
public sub PageWrite(addr as word, byref buffer() as byte, count as byte=PAGESIZE)
    WriteEnable()                   // set Write Enable Latch
    SendCmd(WRITE)                  // send WRITE command
    OutputByte(addr.bytes(1))       // send address MSB
    OutputByte(addr.bytes(0))       // send address LSB

    // loop through data to write (except last byte)
    FSR2 = addressof(buffer)        // point to buffer(0)
    count = count - 1
    while (count <> 0)
        OutputByte(POSTINC2)        // output next byte buffer(ix)
        count = count - 1
    end while
    flags.sendMAK = 0               // send NoMAK
    OutputByte(POSTINC2)            // load final byte
    Standby()                       // gracefully enter Standby

    WIP_Poll()                      // perform WIP Polling
end sub

//--------------------------------------------------------------------
// public sub ByteWrite(addr as word, b as byte)
//  This function writes the byte value 'b' to the serial EEPROM 
//  location specified by 'addr'
//--------------------------------------------------------------------
public sub ByteWrite(addr as word, b as byte)
    WriteEnable()                   // set Write Enable Latch
    SendCmd(WRITE)                  // send WRITE command
    OutputByte(addr.bytes(1))       // send address MSB
    OutputByte(addr.bytes(0))       // send address LSB
    flags.sendMAK = 0               // send NoMAK
    OutputByte(b)                   // send byte b
    Standby()                       // gracefully enter Standby

    WIP_Poll()                      // perform WIP Polling
end sub

//--------------------------------------------------------------------
// public function ByteRead(addr as word) as byte
//  This function reads a byte from the serial EEPROM location specified
//  by 'addr', and returns the byte value read
//--------------------------------------------------------------------
public function ByteRead(addr as word) as byte
    SendCmd(READ)                   // send READ command
    OutputByte(addr.bytes(1))       // send address MSB
    OutputByte(addr.bytes(0))       // send address LSB
    result = InputByte()            // input byte
    flags.sendMAK = 0               // send NoMAK
    AckSequence()                   // perform Acknowledge Sequence
    Standby()                       // gracefully enter Standby
end function

//--------------------------------------------------------------------
// public inline sub ReadEUI48(byref buffer() as byte)
// public inline sub ReadEUI64(byref buffer() as byte)
//  These routines read the pre-programmed EUI48/EUI64 bytes from the
//  EEPROM into the dest 'buffer' array
//--------------------------------------------------------------------
// 11AA02E48 device
public inline sub ReadEUI48(byref buffer() as byte)
    SeqRead(EUI48_ADDR, buffer, 6)
end sub

// 11AA02E64 device
public inline sub ReadEUI64(byref buffer() as byte)
    SeqRead(EUI64_ADDR, buffer, 8)
end sub

//--------------------------------------------------------------------
// public sub Init()
//  init SCIO hardware resources
//--------------------------------------------------------------------
public sub Init()
    // initialize I/O
    // note: the caller must make sure the SCIO pin is in digital mode

    // after a POR/BOR event occurs, a low to high transition on SCIO must be
    // generated before proceeding with any communication
    SCIO = 0                        // set SCIO to drive low
    SCIOTRIS = 0                    // configure SCIO to output
    delayus(1)                      // delay to ensure minimum pulse width of 200 ns
    SCIO = 1                        // bring SCIO high to release device from POR

    // init debug trigger pin (if enabled)
  #if (UNIO_TRIGGER)
    low(TRIGIO)
  #endif

    // initialize Timer1
    TMRCON = TCON_CONFIG            // configure Timer1, 1:1 prescalar, 16-bit
    LOAD_PERIOD(HALF_PERIOD)        // load HALF_PERIOD into CCPR1L
    WRITE_TIMER($0000)              // clear TMR1

    // initialize CCP1
    CCPCON = CCPCON_CONFIG          // configure special event trigger (reset timer)

    StandbyPulse()                  // generate Standby Pulse
end sub


//--------------------------------------------------------------------
// public function check_bit_timing(b as byte) as byte
//  test function useful for measuring sample points/timing 
//  it performs a start, write, and read sequence    
//  TRIGGER pulses must occur within SCIO bit periods
//--------------------------------------------------------------------
#if (UNIO_TRIGGER)
public function check_bit_timing(b as byte) as byte
    Tss()                           // observe Tss time
    StartHeader()                   // output Start Header
    OutputByte(b)                   // send a byte
    result = InputByte()            // input byte
    flags.sendMAK = 0               // send NoMAK
    AckSequence()                   // perform Acknowledge Sequence
    Standby()                       // gracefully enter Standby
end function
#endif      // UNIO_TRIGGER

end module

Sample Code

// UNI/O test program
device = 18F25K22
clock = 16

include "utils.bas"

// set hdw options
#option SCIO_PIN = PORTB.2
#option UNIO_BUSFREQ = 10000
include "unio.bas"

// eeprom page buffer (PAGESIZE is defined in unio module)
public dim
    pageBuffer(PAGESIZE) as byte

sub main()
    dim i as byte
    dim b as byte
    dim addr as word
    dim fail as boolean

    // set all digital pins
    SetAllDigital()

    // init UNIO hdw
    unio.init()

    // see if we can detect a device
    if (unio.devicedetect()) then
        fail = false    // device acks
    else
        fail = true     // things aren't working
    endif        

    // read eeprom status register
    b = unio.ReadStatusReg()

    // check write/read a single byte to addr (page2)
    addr = 2*PAGESIZE + 1           // set address
    unio.ByteWrite(addr, $55)       // write one byte of data
    b = unio.ByteRead(addr)         // read one byte of data
    if (b <> $55) then              // check write vs read
        fail = true
    endif

    // init our data array
    for i = 0 to bound(pageBuffer)
        pageBuffer(i) = i
    next

    // test page write/read functions
    addr = 2*PAGESIZE                           // set address
    unio.PageWrite(addr, pageBuffer, PAGESIZE)  // write one page of data
    clear(pageBuffer)
    unio.SeqRead(addr, pageBuffer, PAGESIZE)    // read one page of data

    // check reading EUI data
    clear(pageBuffer)
    unio.ReadEUI48(pageBuffer)
end sub

main()

end program