// tinyOLEDsegment - using an I²C OLED as an 8-digit 7-segment display
// with an ATtiny13
//
// This is just a little demo on how to use an I²C OLED as an 8-digit
// 7-segment display with the limited capabilities of an ATtiny13. It
// implements a 24-bit hexadecimal counter.
//
// The I²C protocol implementation is based on a crude bitbanging method.
// It was specifically designed for the limited resources of ATtiny10 and
// ATtiny13, but should work with some other AVRs as well.
// To make the code as compact as possible, the following restrictions apply:
// - the clock frequency of the MCU must not exceed 4.8 MHz,
// - the slave device must support fast mode 400 kbps (is mostly the case),
// - the slave device must not stretch the clock (this is usually the case),
// - the acknowledge bit sent by the slave device is ignored.
// If these restrictions are observed, the implementation works almost without
// delays. An SCL HIGH must be at least 600ns long in Fast Mode. At a maximum
// clock rate of 4.8 MHz, this is shorter than three clock cycles. An SCL LOW
// must be at least 1300ns long. Since the SDA signal has to be applied anyway,
// a total of at least six clock cycles pass. Ignoring the ACK signal and
// disregarding clock stretching also saves a few bytes of flash. A function
// for reading from the slave was omitted because it is not necessary here.
// Overall, the I2C implementation only takes up 56 bytes of flash.
//
// Ralph Doncaster (nerdralph) pointed out that the SSD1306 can be controlled
// much faster than specified. Therefore an MCU clock rate of 9.6 MHz is also
// possible in this case.
//
// Don't forget the pull-up resistors on the SDA and SCL lines! Many modules,
// such as the SSD1306 OLED module, have already integrated them.
//
// The functions for the OLED are adapted to the SSD1306 128x32 or 128x64 OLED module,
// but they can easily be modified to be used for other modules. In order to
// save resources, only the basic functionalities are implemented.
//
// +-----------------------------+
// ---|SDA +--------------------+ |
// ---|SCL | SSD1306 OLED | |
// ---|VCC | 128x32 (or 64) | |
// ---|GND +--------------------+ |
// +-----------------------------+
//
// +-\/-+
// --- A0 (D5) PB5 1|° |8 Vcc
// --- A3 (D3) PB3 2| |7 PB2 (D2) A1 --- SCL OLED
// --- A2 (D4) PB4 3| |6 PB1 (D1) ------
// GND 4| |5 PB0 (D0) ------ SDA OLED
// +----+
//
// Controller: ATtiny13
// Core: MicroCore (https://github.com/MCUdude/MicroCore)
// Clockspeed: 4.8 (or 9.6) MHz internal
// BOD: BOD disabled
// Timing: Micros disabled
// Leave the rest on default settings. Don't forget to "Burn bootloader"!
// No Arduino core functions or libraries are used. Use the makefile to
// compile without Arduino IDE.
//
// A big thank you to Ralph Doncaster (nerdralph) for his optimization tips.
// ( https://nerdralph.blogspot.com/ , https://github.com/nerdralph )
//
// 2020 by Stefan Wagner
// Project Files (EasyEDA): https://easyeda.com/wagiminator
// Project Files (Github): https://github.com/wagiminator
// License: http://creativecommons.org/licenses/by-sa/3.0/
// Select the screen size
//#define SCREEN_128x32
#define SCREEN_128x64
#if defined(SCREEN_128x32) and defined(SCREEN_128x64)
#error "Please define either SCREEN_128x32 or SCREEN_128x64 but not both!"
#endif
#if !defined(SCREEN_128x32) and !defined(SCREEN_128x64)
#error "Please define one of SCREEN_128x32 or SCREEN_128x64!"
#endif
// Libraries
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <Arduino.h>
#include "GBUSmini.h"
// Pin definitions
#define I2C_SDA PB4 // serial data pin
#define I2C_SCL PB2 // serial clock pin
// -----------------------------------------------------------------------------
// I2C Master Implementation (Write only)
// -----------------------------------------------------------------------------
// I2C macros
#define I2C_SDA_HIGH() DDRB &= ~(1<<I2C_SDA) // release SDA -> pulled HIGH by resistor
#define I2C_SDA_LOW() DDRB |= (1<<I2C_SDA) // SDA as output -> pulled LOW by MCU
#define I2C_SCL_HIGH() DDRB &= ~(1<<I2C_SCL) // release SCL -> pulled HIGH by resistor
#define I2C_SCL_LOW() DDRB |= (1<<I2C_SCL) // SCL as output -> pulled LOW by MCU
// I2C init function
void I2C_init(void) {
DDRB &= ~((1 << I2C_SDA) | (1 << I2C_SCL)); // pins as input (HIGH-Z) -> lines released
PORTB &= ~((1 << I2C_SDA) | (1 << I2C_SCL)); // should be LOW when as ouput
}
// I2C transmit one data byte to the slave, ignore ACK bit, no clock stretching allowed
void I2C_write(uint8_t data) {
for (uint8_t i = 8; i; i--) { // transmit 8 bits, MSB first
I2C_SDA_LOW(); // SDA LOW for now (saves some flash this way)
if (data & 0x80) I2C_SDA_HIGH(); // SDA HIGH if bit is 1
I2C_SCL_HIGH(); // clock HIGH -> slave reads the bit
data <<= 1; // shift left data byte, acts also as a delay
I2C_SCL_LOW(); // clock LOW again
}
I2C_SDA_HIGH(); // release SDA for ACK bit of slave
I2C_SCL_HIGH(); // 9th clock pulse is for the ACK bit
asm("nop"); // ACK bit is ignored, just a delay
I2C_SCL_LOW(); // clock LOW again
}
// I2C start transmission
void I2C_start(uint8_t addr) {
I2C_SDA_LOW(); // start condition: SDA goes LOW first
I2C_SCL_LOW(); // start condition: SCL goes LOW second
I2C_write(addr); // send slave address
}
// I2C stop transmission
void I2C_stop(void) {
I2C_SDA_LOW(); // prepare SDA for LOW to HIGH transition
I2C_SCL_HIGH(); // stop condition: SCL goes HIGH first
I2C_SDA_HIGH(); // stop condition: SDA goes HIGH second
}
// -----------------------------------------------------------------------------
// OLED Implementation
// -----------------------------------------------------------------------------
// OLED definitions
#define OLED_ADDR 0x78 // OLED write address
#define OLED_CMD_MODE 0x00 // set command mode
#define OLED_DAT_MODE 0x40 // set data mode
#if defined(SCREEN_128x32)
#define OLED_INIT_LEN 12 // 12: no screen flip, 14: screen flip
#define MULTIPLEX 0x1F
#else
#define OLED_INIT_LEN 12 // 7: no screen flip, 9: screen flip
#define MULTIPLEX 0x3F
#endif
// OLED init settings
const uint8_t OLED_INIT_CMD[] PROGMEM = {
0xA8, 63, // set multiplex (HEIGHT-1): 0x1F for 128x32, 0x3F for 128x64
0x20, 0x01, // set vertical memory addressing mode
0x22, 0x00, 0x07, // set min and max page => set in OLED_init
0x8D, 0x14, // enable charge pump
0xAF, // switch on OLED
0xA1, 0xC8 // flip the screen
};
// Simple reduced 3x8 font
const uint8_t OLED_FONT[] PROGMEM = {
0x7F, 0x41, 0x7F, // 0 0
0x00, 0x00, 0x7F, // 1 1
0x79, 0x49, 0x4F, // 2 2
0x41, 0x49, 0x7F, // 3 3
0x0F, 0x08, 0x7E, // 4 4
0x4F, 0x49, 0x79, // 5 5
0x7F, 0x49, 0x79, // 6 6
0x03, 0x01, 0x7F, // 7 7
0x7F, 0x49, 0x7F, // 8 8
0x4F, 0x49, 0x7F, // 9 9
0x7F, 0x09, 0x7F, // A 10
0x7F, 0x48, 0x78, // b 11
0x7F, 0x41, 0x63, // C 12
0x78, 0x48, 0x7F, // d 13
0x7F, 0x49, 0x41, // E 14
0x7F, 0x09, 0x01, // F 15
0x00, 0x60, 0x00, // . 16
0x00, 0x36, 0x00, // : 17
0x08, 0x08, 0x08, // - 18
0x00, 0x00, 0x00 // 19
};
// OLED init function
void OLED_init(void) {
I2C_init(); // initialize I2C first
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_CMD_MODE); // set command mode
for (uint8_t i = 0; i < OLED_INIT_LEN; i++) I2C_write(pgm_read_byte(&OLED_INIT_CMD[i])); // send the command bytes
//I2C_stop();
// stop transmission
//I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
for (uint16_t i = 1024; i; i--) I2C_write(0x00);
I2C_stop(); // stop transmission
}
// OLED stretch a part of a byte
uint8_t OLED_stretch(uint8_t b) {
b = ((b & 2) << 3) | (b & 1); // split 2 LSB into the nibbles
b |= b << 1; // double the bits
b |= b << 2;
//b |= b << 3; // double them again = 4 times
return b; // return the value
}
// OLED print a big digit
void OLED_printD(uint8_t ch) {
uint8_t i, j, k, b; // loop variables
uint8_t sb[4]; // stretched character bytes
ch += ch << 1; // calculate position of character in font array
for (i = 8; i; i--) I2C_write(0x00); // print spacing between characters
for (i = 3; i; i--) { // font has 3 bytes per character
b = pgm_read_byte(&OLED_FONT[ch++]); // read character byte
for (j = 0; j < 4; j++, b >>= 2) sb[j] = OLED_stretch(b); // stretch 4 times
if (i == 2) j = 6; // calculate x-stretch value
while (j--) { // write several times (x-direction)
for (k = 0; k < 4; k++) I2C_write(sb[k]); // the 4 stretched bytes (y-direction)
}
}
}
// OLED print buffer
void OLED_printB(uint8_t *buffer) {
I2C_start(OLED_ADDR); // start transmission to OLED
I2C_write(OLED_DAT_MODE); // set data mode
for (uint8_t i = 0; i < 8; i++) OLED_printD(i); // print buffer
// for (uint8_t i = 0; i < 8; i++) OLED_printD(i); // print buffer
//for (uint8_t i = 0; i < 8; i++) OLED_printD(i); // print buffer
//for (uint8_t i = 0; i < 8; i++) OLED_printD(5); // print buffer
for (uint8_t i = 0; i < 8+24+16+16+16+16; i++) I2C_write(0x00); // print buffer
I2C_stop(); // stop transmission
}
uint8_t buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // screen buffer
void parse(uint8_t num, uint8_t index)
{
buffer[index] = num % 10;
if (num == 0) return;
parse(num / 10, index - 1);
}
// -----------------------------------------------------------------------------
// Main Function
// -----------------------------------------------------------------------------
#define BUS_PIN PB1
#define BUS_RX_ADDR 20
#define BUS_MAIN_HOST_ADDR 254
#define BUTTON_DEBOUNCE_TIMEOUT 200
#define BUTTON_POWER_UP_PIN PB3
#define BUTTON_POWER_DOWN_PIN PB5
#define PWM_PIN PB0
uint8_t power;
byte data[1]; // приёмный буфер (байты)
void setup() {
OLED_init(); // initialize the OLED
pinMode(BUTTON_POWER_UP_PIN, INPUT_PULLUP);
pinMode(BUTTON_POWER_DOWN_PIN, INPUT_PULLUP);
//pinMode(I2C_SDA, OUTPUT);
//pinMode(I2C_SCL, OUTPUT);
pinMode(PWM_PIN, OUTPUT);
pinMode(BUS_PIN, INPUT_PULLUP);
}
void loop() {
buffer[2] = power % 10;
buffer[1] = power / 10 % 10;
buffer[0] = power / 100;
OLED_printB(buffer); // print screen buffer
analogWrite(PWM_PIN, power);
if (digitalRead(BUTTON_POWER_UP_PIN) == LOW && power < 255)
{
_delay_ms(BUTTON_DEBOUNCE_TIMEOUT);
power += 10;
}
if (digitalRead(BUTTON_POWER_DOWN_PIN) == LOW && power > 0)
{
_delay_ms(BUTTON_DEBOUNCE_TIMEOUT);
power -= 10;
}
byte txaddr = GBUS_read(BUS_PIN, BUS_RX_ADDR, data, 1);
if (txaddr == BUS_MAIN_HOST_ADDR) {
power = data[0];
byte buf[] = {power};
buffer[3] = 11;
GBUS_send(BUS_PIN, txaddr, BUS_RX_ADDR, buf, sizeof(buf));
}
}