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:
parent
1daaad2447
commit
d088930664
86
i2c.c
86
i2c.c
|
|
@ -27,7 +27,9 @@
|
||||||
#include <avr/io.h>
|
#include <avr/io.h>
|
||||||
#include <avr/interrupt.h>
|
#include <avr/interrupt.h>
|
||||||
#include <util/twi.h>
|
#include <util/twi.h>
|
||||||
|
#include "delay.h"
|
||||||
#include "pinio.h"
|
#include "pinio.h"
|
||||||
|
#include "memory_barrier.h"
|
||||||
|
|
||||||
|
|
||||||
#if defined I2C_MASTER_MODE && defined I2C_SLAVE_MODE
|
#if defined I2C_MASTER_MODE && defined I2C_SLAVE_MODE
|
||||||
|
|
@ -66,11 +68,7 @@
|
||||||
// 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;
|
volatile uint8_t i2c_state = I2C_MODE_FREE;
|
||||||
// Index into the send/receive buffer.
|
|
||||||
uint8_t i2c_index;
|
|
||||||
// Count of bytes to be sent.
|
|
||||||
uint8_t i2c_byte_count;
|
|
||||||
|
|
||||||
#ifdef I2C_EEPROM_SUPPORT
|
#ifdef I2C_EEPROM_SUPPORT
|
||||||
// For SAWSARP mode (see ENHA in i2c.h).
|
// For SAWSARP mode (see ENHA in i2c.h).
|
||||||
|
|
@ -84,10 +82,17 @@ uint8_t i2c_byte_count;
|
||||||
#ifdef I2C_SLAVE_MODE
|
#ifdef I2C_SLAVE_MODE
|
||||||
uint8_t i2c_in_buffer[I2C_SLAVE_RX_BUFFER_SIZE];
|
uint8_t i2c_in_buffer[I2C_SLAVE_RX_BUFFER_SIZE];
|
||||||
uint8_t i2c_out_buffer[I2C_SLAVE_TX_BUFFER_SIZE];
|
uint8_t i2c_out_buffer[I2C_SLAVE_TX_BUFFER_SIZE];
|
||||||
#else
|
|
||||||
uint8_t *i2c_buffer;
|
|
||||||
#endif /* I2C_SLAVE_MODE */
|
#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.
|
Inititalise the I2C/TWI subsystem.
|
||||||
|
|
@ -129,21 +134,46 @@ void i2c_init(uint8_t address) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Send a data block to a slave device.
|
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_send(uint8_t address, uint8_t* block, uint8_t tx_len) {
|
void i2c_write(uint8_t address, uint8_t data, uint8_t last_byte) {
|
||||||
|
|
||||||
i2c_address = address;
|
if ( ! (i2c_state & I2C_MODE_BUSY)) {
|
||||||
i2c_buffer = block;
|
// No transmission ongoing, start one.
|
||||||
i2c_index = 0;
|
i2c_address = address;
|
||||||
i2c_byte_count = tx_len;
|
|
||||||
|
|
||||||
// Just send.
|
// Just send.
|
||||||
i2c_state = I2C_MODE_SAWP;
|
i2c_state = I2C_MODE_SAWP;
|
||||||
|
|
||||||
// Start transmission.
|
// Start transmission.
|
||||||
TWCR = (1<<TWINT)|(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE);
|
TWCR = (1<<TWINT)|(0<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE);
|
||||||
i2c_state |= I2C_MODE_BUSY;
|
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;
|
break;
|
||||||
case TW_MT_SLA_ACK:
|
case TW_MT_SLA_ACK:
|
||||||
// SLA+W was sent, then ACK received.
|
// SLA+W was sent, then ACK received.
|
||||||
if ((i2c_state & I2C_MODE_MASK) == I2C_MODE_SAWP) {
|
if ((i2c_state & I2C_MODE_MASK) == I2C_MODE_SAWP && buf_canread(send)) {
|
||||||
TWDR = i2c_buffer[i2c_index++];
|
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);
|
||||||
}
|
}
|
||||||
#ifdef I2C_EEPROM_SUPPORT
|
#ifdef I2C_EEPROM_SUPPORT
|
||||||
|
|
@ -226,13 +256,14 @@ ISR(TWI_vect) {
|
||||||
case TW_MT_DATA_ACK:
|
case TW_MT_DATA_ACK:
|
||||||
// A byte was sent, got ACK.
|
// A byte was sent, got ACK.
|
||||||
if ((i2c_state & I2C_MODE_MASK) == I2C_MODE_SAWP) {
|
if ((i2c_state & I2C_MODE_MASK) == I2C_MODE_SAWP) {
|
||||||
if (i2c_index == i2c_byte_count) {
|
if (buf_canread(send)) {
|
||||||
// Last byte, send stop condition.
|
|
||||||
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(0<<TWSTA)|(1<<TWSTO)|(1<<TWEN)|(0<<TWIE);
|
|
||||||
} else {
|
|
||||||
// Send the next byte.
|
// 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);
|
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
|
#ifdef I2C_EEPROM_SUPPORT
|
||||||
|
|
@ -261,13 +292,15 @@ ISR(TWI_vect) {
|
||||||
// It looks like there is another master on the bus.
|
// It looks like there is another master on the bus.
|
||||||
i2c_state |= I2C_ERROR_LOW_PRIO;
|
i2c_state |= I2C_ERROR_LOW_PRIO;
|
||||||
// Setup all again.
|
// Setup all again.
|
||||||
i2c_index = 0;
|
sendtail = sendhead;
|
||||||
#ifdef I2C_EEPROM_SUPPORT
|
#ifdef I2C_EEPROM_SUPPORT
|
||||||
i2c_page_index = 0;
|
i2c_page_index = 0;
|
||||||
#endif
|
#endif
|
||||||
// Try to resend when the bus became free.
|
// Try to resend when the bus became free.
|
||||||
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE);
|
TWCR = (1<<TWINT)|(I2C_MODE<<TWEA)|(1<<TWSTA)|(0<<TWSTO)|(1<<TWEN)|(1<<TWIE);
|
||||||
break;
|
break;
|
||||||
|
#if 0
|
||||||
|
// Note: we currently have no receive buffer, so we can't receive anything.
|
||||||
case TW_MR_SLA_ACK:
|
case TW_MR_SLA_ACK:
|
||||||
// SLA+R was sent, got АСК, then received a byte.
|
// SLA+R was sent, got АСК, then received a byte.
|
||||||
if (i2c_index + 1 == i2c_byte_count) {
|
if (i2c_index + 1 == i2c_byte_count) {
|
||||||
|
|
@ -302,6 +335,7 @@ ISR(TWI_vect) {
|
||||||
// Send stop condition.
|
// 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);
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef I2C_SLAVE_MODE
|
#ifdef I2C_SLAVE_MODE
|
||||||
|
|
||||||
|
|
|
||||||
12
i2c.h
12
i2c.h
|
|
@ -17,7 +17,15 @@
|
||||||
// Comment out if there are external pullups.
|
// Comment out if there are external pullups.
|
||||||
#define I2C_ENABLE_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
|
#ifdef I2C_SLAVE_MODE
|
||||||
#define I2C_SLAVE_RX_BUFFER_SIZE 1
|
#define I2C_SLAVE_RX_BUFFER_SIZE 1
|
||||||
|
|
@ -31,7 +39,7 @@
|
||||||
|
|
||||||
|
|
||||||
void i2c_init(uint8_t address);
|
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 */
|
#endif /* I2C */
|
||||||
|
|
||||||
|
|
|
||||||
69
i2c_test.c
69
i2c_test.c
|
|
@ -28,6 +28,8 @@ static uint8_t display_init[] = {
|
||||||
0xA8, 0x1F, // 1/32 duty.
|
0xA8, 0x1F, // 1/32 duty.
|
||||||
0x40 | 0x00, // Start line (reset).
|
0x40 | 0x00, // Start line (reset).
|
||||||
0x20, 0x02, // Page addressing mode (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).
|
0xA0 | 0x00, // No segment remap (reset).
|
||||||
0xC0 | 0x00, // Normal com pins mapping (reset).
|
0xC0 | 0x00, // Normal com pins mapping (reset).
|
||||||
0xDA, 0x02, // Sequental without remap com pins.
|
0xDA, 0x02, // Sequental without remap com pins.
|
||||||
|
|
@ -39,6 +41,10 @@ static uint8_t display_init[] = {
|
||||||
0xA4, // Resume display.
|
0xA4, // Resume display.
|
||||||
0xAF // Display on.
|
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 {
|
typedef struct {
|
||||||
uint8_t columns;
|
uint8_t columns;
|
||||||
|
|
@ -153,12 +159,37 @@ SYMBOL font_8x4[] = {
|
||||||
|
|
||||||
|
|
||||||
static void i2c_test(void) {
|
static void i2c_test(void) {
|
||||||
static char* message = "Welcome to Teacup";
|
uint16_t i;
|
||||||
static uint8_t block[128];
|
const char* message = "Welcome to Teacup";
|
||||||
uint8_t* pointer = block + 1;
|
|
||||||
|
|
||||||
i2c_send(DISPLAY_I2C_ADDRESS, display_init, sizeof(display_init));
|
for (i = 0; i < sizeof(display_init); i++) {
|
||||||
delay_ms(250);
|
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.
|
Setup cursor on display.
|
||||||
|
|
@ -166,24 +197,30 @@ static void i2c_test(void) {
|
||||||
"Welcome to Teacup" is 64 pixel columns wide, entire display is
|
"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.
|
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.
|
// Line 1.
|
||||||
block[1] = 0xB0 | 1;
|
i2c_write(DISPLAY_I2C_ADDRESS, 0xB0 | 1, 0);
|
||||||
// Column 32.
|
// Column 32.
|
||||||
block[2] = 0x00 | (32 & 0x0F);
|
i2c_write(DISPLAY_I2C_ADDRESS, 0x00 | (32 & 0x0F), 0);
|
||||||
block[3] = 0x10 | ((32 >> 4) & 0x0F);
|
i2c_write(DISPLAY_I2C_ADDRESS, 0x10 | ((32 >> 4) & 0x0F), 0);
|
||||||
i2c_send(DISPLAY_I2C_ADDRESS, block, 4);
|
delay_ms(100);
|
||||||
delay_ms(50);
|
|
||||||
|
|
||||||
// Render text to bitmap.
|
// Render text to bitmap to display.
|
||||||
|
i2c_write(DISPLAY_I2C_ADDRESS, 0x40, 0);
|
||||||
while (*message) {
|
while (*message) {
|
||||||
SYMBOL symbol = font_8x4[(uint8_t)*message - 0x20];
|
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++;
|
message++;
|
||||||
}
|
}
|
||||||
block[0] = 0x40; // Data marker.
|
|
||||||
i2c_send(DISPLAY_I2C_ADDRESS, block, pointer - block);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* I2C_TEST */
|
#endif /* I2C_TEST */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue