chiark / gitweb /
Send HELLO as a result of all slaves being online. Do not crash if slave is slow...
[trains.git] / detpic / i2clib.asm
index b3ff2d371b83f4132889bb4ca17ae63c5a0015c0..09d995dbe56fcf6b69827b98ec76c71701b68525 100644 (file)
 ;
 ; 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
 
-ssp            res     1
-
-st             res     1       ; bitmask:
-st_writing     equ     0
-st_subsequent  equ     0
+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
 
-;--------------------
-i2cs_init
-;      W               slave number            undefined
-               rcall   slave2addr2
+;----------
+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
-               mov_lw  0x1e ; !SSPEN, CKP(release), I2C 7-bit slave S&P
+               clr_f   slave_next
+               mov_lw  0x08    ; !SSPEN, Master mode
                mov_wf  SSPCON1
-               mov_lw  0x01 ; !GCEN, SEN
-               mov_wf  SSPCON2
-               mov_lw  0x8 ; SMP(noslew), !CKE, !BF(empty)
+               clr_f   SSPCON2 ; nothing going
+               mov_lw  0x80    ; SMP(noslew), !CKE(!smbus)
                mov_wf  SSPSTAT
-               bs_f    TRISB, 0
-               bs_f    TRISB, 1
-               bc_f    IPR1, SSPIP
-               bs_f    SSPCON1, SSPEN
-               bs_f    PIE1, SSPIE
-               return
+               bc_f    IPR1, SSPIP ; low priority
+               bra     init_enable
 
-;--------------------
-i2cs_interrupt
+;----------
+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
 
-; Firstly, clear the interrupt flag so that if something else happens
-; while we faff, the interrupt will be regenerated:
                bc_f    PIR1, SSPIF
 
-; Check that nothing obvious is wrong:
-               mov_fw  SSPCON1
-               mov_wf  ssp
-               and_lw  0xc0
-               bra_nz  i2cs_interrupt_wcolsspov_endif
-               panic   morse_SV
-i2cs_interrupt_wcolsspov_endif
-
-; Find out what's just happened:
-               mov_ff  SSPSTAT, ssp
-       ; bits we want to check
-       ;  80  60  20    10    08    04    02  01
-       ;  SMP CKE D_A   P     S     R_W   UA  BF
-       ;  set clr data? stop  start read? clr full?
-
-               mov_fw  ssp
-chkval_lastvalue equ 0
-
-chkval macro value, label
-               xor_lw  value ^ chkval_lastvalue
- chkval_lastvalue equ value
-               bra_z   label
-               endm
+               mov_lw  (1<<WCOL) | (1<<SSPOV)
+               and_wfw sspcon1
+               bra_nz  m_event_bad
 
-               chkval  0x89, s_case_addr_recv_write
-               chkval  0x8d, s_case_addr_recv_read
-               chkval  0xa9, s_case_write_data_recv
+               ; 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_if0 st, st_reading
-               bra     s_ifnot_reading
+               bt_f_if1 sspstat, R_W
+               bra_nz  m_event_bad
 
-               ; only check this if we're reading; otherwise
-               ;  this will be handled by s_case_uninteresting_start
-               chkval  0xac, s_case_read_data_sent
-               chkval  0xa8, s_case_read_data_nack
+               bt_f_if1 st, st_stopping
+               bra     m_event_done_stopping
 
-s_ifnot_reading
+               bt_f_if1 st, st_starting
+               bra     m_event_done_starting
+               ; not just done SEN
 
-chkvalm macro mask, value, label
-               mov_fw  ssp
-               xor_lw  value
-               bra_z   label
-               endm
+               bt_f_if1 st, st_addressing
+               bra     m_event_done_addressing
+
+               bt_f_if1 st, st_acking
+               bra     m_event_done_acking
 
-               chkvalm 0xdf, 0x90, s_case_something_stop
-               chkvalm 0xdb, 0x88, s_case_uninteresting_start
+               bt_f_if1 st, st_writing
+               bra     m_event_done_writing
 
-               mov_ff  ssp, WREG2 ; fixme
-               panic   morse_SS
+               bt_f_if1 st, st_reading
+               bra     m_event_done_reading
+
+m_event_bad
+               i2cpanic morse_SM
+
+;========================================
+; MASTER - STARTING, ADDRESSING, STOPPING
 
 ;----------
-s_case_something_stop
-s_case_something_start
-s_ensure_idle
-               mov_fw  st ; were we doing something ?
-               bt_f_if1 STATUS,Z
+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
-               ; we were, it seems:
+               ; oops:
+;...
 
-               bc_f    SSPCON, 3
-               clr_f   st
-               ; now we're not (but W still has old st)
+m_improper_slave
+;      slave                   slave number
+               i2cpanic morse_SN
 
-               bt_f_if1 WREG, st_writing
-               goto    i2csu_write_done
 
-               bt_f_if1 WREG, st_reading
-               goto    i2csu_read_done
+;----------
+m_event_done_starting
+               mov_fw  slave
+               rcall   slave2addr
 
-               mov_wf  st ; put it back and then ...
+               bt_f_if1 st, st_reading
+               bs_w    0       ; address bottom bit means read
 
-s_panic_st_unexpected
-               panic   morse_ST
+               mov_wf  SSPBUF
+               bc_f    st, st_starting
+               bs_f    st, st_addressing
+               return
 
 ;----------
