;I2C communication routines by John West.
;This code will deal with any number of slave devices on the bus, but only
;one master.
;The hardware is very simple - two output bits and one input. The diode
;gives you a pretend open-collector output. Both lines require a pullup
;resistor (not shown).
;You can do with one output line and a bidirectional I/O line and no diode
;if you're really enthusiastic. I couldn't be bothered.
;You'll have to supply your own routines to read and write these lines - the
;places to put code are marked with !!!
--------+
|
SCL_OUT |----------------> SCL to the RTC
|
SDA_IN |--------+-------> SDA to the RTC
| |
SDA_OUT |---|<---+
|
--------+
;------------------------------------------------------------------------------
.module i2c
.include "hardware.h"
.include "globals.h"
.area _DATA
i2c_err:: .blkb 1
i2c_bufptr:: .blkw 1
i2c_tmp: .blkb 2
.area _CODE
;Idle state is after a stop condition, with SDA and SCL both high.
;Active state is after a start condition, with SDA high and SCL low.
i2c_init::
;Init the I2C bus
jsr stop
jsr stop
jsr stop
rts
i2c_read::
;Read bytes from an I2C device. Device address in A, subaddress in Y,
;number of bytes in X, address of buffer in i2c_bufptr.
sei
jsr stop
lda #0
sta i2c_err
pha
and #$fe
jsr start
jsr sendbyte
jsr readack
tya
jsr sendbyte
jsr readack
jsr start
pla
ora #1
jsr sendbyte
jsr readack
ldy #0
1$: jsr readbyte
sta (i2c_bufptr),y
inc i2c_bufptr
dex
beq 2$
jsr sendack
jmp 1$
2$: jsr stop
cli
rts
i2c_write::
;Write a byte to an I2C device. Device address in A, subaddress in Y,
;data to be written in X. One attempt.
sei
call i2c_init
and #$fe
jsr start
jsr sendbyte
jsr readack
tya
jsr sendbyte
jsr readack
txa
jsr sendbyte
jsr readack
jsr stop
cli
rts
readbyte:
;Read a byte into A. Assumes the bus is in the active state.
stx i2c_tmp
ldx #8
1$: jsr readbit
dex
bne 1$
ldx i2c_tmp
rts
readbit:
;Shift A left one bit and read a bit into A[0]
asl a
jsr sclset
;!!! Read the SDA line and jump to 1$ if it is 0.
ora #1
1$: jsr sclres
rts
sendbyte:
;Send the byte in A. Assumes the bus is in the active state.
sta i2c_tmp
stx i2c_tmp+1
ldx #8
1$: jsr sendbit
dex
bne 1$
lda i2c_tmp
ldx i2c_tmp+1
rts
sendbit:
;Send the bit in A[7]. Returns with A shifted one bit left (ready for
;sending the next bit). Assumes the bus is in the active state.
lsr a
bcc 1$
jsr sdaset
jr 2$
1$: jsr sdares
2$: jsr sclset
jsr sclres
jsr sdaset
rts
readack:
;Read an ACK (if the slave has sent it). Sets I2C_ERR if the slave
;doesn't ACK.
sta i2c_tmp
jsr readbit
and #1
beq 1$
lda #1
sta i2c_err
1$: lda i2c_tmp
rts
sendack:
;Send an ACK. Assumes the bus is in the active state.
jsr sdares
jsr sclset
jsr sclres
jsr sdaset
rts
start:
;Send an I2C start condition. Can be called from idle or active state.
jsr sclset
jsr sdares
jsr sclres
rts
stop:
;Send an I2C stop condition. Assumes the bus is in the active state, and
;puts it in the idle state.
jsr sdares
jsr sclset
jsr sdaset
rts
sdaset:
;!!! Puts a '1' on the SDA line
sdares:
;!!! Puts a '0' on the SDA line
sclset:
;!!! Puts a '1' on the SCL line
sclres:
;!!! Puts a '0' on the SCL line
;Month, dayofmonth, and so on all start at 0. (so January is month 0, the
;first day of the month is day 0).
;Year4 is the bottom two bits of the year (the number of years since the
;last leap year). This is going to fail in 2100, but I plan to be dead by
;then. The year calculation can only give 28 years after 1996 anyway (but
;the calendar repeats in a 28 year cycle, so it doesn't matter).
;1996 is used as the base year because that's when I originally wrote the
;code.
;The RTC's values are all in BCD. Its date values are 1-based, but my date
;code expects 0-based (I convert as I read them).
;Here's the RTC initialization routine: it starts the clock running and
;sets up a 2Hz interrupt.
rtc_init::
lda #rtc_addr
ldy #rtc_status
ldx #%00000100
jsr i2c_write
ldy #rtc_alarm
ldx #%11001001
jsr i2c_write
ldy #rtc_timer
ldx #0x50
jsr i2c_write
rts
;And to acknowledge the interrupt...
rtc_ack_irq::
lda #rtc_addr
ldy #rtc_status
ldx #%00000100
jsr i2c_write
ret
;The evil evil date calculations:
;calculate day of year from dayofmonth, month, and year4.
;Jan 1 is day 0.
;dayofyear = dayofmonth + 28 * month + magic[month]
;magic[]=[0,3,3,6,8,11,13,16,19,21,24,26] if year4!=0
;magic[]=[0,3,4,7,9,12,14,17,20,22,25,27] if year4=0
;magic[] is the accumulated number of days beyond 28 in each month.
;Calculate the day of week of Jan 1 this year from dayofyear
;and dayofweek.
;result in A. Sunday is day 0.
;dayofweekJan1 = (371 + dayofweek - dayofyear) % 7
;371 is added to make sure no negative numbers appear. It is a multiple of
;7, so it can't affect the result. (my 32 bit arithmetic routines are all
;unsigned)
;calculate years since 1996 from dayofweekJan1 and year4.
;1996 is year 0.
;years since 1996 = year4 + 4(((dayofweekJan1 + magic[year4]) * 3) % 7)
;magic[]=[6,4,3,2]
;magic[] is real magic. The whole calculation is magic. I have no
;idea how it works.
;calculate days since 1996 from date_year and date_dayofyear.
;dayssince1996 = 365*yearssince1996 + dayofyear + (yearssince1996 + 3) / 4
;Jan 1 1996 is day 0.
-
This message was sent through the cbm-hackers mailing list.
To unsubscribe: echo unsubscribe | mail cbm-hackers-request@dot.tcm.hut.fi.
Archive generated by hypermail 2.1.1.