summaryrefslogtreecommitdiff
path: root/digital/avr/modules/twi/twi_usi.avr.c
blob: 682bf3e9561df51d0775176ca537f1dd0ab100e2 (plain)
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
/* twi_usi.avr.c - USI implementation. */
/* avr.twi - TWI AVR module. {{{
 *
 * Copyright (C) 2010 Nicolas Schodet
 *
 * APBTeam:
 *        Web: http://apbteam.org/
 *      Email: team AT apbteam DOT org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * }}} */
#include "common.h"
#include "twi.h"

#if TWI_DRIVER == TWI_DRIVER_USI

# include "io.h"

# include <string.h>

# if AC_TWI_PULL_UP
#  error "twi: pull up not supported by USI driver"
# endif
# if AC_TWI_MASTER_ENABLE
#  error "twi: master mode not supported by USI driver"
# endif

#if defined (__AVR_ATtiny25__) \
    || defined (__AVR_ATtiny45__) \
    || defined (__AVR_ATtiny85__)
# define SDA_IO B, 0
# define SCL_IO B, 2
#else
# error "twi: not defined for this chip"
#endif

#if AC_TWI_NO_INTERRUPT
# define HANDLE_START() static void handle_start (void)
# define HANDLE_OVERFLOW() static void handle_overflow (void)
# define IF_INTR(x) 0
#else
# define HANDLE_START() SIGNAL (USI_START_vect)
# define HANDLE_OVERFLOW() SIGNAL (USI_OVF_vect)
# define IF_INTR(x) (x)
#endif

/** Value to be used for USISR to clear all flags, except start condition
 * which is cleared only if CLEAR_START is 1, and to set the counter so that
 * it overflows after BITS bits tranceived. */
#define USISR_SET(clear_start, bits) \
    (((clear_start) << USISIF) \
     | _BV (USIOIF) | _BV (USIPF) | _BV (USIDC) \
     | (((bits) ? (16 - (bits) * 2) : 0) << USICNT0))

/** Value to be used for USICR when waiting for start condition only. */
#define USICR_START_SET \
    (IF_INTR (_BV (USISIE)) | _BV (USIWM1) | _BV (USICS1))

/** Value to be used for USICR when waiting for start condition or counter
 * overflow. */
#define USICR_START_OR_OVERFLOW_SET \
    (IF_INTR (_BV (USISIE) | _BV (USIOIE)) | _BV (USIWM1) | _BV (USIWM0) \
     | _BV (USICS1))

/** USI slave state, see below for state explanation. */
enum
{
    TWI_USI_WAIT_ADDRESS,
    TWI_USI_RECV,
    TWI_USI_RECV_SEND_ACK,
    TWI_USI_SEND_CHECK_ACK,
    TWI_USI_SEND,
    TWI_USI_SEND_RECV_ACK,
};

/** Module context. */
struct twi_usi_t
{
    /** Slave address. */
    uint8_t address;
    /** Current state, only used on counter overflow. */
    uint8_t state;
    /** Reception buffer. */
    uint8_t recv_buffer[AC_TWI_SLAVE_RECV_BUFFER_SIZE];
    /** Reception buffer current size. */
    uint8_t recv_buffer_size;
    /** Transmission buffer. */
    uint8_t send_buffer[AC_TWI_SLAVE_SEND_BUFFER_SIZE];
    /** Transmission buffer current size. */
    uint8_t send_buffer_size;
    /** Index of last transmitted byte in transmission buffer. */
    uint8_t send_buffer_index;
};

/** Global context. */
static struct twi_usi_t twi_usi_global;
#define ctx twi_usi_global

void
twi_init (uint8_t addr)
{
    /* Reset context. */
    ctx.address = addr;
    ctx.recv_buffer_size = 0;
    ctx.send_buffer_size = 0;
    /* Set IO (SDA input, SCL driven (for clock stretching)), enable USI. */
    IO_SET (SDA_IO);
    IO_SET (SCL_IO);
    USISR = USISR_SET (1, 0);
    USICR = USICR_START_SET;
    IO_INPUT (SDA_IO);
    IO_OUTPUT (SCL_IO);
}

void
twi_uninit (void)
{
    /* Stop USI, set IO to input. */
    IO_INPUT (SDA_IO);
    IO_INPUT (SCL_IO);
    USICR = 0;
    IO_CLR (SDA_IO);
    IO_CLR (SCL_IO);
}

void
twi_slave_update (const uint8_t *buffer, uint8_t size)
{
#if !AC_TWI_NO_INTERRUPT
# warning "twi: driver not safe using interrupts"
#endif
    memcpy (ctx.send_buffer, buffer, size);
    ctx.send_buffer_size = size;
}

/** Handle start condition reception. */
HANDLE_START ()
{
    /* Stop driving SDA (start condition acts as a reset). */
    IO_INPUT (SDA_IO);
    /* Wait until the start condition is finished. */
    while (!IO_GET (SDA_IO) && IO_GET (SCL_IO))
	;
    /* Prepare for address reception. */
    ctx.state = TWI_USI_WAIT_ADDRESS;
    USICR = USICR_START_OR_OVERFLOW_SET;
    USISR = USISR_SET (1, 8);
}

/** Handle USI counter overflow. */
HANDLE_OVERFLOW ()
{
    uint8_t sr, send;
    switch (ctx.state)
      {
      case TWI_USI_WAIT_ADDRESS:
	/* Start condition has been received, check if this is our address,
	 * send ACK and go to receiver or sender state. */
	if ((USIDR & 0xfe) == ctx.address)
	  {
	    send = USIDR & 1;
	    /* Send ACK. */
	    USIDR = 0;
	    IO_OUTPUT (SDA_IO);
	    USISR = USISR_SET (0, 1);
	    /* Next state. */
	    if (send)
	      {
		ctx.send_buffer_index = 0;
		ctx.state = TWI_USI_SEND;
	      }
	    else
	      {
		ctx.recv_buffer_size = 0;
		ctx.state = TWI_USI_RECV;
	      }
	  }
	else
	  {
	    USICR = USICR_START_SET;
	    USISR = USISR_SET (0, 0);
	  }
	break;
    /* Receiver mode. */
      case TWI_USI_RECV:
	/* ACK has been transmitted, receive data.  If we are here, there is
	 * room left to receive this data. */
	IO_INPUT (SDA_IO);
	USISR = USISR_SET (0, 8);
	ctx.state = TWI_USI_RECV_SEND_ACK;
	/* Poll for STOP condition detection.
	 * Here, the master will clock SCL to send data, or will send a stop
	 * condition, wait for one of those events. */
	do
	    sr = USISR;
	while (!(sr & _BV (USIPF))
	       && ((sr >> USICNT0) & 0xf) < 16 - 7 * 2);
	/* On STOP condition stop here. */
	if (sr & _BV (USIPF))
	  {
	    USICR = USICR_START_SET;
	    USISR = USISR_SET (0, 0);
	    AC_TWI_SLAVE_RECV (ctx.recv_buffer, ctx.recv_buffer_size);
	  }
	break;
      case TWI_USI_RECV_SEND_ACK:
	/* Data has been received, send ACK if there is room left. */
	ctx.recv_buffer[ctx.recv_buffer_size++] = USIDR;
	if (ctx.recv_buffer_size < AC_TWI_SLAVE_RECV_BUFFER_SIZE)
	  {
	    USIDR = 0;
	    IO_OUTPUT (SDA_IO);
	    USISR = USISR_SET (0, 1);
	    ctx.state = TWI_USI_RECV;
	  }
	else
	  {
	    USICR = USICR_START_SET;
	    USISR = USISR_SET (0, 0);
	    AC_TWI_SLAVE_RECV (ctx.recv_buffer, ctx.recv_buffer_size);
	  }
	break;
    /* Transmitter mode. */
      case TWI_USI_SEND_CHECK_ACK:
	/* Check received ACK, send if master wants to continue. */
	if (USIDR)
	  {
	    /* NACK received, stop here. */
	    USICR = USICR_START_SET;
	    USISR = USISR_SET (0, 0);
	    break;
	  }
	/* no break */
      case TWI_USI_SEND:
	/* ACK has been transmitted or received, send next data. */
	if (ctx.send_buffer_index < ctx.send_buffer_size)
	  {
	    USIDR = ctx.send_buffer[ctx.send_buffer_index++];
	    IO_OUTPUT (SDA_IO);
	    USISR = USISR_SET (0, 8);
	    ctx.state = TWI_USI_SEND_RECV_ACK;
	  }
	else
	  {
	    /* No data to send, stop here. */
	    IO_INPUT (SDA_IO);
	    USICR = USICR_START_SET;
	    USISR = USISR_SET (0, 0);
	  }
	break;
      case TWI_USI_SEND_RECV_ACK:
	/* Data has been sent, receive ACK. */
	USIDR = 0;
	IO_INPUT (SDA_IO);
	USISR = USISR_SET (0, 1);
	ctx.state = TWI_USI_SEND_CHECK_ACK;
	break;
      }
}

# if AC_TWI_NO_INTERRUPT

void
twi_update (void)
{
    if (USISR & _BV (USISIF))
	handle_start ();
    if (USISR & _BV (USIOIF))
	handle_overflow ();
}

# endif /* AC_TWI_NO_INTERRUPT */

#endif /* TWI_DRIVER == TWI_DRIVER_USI */