-s_case_addr_recv_write
-               rcall   s_ensure_idle
-               bs_f    SSPCON, 3; we'll need the Stop interrupt
-               bs_f    st, st_writing
-               ; well, now this is all fine so do carry on:
+m_event_done_addressing
+               bt_f_if1 sspcon2, ACKSTAT
+               bra     m_no_address_ack
+               ; OK, we got ack.
 
-s_write_slurpbyte
-;      W               any                     byte from master
-;      i2c controller  waiting due to SEN etc  continuing with next byte
-               mov_fw  SSPBUF
-               bs_f    SSPCON1, CKP
+               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
 
 ;----------
-s_case_write_data_recv
-               bt_f_if0 st, st_writing
-               bra     s_panic_st_unexpected
-               ; ok, we are writing:
+m_event_done_stopping
+               clr_f   st
+               goto    i2cmu_done
 
-               rcall   s_write_slurpbyte
+;----------
+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
 
-               bt_f_if1 st, st_subsequent
-               goto    i2csu_write_another
-               ; not subsequent (yet):
+m_bad_no_address_ack_write
+               panic   morse_SW
 
-               bs_f    st, st_subsequent
-               goto    i2csu_write_begin
+;========================================
+; MASTER - WRITING
 
 ;----------
-s_case_addr_recv_read
-               rcall   s_ensure_idle
-               bs_f    st, st_reading
-               call    i2csu_read_begin
-               bra     s_cases_read_data_send
+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
 
 ;----------
-s_case_read_data_sent
-               call    i2csu_read_another
+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:
 
-s_cases_read_data_send
                mov_wf  SSPBUF
-               bs_f    SSPCON1, CKP
                return
 
 ;----------
-s_case_read_data_nack
-               rcall   s_ensure_idle
-               goto    i2csu_read_done
+m_event_write_mustfinish
+               bt_f_if0 st, st_subsequent
+               bra     m_improper_write_finish
 
+               bra     m_stop
 
-s_cases_write_alliswell
-               
+;----------
+m_improper_write_finish
+               i2cpanic morse_SF
 
+;========================================
+; MASTER - READING
 
-               bt_f_if1 ssp, I2C_START
-               bra     si_if_start
-
-si_if_notstart
-               ; So it should be stop
-               mov_fw  ssp
-               and_lw  0xdf ; ?D_A
-               xor_lw  0x90 ; SMP, !CKE, P; !S, !R_W, !UA, !BF
-               bra_nz  si_if_bad
-
-si_if_start
-               bt_f_if1 ssp, BF
-               bra     si_if_bufferfull
-si_if_bufferempty
-               bt_f_if1 ssp, R_W ;read?
-               bra     si_if_bufferempty_reading
-si_if_bufferempty_notreading
-               ; So we think this is just a START (which we want to ignore)
-               mov_fw  ssp
-               and_lw  0xdf ; ?D_A
-               xor_lw  0x88 ; SMP, !CKE, !P; S, !R_W, !UA, !BF
-               bra_nz  si_if_bad
-
-               ; OK, ignore it
-               return
+;----------
+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                
 
 ;----------
-s_case_unknown_stop
-s_case_unknown_start
+m_event_done_addressing_read
+m_event_done_acking_readmore
+;      ACKSTAT                 checked
+;      st_addressing/acking    cleared
+               bs_f    SSPCON2, RCEN
                return
 
 ;----------
-s_case_got_write_addr
-               
+m_event_done_reading
+               bt_f_if0 sspstat, BF
+               bra     m_event_bad
 
                mov_fw  SSPBUF
-               and_lw  0xfe
-               bra_nz  nonzero
 
-               mov_wf  ssp
+               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
 
-               mov_fw  SSPSTAT
-               and_lw  0xe7 ; all except P and S
+;----------
+m_improper_read_another
+               i2cpanic morse_SA
+
+;======================================================================
+; SLAVE
 
-               xor_lw  0x80
-                       ; bits which might sensibly be set
+;----------
+i2cs_init
+;      W               slave number            undefined
+               rcall   slave2addr
+               mov_wf  SSPADD
+               clr_f   st
+               mov_lw  0x16 ; !SSPEN, CKP(release), I2C 7-bit slave no-SP-int
+               mov_wf  SSPCON1
+               mov_lw  0x01 ; !GCEN, SEN
+               mov_wf  SSPCON2
+               mov_lw  0x80 ; SMP(noslew), !CKE(!smbus)
+               mov_wf  SSPSTAT
+               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
+
+;========================================
+; 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_last equ 0
-chkval macro value, label
-               xor_lw  value ^ chkval_last
-               bra_z   label
-chkval_last equ value
+;----------
+; Macros: chkvals_start and chkval
+
+chkvals_start macro chvals_what
+               mov_fw  chvals_what
                endm
-               chkval  0x80 ;     addr  dunno 
 
-               mov_lw  0x8
+chkval macro chkval_lastval, chkval_value, chkval_label
+               xor_lw  chkval_value ^ chkval_lastval
+               bra_z   chkval_label
+               endm
 
+near_i2csu code
 
-               bt_f_if0 DATA_ADDRESS, SSPSTAT
-               bra     data
+;----------
+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
 
-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
+;----------------------------------------
+i2cs_read_data
+               i2cs_read_data_macro
                return
 
+;----------------------------------------
+; branches from the ISR
+
+;----------
+s_event_addrrecvwrite
+               s_write_slurpbyte
+               goto    i2csu_write_begin
+
+;----------
+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