Skip to content

Instantly share code, notes, and snippets.

@tylerl
Last active February 9, 2018 08:40
Show Gist options
  • Save tylerl/2f03d4d45e035ca7f38657492483be6f to your computer and use it in GitHub Desktop.
Save tylerl/2f03d4d45e035ca7f38657492483be6f to your computer and use it in GitHub Desktop.
7-segment-4-digit i2c display driver (designed for attiny2313)
#include <Arduino.h>
#include <avr/io.h>
#include <Wire.h>
// Wire plan
// B7(SCL) B5(SDA) for I2C
//
// seg: A B C D E F G DP
// prt: D6 D5 D4 D3 D2 D1 D0 B4
// pin: 11 9 8 7 6 3 2 16
// com: x1 x2 x3 x4
// prt: B0 B1 B2 B3
// pin: 12 13 14 15
// 7seg pinout
//
// x4 A F x3 x2 B
// 12 11 10 09 08 07
// 01 02 03 04 05 06
// E D DP C G x1
#define TWI_ADDR 0x12
#define CMD_SETVAL 1
#define CMD_SETBRIGHT 2
#define SetBit(BIT, PORT) (PORT |= (1<<BIT))
#define ClearBit(BIT, PORT) (PORT &= ~(1<<BIT))
#define SEG_CNT 4
volatile uint8_t seg[SEG_CNT] = {0};
volatile uint8_t bright = 16; // 0-16
void t2313_initpins();
void t2313_setsegs(const uint8_t val);
void t2313_setcom(const uint8_t which, const bool hi);
void receiveEvent(int howMany);
void requestEvent();
void setup() {
t2313_initpins();
// 1,2,3,4 in digit spots.
seg[0] = 0b01100000;
seg[1] = 0b11011010;
seg[2] = 0b11110010;
seg[3] = 0b01100110;
// startup i2c
Wire.begin(TWI_ADDR);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
}
void loop() {
unsigned long end = millis() + 10; // 100Hz refresh
unsigned long dwell = 64 + (128 * bright); // 2% to 85% duty cycle (bright <= 16)
for (uint8_t i = 0; i < SEG_CNT; i++)
{
t2313_setsegs(seg[i]);
t2313_setcom(i, true);
delayMicroseconds(dwell);
t2313_setcom(i, false);
}
t2313_setsegs(0);
unsigned long diff = end - millis();
if (diff >= 1 && diff <= 10)
{
delay(diff);
}
}
void receiveEvent(int howMany) {
int rcv = Wire.read();
uint8_t cmd = (rcv & 0xF0) >> 4;
uint8_t val = (rcv & 0x0F);
if (cmd == CMD_SETVAL)
{
for (int i = 0; i < SEG_CNT && i < val; i++)
{
rcv = Wire.read();
if (rcv == -1)
break;
seg[i] = (uint8_t)rcv;
}
}
else if (cmd == CMD_SETBRIGHT)
{
bright = val;
}
}
void requestEvent() {
Wire.write((uint8_t *)seg, SEG_CNT);
}
// Port configuration specific to attiny2313
void t2313_initpins() {
// NB: PB7 and PB5 are used for I2C
DDRD = 0b01111111; // PD[0:6] are outputs
DDRB = 0b00010000; // all HiZ pins except PB[4] (decimal point).
// Note that since 7seg LED is common-cathode,
// "off" means seg pins go low, and com pins go hi
PORTB = 0b10100000; // pull up B7 and B5 (I2C pins)
PORTD = 0; // no output initially
}
// set the A-G and DP segements (A-G are bits 8 to 1, and DP is bit 0)
void t2313_setsegs(const uint8_t val) {
//A-G = PD[6:0]
PORTD = (PORTD & 0b10000000) | (val >> 1);
// DP = PB[4]
if (val & 1) {
PORTB |= _BV(4);
} else {
PORTB &= ~_BV(4);
}
}
// turn a given com pin (low range of portb) on or off
void t2313_setcom(const uint8_t which, const bool on) {
// on means output low, off means HiZ
// unrolling this logic down to constants may seem a bit silly, but
// it means that SetBit and ClearBit get compiled into sbi and cbi
// instructions instead of a read-modify-write patterns. The latter
// turns out to crash our program when you poke the register during
// and i2c interrupt.
switch (which) {
case 0:
if (on) SetBit(0, DDRB); else ClearBit(0, DDRB);
break;
case 1:
if (on) SetBit(1, DDRB); else ClearBit(1, DDRB);
break;
case 2:
if (on) SetBit(2, DDRB); else ClearBit(2, DDRB);
break;
case 3:
if (on) SetBit(3, DDRB); else ClearBit(3, DDRB);
break;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment