Skip to content

Commit

Permalink
add support for SH1106/SSD1306
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexIII committed Oct 11, 2020
1 parent 841f17c commit 861c880
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 22 deletions.
Binary file added img/res-mod-sh1106.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/sch-sh1106.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/sch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/t-rex-demo-sh1106.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ Just 3 components needed:

[Youtube video](https://www.youtube.com/watch?v=635SnybBRD8)

**Update**
Now supporting [OLED display SH1106/SSD1306 I2C](#using-oled-display-sh1106/ssd1306-i2c).

## Instructions

0. Assemble
Expand All @@ -22,6 +25,32 @@ Just 3 components needed:
2. Play!
3. Repeat from step 2

## Using OLED display SH1106/SSD1306 I2C

<img width="250" src="img/t-rex-demo-sh1106.gif" />

0. In order to use SH1106/SSD1306 you will need to add two 470 ohm resistors to the dispaly board.
You can add these resistors right on top of 10k resistors that already present (or replace them).

<img width="250" src="img/res-mod-sh1106.jpg" />

1. Assemble

<img width="600" src="img/sch-sh1106.png" />

3. Select OLED display type at the beginning of `t-rex-duino.ino` sketch by uncommenting one of the following lines (SSD1309 selected by default).
```
//#define LCD_SSD1309
//#define LCD_SH1106
//#define LCD_SSD1306
```
Flash the sketch.

Note that in order to achieve desirable performance I2C is substantially overclocked (it works at 800kHz vs 400kHz as per display specification).
It is possible that not every display will work under these conditions.
The display I have works fine mostly, but a stray broken frame still appears now and then.
As much as I can assume **small artifacts are unavoidable with this display**.

## License

MIT License © github.com/AlexIII
126 changes: 126 additions & 0 deletions t-rex-duino/I2C.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Project name: T-rex-duino
* Description: T-rex game from Chrome brower rewritten for Arduino
* Project page: https://github.com/AlexIII/t-rex-duino
* Author: github.com/AlexIII
* E-mail: [email protected]
* License: MIT
*/

#ifndef _I2C_CALSS_H_
#define _I2C_CALSS_H_

#include <compat/twi.h>

struct I2C {
I2C() {}

static void init(const uint32_t clock) {
TWSR = 0; // no prescaler
TWBR = ((F_CPU/clock)-16)/2; // must be > 10 for stable operation
}

static void deinit(void) {
TWBR = 0;
TWCR = 0;
}

static uint8_t start(uint8_t address) {
uint8_t twst, i;
// send START condition
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);

// wait until transmission completed
//while(!(TWCR & (1<<TWINT)));
for(i = 255; i; --i) {
if(TWCR & (1<<TWINT)) break;
asm volatile("nop");
asm volatile("nop");
}
if(!i) return 0xFF;

// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;

// send device address
TWDR = address;
TWCR = (1<<TWINT) | (1<<TWEN);

// wail until transmission completed and ACK/NACK has been received
//while(!(TWCR & (1<<TWINT)));
for(i = 255; i; --i) {
if(TWCR & (1<<TWINT)) break;
asm volatile("nop");
asm volatile("nop");
}
if(!i) return 0xFF;

// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;

return 0;
}

static void stop(void) {
//send stop condition
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);

// wait until stop condition is executed and bus released
//while(TWCR & (1<<TWSTO));
for(uint8_t i = 255; i; --i) {
if(!(TWCR & (1<<TWSTO))) break;
asm volatile("nop");
asm volatile("nop");
}
}

static uint8_t write(uint8_t data) {
uint8_t twst;

// send data to the previously addressed device
TWDR = data;
TWCR = (1<<TWINT) | (1<<TWEN);

// wait until transmission completed
//while(!(TWCR & (1<<TWINT)));
uint8_t i;
for(i = 255; i; --i) {
if(TWCR & (1<<TWINT)) break;
asm volatile("nop");
asm volatile("nop");
}
if(!i) return 0xFF;

// check value of TWI Status Register. Mask prescaler bits
twst = TW_STATUS & 0xF8;
if( twst != TW_MT_DATA_ACK) return twst;
return 0;
}

static uint8_t readAck(void) {
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
//while(!(TWCR & (1<<TWINT)));
for(uint8_t i = 255; i; --i) {
if(TWCR & (1<<TWINT)) break;
asm volatile("nop");
asm volatile("nop");
}
return TWDR;
}

static uint8_t readNak(void) {
TWCR = (1<<TWINT) | (1<<TWEN);
//while(!(TWCR & (1<<TWINT)));
for(uint8_t i = 255; i; --i) {
if(TWCR & (1<<TWINT)) break;
asm volatile("nop");
asm volatile("nop");
}
return TWDR;
}
};


