Jak dogadać się z Attiny

Z poprzedniej lekcji wiesz już, jak podłączyć i zaprogramować Attiny2313. Układ nawet autonomicznie daje dużo ciekawych możliwości, ale jeszcze lepiej wypada w kooperacji! W skrócie rzecz ujmując zamierzam przybliżyć sposób na to, jak wydawać komendy dla Attiny poprzez I2C(TWI) (na przykład z poziomu Arduino)

Tu na scenę wkracza dodatkowa biblioteka: TinyWireS. W zamian za klilkaset bajtów pamięci oferuje ona możliwość konfiguracji urządzenia jako I2C(TWI) Slave. Dodam, że banalnie prostej konfiguracji, bo jedyne co tak naprawdę musisz zrobić, to podać wybrany przez Siebie adres! Jeśli jeszcze nie wiesz co to I2C, to po krótce tłumaczę, że jest to bardzo rozpowszechniony standard wymiany danych pomiędzy różnymi urządzeniami. Mówiąc językiem laika jest to medium do prowadzenia rozmów pomiędzy różnymi częściami elektronicznymi. Ale po kolei. Najpierw biblioteka. Pobierz ją z tej strony. Folder „TinyWireS” umieść w folderze libraries swojego Arduino IDE. U mnie to: C:\Program Files (x86)\arduino-1.0.4\libraries Żeby uzmysłowić możliwości tego rozwiązania, pokażę Ci dwa programy. Jeden do Attiny, drugi do Arduino Uno:

Najpierw Arduino będzie wysyłało numery Pinów, których stan ma być zmieniony. To spowoduje włączanie i wyłączanie kolejnych diod podłączonych do Attiny. Potem Arduino wyśle numer 30, który spowoduje, że Attiny samo włączy i wyłączy po kolei wszystkie diody bez udziału Arduino.

Ogólna konstrukcja kodu na Attiny powinna wyglądać mniej więcej tak:

