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
76
i2c.c
76
i2c.c
|
|
@ -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
12
i2c.h
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
69
i2c_test.c
69
i2c_test.c
|
|
@ -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 */
|
||||
|
|
|
|||
Loading…
Reference in New Issue