Stmívač LED pásku - Software v. 1.2

Zápisník experimentátora

Hierarchy: Stmievač LED pásika pomocou ATtiny85

V tomto článku vysvětlím princip činnosti stmívače LED pásků. Je to třetí ze série článků, na kterých vám ukážu, jak postupně probíhá vývoj programů a jak diametrálně se může lišit výsledný program. Postupně projdeme zdrojové kódy programu od verze 1.0 po verzi 1.3.

Tento text popisuje zastaralou verzi kódu. V době psaní článku byla aktuální verze kódu 1.3. Tato verze je zde analyzována proto, abyste si mohli podívat, jak probíhá vývoj programů metodou postupného přibližování.

V ideálním světě napíšete ideální program hned napoprvé a nebudou v něm žádné chyby a bude dělat přesně to, co jste si představovali. A teď vítejte v reálném světě, ve kterém jsem se pokusil naprogramovat logiku stmívání tak, aby vyhovovala práci v kuchyni.

Arduino IDE

Kód byl vyvíjen ještě pro verzi 1.0.6, ale bez problémů jej lze zkompilovat iv 1.6.13 nebo 1.8.2. Zabírá po zkompilovaný 2894 (6110 se zapnutým debuggování) bajtů. Je nutné mít instalovanou​ podporu pro programování ATtiny85.

Programování

V tomto programu jsem se soustředil na změnu programu na stavový automat. Celý vývoj probíhal na vývojové desce pro ATtiny85, kterou jsem již popisoval v předchozím článku.

  • ​Debuggování pomocí SoftwareSerial - ATtiny85 nemá normální sériový port, ale pomocí této knihovny se dá nahradit. Platíte za to ale vysokou daň v podobě velké spotřeby paměti, které máte pouze 8 kB.
  • Stavy - Protože osvětlení se má chovat rozdílné při různých okrajových podmínkách, je logické použít stavy a pro každý stav naprogramovat konkrétní chování programu.
  • Lineární stmívání - lineární stmívání se lépe přizpůsobí lidskému oku. Podrobnosti najdete v samostatném článku.
  • Dva kroky během stmívání - Praktické zkušenosti z používání ukázaly, že je vhodné stmívání rozdělit na dva úseky. První stmívání ztlumí jas na 30 procent a potom následuje delší pauza, během níž má osoba v místnosti čas zareagovat. Nebo odejde pryč a světlo úplně zhasne, nebo se začne pohybovat v místnosti a světlo se nastaví zase na maximální intenzitu.

Debuggování pomocí SoftwareSerial

Debuggování je založeno na existenci nebo neexistenci makra RDEBUG. Pokud neexistuje (finální verze kódu), potom nejsou vůbec použity piny 3 a 4 na které je nastavena knihovna SoftwareSerial, což znamená, že překladač všechen ten kód ze zdrojáků jednoduše odstraní.

Pokud je makro definováno (ladící verze kódu), na sériový port se odesílají ladící výpisy. Ještě stojí za zmínku, že během ladění mám zkrácen interval hold_value na 5 sekund. Je to z praktických důvodů, protože během ladění není důvod čekat desítky sekund na zhasnutí osvětlení.

//#define RDEBUG

#if defined RDEBUG
  #include <SoftwareSerial.h>

  const int dig_tx = 3;
  const int dig_rx = 4;
  SoftwareSerial s85(dig_rx, dig_tx); // RX, TX
#endif

#if defined RDEBUG
  const unsigned long hold_value = 5*1000L;
#else
  // base hold period
  const unsigned long hold_value = 45*1000L;
#endif

void setup() {               
  pinMode(led, OUTPUT);
  pinMode(dig, INPUT);
#if defined RDEBUG
  s85.begin(9600);
  s85.println(F("ATtiny85 LED dimmer"));
#else
  for(int i=0;i<2;i++)
    ShowLedIsLive();
#endif
state=stDark;
}

Stavy

V programu mám tyto stavy:

  • stBoot - Implicitní stav během bootování. Je okamžitě nahrazen stavem stDark ve funkci setup.
  • stDark - Popisuje stav osvětlení, které je vypnuto.
  • stOn - Stav, během kterého se lineárně zapne osvětlení na plnou hodnotu.
  • stHold - Plná hodnota osvětlení, přicházejí signály ze senzoru.
  • stOff - Stav, během kterého klesne osvětlení na 30 procent. Pokud přijde signál senzoru, skočí se na stav stOn.
  • stOff30 - Stav, během kterého klesne osvětlení na nulu. Pokud přijde signál senzoru, skočí se na stav stOn.
  • stHold30 - Stav udržování osvětlení na 30 procent. Pokud přijde signál senzoru, skočí se na stav stOn.
enum states {stBoot, stDark, stOn, stHold, stOff, stOff30, stHold30};
states state=stBoot;