#include "TinyWireS.h" 
void setup () {
  TinyWireS.begin(##numer_urządzenia##);
}
 void loop () {
 if (TinyWireS.available()){
    temp = TinyWireS.receive();
    /*część kodu rozpatrująca co z tym tempem zrobić */
   }
}

I konkretny przykład:

#include "TinyWireS.h" 
byte temp = 0; 
boolean diodki[16]; 
/* Zmienne, które będą potrzebne do zrealizowania przykładu:
 temp - do którego będzie zapisywany aktualnie przechwycony 
 bajt  
 diodki - tu są przetrzymywane informacje o stanie 
 poszczególnych pinów (1- wysoki, 0-niski) Include to 
 oczywiście nowa biblioteka, którą testujemy */
void setup () {
  TinyWireS.begin(0x16); // W tym miejscu inicjalizujemy 
     //TWI i podajemy adres urządzenia. Ja wybrałem taki.
  for (int i = 0; i < 16 ; i++){
    diodki[i] = 0;
    pinMode(i, OUTPUT);
    digitalWrite (i, LOW);
  } 
}

void loop () {

  if (TinyWireS.available()){
    temp = TinyWireS.receive();
/*Te dwie linijki powyżej to w zasadzie najważniejszy punkt 
całego przykładu. Funkcja TinyWireS.available() zwraca ilość
bajtów czekających w buforze. Czyli jeśli nic nie wysyłaliśmy 
do Attiny, to zwraca zero, a jeśli coś wysłaliśmy, to zwraca
ile tego czeka. Jeśli mamy oczekujące dane, to za pomocą 
funkcji TinyWireS.receive() "wyciągamy" dane z bufora i 
zapisujemy je w zmiennej temp. Każde użycie TinyWireS.receive(),
to zapisanie do zmiennej jednego bajtu. (tego, który przyszedł 
najwcześniej) */
// Wolne przejscie sterowane przez Arduino Master
      if (temp < 16){
      diodki[temp] = ~diodki[temp];
      digitalWrite(temp, diodki[temp]);
      delay(20);
    }
     else {
      // Szybkie przejscie sterowane tylko przez Attiny
       for (int i = 0; i < 16; i++){
         digitalWrite(i, ~diodki[i]);
         delay(300);
         digitalWrite(i, diodki[i]);
         delay(300);
       }
     }
   }
}

Potrzebujemy jeszcze kodu, który załadujemy do Arduino, żeby działało jako Master i wysyłało do Attiny odpowiednie komendy.

#include <Wire.h>

void setup()
{
  Wire.begin();
}

void loop()
{
  for (int i = 0; i < 16; i++){
    Wire.beginTransmission(0x16); 
   Wire.write(i);               
   Wire.endTransmission(); 
   delay(1000);
   Wire.beginTransmission(0x16);
   Wire.write(i); 
   Wire.endTransmission(); 
   delay(1000);
  }
  Wire.beginTransmission(0x16);
  Wire.write(30); 
  Wire.endTransmission(); 
  delay(10000);
}

No i oczywiście poglądowe rysunki jak to wszystko popodłączać:

Jako, że nie jestem mistrzem grafiki komputerowej, to krótkie objaśnienie. Diody wpinamy krótką nóżką w niebieską szynę „-” a długą nóżką do pinu Attiny. Rezystory dałem 2k2, ale mogą być różne. Nie chodzi o uzyskanie super jasności, tylko zrozumienie mechanizmów. Dodam również, że można podłaczyć SPI i I2C na raz.

Warto zwrócić tu uwagę na kilka rzeczy (Łatwiej będzie Ci je dostrzec, jeśli teraz siądziesz i zbudujesz zaprezentowany układ):

  •  Po pierwsze funkcja write() wcale nie wysyła danych. Ustawia je tylko w kolejce do wysłania! Dopiero funkcja endTransmission() powoduje wysłanie danych przez I2C.  Łatwo można to sprawdzić pozbywając się niektórych begin i end transmission.
  • Po drugie co się stanie, jeśli wyślemy informację do naszego Attiny w trakcie, gdy używamy na nim funkcji delay()? Warto to sprawdzić zmniejszając czas trwania ostatniego delay() w programie Arduino do wartości 1000; Wtedy zaobserwujemy najpierw powolne przejście, potem szybkie, a potem coś nietypowego. Pierwsze 4 diody zamrugają tylko na chwilę a pozostałe będą normalnie robiły wolne przejście. To znaczy, że dane musiały być odbierane mimo tego, że na naszym Attiny trwał delay.  Gromadziły się one w buforze (o rozmiarze 32 bajtów) a następnie zostały uwolnione wszystkie na raz (stąd ta błyskawiczna falka czterech pierwszych diod).
  • Po trzecie nie można podłączać diod pod piny należące do I2C (choć jak pokazuje przykład można bez większych konsekwencji chwilowo zmienić ich stan)
  • Rozmiar pliku ładowanego do Attiny (Ponad 1500 Bajtów!! na szczęście da się to zmniejszyć, ale o tym w przyszłości)
  • Adres I2C urządzenia slave jest 7-mio bitowy. To znaczy, że maksymalny numer, jaki można tam wprowadzić, to 127 (0x7F)
  • Jeśli podłączasz więcej niż jedno urządzenie I2C, upewnij się, że nie będzie dwóch takich samych adresów.
  • W wypadku niektórych urządzeń może wystąpić konieczność dodania rezystorów pull-up (lub gdy podłączasz więcej urządzeń) do linii SDA i SCL. Polega to na tym, że umieszczasz po jednym rezystorze 4k7 lub większym pomiędzy +5V a liniami SDA i SCL.

Powinno teraz paść ważne pytanie: Po co mi to?:

  • Mogę przecież podłączyć diody bezpośrednio do pinów Arduino! –  Zgadzam się, ale co, jeśli będziesz potrzebował podłączyć coś jeszcze? Arduino nie ma wbrew pozorom aż tak wielu pinów.
  • Mogę też zastosować PCF8574 i poprzez TWI sterować ośmioma portami. Dwa takie układy i mam odpowiednik Attiny. Jak najbardziej! Jednak cały kod odpowiadający za mryganie musi być na Arduino. Wszystkie te delay() opóźniają wykonywanie całego kodu, a nie tylko mrygania.
  • Taki sam efekt można uzyskać używając Multipleksera np CD74HC4067 Zgadzam się! Powiem więcej, konstrukcja z jego użyciem będzie niezastąpiona, jeśli będziesz chciał skorzystać z dobrodziejstw pinów analogowych na Twoim Arduino. Jednak ten układ działa przez SPI, który potrzebuje 4-ech pinów sterujących. (No i wciąż cały kod jest trzymany na Arduino)

Moim celem nie było dowiedzenie, że to rozwiązanie jest najlepsze. Po prostu sprawdza się lepiej w niektórych przypadkach i na nich właśnie się skupiam. Nie ukrywam, że często szybciej i łatwiej jest zastosować prosty układ multipleksera, czy portu.

Podsumowując kiedy warto sięgnąć po Attiny?:

  • Jeśli chcemy utworzyć całą sekwencję „mrygnięć” a zależy nam na czasie wykonania głównego programu na Arduino (jednym bajtem wysłanym przez I2C włączamy całą sekwencję) Np. Animacja zajętości.
  • Z powodu chwilowego braku dostępności innych części (ja tak zacząłem)
  • Kiedy chcemy się rozwijać i testować nowe możliwości i rozwiązania. (czyt. kiedy nuda dopada)

Przy realizacji tego przykładu warto zaopatrzyć się w Starter Kit. Ma on diody, rezystory, kabelki i wiele innych przydatnych elementów, które w przyszłości wykorzystamy.