1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-23 08:06:13 +01:00

Add I2C support functions

Add new read/write functions to I2CManager class, and modify the LCD, OLED and PWM classes to use them effectively.
This commit is contained in:
Neil McKechnie 2021-03-31 12:19:55 +01:00
parent b1d3f3200a
commit 43319fd3dd
9 changed files with 207 additions and 371 deletions

View File

@ -17,6 +17,8 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <stdarg.h>
#include <Wire.h>
#include "I2CManager.h" #include "I2CManager.h"
// If not already initialised, initialise I2C (wire). // If not already initialised, initialise I2C (wire).
@ -48,10 +50,80 @@ void I2CManagerClass::forceClock(uint32_t speed) {
// Check if specified I2C address is responding. // Check if specified I2C address is responding.
// Returns 0 if OK, or error code. // Returns 0 if OK, or error code.
uint8_t I2CManagerClass::exists(uint8_t address) { uint8_t I2CManagerClass::checkAddress(uint8_t address) {
begin(); begin();
Wire.beginTransmission(address); Wire.beginTransmission(address);
return Wire.endTransmission(); return Wire.endTransmission();
} }
bool I2CManagerClass::exists(uint8_t address) {
return checkAddress(address)==0;
}
// Write a complete transmission to I2C using a supplied buffer of data
uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size) {
Wire.beginTransmission(address);
Wire.write(buffer, size);
return Wire.endTransmission();
}
// Write a complete transmission to I2C using a supplied buffer of data in Flash
uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_t size) {
uint8_t ramBuffer[size];
memcpy_P(ramBuffer, buffer, size);
return write(address, ramBuffer, size);
}
// Write a complete transmission to I2C using a list of data
uint8_t I2CManagerClass::write(uint8_t address, int nBytes, ...) {
uint8_t buffer[nBytes];
va_list args;
va_start(args, nBytes);
for (uint8_t i=0; i<nBytes; i++)
buffer[i] = va_arg(args, int);
va_end(args);
return write(address, buffer, nBytes);
}
// Write a command and read response, returns number of bytes received.
// Different modules use different ways of accessing registers:
// PCF8574 I/O expander justs needs the address (no data);
// PCA9685 needs a two byte command to select the register(s) to be read;
// MCP23016 needs a one-byte command to select the register.
// Some devices use 8-bit registers exclusively and some have 16-bit registers.
// Therefore the following function is general purpose, to apply to any
// type of I2C device.
//
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
uint8_t writeBuffer[], uint8_t writeSize) {
if (writeSize > 0) {
Wire.beginTransmission(address);
Wire.write(writeBuffer, writeSize);
Wire.endTransmission(false); // Don't free bus yet
}
Wire.requestFrom(address, readSize);
uint8_t nBytes = 0;
while (Wire.available() && nBytes < readSize)
readBuffer[nBytes++] = Wire.read();
return nBytes;
}
// Overload of read() to allow command to be specified as a series of bytes.
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
uint8_t writeSize, ...) {
va_list args;
// Copy the series of bytes into an array.
va_start(args, writeSize);
uint8_t writeBuffer[writeSize];
for (uint8_t i=0; i<writeSize; i++)
writeBuffer[i] = va_arg(args, int);
va_end(args);
return read(address, readBuffer, readSize, writeBuffer, writeSize);
}
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize) {
return read(address, readBuffer, readSize, NULL, 0);
}
I2CManagerClass I2CManager = I2CManagerClass(); I2CManagerClass I2CManager = I2CManagerClass();

View File

