-
Notifications
You must be signed in to change notification settings - Fork 0
/
i2cmaster.S
351 lines (303 loc) · 9.51 KB
/
i2cmaster.S
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
;*************************************************************************
; Title : I2C (Single) Master Implementation
; Author: Peter Fleury <[email protected]>
; based on Atmel Appl. Note AVR300
; File: $Id: i2cmaster.S,v 1.13 2015/09/16 11:21:00 peter Exp $
; Software: AVR-GCC 4.x
; Target: any AVR device
;
; DESCRIPTION
; Basic routines for communicating with I2C slave devices. This
; "single" master implementation is limited to one bus master on the
; I2C bus.
;
; Based on the Atmel Application Note AVR300, corrected and adapted
; to GNU assembler and AVR-GCC C call interface
; Replaced the incorrect quarter period delays found in AVR300 with
; half period delays.
;
; USAGE
; These routines can be called from C, refere to file i2cmaster.h.
; See example test_i2cmaster.c
; Adapt the SCL and SDA port and pin definitions and eventually
; the delay routine to your target !
; Use 4.7k pull-up resistor on the SDA and SCL pin.
;
; NOTES
; The I2C routines can be called either from non-interrupt or
; interrupt routines, not both.
;
;*************************************************************************
#include <avr/io.h>
#undef SCL_PORT
#undef SCL_DDR
;******----- Adapt these SCA and SCL port and pin definition to your target !!
;
#ifdef LEVELMETER
#define SDA 4
#define SCL 2
#endif
#ifdef GASCOUNTER_MODULE
#define SDA 1
#define SCL 0
#endif
#ifdef ILM_MODULE
#define SDA 1
#define SCL 0
#endif
#define SDA_PORT PORTC // SDA Port C
#define SCL_PORT PORTC // SCL Port C
;******----------------------------------------------------------------------
;-- map the IO register back into the IO address space
#define SDA_DDR (_SFR_IO_ADDR(SDA_PORT) - 1)
#define SCL_DDR (_SFR_IO_ADDR(SCL_PORT) - 1)
#define SDA_OUT _SFR_IO_ADDR(SDA_PORT)
#define SCL_OUT _SFR_IO_ADDR(SCL_PORT)
#define SDA_IN (_SFR_IO_ADDR(SDA_PORT) - 2)
#define SCL_IN (_SFR_IO_ADDR(SCL_PORT) - 2)
#ifndef __tmp_reg__
#define __tmp_reg__ 0
#endif
.section .text
;*************************************************************************
; delay half period
; For I2C in normal mode (100kHz), use T/2 > 5us
; For I2C in fast mode (400kHz), use T/2 > 1.25us
;*************************************************************************
.stabs "",100,0,0,i2c_delay_T2
.stabs "i2cmaster.S",100,0,0,i2c_delay_T2
.func i2c_delay_T2 ; delay 5.0 microsec with 4 Mhz crystal
i2c_delay_T2: ; 3 cycles
#if F_CPU <= 4000000UL
rjmp 1f ; 2 "
1: rjmp 2f ; 2 "
2: rjmp 3f ; 2 "
3: rjmp 4f ; 2 "
4: rjmp 5f ; 2 "
5: rjmp 6f ; 2 "
6: nop ; 1 "
ret ; 4 " total 20 cyles = 5.0 microsec with 4 Mhz crystal
#elif F_CPU <= 8000000UL
push r24 ; 2 cycle
ldi r24, 7 ; 1 cycle
nop ; 1 cycle
1: sbiw r24, 1 ; 2 cycle
brne 1b ; 2 or 1 cycle, 4 cycles per loop
pop r24 ; 2 ycle
ret ; 4 cycle = total 60 cycles = 5.0 microsec with 12 Mhz crystal
#elif F_CPU <= 12000000UL
push r24 ; 2 cycle
ldi r24, 12 ; 1 cycle
nop ; 1 cycle
1: sbiw r24, 1 ; 2 cycle
brne 1b ; 2 or 1 cycle, 4 cycles per loop
pop r24 ; 2 ycle
ret ; 4 cycle = total 60 cycles = 5.0 microsec with 12 Mhz crystal
#elif F_CPU <= 16000000UL
push r24 ; 2 cycle
ldi r24, 17 ; 1 cycle
nop ; 1 cycle
1: sbiw r24, 1 ; 2 cycle
brne 1b ; 2 or 1 cycle, 4 cycles per loop
pop r24 ; 2 ycle
ret ; 4 cycle = total 80 cycles = 5.0 microsec with 16 Mhz crystal
#else
push r24 ; 2 cycle
ldi r24, 22 ; 1 cycle
nop ; 1 cycle
1: sbiw r24, 1 ; 2 cycle
brne 1b ; 2 or 1 cycle, 4 cycles per loop
pop r24 ; 2 ycle
ret ; 4 cycle = total 100 cycles = 5.0 microsec with 20 Mhz crystal
#endif
.endfunc ;
;*************************************************************************
; Initialization of the I2C bus interface. Need to be called only once
;
; extern void i2c_init(void)
;*************************************************************************
.global i2c_init
.func i2c_init
i2c_init:
cbi SDA_DDR,SDA ;release SDA
cbi SCL_DDR,SCL ;release SCL
cbi SDA_OUT,SDA
cbi SCL_OUT,SCL
ret
.endfunc
;*************************************************************************
; Issues a start condition and sends address and transfer direction.
; return 0 = device accessible, 1= failed to access device
;
; extern unsigned char i2c_start(unsigned char addr);
; addr = r24, return = r25(=0):r24
;*************************************************************************
.global i2c_start
.func i2c_start
i2c_start:
sbi SDA_DDR,SDA ;force SDA low
rcall i2c_delay_T2 ;delay T/2
rcall i2c_write ;write address
ret
.endfunc
;*************************************************************************
; Issues a repeated start condition and sends address and transfer direction.
; return 0 = device accessible, 1= failed to access device
;
; extern unsigned char i2c_rep_start(unsigned char addr);
; addr = r24, return = r25(=0):r24
;*************************************************************************
.global i2c_rep_start
.func i2c_rep_start
i2c_rep_start:
sbi SCL_DDR,SCL ;force SCL low
rcall i2c_delay_T2 ;delay T/2
cbi SDA_DDR,SDA ;release SDA
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
rcall i2c_delay_T2 ;delay T/2
sbi SDA_DDR,SDA ;force SDA low
rcall i2c_delay_T2 ;delay T/2
rcall i2c_write ;write address
ret
.endfunc
;*************************************************************************
; Issues a start condition and sends address and transfer direction.
; If device is busy, use ack polling to wait until device is ready
;
; extern void i2c_start_wait(unsigned char addr);
; addr = r24
;*************************************************************************
.global i2c_start_wait
.func i2c_start_wait
i2c_start_wait:
mov __tmp_reg__,r24
i2c_start_wait1:
sbi SDA_DDR,SDA ;force SDA low
rcall i2c_delay_T2 ;delay T/2
mov r24,__tmp_reg__
rcall i2c_write ;write address
tst r24 ;if device not busy -> done
breq i2c_start_wait_done
rcall i2c_stop ;terminate write operation
rjmp i2c_start_wait1 ;device busy, poll ack again
i2c_start_wait_done:
ret
.endfunc
;*************************************************************************
; Terminates the data transfer and releases the I2C bus
;
; extern void i2c_stop(void)
;*************************************************************************
.global i2c_stop
.func i2c_stop
i2c_stop:
sbi SCL_DDR,SCL ;force SCL low
sbi SDA_DDR,SDA ;force SDA low
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
rcall i2c_delay_T2 ;delay T/2
cbi SDA_DDR,SDA ;release SDA
rcall i2c_delay_T2 ;delay T/2
ret
.endfunc
;*************************************************************************
; Send one byte to I2C device
; return 0 = write successful, 1 = write failed
;
; extern unsigned char i2c_write( unsigned char data );
; data = r24, return = r25(=0):r24
;*************************************************************************
.global i2c_write
.func i2c_write
i2c_write:
sec ;set carry flag
rol r24 ;shift in carry and out bit one
rjmp i2c_write_first
i2c_write_bit:
lsl r24 ;if transmit register empty
i2c_write_first:
breq i2c_get_ack
sbi SCL_DDR,SCL ;force SCL low
brcc i2c_write_low
nop
cbi SDA_DDR,SDA ;release SDA
rjmp i2c_write_high
i2c_write_low:
sbi SDA_DDR,SDA ;force SDA low
rjmp i2c_write_high
i2c_write_high:
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
rcall i2c_delay_T2 ;delay T/2
rjmp i2c_write_bit
i2c_get_ack:
sbi SCL_DDR,SCL ;force SCL low
cbi SDA_DDR,SDA ;release SDA
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
i2c_ack_wait:
sbis SCL_IN,SCL ;wait SCL high (in case wait states are inserted)
rjmp i2c_ack_wait
clr r24 ;return 0
sbic SDA_IN,SDA ;if SDA high -> return 1
ldi r24,1
rcall i2c_delay_T2 ;delay T/2
clr r25
ret
.endfunc
;*************************************************************************
; read one byte from the I2C device, send ack or nak to device
; (ack=1, send ack, request more data from device
; ack=0, send nak, read is followed by a stop condition)
;
; extern unsigned char i2c_read(unsigned char ack);
; ack = r24, return = r25(=0):r24
; extern unsigned char i2c_readAck(void);
; extern unsigned char i2c_readNak(void);
; return = r25(=0):r24
;*************************************************************************
.global i2c_readAck
.global i2c_readNak
.global i2c_read
.func i2c_read
i2c_readNak:
clr r24
rjmp i2c_read
i2c_readAck:
ldi r24,0x01
i2c_read:
ldi r23,0x01 ;data = 0x01
i2c_read_bit:
sbi SCL_DDR,SCL ;force SCL low
cbi SDA_DDR,SDA ;release SDA (from previous ACK)
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
rcall i2c_delay_T2 ;delay T/2
i2c_read_stretch:
sbis SCL_IN, SCL ;loop until SCL is high (allow slave to stretch SCL)
rjmp i2c_read_stretch
clc ;clear carry flag
sbic SDA_IN,SDA ;if SDA is high
sec ; set carry flag
rol r23 ;store bit
brcc i2c_read_bit ;while receive register not full
i2c_put_ack:
sbi SCL_DDR,SCL ;force SCL low
cpi r24,1
breq i2c_put_ack_low ;if (ack=0)
cbi SDA_DDR,SDA ; release SDA
rjmp i2c_put_ack_high
i2c_put_ack_low: ;else
sbi SDA_DDR,SDA ; force SDA low
i2c_put_ack_high:
rcall i2c_delay_T2 ;delay T/2
cbi SCL_DDR,SCL ;release SCL
i2c_put_ack_wait:
sbis SCL_IN,SCL ;wait SCL high
rjmp i2c_put_ack_wait
rcall i2c_delay_T2 ;delay T/2
mov r24,r23
clr r25
ret
.endfunc