I2C: use ringbuffer for data buffering.

This removes the need to write in full blocks, so data can be
sent from loops and/or program memory.

This capability allows to clear the screen without too much
effort, see i2c_test.c.

Still two weaknesses left:

 - Transmission end is currently detected by ringbuffer becoming
   empty, so delays are needed to make sure a transmission is
   completed before the next one is sent to the buffer.

 - Error handling is still only half existent. Any error on the
   bus will stop I2C entirely. A recovery strategy is required.

Sizes show, taking into account the additional screen clearing
code, no significant change:

    FLASH  : 23216 bytes
    RAM    :  2052 bytes
    EEPROM :    32 bytes
This commit is contained in:
Markus Hitter 2016-04-04 18:24:10 +02:00
parent 1daaad2447
commit d088930664
3 changed files with 123 additions and 44 deletions

76
i2c.c
View File

@ -27,7 +27,9 @@
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/twi.h>
#include "delay.h"
#include "pinio.h"
#include "memory_barrier.h"
#if defined I2C_MASTER_MODE && defined I2C_SLAVE_MODE
@ -66,11 +68,7 @@
// Address of the device that is communicated with.
uint8_t i2c_address;
// State of TWI component of MCU.
volatile uint8_t i2c_state;
// Index into the send/receive buffer.
uint8_t i2c_index;
// Count of bytes to be sent.
uint8_t i2c_byte_count;
volatile uint8_t i2c_state = I2C_MODE_FREE;
#ifdef I2C_EEPROM_SUPPORT
// For SAWSARP mode (see ENHA in i2c.h).
@ -84,10 +82,17 @@ uint8_t i2c_byte_count;
#ifdef I2C_SLAVE_MODE
uint8_t i2c_in_buffer[I2C_SLAVE_RX_BUFFER_SIZE];
uint8_t i2c_out_buffer[I2C_SLAVE_TX_BUFFER_SIZE];
#else
uint8_t *i2c_buffer;
#endif /* I2C_SLAVE_MODE */
// Ringbuffer logic for buffer 'send'.
#define BUFSIZE I2C_BUFFER_SIZE
volatile uint8_t sendhead = 0;
volatile uint8_t sendtail = 0;
volatile uint8_t sendbuf[BUFSIZE];
#include "ringbuffer.h"
/**
Inititalise the I2C/TWI subsystem.
@ -129,14 +134,31 @@ void i2c_init(uint8_t address) {
}
/**
Send a data block to a slave device.
*/
void i2c_send(uint8_t address, uint8_t* block, uint8_t tx_len) {
Send a byte to the I2C partner.
\param address I2C address of the communications partner.
\param data The byte to be buffered/sent.
\param last_byte Wether this is the last byte of a transaction.
Unlike many other protocols (serial, SPI), I2C has an explicite transmission
start and transmission end. Transmission start is detected automatically,
but end of the transmission has to be told by the invoking code.
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
previous data was sent.
TODO: for now this function assumes that the buffer drains between distinct
transmissions. This means, last_byte is ignored and transmission is
ended as soon as the buffer drains.
*/
void i2c_write(uint8_t address, uint8_t data, uint8_t last_byte) {
if ( ! (i2c_state & I2C_MODE_BUSY)) {
// No transmission ongoing, start one.
i2c_address = address;
i2c_buffer = block;
i2c_index = 0;
i2c_byte_count = tx_len;
// Just send.
i2c_state = I2C_MODE_SAWP;
@ -144,6 +166,14 @@ void i2c_send(uint8_t address, uint8_t* block, uint8_t tx_len) {
// Start transmission.
TWCR = (1<<TWINT)|(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE);
i2c_state |= I2C_MODE_BUSY;
}
while ( ! buf_canwrite(send))
delay_us(10);
ATOMIC_START
buf_push(send, data);
ATOMIC_END
}
/**
@ -206,8 +236,8 @@ ISR(TWI_vect) {
break;
case TW_MT_SLA_ACK:
// SLA+W was sent, then ACK received.
if ((i2c_state & I2C_MODE_MASK) == I2C_MODE_SAWP) {
TWDR = i2c_buffer[i2c_index++];
if ((i2c_state & I2C_MODE_MASK) == I2C_MODE_SAWP && buf_canread(send)) {
buf_pop(send, TWDR);
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE);
}
#ifdef I2C_EEPROM_SUPPORT
@ -226,13 +256,14 @@ ISR(TWI_vect) {
case TW_MT_DATA_ACK:
// A byte was sent, got ACK.
if ((i2c_state & I2C_MODE_MASK) == I2C_MODE_SAWP) {
if (i2c_index == i2c_byte_count) {
// Last byte, send stop condition.
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|(1<<TWEN)|(0<<TWIE);
} else {
if (buf_canread(send)) {
// Send the next byte.
TWDR = i2c_buffer[i2c_index++];
buf_pop(send, TWDR);
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE);
} else {
// Last byte, send stop condition.
i2c_state = I2C_MODE_FREE;
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|(1<<TWEN)|(0<<TWIE);
}
}
#ifdef I2C_EEPROM_SUPPORT
@ -261,13 +292,15 @@ ISR(TWI_vect) {
// It looks like there is another master on the bus.
i2c_state |= I2C_ERROR_LOW_PRIO;
// Setup all again.
i2c_index = 0;
sendtail = sendhead;
#ifdef I2C_EEPROM_SUPPORT
i2c_page_index = 0;
#endif
// Try to resend when the bus became free.
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE);
break;
#if 0
// Note: we currently have no receive buffer, so we can't receive anything.
case TW_MR_SLA_ACK:
// SLA+R was sent, got АСК, then received a byte.
if (i2c_index + 1 == i2c_byte_count) {
@ -302,6 +335,7 @@ ISR(TWI_vect) {
// Send stop condition.
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|(1<<TWEN)|(0<<TWIE);
break;
#endif
#ifdef I2C_SLAVE_MODE

12
i2c.h
View File

@ -17,7 +17,15 @@
// Comment out if there are external pullups.
#define I2C_ENABLE_PULLUPS
#define I2C_BUFFER_SIZE 4
/** \def I2C_BUFFER_SIZE
Size of send buffer. MUST be a \f$2^n\f$ value, maximum is 512.
A larger buffer allows to store more display data immediately, so it can
speed operations up. An exhaused buffer doesn't mean data gets lost, writing
to the buffer then waits until sufficient previous data is sent.
*/
#define I2C_BUFFER_SIZE 128
#ifdef I2C_SLAVE_MODE
#define I2C_SLAVE_RX_BUFFER_SIZE 1
@ -31,7 +39,7 @@
void i2c_init(uint8_t address);
void i2c_send(uint8_t address, uint8_t* block, uint8_t tx_len);
void i2c_write(uint8_t address, uint8_t data, uint8_t last_byte);
#endif /* I2C */

View File

@ -28,6 +28,8 @@ static uint8_t display_init[] = {
0xA8, 0x1F, // 1/32 duty.
0x40 | 0x00, // Start line (reset).
0x20, 0x02, // Page addressing mode (reset).
0x22, 0x00, 0x03, // Start and end page in horiz./vert. addressing mode[1].
0x21, 0x00, 0x7F, // Start and end column in horiz./vert. addressing mode.
0xA0 | 0x00, // No segment remap (reset).
0xC0 | 0x00, // Normal com pins mapping (reset).
0xDA, 0x02, // Sequental without remap com pins.
@ -39,6 +41,10 @@ static uint8_t display_init[] = {
0xA4, // Resume display.
0xAF // Display on.
};
// [1] Do not set this to 0x00..0x07 on a 32 pixel high display, or vertical
// addressing mode will mess up. 32 pixel high displays have only 4 pages
// (0..3), still addressing logic accepts, but can't deal with the 0..7
// meant for 64 pixel high displays.
typedef struct {
uint8_t columns;
@ -153,12 +159,37 @@ SYMBOL font_8x4[] = {
static void i2c_test(void) {
static char* message = "Welcome to Teacup";
static uint8_t block[128];
uint8_t* pointer = block + 1;
uint16_t i;
const char* message = "Welcome to Teacup";
i2c_send(DISPLAY_I2C_ADDRESS, display_init, sizeof(display_init));
delay_ms(250);
for (i = 0; i < sizeof(display_init); i++) {
i2c_write(DISPLAY_I2C_ADDRESS, display_init[i], 0);
}
delay_ms(500);
/**
Clear the screen. As this display supports many sophisticated commands,
but not a simple 'clear', we have to overwrite the entire memory with
zeros, byte by byte.
*/
// Set horizontal adressing mode.
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x20, 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0);
delay_ms(100);
// Write 512 zeros.
i2c_write(DISPLAY_I2C_ADDRESS, 0x40, 0);
for (i = 0; i < 512; i++) {
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0);
}
delay_ms(2000);
// Return to page adressing mode.
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x20, 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x02, 0);
delay_ms(100);
/**
Setup cursor on display.
@ -166,24 +197,30 @@ static void i2c_test(void) {
"Welcome to Teacup" is 64 pixel columns wide, entire display is
128 columns, so we offset by 32 columns to get it to the center.
*/
block[0] = 0x00;
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0);
// Line 1.
block[1] = 0xB0 | 1;
i2c_write(DISPLAY_I2C_ADDRESS, 0xB0 | 1, 0);
// Column 32.
block[2] = 0x00 | (32 & 0x0F);
block[3] = 0x10 | ((32 >> 4) & 0x0F);
i2c_send(DISPLAY_I2C_ADDRESS, block, 4);
delay_ms(50);
i2c_write(DISPLAY_I2C_ADDRESS, 0x00 | (32 & 0x0F), 0);
i2c_write(DISPLAY_I2C_ADDRESS, 0x10 | ((32 >> 4) & 0x0F), 0);
delay_ms(100);
// Render text to bitmap.
// Render text to bitmap to display.
i2c_write(DISPLAY_I2C_ADDRESS, 0x40, 0);
while (*message) {
SYMBOL symbol = font_8x4[(uint8_t)*message - 0x20];
memcpy(pointer, symbol.data, symbol.columns);
pointer += symbol.columns + FONT_SYMBOLS_SPACE;
// Send the character bitmap.
for (i = 0; i < symbol.columns; i++) {
i2c_write(DISPLAY_I2C_ADDRESS, symbol.data[i], 0);
}
// Send space between characters.
for (i = 0; i < FONT_SYMBOLS_SPACE; i++) {
i2c_write(DISPLAY_I2C_ADDRESS, 0x00, 0);
}
message++;
}
block[0] = 0x40; // Data marker.
i2c_send(DISPLAY_I2C_ADDRESS, block, pointer - block);
}
#endif /* I2C_TEST */