@ -20,7 +20,7 @@
#ifndef I2CManager_h #ifndef I2CManager_h
#define I2CManager_h #define I2CManager_h
#include <Wire.h> #include "FSH.h"
/* /*
* Helper class to manage access to the I2C 'Wire' subsystem. * Helper class to manage access to the I2C 'Wire' subsystem.
@ -48,12 +48,27 @@ public:
// Force clock speed // Force clock speed
void forceClock(uint32_t speed); void forceClock(uint32_t speed);
// Check if specified I2C address is responding. // Check if specified I2C address is responding.
uint8_t exists(uint8_t address); uint8_t checkAddress(uint8_t address);
bool exists(uint8_t address);
// Write a complete transmission to I2C from an array in RAM
uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size);
// Write a complete transmission to I2C from an array in Flash
uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size);
// Write a transmission to I2C from a list of bytes.
uint8_t write(uint8_t address, int nBytes, ...);
// Write a command from an array in RAM and read response
uint8_t read(uint8_t address, uint8_t writeBuffer[], uint8_t writeSize,
uint8_t readBuffer[], uint8_t readSize);
// Write a command from an arbitrary list of bytes and read response
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
uint8_t writeSize, ...);
// Write a null command and read the response.
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize);
private: private:
bool _beginCompleted = false; bool _beginCompleted = false;
bool _clockSpeedFixed = false; bool _clockSpeedFixed = false;
uint32_t _clockSpeed = 1000000L; // 1MHz max on Arduino. uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino.
}; };
extern I2CManagerClass I2CManager; extern I2CManagerClass I2CManager;

View File

@ -24,7 +24,6 @@
#include "I2CManager.h" #include "I2CManager.h"
#include "SSD1306Ascii.h" #include "SSD1306Ascii.h"
#include "Wire.h"
SSD1306AsciiWire LCDDriver; SSD1306AsciiWire LCDDriver;
// DEVICE SPECIFIC LCDDisplay Implementation for OLED // DEVICE SPECIFIC LCDDisplay Implementation for OLED
@ -34,10 +33,9 @@ LCDDisplay::LCDDisplay() {
I2CManager.begin(); I2CManager.begin();
I2CManager.setClock(400000L); // Set max supported I2C speed I2CManager.setClock(400000L); // Set max supported I2C speed
for (byte address = 0x3c; address <= 0x3d; address++) { for (byte address = 0x3c; address <= 0x3d; address++) {
byte error = I2CManager.exists(address); if (I2CManager.exists(address)) {
if (!error) {
// Device found // Device found
DIAG(F("OLED display found at 0x%x"), address); DIAG(F("\nOLED display found at 0x%x"), address);
interfake(OLED_DRIVER, 0); interfake(OLED_DRIVER, 0);
const DevType *devType; const DevType *devType;
if (lcdCols == 132) if (lcdCols == 132)
@ -65,7 +63,7 @@ void LCDDisplay::interfake(int p1, int p2, int p3) {
void LCDDisplay::clearNative() { LCDDriver.clear(); } void LCDDisplay::clearNative() { LCDDriver.clear(); }
void LCDDisplay::setRowNative(byte row) { void LCDDisplay::setRowNative(byte row) {
// Positions text write to start of row 1..n and clears previous text // Positions text write to start of row 1..n
int y = row; int y = row;
LCDDriver.setCursor(0, y); LCDDriver.setCursor(0, y);
} }

View File

@ -15,32 +15,13 @@
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>. * along with CommandStation-EX. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <Arduino.h>
#include "LiquidCrystal_I2C.h" #include "LiquidCrystal_I2C.h"
#include "I2CManager.h" #include "I2CManager.h"
#include <inttypes.h>
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#define printIIC(args) Wire.write(args)
inline size_t LiquidCrystal_I2C::write(uint8_t value) {
send(value, Rs);
return 1;
}
#else
#include "WProgram.h"
#define printIIC(args) Wire.send(args)
inline void LiquidCrystal_I2C::write(uint8_t value) { send(value, Rs); }
#endif
#include "Wire.h"
// When the display powers up, it is configured as follows: // When the display powers up, it is configured as follows:
// //
// 1. Display clear // 1. Display clear
@ -72,7 +53,7 @@ void LiquidCrystal_I2C::init() { init_priv(); }
void LiquidCrystal_I2C::init_priv() { void LiquidCrystal_I2C::init_priv() {
I2CManager.begin(); I2CManager.begin();
I2CManager.setClock(100000L); // PCF8574 is limited to 100kHz. I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz.
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
begin(_cols, _rows); begin(_cols, _rows);
@ -85,7 +66,6 @@ void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines) {
_numlines = lines; _numlines = lines;
(void)cols; // Suppress compiler warning. (void)cols; // Suppress compiler warning.
// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
// according to datasheet, we need at least 40ms after power rises above 2.7V // according to datasheet, we need at least 40ms after power rises above 2.7V
// before sending commands. Arduino can turn on way befer 4.5V so we'll allow // before sending commands. Arduino can turn on way befer 4.5V so we'll allow
// 100 milliseconds after pulling both RS and R/W and backlight pin low // 100 milliseconds after pulling both RS and R/W and backlight pin low
@ -167,23 +147,11 @@ void LiquidCrystal_I2C::backlight(void) {
expanderWrite(0); expanderWrite(0);
} }
void LiquidCrystal_I2C::setBacklight(uint8_t new_val) {
if (new_val) {
backlight(); // turn backlight on
} else {
noBacklight(); // turn backlight off
}
}
void LiquidCrystal_I2C::printstr(const char c[]) {
// This function is not identical to the function used for "real" I2C displays
// it's here so the user sketch doesn't have to be changed
print(c);
}
/*********** mid level commands, for sending data/cmds */ /*********** mid level commands, for sending data/cmds */
inline void LiquidCrystal_I2C::command(uint8_t value) { send(value, 0); } inline void LiquidCrystal_I2C::command(uint8_t value) {
send(value, 0);
}
/************ low level data pushing commands **********/ /************ low level data pushing commands **********/
@ -209,37 +177,37 @@ inline void LiquidCrystal_I2C::command(uint8_t value) { send(value, 0); }
* transmission and start another one is a stop bit, a start bit, 8 address bits, * transmission and start another one is a stop bit, a start bit, 8 address bits,
* an ack, 8 data bits and another ack; this is at least 20 bits, i.e. >50us * an ack, 8 data bits and another ack; this is at least 20 bits, i.e. >50us
* at 400kHz and >200us at 100kHz. Therefore, we don't need additional delay. * at 400kHz and >200us at 100kHz. Therefore, we don't need additional delay.
*
* Similarly, the Enable must be set/reset for at least 450ns. This is
* well within the I2C clock cycle time of 2.5us at 400kHz. Data is clocked in
* to the HD44780 on the trailing edge of the Enable pin, so we set the Enable
* as we present the data, then in the next byte we reset Enable without changing
* the data.
*/ */
// write either command or data (8 bits) to the HD44780 as // write either command or data (8 bits) to the HD44780 LCD controller as
// a single I2C transmission. // a single I2C transmission.
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) { void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
uint8_t highnib = value & 0xf0; mode |= _backlightval;
uint8_t lownib = (value << 4) & 0xf0; uint8_t highnib = (value & 0xf0) | mode;
uint8_t lownib = ((value << 4) & 0xf0) | mode;
// Send both nibbles // Send both nibbles
Wire.beginTransmission(_Addr); byte buffer[] = {(byte)(highnib|En), highnib, (byte)(lownib|En), lownib};
write4bits(highnib | mode, true); I2CManager.write(_Addr, buffer, sizeof(buffer));
write4bits(lownib | mode, true);
Wire.endTransmission();
} }
// write 4 bits to the HD44780 interface. If inTransmission is false // write 4 bits to the HD44780 LCD controller.
// then the nibble will be sent in its own I2C transmission. void LiquidCrystal_I2C::write4bits(uint8_t value) {
void LiquidCrystal_I2C::write4bits(uint8_t value, bool inTransmission) { uint8_t _data = value | _backlightval;
int _data = (int)value | _backlightval;
if (!inTransmission) Wire.beginTransmission(_Addr);
// Enable must be set/reset for at least 450ns. This is well within the // Enable must be set/reset for at least 450ns. This is well within the
// I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the // I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the
// HD44780 on the trailing edge of the Enable pin. // HD44780 on the trailing edge of the Enable pin.
printIIC(_data | En); byte buffer[] = {(byte)(_data|En), _data};
printIIC(_data); I2CManager.write(_Addr, buffer, sizeof(buffer));
if (!inTransmission) Wire.endTransmission();
} }
// write a byte to the PCF8574 I2C interface // write a byte to the PCF8574 I2C interface. We don't need to set
// the enable pin for this.
void LiquidCrystal_I2C::expanderWrite(uint8_t value) { void LiquidCrystal_I2C::expanderWrite(uint8_t value) {
int _data = (int)value | _backlightval; I2CManager.write(_Addr, 1, value | _backlightval);
Wire.beginTransmission(_Addr);
printIIC(_data);
Wire.endTransmission();
} }

View File

@ -21,9 +21,7 @@
#ifndef LiquidCrystal_I2C_h #ifndef LiquidCrystal_I2C_h
#define LiquidCrystal_I2C_h #define LiquidCrystal_I2C_h
#include <inttypes.h> #include <Arduino.h>
#include "Print.h"
#include <Wire.h>
// commands // commands
#define LCD_CLEARDISPLAY 0x01 #define LCD_CLEARDISPLAY 0x01
@ -82,25 +80,15 @@ public:
void backlight(); void backlight();
void setCursor(uint8_t, uint8_t); void setCursor(uint8_t, uint8_t);
#if defined(ARDUINO) && ARDUINO >= 100
virtual size_t write(uint8_t); virtual size_t write(uint8_t);
#else
virtual void write(uint8_t);
#endif
void command(uint8_t); void command(uint8_t);
void init(); void init();
void oled_init();
////compatibility API function aliases
void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight()
void printstr(const char[]);
private: private:
void init_priv(); void init_priv();
void send(uint8_t, uint8_t); void send(uint8_t, uint8_t);
void write4bits(uint8_t, bool inTransmission=false); void write4bits(uint8_t);
void expanderWrite(uint8_t); void expanderWrite(uint8_t);
void pulseEnable(uint8_t);
uint8_t _Addr; uint8_t _Addr;
uint8_t _displayfunction; uint8_t _displayfunction;
uint8_t _displaycontrol; uint8_t _displaycontrol;

View File

@ -23,7 +23,6 @@
* BSD license, all text above must be included in any redistribution * BSD license, all text above must be included in any redistribution
*/ */
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h>
#include "PWMServoDriver.h" #include "PWMServoDriver.h"
#include "DIAG.h" #include "DIAG.h"
#include "I2CManager.h" #include "I2CManager.h"
@ -60,7 +59,7 @@ bool PWMServoDriver::setup(int board) {
uint8_t i2caddr=PCA9685_I2C_ADDRESS + board; uint8_t i2caddr=PCA9685_I2C_ADDRESS + board;
// Test if device is available // Test if device is available
byte error = I2CManager.exists(i2caddr); byte error = I2CManager.checkAddress(i2caddr);
if (error) { if (error) {
DIAG(F("I2C Servo device 0x%x Not Found %d"),i2caddr, error); DIAG(F("I2C Servo device 0x%x Not Found %d"),i2caddr, error);
failFlags|=1<<board; failFlags|=1<<board;
@ -85,20 +84,14 @@ void PWMServoDriver::setServo(byte servoNum, uint16_t value) {
if (setup(board)) { if (setup(board)) {
DIAG(F("SetServo %d %d"),servoNum,value); DIAG(F("SetServo %d %d"),servoNum,value);
Wire.beginTransmission(PCA9685_I2C_ADDRESS + board); uint8_t buffer[] = {(uint8_t)(PCA9685_FIRST_SERVO + 4 * pin), // 4 registers per pin
Wire.write(PCA9685_FIRST_SERVO + 4 * pin); // 4 registers per pin 0, 0, (uint8_t)(value & 0xff), (uint8_t)(value >> 8)};
Wire.write(0); if (value == 4095) buffer[2] = 0x10; // Full on
Wire.write(0); byte error=I2CManager.write(PCA9685_I2C_ADDRESS + board, buffer, sizeof(buffer));
Wire.write(value);
Wire.write(value >> 8);
byte error=Wire.endTransmission();
if (error!=0) DIAG(F("SetServo error %d"),error); if (error!=0) DIAG(F("SetServo error %d"),error);
} }
} }
void PWMServoDriver::writeRegister(uint8_t i2caddr,uint8_t hardwareRegister, uint8_t d) { void PWMServoDriver::writeRegister(uint8_t i2caddr,uint8_t hardwareRegister, uint8_t d) {
Wire.beginTransmission(i2caddr); I2CManager.write(i2caddr, 2, hardwareRegister, d);
Wire.write(hardwareRegister);
Wire.write(d);
Wire.endTransmission();
} }

View File

@ -16,27 +16,38 @@
* <http://www.gnu.org/licenses/>. * <http://www.gnu.org/licenses/>.
*/ */
#include "SSD1306Ascii.h" #include "SSD1306Ascii.h"
#include "I2CManager.h"
#include "FSH.h"
// Maximum number of bytes we can send per transmission is 32.
const uint8_t FLASH SSD1306AsciiWire::blankPixels[32] =
{0x40, // First byte specifies data mode
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
//============================================================================== //==============================================================================
// SSD1306Ascii Method Definitions // SSD1306AsciiWire Method Definitions
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void SSD1306Ascii::clear() { void SSD1306AsciiWire::clear() {
clear(0, displayWidth() - 1, 0, displayRows() - 1); clear(0, displayWidth() - 1, 0, displayRows() - 1);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void SSD1306Ascii::clear(uint8_t c0, uint8_t c1, uint8_t r0, uint8_t r1) { void SSD1306AsciiWire::clear(uint8_t columnStart, uint8_t columnEnd,
uint8_t rowStart, uint8_t rowEnd) {
const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire
// Ensure only rows on display will be cleared. // Ensure only rows on display will be cleared.
if (r1 >= displayRows()) r1 = displayRows() - 1; if (rowEnd >= displayRows()) rowEnd = displayRows() - 1;
for (uint8_t r = rowStart; r <= rowEnd; r++) {
for (uint8_t r = r0; r <= r1; r++) { setCursor(columnStart, r); // Position at start of row to be erased
setCursor(c0, r); for (uint8_t c = columnStart; c <= columnEnd; c += maxBytes-1) {
for (uint8_t c = c0; c <= c1; c++) ssd1306WriteRamBuf(0); uint8_t len = min((uint8_t)(columnEnd-c+1), maxBytes-1) + 1;
I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write up to 31 blank columns
}
} }
setCursor(c0, r0);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void SSD1306Ascii::init(const DevType* dev) { void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) {
m_i2cAddr = i2cAddr;
m_col = 0; m_col = 0;
m_row = 0; m_row = 0;
#ifdef __AVR__ #ifdef __AVR__
@ -48,125 +59,50 @@ void SSD1306Ascii::init(const DevType* dev) {
m_displayWidth = readFontByte(&dev->lcdWidth); m_displayWidth = readFontByte(&dev->lcdWidth);
m_displayHeight = readFontByte(&dev->lcdHeight); m_displayHeight = readFontByte(&dev->lcdHeight);
m_colOffset = readFontByte(&dev->colOffset); m_colOffset = readFontByte(&dev->colOffset);
for (uint8_t i = 0; i < size; i++) { I2CManager.write_P(m_i2cAddr, table, size);
ssd1306WriteCmd(readFontByte(table + i));
}
clear();
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void SSD1306Ascii::setCol(uint8_t col) { void SSD1306AsciiWire::setContrast(uint8_t value) {
if (col < m_displayWidth) { I2CManager.write(m_i2cAddr, 2,
m_col = col; 0x00, // Set to command mode
col += m_colOffset; SSD1306_SETCONTRAST, value);
ssd1306WriteCmd(SSD1306_SETLOWCOLUMN | (col & 0XF)); }
ssd1306WriteCmd(SSD1306_SETHIGHCOLUMN | (col >> 4)); //------------------------------------------------------------------------------
void SSD1306AsciiWire::setCursor(uint8_t col, uint8_t row) {
if (row < displayRows() && col < m_displayWidth) {
m_row = row;
m_col = col + m_colOffset;
I2CManager.write(m_i2cAddr, 4,
0x00, // Set to command mode
SSD1306_SETLOWCOLUMN | (col & 0XF),
SSD1306_SETHIGHCOLUMN | (col >> 4),
SSD1306_SETSTARTPAGE | m_row);
} }
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void SSD1306Ascii::setContrast(uint8_t value) { void SSD1306AsciiWire::setFont(const uint8_t* font) {
ssd1306WriteCmd(SSD1306_SETCONTRAST);
ssd1306WriteCmd(value);
}
//------------------------------------------------------------------------------
void SSD1306Ascii::setCursor(uint8_t col, uint8_t row) {
setCol(col);
setRow(row);
}
//------------------------------------------------------------------------------
void SSD1306Ascii::setFont(const uint8_t* font) {
m_font = font; m_font = font;
m_fontFirstChar = readFontByte(m_font + FONT_FIRST_CHAR); m_fontFirstChar = readFontByte(m_font + FONT_FIRST_CHAR);
m_fontCharCount = readFontByte(m_font + FONT_CHAR_COUNT); m_fontCharCount = readFontByte(m_font + FONT_CHAR_COUNT);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void SSD1306Ascii::setRow(uint8_t row) { size_t SSD1306AsciiWire::write(uint8_t ch) {
if (row < displayRows()) {
m_row = row;
ssd1306WriteCmd(SSD1306_SETSTARTPAGE | m_row);
}
}
//------------------------------------------------------------------------------
void SSD1306Ascii::ssd1306WriteRam(uint8_t c) {
if (m_col < m_displayWidth) {
writeDisplay(c, SSD1306_MODE_RAM);
m_col++;
}
}
//------------------------------------------------------------------------------
void SSD1306Ascii::ssd1306WriteRamBuf(uint8_t c) {
if (m_col < m_displayWidth) {
writeDisplay(c, SSD1306_MODE_RAM_BUF);
m_col++;
}
}
//------------------------------------------------------------------------------
size_t SSD1306Ascii::write(uint8_t ch) {
if (!m_font) {
return 0;
}
const uint8_t* base = m_font + FONT_WIDTH_TABLE; const uint8_t* base = m_font + FONT_WIDTH_TABLE;
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount)) if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
return 0; return 0;
ch -= m_fontFirstChar; ch -= m_fontFirstChar;
base += fontWidth * ch; base += fontWidth * ch;
for (uint8_t c = 0; c < fontWidth; c++) { uint8_t buffer[1+fontWidth+letterSpacing];
uint8_t b = readFontByte(base + c); buffer[0] = 0x40; // set SSD1306 controller to data mode
ssd1306WriteRamBuf(b); uint8_t bufferPos = 1;
} // Copy character pixel columns
for (uint8_t i = 0; i < letterSpacing; i++) { for (uint8_t i = 0; i < fontWidth; i++)
ssd1306WriteRamBuf(0); buffer[bufferPos++] = readFontByte(base++);
} // Add blank pixels between letters
flushDisplay(); for (uint8_t i = 0; i < letterSpacing; i++)
buffer[bufferPos++] = 0;
// Write the data to I2C display
I2CManager.write(m_i2cAddr, buffer, bufferPos);
return 1; return 1;
} }
//=============================================================================
// SSD1306AsciiWire method definitions
#define m_oledWire Wire
void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) {
#if OPTIMIZE_I2C
m_nData = 0;
#endif // OPTIMIZE_I2C
m_i2cAddr = i2cAddr;
init(dev);
}
//------------------------------------------------------------------------------
void SSD1306AsciiWire::writeDisplay(uint8_t b, uint8_t mode) {
#if OPTIMIZE_I2C
if (m_nData > 16 || (m_nData && mode == SSD1306_MODE_CMD)) {
m_oledWire.endTransmission();
m_nData = 0;
}
if (m_nData == 0) {
m_oledWire.beginTransmission(m_i2cAddr);
m_oledWire.write(mode == SSD1306_MODE_CMD ? 0X00 : 0X40);
}
m_oledWire.write(b);
if (mode == SSD1306_MODE_RAM_BUF) {
m_nData++;
} else {
m_oledWire.endTransmission();
m_nData = 0;
}
#else // OPTIMIZE_I2C
m_oledWire.beginTransmission(m_i2cAddr);
m_oledWire.write(mode == SSD1306_MODE_CMD ? 0X00 : 0X40);
m_oledWire.write(b);
m_oledWire.endTransmission();
#endif // OPTIMIZE_I2C
}
//------------------------------------------------------------------------------
void SSD1306AsciiWire::flushDisplay() {
#if OPTIMIZE_I2C
if (m_nData) {
m_oledWire.endTransmission();
m_nData = 0;
}
#endif // OPTIMIZE_I2C
}
//------------------------------------------------------------------------------

View File

@ -12,127 +12,41 @@
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with the Arduino SSD1306Ascii Library. If not, see * along with this software. If not, see
* <http://www.gnu.org/licenses/>. * <http://www.gnu.org/licenses/>.
*/ */
/**
* @file SSD1306AsciiWire.h
* @brief Class for I2C displays using Wire.
*/
#ifndef SSD1306AsciiWire_h
#define SSD1306AsciiWire_h
#include <Wire.h> #ifndef SSD1306Ascii_h
#define SSD1306Ascii_h
#include "Arduino.h" #include "Arduino.h"
#include "LCDDisplay.h"
#include "SSD1306font.h" #include "SSD1306font.h"
#include "SSD1306init.h" #include "SSD1306init.h"
//------------------------------------------------------------------------------ class SSD1306AsciiWire : public Print {
/** SSD1306Ascii version basis */
#define SDD1306_ASCII_VERSION 1.3.0
//------------------------------------------------------------------------------
// Configuration options.
/** Use larger faster I2C code. */
#define OPTIMIZE_I2C 1
//------------------------------------------------------------------------------
// Values for writeDisplay() mode parameter.
/** Write to Command register. */
#define SSD1306_MODE_CMD 0
/** Write one byte to display RAM. */
#define SSD1306_MODE_RAM 1
/** Write to display RAM with possible buffering. */
#define SSD1306_MODE_RAM_BUF 2
//------------------------------------------------------------------------------
/**
* @class SSD1306Ascii
* @brief SSD1306 base class
*/
class SSD1306Ascii : public Print {
public: public:
using Print::write; using Print::write;
SSD1306Ascii() {} SSD1306AsciiWire() {}
/** // Initialize the display controller.
* @brief Clear the display and set the cursor to (0, 0). void begin(const DevType* dev, uint8_t i2cAddr);
*/ // Clear the display and set the cursor to (0, 0).
void clear(); void clear();
/** // Clear a region of the display.
* @brief Clear a region of the display.
*
* @param[in] c0 Starting column.
* @param[in] c1 Ending column.
* @param[in] r0 Starting row;
* @param[in] r1 Ending row;
* @note The final cursor position will be (c0, r0).
*/
void clear(uint8_t c0, uint8_t c1, uint8_t r0, uint8_t r1); void clear(uint8_t c0, uint8_t c1, uint8_t r0, uint8_t r1);
/** // The current column in pixels.
* @brief Clear a field of n fieldWidth() characters.
*
* @param[in] col Field start column.
*
* @param[in] row Field start row.
*
* @param[in] n Number of characters in the field.
*
*/
void clearField(uint8_t col, uint8_t row, uint8_t n);
/**
* @brief Clear the display to the end of the current line.
* @note The number of rows cleared will be determined by the height
* of the current font.
* @note The cursor will be returned to the original position.
*/
void clearToEOL();
/**
* @return The current column in pixels.
*/
inline uint8_t col() const { return m_col; } inline uint8_t col() const { return m_col; }
/** // The display hight in pixels.
* @return The display hight in pixels.
*/
inline uint8_t displayHeight() const { return m_displayHeight; } inline uint8_t displayHeight() const { return m_displayHeight; }
/** // The display height in rows with eight pixels to a row.
* @brief Set display to normal or 180 degree remap mode.
*
* @param[in] mode true for normal mode, false for remap mode.
*
* @note Adafruit and many ebay displays use remap mode.
* Use normal mode to rotate these displays 180 degrees.
*/
void displayRemap(bool mode);
/**
* @return The display height in rows with eight pixels to a row.
*/
inline uint8_t displayRows() const { return m_displayHeight / 8; } inline uint8_t displayRows() const { return m_displayHeight / 8; }
/** // The display width in pixels.
* @return The display width in pixels.
*/
inline uint8_t displayWidth() const { return m_displayWidth; } inline uint8_t displayWidth() const { return m_displayWidth; }
/** // Set the cursor position to (0, 0).
* @brief Set the cursor position to (0, 0).
*/
inline void home() { setCursor(0, 0); } inline void home() { setCursor(0, 0); }
/** // Initialize the display controller.
* @brief Initialize the display controller.
*
* @param[in] dev A display initialization structure.
*/
void init(const DevType* dev); void init(const DevType* dev);
/** // the current row number with eight pixels to a row.
* @return the current row number with eight pixels to a row.
*/
inline uint8_t row() const { return m_row; } inline uint8_t row() const { return m_row; }
/**
* @brief Set the current column number.
*
* @param[in] col The desired column number in pixels.
*/
void setCol(uint8_t col);
/** /**
* @brief Set the display contrast. * @brief Set the display contrast.
* *
@ -152,34 +66,6 @@ class SSD1306Ascii : public Print {
* @param[in] font Pointer to a font table. * @param[in] font Pointer to a font table.
*/ */
void setFont(const uint8_t* font); void setFont(const uint8_t* font);
/**
* @brief Set the current row number.
*
* @param[in] row the row number in eight pixel rows.
*/
void setRow(uint8_t row);
/**
* @brief Write a command byte to the display controller.
*
* @param[in] c The command byte.
* @note The byte will immediately be sent to the controller.
*/
inline void ssd1306WriteCmd(uint8_t c) { writeDisplay(c, SSD1306_MODE_CMD); }
/**
* @brief Write a byte to RAM in the display controller.
*
* @param[in] c The data byte.
* @note The byte will immediately be sent to the controller.
*/
void ssd1306WriteRam(uint8_t c);
/**
* @brief Write a byte to RAM in the display controller.
*
* @param[in] c The data byte.
* @note The byte may be buffered until a call to ssd1306WriteCmd
* or ssd1306WriteRam or flushDisplay.
*/
void ssd1306WriteRamBuf(uint8_t c);
/** /**
* @brief Display a character. * @brief Display a character.
* *
@ -188,47 +74,24 @@ class SSD1306Ascii : public Print {
*/ */
size_t write(uint8_t c); size_t write(uint8_t c);
protected: private:
virtual void writeDisplay(uint8_t b, uint8_t mode) = 0;
virtual void flushDisplay() = 0;
uint8_t m_col; // Cursor column. uint8_t m_col; // Cursor column.
uint8_t m_row; // Cursor RAM row. uint8_t m_row; // Cursor RAM row.
uint8_t m_displayWidth; // Display width. uint8_t m_displayWidth; // Display width.
uint8_t m_displayHeight; // Display height. uint8_t m_displayHeight; // Display height.
uint8_t m_colOffset; // Column offset RAM to SEG. uint8_t m_colOffset; // Column offset RAM to SEG.
const uint8_t* m_font = nullptr; // Current font. const uint8_t* m_font = NULL; // Current font.
// Only fixed size 5x7 fonts in a 6x8 cell are supported. // Only fixed size 5x7 fonts in a 6x8 cell are supported.
const int fontWidth = 5; const uint8_t fontWidth = 5;
const int fontHeight = 7; const uint8_t fontHeight = 7;
const uint8_t letterSpacing = 1; const uint8_t letterSpacing = 1;
uint8_t m_fontFirstChar; uint8_t m_fontFirstChar;
uint8_t m_fontCharCount; uint8_t m_fontCharCount;
};
/**
* @class SSD1306AsciiWire
* @brief Class for I2C displays using Wire.
*/
class SSD1306AsciiWire : public SSD1306Ascii {
public:
/**
* @brief Initialize the display controller.
*
* @param[in] dev A device initialization structure.
* @param[in] i2cAddr The I2C address of the display controller.
*/
void begin(const DevType* dev, uint8_t i2cAddr);
protected:
void writeDisplay(uint8_t b, uint8_t mode);
void flushDisplay();
protected:
uint8_t m_i2cAddr; uint8_t m_i2cAddr;
#if OPTIMIZE_I2C
uint8_t m_nData; static const uint8_t blankPixels[];
#endif // OPTIMIZE_I2C
}; };
#endif // SSD1306AsciiWire_h #endif // SSD1306Ascii_h

View File

@ -118,6 +118,7 @@ struct DevType {
/** Initialization commands for a 128x32 SSD1306 oled display. */ /** Initialization commands for a 128x32 SSD1306 oled display. */
static const uint8_t MEM_TYPE Adafruit128x32init[] = { static const uint8_t MEM_TYPE Adafruit128x32init[] = {
// Init sequence for Adafruit 128x32 OLED module // Init sequence for Adafruit 128x32 OLED module
0x00, // Set to command mode
SSD1306_DISPLAYOFF, SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80 SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32 SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
@ -148,6 +149,7 @@ static const DevType MEM_TYPE Adafruit128x32 = {
/** Initialization commands for a 128x64 SSD1306 oled display. */ /** Initialization commands for a 128x64 SSD1306 oled display. */
static const uint8_t MEM_TYPE Adafruit128x64init[] = { static const uint8_t MEM_TYPE Adafruit128x64init[] = {
// Init sequence for Adafruit 128x64 OLED module // Init sequence for Adafruit 128x64 OLED module
0x00, // Set to command mode
SSD1306_DISPLAYOFF, SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80 SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64 SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
@ -177,6 +179,7 @@ static const DevType MEM_TYPE Adafruit128x64 = {
// This section is based on https://github.com/stanleyhuangyc/MultiLCD // This section is based on https://github.com/stanleyhuangyc/MultiLCD
/** Initialization commands for a 128x64 SH1106 oled display. */ /** Initialization commands for a 128x64 SH1106 oled display. */
static const uint8_t MEM_TYPE SH1106_128x64init[] = { static const uint8_t MEM_TYPE SH1106_128x64init[] = {
0x00, // Set to command mode
SSD1306_DISPLAYOFF, SSD1306_DISPLAYOFF,
SSD1306_SETSTARTPAGE | 0X0, // set page address SSD1306_SETSTARTPAGE | 0X0, // set page address
SSD1306_SETCONTRAST, 0x80, // 128 SSD1306_SETCONTRAST, 0x80, // 128