UNIO
SwordfishUser.UNIO History
Hide minor edits - Show changes to output
Added lines 1-808:
This module provides support for serial EEPROMs and the Microchip UNI/O one-wire bus.
!!! UNIO Module
=code [=
//-----------------------------------------------------------------------------
// 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
=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
=]
!!! UNIO Module
=code [=
//-----------------------------------------------------------------------------
// 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
=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
=]