Skip to content

Commit b64b6e6

Browse files
author
Jamie Smith
authored
LPC1768: Rewrite I2CSlave HAL to match datasheet way of doing it (#280)
* LPC1768: Rewrite I2CSlave HAL to match datasheet way of doing it * Style and doc fixes
1 parent 87bc6de commit b64b6e6

File tree

3 files changed

+122
-44
lines changed

3 files changed

+122
-44
lines changed

drivers/include/drivers/I2CSlave.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,10 @@ class I2CSlave {
165165
*/
166166
int receive(void);
167167

168-
/** Read bytes transmitted to this MCU from an I2C master.
168+
/**
169+
* @brief Read bytes transmitted to this MCU from an I2C master.
170+
*
171+
* Call this function only once \c receive() returns WriteAddressed or WriteGeneral.
169172
*
170173
* @param data Pointer to the buffer to read data into.
171174
* @param length Maximum number of bytes to read.
@@ -180,7 +183,11 @@ class I2CSlave {
180183
*/
181184
int read(void);
182185

183-
/** Write to an I2C master.
186+
/**
187+
* @brief Write to an I2C master which is addressing this slave device.
188+
*
189+
* Call this function only once \c receive() returns ReadAddressed.
190+
* This will send the given data bytes to the master and then send a NACK to end the read transaction.
184191
*
185192
* @param data Pointer to the buffer containing the data to be sent.
186193
* @param length Number of bytes to send.
@@ -204,7 +211,7 @@ class I2CSlave {
204211

205212
/** Set the I2C slave address.
206213
*
207-
* @param address The address to set for the slave (least significant bit is ignored).
214+
* @param address The 8-bit address to set for the slave (least significant bit is ignored).
208215
*
209216
* @note If address is set to 0, the slave will only respond to the
210217
* general call address.

hal/include/hal/i2c_api.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ int i2c_slave_read(i2c_t *obj, char *data, int length);
331331
* @param obj The I2C object
332332
* @param data The buffer for sending
333333
* @param length Number of bytes to write
334-
* @return non-zero if a value is available
334+
* @return number of bytes actually written to the master, or negative value on error.
335335
*/
336336
int i2c_slave_write(i2c_t *obj, const char *data, int length);
337337

targets/TARGET_NXP/TARGET_LPC176X/i2c_api.c

Lines changed: 111 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -382,56 +382,127 @@ int i2c_slave_receive(i2c_t *obj) {
382382
}
383383

384384
int i2c_slave_read(i2c_t *obj, char *data, int length) {
385+
385386
int count = 0;
386-
int status;
387-
388-
do {
389-
i2c_clear_SI(obj);
390-
i2c_wait_SI(obj);
391-
status = i2c_status(obj);
392-
if((status == 0x80) || (status == 0x90)) {
393-
data[count] = I2C_DAT(obj) & 0xFF;
387+
388+
if(i2c_status(obj) != 0x60 && i2c_status(obj) != 0x70) {
389+
return -1; // I2C peripheral not in setup-write-to-slave mode
390+
}
391+
392+
if(i2c_status(obj) == 0x70) {
393+
// When addressed with the general call address, per the manual we can only receive a max of 1 byte.
394+
if(length > 1) {
395+
length = 1;
394396
}
395-
count++;
396-
} while (((status == 0x80) || (status == 0x90) ||
397-
(status == 0x060) || (status == 0x70)) && (count < length));
398-
399-
// Clear old status and wait for Serial Interrupt.
400-
i2c_clear_SI(obj);
401-
i2c_wait_SI(obj);
402-
403-
// Obtain new status.
404-
status = i2c_status(obj);
405-
406-
if(status != 0xA0) {
407-
i2c_stop(obj);
408397
}
409-
398+
399+
i2c_conset(obj, 0, 0, 0, length > 0); // Set AA flag to acknowledge write as long as we have buffer space to store a byte in
410400
i2c_clear_SI(obj);
411-
412-
return count;
401+
402+
// This is implemented as a state machine according to section 19.10.8 in the reference manual.
403+
while(true) {
404+
405+
// Wait until the I2C peripheral has an event for us
406+
i2c_wait_SI(obj);
407+
408+
#if LPC1768_I2C_DEBUG
409+
printf("i2c_slave_read(): in state 0x%hhx\n", i2c_status(obj));
410+
#endif
411+
412+
switch(i2c_status(obj)) {
413+
case 0x68:
414+
case 0x78:
415+
// Arbitration Lost event. I'm a bit confused about how this can happen as a slave device but the manual says it can...
416+
i2c_conclr(obj, 1, 0, 0, 1); // Clear start and ack
417+
i2c_clear_SI(obj);
418+
419+
// Reset the I2C state machine. This doesn't actually send a STOP condition in slave mode.
420+
i2c_stop(obj);
421+
return -2; // Arbitration lost
422+
423+
case 0x80:
424+
case 0x90:
425+
// Received another data byte from master. ACK has been returned.
426+
data[count++] = I2C_DAT(obj) & 0xFF;
427+
428+
if(count >= length) {
429+
// Out of buffer space, NACK the next byte
430+
i2c_conclr(obj, 0, 0, 0, 1);
431+
i2c_clear_SI(obj);
432+
}
433+
else {
434+
// Ack the next byte
435+
i2c_conset(obj, 0, 0, 0, 1);
436+
i2c_clear_SI(obj);
437+
}
438+
break;
439+
440+
case 0x88:
441+
case 0x98:
442+
// Master wrote us a byte and we NACKed it. Slave receive mode has been exited.
443+
i2c_conset(obj, 0, 0, 0, 1); // Set AA flag so that we go back to idle slave state
444+
i2c_clear_SI(obj);
445+
return count;
446+
447+
case 0xA0:
448+
// Stop condition received. Slave receive mode has been exited.
449+
i2c_conset(obj, 0, 0, 0, 1); // Set AA flag so that we go back to idle slave state
450+
i2c_clear_SI(obj);
451+
return count;
452+
}
453+
}
413454
}
414455

415456
int i2c_slave_write(i2c_t *obj, const char *data, int length) {
416-
int count = 0;
417-
int status;
418-
419-
if(length <= 0) {
420-
return(0);
421-
}
422-
423-
do {
424-
status = i2c_do_write(obj, data[count], 0);
425-
count++;
426-
} while ((count < length) && (status == 0xB8));
427-
428-
if ((status != 0xC0) && (status != 0xC8)) {
429-
i2c_stop(obj);
457+
458+
int next_byte_idx = 0;
459+
460+
if(i2c_status(obj) != 0xA8) {
461+
return -1; // I2C peripheral not in setup-read-from-slave mode
430462
}
431463

464+
// Write first data byte. Note that there's no way for the slave to NACK a read-from-slave event, so if we run out of bytes,
465+
// just send zeroes.
466+
I2C_DAT(obj) = (next_byte_idx < length) ? data[next_byte_idx] : 0;
467+
++next_byte_idx;
468+
469+
i2c_conset(obj, 0, 0, 0, 1); // Set AA flag to acknowledge read as long as we have something to transmit
432470
i2c_clear_SI(obj);
433-
434-
return(count);
471+
472+
// This is implemented as a state machine according to section 19.10.9 in the reference manual.
473+
while(true) {
474+
475+
// Wait until the I2C peripheral has an event for us
476+
i2c_wait_SI(obj);
477+
478+
switch(i2c_status(obj)) {
479+
case 0xB0:
480+
// Arbitration Lost event. I'm a bit confused about how this can happen as a slave device but the manual says it can...
481+
i2c_conclr(obj, 1, 0, 0, 1); // Clear start and ack
482+
i2c_clear_SI(obj);
483+
484+
// Reset the I2C state machine. This doesn't actually send a STOP condition in slave mode.
485+
i2c_stop(obj);
486+
return -2; // Arbitration lost
487+
488+
case 0xB8:
489+
// Prev data byte has been transmitted. ACK has been received. Write the next data byte.
490+
I2C_DAT(obj) = (next_byte_idx < length) ? data[next_byte_idx] : 0;
491+
++next_byte_idx;
492+
493+
i2c_conset(obj, 0, 0, 0, 1); // Set AA flag to acknowledge read as long as we have something to transmit
494+
i2c_clear_SI(obj);
495+
break;
496+
497+
case 0xC0:
498+
case 0xC8:
499+
// Last data byte transmitted (ended either by us not setting AA, or by the master NACKing)
500+
i2c_conset(obj, 0, 0, 0, 1); // Set AA flag so that we go back to idle slave state
501+
i2c_clear_SI(obj);
502+
503+
return next_byte_idx;
504+
}
505+
}
435506
}
436507

437508
void i2c_slave_address(i2c_t *obj, int idx, uint32_t address, uint32_t mask) {

0 commit comments

Comments
 (0)