I2C: distinguish between individual transmissions.

So far calling code had to wait long enough between individual
transmissions to make sure they end up in distinc ones. Now
calling code can stuff as fast as it wants, i2c_write() takes
care of the distinction.
This commit is contained in:
Markus Hitter 2016-04-05 16:53:51 +02:00
parent d088930664
commit 2298973343
2 changed files with 40 additions and 23 deletions

42
i2c.c
View File

@ -67,9 +67,17 @@
// Address of the device that is communicated with. // Address of the device that is communicated with.
uint8_t i2c_address; uint8_t i2c_address;
// State of TWI component of MCU. // State of TWI component of MCU.
volatile uint8_t i2c_state = I2C_MODE_FREE; volatile uint8_t i2c_state = I2C_MODE_FREE;
/**
Wether transmission should be terminated on buffer drain. This also means
no new bytes get stuffed into the buffer until this drain happened. It's
used to allow distinct transmissions.
*/
volatile uint8_t i2c_should_end = 0;
#ifdef I2C_EEPROM_SUPPORT #ifdef I2C_EEPROM_SUPPORT
// For SAWSARP mode (see ENHA in i2c.h). // For SAWSARP mode (see ENHA in i2c.h).
uint8_t i2c_page_address[I2C_PAGE_ADDRESS_SIZE]; uint8_t i2c_page_address[I2C_PAGE_ADDRESS_SIZE];
@ -140,23 +148,35 @@ void i2c_init(uint8_t address) {
\param data The byte to be buffered/sent. \param data The byte to be buffered/sent.
\param last_byte Wether this is the last byte of a transaction. \param last_byte Wether this is the last byte of a transmission.
Unlike many other protocols (serial, SPI), I2C has an explicite transmission Unlike many other protocols (serial, SPI), I2C has an explicite transmission
start and transmission end. Transmission start is detected automatically, start and transmission end. Invoking code has to tell wether the given byte
but end of the transmission has to be told by the invoking code. is the last byte of a transmission, so sending code can properly end it.
This function has been tested to properly distinguish between individual
transmissions separated by last_byte. Other than setting this flag, invoking
code doesn't have to care about distinction, but may experience substantial
delays (up to several milliseconds) if the bus is already busy with a
distinct previous transmission.
Data is buffered, so this returns quickly for small amounts of data. Large Data is buffered, so this returns quickly for small amounts of data. Large
amounts don't get lost, but this function has to wait until sufficient amounts don't get lost, but this function has to wait until sufficient
previous data was sent. previous data was sent.
TODO: for now this function assumes that the buffer drains between distinct Note that calling code has to send bytes quickly enough to not drain the
transmissions. This means, last_byte is ignored and transmission is buffer. It looks like the I2C protocol doesn't, unlike e.g. SPI, allow
ended as soon as the buffer drains. to pause sending without dropping the transmission. Positive of this
limitation is, one can end a transmisson by simply not writing for a while,
until it's sure the buffer became empty.
*/ */
void i2c_write(uint8_t address, uint8_t data, uint8_t last_byte) { void i2c_write(uint8_t address, uint8_t data, uint8_t last_byte) {
if ( ! (i2c_state & I2C_MODE_BUSY)) { while (i2c_should_end || ! buf_canwrite(send)) {
delay_us(10);
}
if (i2c_state & I2C_MODE_FREE) {
// No transmission ongoing, start one. // No transmission ongoing, start one.
i2c_address = address; i2c_address = address;
@ -168,11 +188,9 @@ void i2c_write(uint8_t address, uint8_t data, uint8_t last_byte) {
i2c_state |= I2C_MODE_BUSY; i2c_state |= I2C_MODE_BUSY;
} }
while ( ! buf_canwrite(send))
delay_us(10);
ATOMIC_START ATOMIC_START
buf_push(send, data); buf_push(send, data);
i2c_should_end = last_byte;
ATOMIC_END ATOMIC_END
} }
@ -261,8 +279,10 @@ ISR(TWI_vect) {
buf_pop(send, TWDR); buf_pop(send, TWDR);
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE); TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE);
} else { } else {
// Last byte, send stop condition. // Buffer drained because transmission is completed.
i2c_state = I2C_MODE_FREE; i2c_state = I2C_MODE_FREE;
i2c_should_end = 0;
// Send stop condition.
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|(1<<TWEN)|(0<<TWIE); TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|(1<<TWEN)|(0<<TWIE);
} }
} }

View File

@ -15,7 +15,6 @@
#include <string.h> #include <string.h>
#include "config_wrapper.h" #include "config_wrapper.h"
#include "delay.h"
#include "i2c.h" #include "i2c.h"
@ -157,15 +156,15 @@ SYMBOL font_8x4[] = {
#define FONT_SYMBOLS_SPACE 1 #define FONT_SYMBOLS_SPACE 1
static void i2c_test(void) { static void i2c_test(void) {
uint16_t i; uint16_t i;
const char* message = "Welcome to Teacup"; const char* message = "Welcome to Teacup";
for (i = 0; i < sizeof(display_init); i++) { for (i = 0; i < sizeof(display_init); i++) {
i2c_write(DISPLAY_I2C_ADDRESS, display_init[i], 0); // Send last byte with 'last_byte' set.
i2c_write(DISPLAY_I2C_ADDRESS, display_init[i],
(i == sizeof(display_init) - 1));
} }
delay_ms(500);
/** /**
Clear the screen. As this display supports many sophisticated commands, Clear the screen. As this display supports many sophisticated commands,
@ -175,21 +174,18 @@ static void i2c_test(void) {
// Set horizontal adressing mode. // Set horizontal adressing mode.
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x20, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x20, 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 1);
delay_ms(100);
// Write 512 zeros. // Write 512 zeros.
i2c_write(DISPLAY_I2C_ADDRESS, 0x40, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x40, 0);
for (i = 0; i < 512; i++) { for (i = 0; i < 512; i++) {
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x00, (i == 511));
} }
delay_ms(2000);
// Return to page adressing mode. // Return to page adressing mode.
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x20, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x20, 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x02, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x02, 1);
delay_ms(100);
/** /**
Setup cursor on display. Setup cursor on display.
@ -202,8 +198,7 @@ static void i2c_test(void) {
i2c_write(DISPLAY_I2C_ADDRESS, 0xB0 | 1, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0xB0 | 1, 0);
// Column 32. // Column 32.
i2c_write(DISPLAY_I2C_ADDRESS, 0x00 | (32 & 0x0F), 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x00 | (32 & 0x0F), 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x10 | ((32 >> 4) & 0x0F), 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x10 | ((32 >> 4) & 0x0F), 1);
delay_ms(100);
// Render text to bitmap to display. // Render text to bitmap to display.
i2c_write(DISPLAY_I2C_ADDRESS, 0x40, 0); i2c_write(DISPLAY_I2C_ADDRESS, 0x40, 0);
@ -221,6 +216,8 @@ static void i2c_test(void) {
message++; message++;
} }
// Send another space for transmission end.
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 1);
} }
#endif /* I2C_TEST */ #endif /* I2C_TEST */