246 lines
16 KiB
Plaintext
246 lines
16 KiB
Plaintext
|
{{┌──────────────────────────────────────────┐
|
||
|
│ I2C slave object │
|
||
|
│ Author: Chris Gadd │
|
||
|
│ Copyright (c) 2013 Chris Gadd │
|
||
|
│ See end of file for terms of use. │
|
||
|
└──────────────────────────────────────────┘
|
||
|
|
||
|
This object creates an I2C slave device with 32 byte-sized registers on the Propeller, which an I2C master device can read from and write to using standard protocol:
|
||
|
7-bit device ID + read/write bit, 8-bit register address, 8 bits of data
|
||
|
Supports single, repeated, and page reads and writes
|
||
|
Does NOT check for overruns - Writing 5 bytes starting at register 31 may produce unexpected results
|
||
|
|
||
|
To use:
|
||
|
slave.start(28,29,$42) Start the slave object using p28 for clock, p29 for data, with device ID $42
|
||
|
slave.check Returns the index (31-0) of the highest byte in the register that was written to by a master
|
||
|
Subsequent calls to slave.check return the index of the next highest byte with new data
|
||
|
slave.check_reg(5) Returns the contents of register 5 only if the new-data flag for that register is set, returns -1 otherwise
|
||
|
slave.get(10) Returns the value of register 10
|
||
|
slave.put(11,#2) Stores the value 2 in register 11
|
||
|
slave.flush Clears all 32 registers to 0
|
||
|
slave.address Returns the base address of the slave registers - useful for directly operating on the registers
|
||
|
by higher-level objects
|
||
|
|
||
|
Tested up to 769Kbps (max transmit speed of the master object)
|
||
|
|
||
|
}}
|
||
|
VAR
|
||
|
long flags ' Used to determine if a register has new data from the master
|
||
|
|
||
|
byte _slave_address
|
||
|
byte SCL_pin
|
||
|
byte SDA_pin
|
||
|
|
||
|
byte register[32]
|
||
|
|
||
|
byte cog
|
||
|
|
||
|
PUB start(clk_pin, data_pin, slave_address) : okay
|
||
|
stop
|
||
|
_slave_address := slave_address
|
||
|
SCL_pin := clk_pin
|
||
|
SDA_pin := data_pin
|
||
|
|
||
|
okay := cog := cognew(@entry, @flags) + 1
|
||
|
|
||
|
PUB stop
|
||
|
if cog
|
||
|
cogstop(cog~ - 1)
|
||
|
|
||
|
PUB address
|
||
|
return @register
|
||
|
|
||
|
PUB check : index
|
||
|
{{
|
||
|
Returns the number of the highest byte that was written to:
|
||
|
If an I2C master wrote to addresses 3 and 7 of the slave's buffer, #7 is returned
|
||
|
The flag for the highest byte is then cleared, so a subsequent check would return #3
|
||
|
Returns -1 if all updated byte addresses have been returned (no new data)
|
||
|
}}
|
||
|
index := (>| flags) - 1
|
||
|
flags := flags & !(|< index) ' Clear the highest set bit
|
||
|
|
||
|
PUB check_reg(index)
|
||
|
{{
|
||
|
Returns the value of the indexed register if that register has new data
|
||
|
Returns -1 otherwise
|
||
|
|
||
|
}}
|
||
|
|
||
|
if |< index & flags
|
||
|
flags := flags & !(|< index)
|
||
|
return register[index]
|
||
|
return -1
|
||
|
|
||
|
PUB get(index)
|
||
|
return register[index]
|
||
|
|
||
|
PUB put(index,data)
|
||
|
register[index] := data
|
||
|
|
||
|
PUB flush | i
|
||
|
flags~
|
||
|
repeat i from 0 to 31
|
||
|
register[i]~
|
||
|
|
||
|
DAT org
|
||
|
entry
|
||
|
mov t1,par
|
||
|
mov flags_address,t1 ' Retrieve all of the addresses and
|
||
|
add t1,#4 ' pin assignments from the VAR block,
|
||
|
rdbyte device_address,t1 ' and create bit masks
|
||
|
shl device_address,#1
|
||
|
add t1,#1
|
||
|
rdbyte t2,t1
|
||
|
mov SCL_mask,#1
|
||
|
shl SCL_mask,t2
|
||
|
add t1,#1
|
||
|
rdbyte t2,t1
|
||
|
mov SDA_mask,#1
|
||
|
shl SDA_mask,t2
|
||
|
add t1,#1
|
||
|
mov register_address,t1
|
||
|
mov idle_mask,SCL_mask
|
||
|
or idle_mask,SDA_mask
|
||
|
'----------------------------------------------------------------------------------------------------------------------
|
||
|
main
|
||
|
call #wait_for_start
|
||
|
start_detected
|
||
|
call #receive
|
||
|
mov t1,I2C_byte
|
||
|
and t1,#%1111_1110 ' clear the read/write flag and compare received
|
||
|
cmp t1,device_address wz ' device address with assigned address
|
||
|
if_ne jmp #main
|
||
|
call #ack
|
||
|
test I2C_byte,#%0000_0001 wc ' test read(1) or write(0) bit of device address
|
||
|
if_nc jmp #write
|
||
|
read '(from_master)
|
||
|
call #respond ' The master sends an ACK or NAK in response to
|
||
|
add data_address,#1 ' every byte sent back from the slave
|
||
|
if_nc jmp #read ' Send another byte if ACK (c=0)
|
||
|
jmp #main ' Stop if NAK (c=1)
|
||
|
write '(from_master)
|
||
|
rdlong t1,flags_address ' Use t1 to hold all flags
|
||
|
mov t2,#1 ' Use t2 to hold the flag of the current register
|
||
|
mov data_address,register_address ' Prepare the to store new data
|
||
|
call #receive ' First byte received is a register address
|
||
|
add data_address,I2C_byte
|
||
|
shl t2,I2C_byte ' Shift the flag to the appropriate register
|
||
|
call #ack
|
||
|
:loop
|
||
|
call #receive ' Receive a data byte
|
||
|
wrbyte I2C_byte,data_address ' Store in the addressed register
|
||
|
add data_address,#1 ' Address the next register
|
||
|
or t1,t2 ' Update the flags
|
||
|
wrlong t1,flags_address
|
||
|
shl t2,#1 ' Shift the flag to the next register
|
||
|
call #ack
|
||
|
jmp #:loop
|
||
|
'======================================================================================================================
|
||
|
wait_for_start ' SCL
|
||
|
waitpeq idle_mask,idle_mask ' SDA
|
||
|
:loop
|
||
|
waitpne SDA_mask,SDA_mask
|
||
|
test SCL_mask,ina wc
|
||
|
if_nc jmp #wait_for_start
|
||
|
wait_for_start_ret ret
|
||
|
'----------------------------------------------------------------------------------------------------------------------
|
||
|
receive ' (Read)
|
||
|
mov loop_counter,#8 '
|
||
|
mov I2C_byte,#0 ' SCL
|
||
|
:loop ' SDA ───────
|
||
|
waitpne SCL_mask,SCL_mask
|
||
|
waitpeq SCL_mask,SCL_mask
|
||
|
test SDA_mask,ina wc
|
||
|
rcl I2C_byte,#1
|
||
|
call #detect_st_or_sp ' Check to see if the received bit is a stop or restart
|
||
|
djnz loop_counter,#:loop
|
||
|
receive_ret ret
|
||
|
'----------------------------------------------------------------------------------------------------------------------
|
||
|
respond ' (Write) (Read ACK or NAK)
|
||
|
mov loop_counter,#8 '
|
||
|
rdbyte I2C_byte,data_address ' SCL
|
||
|
shl I2C_byte,#32-8 ' SDA ───────
|
||
|
:loop
|
||
|
waitpne SCL_mask,SCL_mask
|
||
|
shl I2C_byte,#1 wc
|
||
|
muxnc dira,SDA_mask
|
||
|
waitpeq SCL_mask,SCL_mask
|
||
|
waitpne SCL_mask,SCL_mask
|
||
|
djnz loop_counter,#:loop
|
||
|
andn dira,SDA_mask
|
||
|
'receive_ack_or_nak
|
||
|
waitpne SCL_mask,SCL_mask
|
||
|
waitpeq SCL_mask,SCL_mask
|
||
|
test SDA_mask,ina wc ' C is set if NAK
|
||
|
call #detect_st_or_sp
|
||
|
respond_ret ret
|
||
|
'----------------------------------------------------------------------------------------------------------------------
|
||
|
ack ' SCL
|
||
|
waitpne SCL_mask,SCL_mask ' SDA
|
||
|
or dira,SDA_mask
|
||
|
waitpeq SCL_mask,SCL_mask
|
||
|
waitpne SCL_mask,SCL_mask
|
||
|
andn dira,SDA_mask
|
||
|
ack_ret ret
|
||
|
'----------------------------------------------------------------------------------------------------------------------
|
||
|
nak ' SCL
|
||
|
waitpne SCL_mask,SCL_mask ' SDA
|
||
|
waitpeq SCL_mask,SCL_mask
|
||
|
call #detect_st_or_sp
|
||
|
waitpne SCL_mask,SCL_mask
|
||
|
nak_ret ret
|
||
|
'----------------------------------------------------------------------------------------------------------------------
|
||
|
detect_st_or_sp
|
||
|
' test SDA_mask,ina wc ' routine is called with C already set to SDA
|
||
|
if_c jmp #:detect_restart
|
||
|
:detect_stop ' SCL
|
||
|
test SCL_mask,ina wz ' SDA
|
||
|
if_z jmp detect_st_or_sp_ret
|
||
|
test SDA_mask,ina wz
|
||
|
if_nz jmp #main
|
||
|
jmp #:detect_stop
|
||
|
:detect_restart ' SCL
|
||
|
test SCL_mask,ina wz ' SDA
|
||
|
if_z jmp detect_st_or_sp_ret
|
||
|
test SDA_mask,ina wz
|
||
|
if_z jmp #start_detected
|
||
|
jmp #:detect_restart
|
||
|
detect_st_or_sp_ret ret
|
||
|
'----------------------------------------------------------------------------------------------------------------------
|
||
|
SCL_mask res 1
|
||
|
SDA_mask res 1
|
||
|
idle_mask res 1
|
||
|
|
||
|
device_address res 1
|
||
|
register_address res 1
|
||
|
data_address res 1
|
||
|
flags_address res 1
|
||
|
|
||
|
I2C_byte res 1
|
||
|
loop_counter res 1
|
||
|
t1 res 1
|
||
|
t2 res 1
|
||
|
|
||
|
|
||
|
fit
|
||
|
|
||
|
DAT
|
||
|
{{
|
||
|
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||
|
│ TERMS OF USE: MIT License │
|
||
|
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||
|
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
|
||
|
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
|
||
|
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
|
||
|
│is furnished to do so, subject to the following conditions: │
|
||
|
│ │
|
||
|
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
|
||
|
│ │
|
||
|
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
|
||
|
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
|
||
|
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
|
||
|
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
|
||
|
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||
|
}}
|