// ATtiny85 Timeline firmware with SoftwareSerial and adjusted pins
// Charlieplex LEDs: PB2, PB3, PB4 (Pins 2, 3, 4)
// Serial via SoftwareSerial: RX = PB0, TX = PB1
#include <EEPROM.h>
#include <SoftwareSerial.h>
#define RX_PIN 0 // PB0
#define TX_PIN 1 // PB1
SoftwareSerial softSerial(RX_PIN, TX_PIN); // RX, TX
const uint8_t pinsArr[3] = {2, 3, 4}; // PB2, PB3, PB4 for Charlieplex
#define EEPROM_BASE 0
struct Frame
{
uint8_t led; // 0..5
uint16_t start; // ms
uint16_t duration; // ms
};
#define MAX_RAM_FRAMES 32
Frame framesRAM[MAX_RAM_FRAMES];
uint8_t frameCount = 0;
void clearPins()
{
for(uint8_t i = 0; i < 3; i++) pinMode(pinsArr[i], INPUT);
}
void setSingleLED(uint8_t led)
{
clearPins();
switch(led)
{
case 0: pinMode(pinsArr[0], OUTPUT); pinMode(pinsArr[1], OUTPUT); digitalWrite(pinsArr[0], HIGH); digitalWrite(pinsArr[1], LOW); break;
case 1: pinMode(pinsArr[1], OUTPUT); pinMode(pinsArr[0], OUTPUT); digitalWrite(pinsArr[1], HIGH); digitalWrite(pinsArr[0], LOW); break;
case 2: pinMode(pinsArr[1], OUTPUT); pinMode(pinsArr[2], OUTPUT); digitalWrite(pinsArr[1], HIGH); digitalWrite(pinsArr[2], LOW); break;
case 3: pinMode(pinsArr[2], OUTPUT); pinMode(pinsArr[1], OUTPUT); digitalWrite(pinsArr[2], HIGH); digitalWrite(pinsArr[1], LOW); break;
case 4: pinMode(pinsArr[2], OUTPUT); pinMode(pinsArr[0], OUTPUT); digitalWrite(pinsArr[2], HIGH); digitalWrite(pinsArr[0], LOW); break;
case 5: pinMode(pinsArr[0], OUTPUT); pinMode(pinsArr[2], OUTPUT); digitalWrite(pinsArr[0], HIGH); digitalWrite(pinsArr[2], LOW); break;
default: clearPins(); break;
}
}
String readLine()
{
String s = "";
while (softSerial.available())
{
char c = softSerial.read();
if(c == '\r') continue;
if(c == '\n') break;
s += c;
}
return s;
}
void saveToEEPROM()
{
uint16_t eplen = EEPROM.length();
uint16_t perFrame = sizeof(Frame);
uint16_t maxFramesEEP = (eplen <= 1)?0:(eplen - 1) / perFrame;
uint8_t toWrite = frameCount;
if(toWrite > maxFramesEEP) toWrite=maxFramesEEP;
EEPROM.update(EEPROM_BASE, toWrite);
uint16_t addr = EEPROM_BASE + 1;
for(uint8_t i = 0; i < toWrite; i++)
{
EEPROM.put(addr + i * perFrame, framesRAM[i]);
}
softSerial.println("OK SAVED");
}
void loadFromEEPROM()
{
uint16_t eplen = EEPROM.length();
uint16_t perFrame = sizeof(Frame);
if(eplen <= 1){ frameCount = 0; softSerial.println("ERR EEPROM TOO SMALL"); return; }
uint8_t cnt = 0;
EEPROM.get(EEPROM_BASE, cnt);
uint16_t maxFramesEEP = (eplen - 1) / perFrame;
if(cnt > maxFramesEEP) cnt = 0;
frameCount = cnt;
uint16_t addr = EEPROM_BASE + 1;
for(uint8_t i = 0; i < frameCount; i++)
{
EEPROM.get(addr + i * perFrame, framesRAM[i]);
}
softSerial.println("OK LOADED");
}
void dumpFrames()
{
for(uint8_t i = 0; i < frameCount; i++)
{
softSerial.print(i); softSerial.print(": LED ");
softSerial.print(framesRAM[i].led);
softSerial.print(" START "); softSerial.print(framesRAM[i].start);
softSerial.print(" DUR "); softSerial.println(framesRAM[i].duration);
}
}
void playTimelineFromRAM()
{
if(frameCount == 0) { softSerial.println("OK PLAYED (empty)"); return; }
uint32_t total = 0;
for(uint8_t i = 0; i < frameCount; i++)
{
uint32_t endt = (uint32_t)framesRAM[i].start + framesRAM[i].duration;
if(endt > total) total = endt;
}
uint32_t startTime = millis();
const uint16_t tickMs = 20, pulseMs = 4;
while((uint32_t)(millis() - startTime) <= total)
{
uint32_t t = millis() - startTime;
uint8_t activeMask = 0;
for(uint8_t i = 0; i < frameCount; i++)
{
uint32_t s = framesRAM[i].start, e = s + framesRAM[i].duration;
if((t >= s) && (t < e) && (framesRAM[i].led < 6)) activeMask |= (1 << framesRAM[i].led);
}
if(activeMask == 0) { clearPins(); delay(tickMs); continue; }
for(uint8_t led = 0; led < 6; led++)
{
if(activeMask & (1 << led)) { setSingleLED(led); delay(pulseMs); clearPins(); }
}
delay(tickMs - pulseMs);
}
clearPins(); softSerial.println("OK PLAYED");
}
void setup()
{
softSerial.begin(9600);
clearPins();
frameCount = 0;
softSerial.println("READY");
for(uint8_t i = 0; i < 6; i++)
{
setSingleLED(i);
delay(500);
clearPins();
}
}
void loop()
{
if(softSerial.available())
{
String line = readLine();
line.trim();
if(!line.length()) return;
if(line.startsWith("ADD"))
{
int a,b,c;
int n = sscanf(line.c_str(),"ADD %d %d %d",&a,&b,&c);
if((n == 3) && (frameCount < MAX_RAM_FRAMES) && (a >= 0) && (a < 6) && (b >= 0) && (c > 0))
{
framesRAM[frameCount].led = a;
framesRAM[frameCount].start = b;
framesRAM[frameCount].duration = c;
frameCount++;
softSerial.println("OK ADDED");
}
else softSerial.println("ERR ADD");
}
else if(line == "SAVE") { saveToEEPROM(); }
else if(line == "LOAD") { loadFromEEPROM(); }
else if(line == "DUMP") { dumpFrames(); }
else if(line == "CLEAR") { frameCount = 0; softSerial.println("OK CLEARED"); }
else if(line == "PLAY") { playTimelineFromRAM(); }
else softSerial.println("ERR UNKNOWN");
}
}