#endif
120 changes: 120 additions & 0 deletions t-rex-duino/SH1106.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Project name: T-rex-duino
* Description: T-rex game from Chrome brower rewritten for Arduino
* Project page: https://github.com/AlexIII/t-rex-duino
* Author: github.com/AlexIII
* E-mail: [email protected]
* License: MIT
*/

#ifndef _SH1106_H_
#define _SH1106_H_

#define SH1106_I2C_SCL_CLOCK 800000UL
#define SH1106_I2C_ADDR 0b01111000
#define SH1106_COMMAND 0x00
#define SH1106_DATA 0x40

#define SH1106_COLS_USED 128
#define SH1106_PAGES 8

#ifndef LCD_SSD1306
#define SH1106_COLS_TOTAL 130
#else
#define SH1106_COLS_TOTAL SH1106_COLS_USED
#endif

template<class I2C_TYPE>
class SH1106 {
I2C_TYPE &i2c;
const uint16_t screenBufferSize;
public:
enum AddressingMode {
HorizontalAddressingMode = 0x00,
VerticalAddressingMode = 0x01,
PageAddressingMode = 0x02,
};
SH1106(I2C_TYPE &i2c, const uint16_t screenBufferSize):
i2c(i2c), screenBufferSize(screenBufferSize) {}
void begin() {
uint8_t col = 0;
uint8_t page = 0;
}
void fillScreen(const uint8_t* buffer) {
reinit();
sendd(buffer, screenBufferSize);
}
void fillScreen(const uint8_t* buffer, const uint16_t size, const uint8_t stride = 0) { //stride is not supported
reinit();
sendd(buffer, size);
}
void setInverse(const bool v) {
inverted = v;
reinit();
}
//Only PageAddressingMode works for SH1106
//Here we're emulating HorizontalAddressingMode programmatically
void setAddressingMode(const AddressingMode addressingMode) {}

private:
/* lcd control */
void reinit() {
i2c.deinit();
i2c.init(SH1106_I2C_SCL_CLOCK);
sendc(0xAF); //display on
sendc(inverted? 0xA7 : 0xA6); //inversion
}

/* send bytes */
void sendc(const uint8_t cmd) {
i2c.start(SH1106_I2C_ADDR);
i2c.write(SH1106_COMMAND);
i2c.write(cmd);
i2c.stop();
}
void sendc(const uint8_t cmd1, const uint8_t cmd2) {
i2c.start(SH1106_I2C_ADDR);
i2c.write(SH1106_COMMAND);
i2c.write(cmd1);
i2c.write(cmd2);
i2c.stop();
}
void sendc(const uint8_t cmd1, const uint8_t cmd2, const uint8_t cmd3) {
i2c.start(SH1106_I2C_ADDR);
i2c.write(SH1106_COMMAND);
i2c.write(cmd1);
i2c.write(cmd2);
i2c.write(cmd3);
i2c.stop();
}
void sendd(const uint8_t* d, uint16_t sz) {
const uint8_t blackPx = inverted? 0xFF : 0;
while(sz) { //page cycle
sendc(0xB0 + page); //set page address to i (0..7)
sendc(0x00 + (col&0x0F)); //Sets 4 lower bits of column address
sendc(0x10 + (col>>4)); //Sets 4 higher bits of column address

i2c.start(SH1106_I2C_ADDR);
i2c.write(SH1106_DATA);
while(sz) { //column cycle
i2c.write(*d++);
--sz;
if(++col >= SH1106_COLS_USED) {
while(col++ < SH1106_COLS_TOTAL) i2c.write(blackPx);
col = 0;
break;
}
}
i2c.stop();

if(col == 0 && ++page >= SH1106_PAGES) page = 0;
}
}

uint8_t col = 0;
uint8_t page = 0;
bool inverted = false;
};


#endif
Loading

0 comments on commit 861c880

Please sign in to comment.