​A jednotlivé stavy mají v kódu své funkce, které řídí chování osvětlení.

void loop() {
  switch(state) {
    case stDark:
      LightDark();
      break;
    case stOn:
      LightOn();
      break;
    case stHold:
      LightHold();
      break;
    case stOff:
      LightOff();
      break;
    case stHold30:
      LightHold30();
      break;
    case stOff30:
      LightOff30();
      break;
  }
}

Lineární stmívání

Lineární stmívání je zajištěno pomocí tabulky predpočítaných hodnot.

const byte table[] PROGMEM = {
  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
  0,   0,   0,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   2,   2,   2,   2,   2,
  2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   3,   3,   3,   3,   3,   3,
  3,   3,   3,   3,   3,   3,   4,   4,   4,   4,   4,   4,   4,   4,   4,   5,
  5,   5,   5,   5,   5,   5,   5,   6,   6,   6,   6,   6,   6,   6,   7,   7,
  7,   7,   7,   8,   8,   8,   8,   8,   9,   9,   9,   9,   9,  10,  10,  10,
 10,  11,  11,  11,  11,  12,  12,  12,  12,  13,  13,  13,  14,  14,  14,  15,
 15,  15,  16,  16,  16,  17,  17,  18,  18,  18,  19,  19,  20,  20,  21,  21,
 22,  22,  23,  23,  24,  24,  25,  25,  26,  26,  27,  28,  28,  29,  30,  30,
 31,  32,  32,  33,  34,  35,  35,  36,  37,  38,  39,  40,  40,  41,  42,  43,
 44,  45,  46,  47,  48,  49,  51,  52,  53,  54,  55,  56,  58,  59,  60,  62,
 63,  64,  66,  67,  69,  70,  72,  73,  75,  77,  78,  80,  82,  84,  86,  88,
 90,  91,  94,  96,  98, 100, 102, 104, 107, 109, 111, 114, 116, 119, 122, 124,
127, 130, 133, 136, 139, 142, 145, 148, 151, 155, 158, 161, 165, 169, 172, 176,
180, 184, 188, 192, 196, 201, 205, 210, 214, 219, 224, 229, 234, 239, 244, 250
};

Například během zhasínání vypadá kód takto. Když to teď zpětně popisuji, uvědomuji si, že tato funkce má své mouchy a část kódu se nikdy neprovede. Neboť když klesne hodnota v proměnné value pod 20, vyskočí se ven z funkce a kód na jejím konci se nikdy neprovede.

void LightOff() {
#if defined RDEBUG
  s85.println(F("Light OFF"));
#endif
unsigned long ms=millis();
unsigned long m=0;
unsigned long m2=pot_value*6; // 6x on period
while(m<m2)
  {
  int p=digitalRead(dig);
  if(p)
    {
    state=stOn;
    #if defined RDEBUG
      s85.print(F("Signal break at "));
      //s85.println(value);
    #endif
    return;
    }
  value=map(m,0,m2,255,0); 
  int vm=pgm_read_byte(&table[value]);
  value=vm;
  analogWrite(led,vm);
  if(value<20) {
    state=stHold30;
    return;
    }
  m=millis()-ms;
  }
digitalWrite(led, LOW);
value=0;
#if defined RDEBUG
  s85.println(F("Dark"));
#endif
state=stDark;
}

Dva kroky stmívání

Toto je velmi důležitá úprava. Stmívání se totiž provádí pouze pár sekund a pokud se začalo stmívání, protože jste se v místnosti přestali pohybovat (například jste se začetli do časopisu), neměli byste dostatek času na zamávání rukou, aby se světlo zase zapnulo na plnou intenzitu. Proto je stmívání rozděleno na dva intervaly. Mezi nimi je vložen udržovací interval v šeru, který vám dává dostatek času na reakci.

///
/// Handled dusk
///
void LightHold30() {
#if defined RDEBUG
  s85.println(F("Light HOLD 30"));
#endif
dig_last=millis()+hold_value;
while(millis()<dig_last)
  {
  int p=digitalRead(dig);
  if(p) // still ON
    {
    #if defined RDEBUG
      //s85.print(F("dig_last="));
      //s85.println(dig_last);
    #endif
    state=stOn;
    return;
    }
  }
state=stOff30;
}

///
/// From dusk to darkness
///
void LightOff30() {
#if defined RDEBUG
  s85.println(F("OFF 30"));
#endif
unsigned long ms=millis();
unsigned long m=0;
unsigned long m2=pot_value;
while(m<m2)
  {
  int p=digitalRead(dig);
  if(p)
    {
    state=stOn;
    return;
    }
  int v=map(m,0,m2,value,0); 
  analogWrite(led,v);
  value=v;
  m=millis()-ms;
  }
digitalWrite(led, LOW);
value=0;
state=stDark;
}

Zdrojový kód

Zdrojový kód se nachází na GitHub.


30.07.2017


Menu