-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtwi_sw_master.c
492 lines (412 loc) · 12.4 KB
/
twi_sw_master.c
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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
/* Copyright (c) 2009 Nordic Semiconductor. All Rights Reserved.
*
* The information contained herein is property of Nordic Semiconductor ASA.
* Terms and conditions of usage are described in detail in NORDIC
* SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
*
* Licensees are granted free, non-transferable use of the information. NO
* WARRANTY of ANY KIND is provided. This heading must NOT be removed from
* the file.
*
*/
#include <stdbool.h>
#include <stdint.h>
#include "twi_master.h"
#include "nrf_delay.h"
#include "twi_master_config.h"
/*lint -e415 -e845 -save "Out of bounds access" */
#define TWI_SDA_STANDARD0_NODRIVE1() do { \
NRF_GPIO->PIN_CNF[TWI_MASTER_CONFIG_DATA_PIN_NUMBER] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) \
|(GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) \
|(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) \
|(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) \
|(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos); \
} while (0) /*!< Configures SDA pin to Standard-0, No-drive 1 */
#define TWI_SCL_STANDARD0_NODRIVE1() do { \
NRF_GPIO->PIN_CNF[TWI_MASTER_CONFIG_CLOCK_PIN_NUMBER] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) \
|(GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) \
|(GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) \
|(GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) \
|(GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos); \
} while (0) /*!< Configures SCL pin to Standard-0, No-drive 1 */
/*lint -restore */
#ifndef TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE
#define TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE (0UL) //!< Unit is number of empty loops. Timeout for SMBus devices is 35 ms. Set to zero to disable slave timeout altogether.
#endif
static bool twi_master_clear_bus(void);
static bool twi_master_issue_startcondition(void);
static bool twi_master_issue_stopcondition(void);
static bool twi_master_clock_byte(uint_fast8_t databyte);
static bool twi_master_clock_byte_in(uint8_t * databyte, bool ack);
static bool twi_master_wait_while_scl_low(void);
bool twi_master_init(void)
{
// Configure both pins to output Standard 0, No-drive (open-drain) 1
TWI_SDA_STANDARD0_NODRIVE1(); /*lint !e416 "Creation of out of bounds pointer" */
TWI_SCL_STANDARD0_NODRIVE1(); /*lint !e416 "Creation of out of bounds pointer" */
// Configure SCL as output
TWI_SCL_HIGH();
TWI_SCL_OUTPUT();
// Configure SDA as output
TWI_SDA_HIGH();
TWI_SDA_OUTPUT();
return twi_master_clear_bus();
}
bool twi_master_transfer(uint8_t address, uint8_t * data, uint8_t data_length, bool issue_stop_condition)
{
bool transfer_succeeded = true;
transfer_succeeded &= twi_master_issue_startcondition();
transfer_succeeded &= twi_master_clock_byte(address);
if (address & TWI_READ_BIT)
{
/* Transfer direction is from Slave to Master */
while (data_length-- && transfer_succeeded)
{
// To indicate to slave that we've finished transferring last data byte
// we need to NACK the last transfer.
if (data_length == 0)
{
transfer_succeeded &= twi_master_clock_byte_in(data, (bool)false);
}
else
{
transfer_succeeded &= twi_master_clock_byte_in(data, (bool)true);
}
data++;
}
}
else
{
/* Transfer direction is from Master to Slave */
while (data_length-- && transfer_succeeded)
{
transfer_succeeded &= twi_master_clock_byte(*data);
data++;
}
}
if (issue_stop_condition || !transfer_succeeded)
{
transfer_succeeded &= twi_master_issue_stopcondition();
}
return transfer_succeeded;
}
/**
* @brief Function for detecting stuck slaves and tries to clear the bus.
*
* @return
* @retval false Bus is stuck.
* @retval true Bus is clear.
*/
static bool twi_master_clear_bus(void)
{
bool bus_clear;
TWI_SDA_HIGH();
TWI_SCL_HIGH();
TWI_DELAY();
if (TWI_SDA_READ() == 1 && TWI_SCL_READ() == 1)
{
bus_clear = true;
}
else if (TWI_SCL_READ() == 1)
{
bus_clear = false;
// Clock max 18 pulses worst case scenario(9 for master to send the rest of command and 9 for slave to respond) to SCL line and wait for SDA come high
for (uint_fast8_t i = 18; i--;)
{
TWI_SCL_LOW();
TWI_DELAY();
TWI_SCL_HIGH();
TWI_DELAY();
if (TWI_SDA_READ() == 1)
{
bus_clear = true;
break;
}
}
}
else
{
bus_clear = false;
}
return bus_clear;
}
/**
* @brief Function for issuing TWI START condition to the bus.
*
* START condition is signaled by pulling SDA low while SCL is high. After this function SCL and SDA will be low.
*
* @return
* @retval false Timeout detected
* @retval true Clocking succeeded
*/
static bool twi_master_issue_startcondition(void)
{
#if 0
if (TWI_SCL_READ() == 1 && TWI_SDA_READ() == 1)
{
// Pull SDA low
TWI_SDA_LOW();
}
else if (TWI_SCL_READ() == 1 && TWI_SDA_READ() == 0)
{
// Issue Stop by pulling SDA high
TWI_SDA_HIGH();
TWI_DELAY();
// Then Start by pulling SDA low
TWI_SDA_LOW();
}
else if (TWI_SCL_READ() == 0 && TWI_SDA_READ() == 0)
{
// First pull SDA high
TWI_SDA_HIGH();
// Then SCL high
if (!twi_master_wait_while_scl_low())
{
return false;
}
// Then SDA low
TWI_SDA_LOW();
}
else if (TWI_SCL_READ() == 0 && TWI_SDA_READ() == 1)
{
// SCL high
if (!twi_master_wait_while_scl_low())
{
return false;
}
// Then SDA low
TWI_SDA_LOW();
}
TWI_DELAY();
TWI_SCL_LOW();
#endif
// Make sure both SDA and SCL are high before pulling SDA low.
TWI_SDA_HIGH();
TWI_DELAY();
if (!twi_master_wait_while_scl_low())
{
return false;
}
TWI_SDA_LOW();
TWI_DELAY();
// Other module function expect SCL to be low
TWI_SCL_LOW();
TWI_DELAY();
return true;
}
/**
* @brief Function for issuing TWI STOP condition to the bus.
*
* STOP condition is signaled by pulling SDA high while SCL is high. After this function SDA and SCL will be high.
*
* @return
* @retval false Timeout detected
* @retval true Clocking succeeded
*/
static bool twi_master_issue_stopcondition(void)
{
#if 0
if (TWI_SCL_READ() == 1 && TWI_SDA_READ() == 1)
{
// Issue start, then issue stop
// Pull SDA low to issue START
TWI_SDA_LOW();
TWI_DELAY();
// Pull SDA high while SCL is high to issue STOP
TWI_SDA_HIGH();
}
else if (TWI_SCL_READ() == 1 && TWI_SDA_READ() == 0)
{
// Pull SDA high while SCL is high to issue STOP
TWI_SDA_HIGH();
}
else if (TWI_SCL_READ() == 0 && TWI_SDA_READ() == 0)
{
if (!twi_master_wait_while_scl_low())
{
return false;
}
// Pull SDA high while SCL is high to issue STOP
TWI_SDA_HIGH();
}
else if (TWI_SCL_READ() == 0 && TWI_SDA_READ() == 1)
{
TWI_SDA_LOW();
TWI_DELAY();
// SCL high
if (!twi_master_wait_while_scl_low())
{
return false;
}
// Pull SDA high while SCL is high to issue STOP
TWI_SDA_HIGH();
}
TWI_DELAY();
#endif
TWI_SDA_LOW();
TWI_DELAY();
if (!twi_master_wait_while_scl_low())
{
return false;
}
TWI_SDA_HIGH();
TWI_DELAY();
return true;
}
/**
* @brief Function for clocking one data byte out and reads slave acknowledgment.
*
* Can handle clock stretching.
* After calling this function SCL is low and SDA low/high depending on the
* value of LSB of the data byte.
* SCL is expected to be output and low when entering this function.
*
* @param databyte Data byte to clock out.
* @return
* @retval true Slave acknowledged byte.
* @retval false Timeout or slave didn't acknowledge byte.
*/
static bool twi_master_clock_byte(uint_fast8_t databyte)
{
bool transfer_succeeded = true;
/** @snippet [TWI SW master write] */
// Make sure SDA is an output
TWI_SDA_OUTPUT();
// MSB first
for (uint_fast8_t i = 0x80; i != 0; i >>= 1)
{
TWI_SCL_LOW();
TWI_DELAY();
if (databyte & i)
{
TWI_SDA_HIGH();
}
else
{
TWI_SDA_LOW();
}
if (!twi_master_wait_while_scl_low())
{
transfer_succeeded = false; // Timeout
break;
}
}
// Finish last data bit by pulling SCL low
TWI_SCL_LOW();
TWI_DELAY();
/** @snippet [TWI SW master write] */
// Configure TWI_SDA pin as input for receiving the ACK bit
TWI_SDA_INPUT();
// Give some time for the slave to load the ACK bit on the line
TWI_DELAY();
// Pull SCL high and wait a moment for SDA line to settle
// Make sure slave is not stretching the clock
transfer_succeeded &= twi_master_wait_while_scl_low();
// Read ACK/NACK. NACK == 1, ACK == 0
transfer_succeeded &= !(TWI_SDA_READ());
// Finish ACK/NACK bit clock cycle and give slave a moment to release control
// of the SDA line
TWI_SCL_LOW();
TWI_DELAY();
// Configure TWI_SDA pin as output as other module functions expect that
TWI_SDA_OUTPUT();
return transfer_succeeded;
}
/**
* @brief Function for clocking one data byte in and sends ACK/NACK bit.
*
* Can handle clock stretching.
* SCL is expected to be output and low when entering this function.
* After calling this function, SCL is high and SDA low/high depending if ACK/NACK was sent.
*
* @param databyte Data byte to clock out.
* @param ack If true, send ACK. Otherwise send NACK.
* @return
* @retval true Byte read succesfully
* @retval false Timeout detected
*/
static bool twi_master_clock_byte_in(uint8_t *databyte, bool ack)
{
uint_fast8_t byte_read = 0;
bool transfer_succeeded = true;
/** @snippet [TWI SW master read] */
// Make sure SDA is an input
TWI_SDA_INPUT();
// SCL state is guaranteed to be high here
// MSB first
for (uint_fast8_t i = 0x80; i != 0; i >>= 1)
{
if (!twi_master_wait_while_scl_low())
{
transfer_succeeded = false;
break;
}
if (TWI_SDA_READ())
{
byte_read |= i;
}
else
{
// No need to do anything
}
TWI_SCL_LOW();
TWI_DELAY();
}
// Make sure SDA is an output before we exit the function
TWI_SDA_OUTPUT();
/** @snippet [TWI SW master read] */
*databyte = (uint8_t)byte_read;
// Send ACK bit
// SDA high == NACK, SDA low == ACK
if (ack)
{
TWI_SDA_LOW();
}
else
{
TWI_SDA_HIGH();
}
// Let SDA line settle for a moment
TWI_DELAY();
// Drive SCL high to start ACK/NACK bit transfer
// Wait until SCL is high, or timeout occurs
if (!twi_master_wait_while_scl_low())
{
transfer_succeeded = false; // Timeout
}
// Finish ACK/NACK bit clock cycle and give slave a moment to react
TWI_SCL_LOW();
TWI_DELAY();
return transfer_succeeded;
}
/**
* @brief Function for pulling SCL high and waits until it is high or timeout occurs.
*
* SCL is expected to be output before entering this function.
* @note If TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE is set to zero, timeout functionality is not compiled in.
* @return
* @retval true SCL is now high.
* @retval false Timeout occurred and SCL is still low.
*/
static bool twi_master_wait_while_scl_low(void)
{
#if TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE != 0
uint32_t volatile timeout_counter = TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE;
#endif
// Pull SCL high just in case if something left it low
TWI_SCL_HIGH();
TWI_DELAY();
while (TWI_SCL_READ() == 0)
{
// If SCL is low, one of the slaves is busy and we must wait
#if TWI_MASTER_TIMEOUT_COUNTER_LOAD_VALUE != 0
if (timeout_counter-- == 0)
{
// If timeout_detected, return false
return false;
}
#endif
}
return true;
}
/*lint --flb "Leave library region" */