;
; See i2clib.asm for documentation of the interface to this file.
- include /usr/share/gputils/header/p18f458.inc
- radix dec
- include ../iwjpictest/insn-aliases.inc
+ include /usr/share/gputils/header/p18f458.inc
+ radix dec
+ include ../iwjpictest/insn-aliases.inc
+
+ include ../iwjpictest/clockvaries.inc
+ include panic.inc
+ include morse+auto.inc
+ include i2clib.incm
+
+;======================================================================
+; NOTATION
+
+; Naming conventions
+;
+; m_... routines used by master only
+; s_... routines used by slave only
+; <any other name> routines used by both
+;
+; [ms]_event_... event handler, branched to from interrupt
+; handler; conditions are as in the name;
+; should `return' at end which will return
+; from i2c[ms]_interrupt
+;
+; [sm]_event_bad[_...] event handler which panics; called when i2c
+; controller did something unexpected
+;
+; m_improper_... panics; called when main program
+; does something wrong
+;
+; [ms]_<anything else> routines or labels of some other kind
+
+; Whenever flow does not pass past the end of some code, we
+; have a boundary `;----------', and when flow passes past
+; an important label we sometimes mark it specially with `;...',
+; like this:
+;
+; ;----------
+; m_event_spong
+; bt_f_if0 st, st_something
+; bra m_event_bad
+; ;...
+;
+; m_event_several_including_spong
+; bs_f st, st_sponging
+; bra metasyntacticing
+;
+; ;----------
+; m_event_wombat
;============================================================
-; COMMON ADMINISTRATIVE ROUTINES
+; COMMON ADMINISTRATIVE ROUTINES and VARIABLES
udata_acs
-t res 1
+
+sspstat res 1 ; master only
+sspcon1 res 1 ; master only
+sspcon2 res 1 ; master only
+slave res 1 ; master only
+slave_next res 1 ; master only
+
+st res 1
+st_orig res 1
+
+; st is a bitmask, bit set in visible states:
+ ; master
+st_starting equ 7 ; Writing-Setup?, Reading-Busy?
+st_addressing equ 6 ; Writing-Setup?, Reading-Busy?
+st_writing equ 5 ; Writing-*, Stopping(after Reading-Wait:write_start)
+st_subsequent equ 4 ; Writing?
+st_reading equ 3 ; Reading-*
+st_awaiting equ 2 ; Reading-Wait
+st_acking equ 1 ; Reading-Busy?, Stopping(from read)
+st_stopping equ 0 ; Stopping
+ ; ...? means not always set in that state
code
-;--------------------
+;----------
+slave2addr
+; computes slave address in form suitable for use in i2c controller
+; actual i2c slave address is (slave number) + 0b0001000
+; W slave number i2c address * 2
+ add_lw b'0001000'
+ rlc_w
+ return
+
+;----------
+improper_read_done_data
+ i2cpanic morse_SD
+
+;======================================================================
+; MASTER
+
+;----------
+i2cm_init
+ mov_lw i2c_sspadd
+ mov_wf SSPADD
+ clr_f st
+ clr_f slave_next
+ mov_lw 0x08 ; !SSPEN, Master mode
+ mov_wf SSPCON1
+ clr_f SSPCON2 ; nothing going
+ mov_lw 0x80 ; SMP(noslew), !CKE(!smbus)
+ mov_wf SSPSTAT
+ bc_f IPR1, SSPIP ; low priority
+ bra init_enable
+
+;----------
+i2cm_interrupt
+ bt_f_if0 PIR1, SSPIF
+ return
+ ; We have an interrupt:
+;...
+i2cm_interrupt_definite
+ mov_ff SSPSTAT, sspstat
+ mov_ff SSPCON1, sspcon1
+ mov_ff SSPCON2, sspcon2
+
+ bc_f PIR1, SSPIF
+
+ mov_lw (1<<WCOL) | (1<<SSPOV)
+ and_wfw sspcon1
+ bra_nz m_event_bad
+
+ ; No ? Well, then the I2C should be idle now:
+ mov_fw sspcon2
+ and_lw ~((1<<ACKSTAT) | (1<<ACKDT)) ; those two are ok if set
+ bra_nz m_event_bad
+ ; OK...
+
+ bt_f_if1 sspstat, R_W
+ bra_nz m_event_bad
+
+ bt_f_if1 st, st_stopping
+ bra m_event_done_stopping
+
+ bt_f_if1 st, st_starting
+ bra m_event_done_starting
+ ; not just done SEN
+
+ bt_f_if1 st, st_addressing
+ bra m_event_done_addressing
+
+ bt_f_if1 st, st_acking
+ bra m_event_done_acking
+
+ bt_f_if1 st, st_writing
+ bra m_event_done_writing
+
+ bt_f_if1 st, st_reading
+ bra m_event_done_reading
+
+m_event_bad
+ i2cpanic morse_SM
+
+;========================================
+; MASTER - STARTING, ADDRESSING, STOPPING
+
+;----------
+m_start
+; st checked for busyness correct
+; st_reading/writing one set, one clear unchanged
+; st_starting clear set
+; W slave number any
+; slave any slave_number
+; expects to return directly to main program (caller)
+ mov_wf slave
+ bs_f SSPCON2, SEN
+m_start_or_restart
+ and_lw ~31
+ bra_nz m_improper_slave
+ bs_f st, st_starting
+ tst_f_ifnz slave
+ return
+ ; oops:
+;...
+
+m_improper_slave
+; slave slave number
+ i2cpanic morse_SN
+
+
+;----------
+m_event_done_starting
+ mov_fw slave
+ rcall slave2addr
+
+ bt_f_if1 st, st_reading
+ bs_w 0 ; address bottom bit means read
+
+ mov_wf SSPBUF
+ bc_f st, st_starting
+ bs_f st, st_addressing
+ return
+
+;----------
+m_event_done_addressing
+ bt_f_if1 sspcon2, ACKSTAT
+ bra m_no_address_ack
+ ; OK, we got ack.
+
+ bc_f st, st_addressing
+ bt_f_if1 st, st_reading
+ bra m_event_done_addressing_read
+ bra m_event_done_addressing_write
+
+;----------
+m_stop
+; st_stopping clear set
+; st_reading/acking/writing any unchanged
+; expects to return directly to main program or to end interrupt handler
+ bs_f st, st_stopping
+ bs_f SSPCON2, PEN
+ return
+
+;----------
+m_event_done_stopping
+ clr_f st
+ goto i2cmu_done
+
+;----------
+m_no_address_ack
+ bt_f_if0 st, st_reading
+ bra m_bad_no_address_ack_write
+ clr_f st
+ rcall m_stop
+ goto i2cmu_slave_no_ack
+
+m_bad_no_address_ack_write
+ panic morse_SW
+
+;========================================
+; MASTER - WRITING
+
+;----------
+i2cm_write_start
+; At call On return
+; State Idle/Reading-Wait Writing-Setup
+; W slave number any
+ tst_f_ifnz st
+ bra m_write_start_busy
+
+ bs_f st, st_writing
+ bra m_start
+
+;----------
+m_event_done_writing
+ ; Did slave ack our byte ? It had better have done !
+ bt_f_if1 sspcon2, ACKSTAT
+ bra m_event_bad
+
+ bs_f st, st_subsequent
+;...
+
+m_event_done_addressing_write
+; ACKSTAT checked
+; st_addressing cleared
+ call i2cmu_write_next_byte
+ bra_z m_event_write_mustfinish
+ ; OK, we have the next byte:
+
+ mov_wf SSPBUF
+ return
+
+;----------
+m_event_write_mustfinish
+ bt_f_if0 st, st_subsequent
+ bra m_improper_write_finish
+
+ bra m_stop
+
+;----------
+m_improper_write_finish
+ i2cpanic morse_SF
+
+;========================================
+; MASTER - READING
+
+;----------
+i2cm_read_start
+; At call On return
+; State Idle Reading-Busy
+; W slave number any
+ tst_f_ifnz st
+ bra m_read_start_busy
+
+ bs_f st, st_reading
+ bra m_start
+
+;----------
+m_write_start_busy
+ bs_f st, st_writing
+m_read_start_busy
+ bt_f_if1 st, st_awaiting
+ bra m_address_different
+ i2cpanic morse_SB
+
+;----------
+m_address_different
+; Main program would like to address another slave for reading.
+ mov_wf slave_next
+ tst_f_ifnz slave_next
+ bra i2cm_read_done
+ panic morse_SO
+
+;----------
+m_event_done_addressing_read
+m_event_done_acking_readmore
+; ACKSTAT checked
+; st_addressing/acking cleared
+ bs_f SSPCON2, RCEN
+ return
+
+;----------
+m_event_done_reading
+ bt_f_if0 sspstat, BF
+ bra m_event_bad
+
+ mov_fw SSPBUF
+
+ bs_f st, st_awaiting
+ goto i2cmu_read_got_byte
+
+;----------
+i2cm_read_another
+; State Reading-Wait Reading-Busy
+ bt_f_if0 st, st_awaiting
+ bra m_improper_read_another
+ ; OK, we're fine to read another:
+;...
+
+m_read_ack
+; st_reading 1 iff not done unchanged
+; st_awaiting still set cleared
+; st_acking clear set
+; expects to return directly to main program or to end interrupt handler
+ bc_f st, st_awaiting
+ bs_f st, st_acking
+ bc_f SSPCON2, ACKDT ; ACKDT=0 means to acknowledge
+ bt_f_if0 st, st_reading
+ bs_f SSPCON2, ACKDT ; don't ack last byte
+ bs_f SSPCON2, ACKEN
+ return
+
+;----------
+i2cm_read_done
+; State Reading-Wait Stopping
+ bc_f st, st_reading
+
+ bt_f_if0 st, st_awaiting
+ bra improper_read_done_data
+ ; OK:
+
+ bra m_read_ack
+
+;----------
+m_event_done_acking
+ bc_f st, st_acking
+
+ bt_f_if1 st, st_reading
+ bra m_event_done_acking_readmore
+
+ mov_fw slave_next
+ bra_z m_stop
+; ok, we want to read another:
+ mov_wf slave
+ clr_f slave_next
+ bt_f_if0 st, st_writing ; because of i2cm_write_start ?
+ bs_f st, st_reading ; no, then we will want to read
+ bs_f SSPCON2, RSEN
+ bra m_start_or_restart
+
+;----------
+m_improper_read_another
+ i2cpanic morse_SA
+
+;======================================================================
+; SLAVE
+
+;----------
i2cs_init
; W slave number undefined
- rcall slave2addr2
+ rcall slave2addr
mov_wf SSPADD
- mov_lw 0x16 ; !SSPEN, CKP(release), I2C 7-bit slave
+ clr_f st
+ mov_lw 0x16 ; !SSPEN, CKP(release), I2C 7-bit slave no-SP-int
mov_wf SSPCON1
- mov_lw 0x81 ; GCEN, SEN
+ mov_lw 0x01 ; !GCEN, SEN
mov_wf SSPCON2
- mov_lw 0x8 ; SMP(noslew), !CKE, !BF(empty)
+ mov_lw 0x80 ; SMP(noslew), !CKE(!smbus)
mov_wf SSPSTAT
- bs_f TRISB, 0
- bs_f TRISB, 1
- bc_f IPR1, SSPIP
+ bs_f IPR1, SSPIP ; high priority
+init_enable
+; Actually engages the I2C controller, which must already have
+; been set up (all but SSPEN):
+; SSPADD,SSPCON1,SSPCON2 configured correctly unchanged
+; SSPSTAT configured correctly unchanged, except:
+; SSPSTAT<SSPEN> 0 (disabled) 1 (enabled)
+; SSPIE 0 (disabled) 1 (enabled)
+; SSPIF configured correctly unchanged
+; TRISB<1,0> any configured for I2C
+; SSPIP any configured correctly
+; GIEL 0 (disabled) 0 (disabled)
+; ssp* shadows any all bits set
+ set_f sspstat
+ set_f sspcon1
+ set_f sspcon2
+ set_f st_orig
+ bs_f TRISC, 3
+ bs_f TRISC, 4
bs_f SSPCON1, SSPEN
bs_f PIE1, SSPIE
return
-;--------------------
-i2cs_interrupt
- bt_f_if0 PIR1, SSPIF
- return
-; We have an interrupt. What are the possibilities ?
- mov_ff SSPSTAT, t
-
-chkval macro mask, value, label
- mov_fw t
- and_lw mask
- xor_lw value
- bra_z label
- endm
- ; bits we want to check
- ; 80 60 20 04 02 01
- ; SMP CKE D_A R_W UA BF
- ; set clr data? read? clr full?
+;========================================
+; SLAVE
+;
+; In general, we figure out our state and then see what kind of events
+; we were expecting. Bits we want to check:
+; 80 40 20 10 08 04 02 01
+; SMP CKE D_A P S R_W UA BF
+; set clr data? stop start read? clr full?
+; (we don't usually mention SMP, CKE and UA below)
+;
+; Labels of the form s_event_* are branches of the interrupt
+; handler and are supposed to finish with return.
- chkval 0xe3, 0x81, got_addr
- chkval 0xe7, 0xa1, got_data_write
- chkval 0xe7, 0xa4, sent_data_read
+;----------
+; Macros: chkvals_start and chkval
-
+chkvals_start macro chvals_what
+ mov_fw chvals_what
+ endm
- mov_fw SSPSTAT
- and_lw 0xe7 ; all except P and S
+chkval macro chkval_lastval, chkval_value, chkval_label
+ xor_lw chkval_value ^ chkval_lastval
+ bra_z chkval_label
+ endm
- xor_lw 0x80
- ; bits which might sensibly be set
+near_i2csu code
-chkval_last equ 0
-chkval macro value, label
- xor_lw value ^ chkval_last
- bra_z label
-chkval_last equ value
+;----------
+s_write_slurpbyte macro
+; W any byte from master
+; i2c controller waiting due to SEN etc continuing with next byte
+ mov_fw SSPBUF
+ bs_f SSPCON1, CKP
endm
- chkval 0x80 ; addr dunno
- mov_lw 0x8
+;----------------------------------------
+i2cs_read_data
+ i2cs_read_data_macro
+ return
+;----------------------------------------
+; branches from the ISR
- bt_f_if0 DATA_ADDRESS, SSPSTAT
- bra data
+;----------
+s_event_addrrecvwrite
+ s_write_slurpbyte
+ goto i2csu_write_begin
-slave2addr
-; computes slave address in form suitable for use in i2c controller
-; actual i2c slave address is (slave number) + 0b0001000
-; W slave number i2c address * 2
- add_lw 0b0001000
- rlc_w
+;----------
+s_event_reading_datanack
return
+;----------
+s_event_writing_datarecv
+ s_write_slurpbyte
+ goto i2csu_write_data
+
+;----------
+s_event_bad_intr
+ i2cpanic morse_IH ; unknown high-priority interrupt
+
+;----------------------------------------
+i2cs_interrupt ; 4cy interrupt latency + 3cy until branch to here
+ bt_f_if0 PIR1, SSPIF
+ bra s_event_bad_intr
+ ; We have an interrupt:
+
+ mov_lw (1<<WCOL) | (1<<SSPOV)
+ and_wfw SSPCON1
+ bra_nz s_event_bad
+
+; Firstly, clear the interrupt flag so that if something else happens
+; while we faff, the interrupt will be regenerated:
+ bc_f PIR1, SSPIF
+
+ chkvals_start SSPSTAT
+ chkval 0, 0x8c, i2csu_read_begin ;A,!P, S,R,!BF
+ chkval 0x8c,0xac, i2csu_read_another ;D,!P, S,R,!BF
+ chkval 0xac,0x89, s_event_addrrecvwrite ;A,!P, S,W,BF
+ chkval 0x89,0xa9, s_event_writing_datarecv ;D,!P, S,W,BF
+ chkval 0xa9,0xa8, s_event_reading_datanack ;D,!P, S,!R,!BF
+s_event_bad
+ i2cpanic morse_SS
+;======================================================================
+ include program+externs.fin
+ include i2clib.inc
- include i2clib.inc
- end
+ end