;###################################################################### ; i2clib.inc - I2C LIBRARY - IMPLEMENTATION ; ; 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 panic.inc ;====================================================================== ; NOTATION ; Naming conventions ; ; m_... routines used by master only ; s_... routines used by slave only ; 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]_ 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 i2c_st, st_something ; bra m_event_bad ; ;... ; ; m_event_several_including_spong ; bs_f i2c_st, st_sponging ; bra metasyntacticing ; ; ;---------- ; m_event_wombat ;============================================================ ; COMMON ADMINISTRATIVE ROUTINES and VARIABLES udata_acs i2c_sspstat res 1 i2c_sspcon1 res 1 i2c_sspcon2 res 1 ; master only i2c_slave res 1 ; master only i2c_st res 1 ; i2c_st is a bitmask, bit set in visible states: ; master slave st_starting equ 7 ; Writing-Setup?,Reading-Busy? st_addressing equ 6 ; Writing-Setup?,Reading-Busy? st_writing equ 5 ; Writing-* [Idle-going-]Receiving st_subsequent equ 4 ; Writing? Receiving st_reading equ 3 ; Reading-* Transmitting 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 ;====================================================================== ; MASTER ;---------- i2cm_init mov_lw 100-1 ; baud rate = Fosc/(4*(SSPADD+1)) mov_wf SSPADD ; Fosc=20MHz, so SSPADD==99 means 50kbit/s mov_lw 0x08 ; !SSPEN, Master mode mov_wf SSPCON1 clr_f SSPCON2 ; nothing going mov_lw 0x80 ; SMP(noslew), !CKE(!smbus) bra init_enable ;---------- i2cm_interrupt bt_f_if0 PIR1, SSPIF return ; We have an interrupt: mov_ff SSPSTAT, i2c_sspstat mov_ff SSPCON1, i2c_sspcon1 mov_ff SSPCON2, i2c_sspcon2 bt_f_if1 i2c_sspcon1, WCOL bra_z m_event_bad bt_f_if1 i2c_sspcon1, SSPOV bra_z m_event_bad ; No ? Well, then the I2C should be idle now: mov_fw i2c_sspcon2 and_lw ~0x60 ; ACKSTAT,ACKDT bra_nz m_event_bad ; OK... bt_f_if1 i2c_sspstat, R_W bra_nz m_event_bad bt_f_if1 i2c_st, st_stopping bra m_event_done_stopping bt_f_if1 i2c_st, st_starting bra m_event_done_starting ; not just done SEN bt_f_if1 i2c_st, st_addressing bra m_event_done_addressing bt_f_if1 i2c_st, st_writing bra m_event_done_writing bt_f_if1 i2c_st, st_acking bra m_event_done_acking bt_f_if1 i2c_st, st_reading bra m_event_done_reading m_event_bad panic morse_SM ;======================================== ; MASTER - STARTING, ADDRESSING, STOPPING ;---------- m_start ; i2c_st checked for busyness correct ; st_reading/writing set unchanged ; st_starting clear set ; W slave number any ; i2c_slave any slave_number ; expects to return directly to main program (caller) mov_wf i2c_slave and_lw 31 bra_nz m_improper_slave bs_f i2c_st, st_starting bs_f SSPCON2, SEN return ;---------- m_event_done_starting mov_fw i2c_slave rcall slave2addr bt_f_if1 i2c_st, st_reading bs_w 0 ; address bottom bit means read mov_wf SSPBUF bc_f i2c_st, st_starting bs_f i2c_st, st_addressing return ;---------- m_event_done_addressing bt_f_if1 i2c_sspcon2, ACKSTAT bra m_bad_address_ack ; OK, we got ack. bc_f i2c_st, st_addressing bt_f_if1 i2c_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 i2c_st, st_stopping bs_f SSPCON2, PEN return ;---------- m_event_done_stopping clr_f i2c_st goto i2cmu_done ;---------- m_bad_address_ack panic morse_SK ;---------- m_improper_slave ; i2c_slave slave number panic morse_SN ;======================================== ; MASTER - WRITING ;---------- i2cm_write_start ; At call On return ; State Idle Writing-Setup ; W slave number any tst_f_ifnz i2c_st bra m_improper_write_start bs_f i2c_st, st_writing bra m_start ;---------- m_event_done_writing ; Did slave ack our byte ? It had better have done ! bt_f_if1 i2c_sspcon2, ACKSTAT bra m_event_bad bs_f i2c_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 i2c_st, st_subsequent bra m_improper_write_finish bra m_stop ;---------- m_improper_write_start panic morse_SW ;---------- m_improper_write_finish panic morse_SF ;======================================== ; MASTER - READING ;---------- i2cm_read_start ; At call On return ; State Idle Reading-Busy ; W slave number any tst_f_ifnz i2c_st bra m_improper_read_start bs_f i2c_st, st_reading bra m_start ;---------- 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 i2c_sspstat, BF bra m_event_bad mov_fw SSPBUF bs_f i2c_st, st_awaiting goto i2cmu_read_got_byte ;---------- i2cm_read_another ; State Reading-Wait Reading-Busy bt_f_if0 i2c_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 i2c_st, st_awaiting bs_f i2c_st, st_acking bc_f SSPCON2, ACKDT ; ACKDT=0 means to acknowledge bs_f SSPCON2, ACKEN return ;---------- i2cm_read_done ; State Reading-Wait Stopping bc_f i2c_st, st_reading bt_f_if0 i2c_st, st_awaiting bra m_improper_read_done ; OK: bra m_read_ack ;---------- m_event_done_acking bc_f i2c_st, st_acking bt_f_if1 i2c_st, st_reading bra m_event_done_acking_readmore bra m_stop ;---------- m_improper_read_start panic morse_SR ;---------- m_improper_read_another panic morse_SA ;---------- m_improper_read_done panic morse_SD ;====================================================================== ; SLAVE ;---------- i2cs_init ; W slave number undefined rcall slave2addr mov_wf SSPADD clr_f i2c_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 0x8 ; SMP(noslew), !CKE(!smbus) mov_wf SSPSTAT 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 0 (disabled) 1 (enabled) ; SSPIE 0 (disabled) 1 (enabled) ; TRISB<1,0> any configured for I2C ; SSPIP any configured correctly ; GIEL 0 (disabled) 0 (disabled) ; ssp* shadows any all bits set set_f i2c_sspstat set_f i2c_sspcon1 set_f i2c_sspcon2 bs_f TRISB, 0 bs_f TRISB, 1 bc_f IPR1, SSPIP bs_f SSPCON1, SSPEN bs_f PIE1, SSPIE return ;======================================== ; SLAVE - INTERRUPT HANDLING ; In general, we figure out our state and then see what kind of events ; we were expecting. 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? ; (we don't usually mention SMP, CKE and UA below) ; Some macros: chkvals_start_sspstat macro mov_fw i2c_sspstat endm chkval macro lastval, value, label xor_lw value ^ lastval bra_z label endm chkvals_addrrecv macro lastval chkval lastval, 0x8c, s_event_idle_addrrecvread ; A,!P, S,R,!BF chkval 0x8c, 0x89, s_event_idle_addrrecvwrite ; A,!P, S,W,BF endm chkvals_addrrecv_lastval equ 0x89 ;---------- i2cs_interrupt bt_f_if0 PIR1, SSPIF return ; We have an interrupt: bt_f_if1 i2c_sspcon1, WCOL bra_z s_event_bad bt_f_if1 i2c_sspcon1, SSPOV bra_z 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 mov_ff SSPSTAT, i2c_sspstat mov_ff SSPCON1, i2c_sspcon1 bt_f_if0 i2c_st, st_reading bra s_event_reading bt_f_if0 i2c_st, st_writing bra s_event_writing s_event_idle chkvals_start_sspstat chkvals_addrrecv 0 s_event_bad panic morse_SS ; slave, interrupt, controller in bad state ;======================================== ; SLAVE - READING ;---------- s_event_idle_addrrecvread bs_f i2c_st, st_reading call i2csu_read_begin bra s_events_reading_datasend ;---------- s_event_reading chkvals_start_sspstat chkval 0, 0xac, s_event_reading_datasent ; D,!P, S,R,!BF ; Whatever is happening, we're done reading now ! clr_f i2c_st call i2csu_read_done chkvals_start_sspstat chkval 0, 0xa8, s_event_reading_datanack ; D,!P, S,!R,!BF ; Or, maybe it was nack and then we were reselected: chkvals_addrrecv 0xa8 bra s_event_bad ;---------- s_event_reading_datasent call i2csu_read_another s_events_reading_datasend mov_wf SSPBUF bs_f SSPCON1, CKP s_event_reading_datanack return ;======================================== ; SLAVE - WRITING ;---------- s_event_idle_addrrecvwrite bs_f SSPCON1, 3 ; we'll need the Stop interrupt bs_f i2c_st, st_writing ; well, this is all fine so far, so do carry on: 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 return ;---------- s_event_writing chkvals_start_sspstat chkval 0, 0xa9, s_event_writing_datarecv ; D,!P, S,W,BF ; Well, we're done writing now in any case clr_f i2c_st bc_f SSPCON1, 3 ; no Start and Stop interrupts any more call i2csu_write_done ; Who knows what might have happened. We may have ; missed a number of S and P due to delay between ; clearing SSPIF and SSPM3(s&p-intrs) so we can't be ; too picky. ; First, the nice cases: chkvals_start_sspstat chkvals_addrrecv 0 ; Then random junk: mov_fw i2c_sspstat and_lw 0xc7 ; ?D_A, ?P; ?S xor_lw 0x80 ; SMP, !CKE, !R_W, !UA, !BF bt_f_if1 STATUS, Z return ; no good bra s_event_bad ;---------- s_event_writing_datarecv rcall s_write_slurpbyte bt_f_if1 i2c_st, st_subsequent goto i2csu_write_another ; not subsequent (yet): bs_f i2c_st, st_subsequent goto i2csu_write_begin ;====================================================================== include panic.fin include i2clib.inc end