Pixy (2) … jak se jednoduše domluvit

V minulém dílu jsme seznámili s tím, jak naučit kameru Pixy rozeznávat určité objekty podle barvy, ale vše se odehrávalo jen v okně programu PixyMon. Nyní připojíme výstup z kamery k mikrokontroléru.

Pixy má na zadní straně desetipinový konektor, na němž jsou soustředěna rozhraní, jimiž může komunikovat s řídícím mikrokontrolérem. Všechny výstupy jsou v úrovních 3,3 V, všechny vstupy jsou také v úrovních 3,3 V, ale tolerují logické úrovně 5 V. Toto je nákres polohy pinů převzatý z internetové dokumentace:

pix09

pix10

I když voleb se zdá méně, můžeme si vybrat z následujících možností:

1/ digitální (jednobitový) výstup vázaný na osu x (logika 3,3 V)
2/ analogový výstup vázaný na osu x (0 - 3,3 V)
3/ digitální (jednobitový) výstup vázaný na osu y (logika 3,3 V)
4/ analogový výstup výzaný na osu y (0 - 3,3 V)
5/ UART - standardní sériový port (rychlost přenosu můžeme zadat)
6/ I2C - adresu portu můžeme zadat
7/ LEGO I2C - speciální případ I2C - spolupracuje s řídící jednotkou LEGO
8/ SPI - obecné propojení SPI a konfigurací slave portu
9/ Arduino ICSP SPI - přímé propojení s Arduinem kabelem (default)

Důležité upozornění! Je-li kamera Pixy připojena USB kabelem k počítači a je nastaveno zobrazování z kamery v režimu mixovaného obrazu (snímek s vyznačenými objekty) nebo jen obrazu, potom veškerá komunikace s mikrokontrolérem je vypnutá a  většinou „zamrzne“ v posledním stavu. Lze mít kameru napájenou z USB nebo připojenou k počítači, lze i zobrazovat, co vidí (to se velmi hodí při kontrole toho, jaká data kamera odesílá), ale výhradně v režimu „default program“ čili s černým pozadím a barevnými detekovanými popsanými obdélníky. Je-li Pixy odpojena od počítače, problém samozřejmě nastat nemůže.


Začneme tím nejjednodušším. Chceme pouze informaci o tom, zda naučený předmět (nebo kterýkoli z více naučených předmětů) je v obraze, nebo není. K tomu nám stačí připojit se kromě země (pin 10) na logický výstup (pin 1) a nastavit režim 1 nebo 3. V daném případě je jedno, jestli to bude režim 1 nebo 3 (vazba na X nebo Y souřadnici). Jestliže je přemět v obraze, je na výstupu úroveň H (3,3 V), jestliže není, úroveň L.

Tento režim v podstatě ani nepotřebuje mikrokontrolér, může třeba zastavit dopravník, když se na něm objeví červený předmět, může počítat průjezdy zeleného auta na autodráze (jiné barvy ignoruje) a podobně.

Máme možnost do jisté míry ovlivnit detekci předmětů dané barvy nebo vzdálenost, na kterou se předmět detekuje, protože můžeme nastavit parametr „min blok area“ tedy počet vyhodnocovaných polí v obraze, které musí předmět mít, aby se detekoval. Tím můžeme vyloučit buď předměty malé nebo vzdálené.

pix11


V dalším kroku chceme nejen vědět, jestli některý z naučených předmětů je v obraze, ale také kde je. Musíme si ale vybrat buď jen informaci o horizontální poloze (vpravo / vlevo … režim 2) nebo vertikální poloze (nahoře / dole … režim 4). Obě současně mít bohužel nemůžeme. I přes tato omezení lze výstup dobře využít.

Připojíme se kromě země (pin 10) na výstup DA převodníku, to je pin 3. Na něm najdeme napětí v rozsahu 0 až 3,3 V v režimu 2 úměrné poloze předmětu v ose X (analogicky pro režim 4 a osu Y). Současně můžeme využívat i logický signál na pinu 1. Když není předmět v zorném poli, je na výstupu napětí 0 V, když je vlevo, napětí <1.65 V, uprostřed 1,65 V, vpravo > 1.65 V. Vyhodnocuje se střed detekovaného objektu a pokud kamera detekuje více objektů současně, pak se bere ten, který je v obraze největší.

Tento způsob komunikace zvládne jakýkoli mikrokontrolér, který má AD převodník, na rychlosti a výpočetním výkonu nesejde. Představme si situaci, kdy na ploše stojí několik stejných válečků, které má robot posbírat. Vyhodnotíme signál podle osy X (průměru válečku), kamera uvidí vždy ten největší (tj. nejbližší). Podle umístění se může robot navigovat na váleček.

Nebo jiná úloha. Trasa robota je vyznačena barevnou přerušovanou čarou, kamera shlíží šikmo dolů tak, aby již neviděla přední část robota (není problém zorné pole zaclonit nebo případně i vyměnit optiku kamery, má standardní uchycení). Kamera vidí vždy jen nejbližší (největší) tečku z čáry a vyhodnotí směr k ní, jakmile tečka vyjede ze zorného pole (robot je příliš blízko), uvidí další nejbližší tečku. Tím máme kamerový „line folower“ který reaguje jen na čáru své barvy (nebo několika svých barev).


Dostáváme se ke skutečnému využití možností Pixy, nejprve s komunikací přes UART (standardní sériová komunikace). Tu má prakticky každý mikrokontrolér. Rychlost komunikace lze v široké míře nastavit. Může ovšem nastat problém s tím, že objem přenášených dat se prostě nedá při nízké přenosové rychlosti sériovou komunikací „protlačit“, zejména když si uvědomíme, že kamera generuje 50 snímků za sekundu a detekovaných objektů mohou být desítky až stovky. Musíme tedy ohlídat, aby možnosti přenosu dat korespondovaly se scénou, kterou Pixy „vidí“. Minimální použitelná rychlost je 9600 Bd, při ní se v každém snímku stíhá spolehlivě s rezervou jeden předmět.

Využijeme kromě pinu 10 (zem) jen pin 4 (UART Tx), nastavíme režim 4 (standardní sériový port) a rychlost komunikace 38400 Bd. Přenos je 8-bitový, bez parity, 1 stopbit. Maximální rychlost přenosu z Pixy je 230 kBd. V případě, že budeme chtít i dávat povely kameře, využijeme také pin 1 (v tomto režimu má význam UART Rx).

Začneme detekcí jednoho objektu. Když není v zorném poli, vysílají se jen synchronizační nuly. Když je, pošle se zpráva obsahující:

1/ 2 byty synchronizační hlavičky - 0xaa55 pro jednobarevné objekty
                                  - 0xaa56 pro vícebarevné kódy
2/ 2 byty kontrolního součtu dat podle bodů 3 až 8
3/ 2 byty identifikace objektu nebo barevného kódu (signature, CC)
4/ 2 byty X souřadnice středu objektu
5/ 2 byty Y souřadnice středu objektu
6/ 2 byty šířky objektu (ve směru osy X)
7/ 2 byty výšky objektu (ve směru osy Y)
8/ 2 byty úhlu natočení objektu (jen pro vícebarevné kódy)

Ze šestnáctibitových slov se vždy vysílá nejdřív nižší, potom vyšší byte. Každý snímek je oddělen vložením samostatné hlavičky 0xaa55, takže snímek vždy začíná sekvencí „55 aa 55 aa“ nebo „55 aa 56 aa“. Má-li mikrokontrolér programovou obsluhu přijetí sériového signálu, může být problém stihnout příjem dat, zejména je-li objektů více. Výhodnější je HW podpora příjmu a dostatečně velký buffer pro data. Na obrázku je signál z kamery na osciloskopu s dekódováním dat při dvou jednobarevných objektech.

DS2_QuickPrint5Jako ukázku si uvedeme program pro Arduino Uno, který nejprve bez jakéhokoli zdržování a vyhodnocení (dokonce se nesynchronizuje ani na hlavičku snímku) načte do pole data ze vstupu a potom v nich vyhledá a vypíše údaje o nalezených objektech. Arduino může signál zpracovat mnohem efektivněji, ale tento způsob nevyužívá žádné specializované kmihovny a dá se aplikovat na jakýkoli mikrokontrolér, který alespoň stihne seriový signál přečíst a uložit. Program je kvůli zjednodušení nastaven jen na práci s jednobarevnými objekty a jejich počet je omezen na 5 (s rezervou).

 // ukazka cteni udaju ze signalu kamery Pixy - omezeno na 5 jednobarevných objektu
#include <SoftwareSerial.h>                    // knihovna pro seriovou komunikaci Pixy/Arduino
SoftwareSerial mySerial(2, 3); // RX, TX       // Arduino pin 2 = Rx, pin 3 = Tx

uint8_t b[200];                                // pole pro ulozeni ctenych dat
int p;                                         // poloha v poli (funkcne ukazatel)
uint16_t kontrola1, kontrola2;                 // kontrolní soucet z dat a pocitany
uint16_t sig, stred_x, stred_y, sirka, vyska;  // parametry objektu

void setup() {
  Serial.begin(115200);                        // inicializace komunikace s PC
  mySerial.begin(38400);                       // inicializace komunikace s Pixy
}

void loop() {
  while (mySerial.available() > 0) {           // vyprazdneni bufferu
    b[0] = (mySerial.read());
  }
  for (int i = 0; i < 200; i++)                // cyklus cteni do pole bez synchronizace
  { while (mySerial.available() < 1) {}        // cekani na byte v bufferu
    b[i] = (mySerial.read());                  // ulozeni hodnoty do pole
  }
  for (int i = 3; i < 200; i++) {              // hledani hlavicky snimku
    if ((b[i - 3] == 0x55) && (b[i - 2] == 0xaa) && (b[i - 1] == 0x55) && (b[i] == 0xaa)) {
      p = i - 1;
      break;
    }
  }                                            // p ukazuje za hlavicku snimku na hlavicku objektu
  while (true) {
    if ((b[p] == 0x55) && (b[p + 1] == 0xaa) && !((b[p + 2] == 0x55) && (b[p + 3] == 0xaa))) {
      cteni();                                 // dekoduje objekty v celem snimku
    } else {
      break;
    }
  }
  Serial.println();                            // volny radek mezi snimky
  delay(5000);                                 // dela se vypis jednou za cca 5 sekund
}

void cteni () {                                // p ukazuje na zacatek hlavicky objektu
  p++; p++;
  kontrola1 = b[p + 1] * 256 + b[p];           // nacteni kontrolniho souctu
  p++; p++;
  sig = b[p + 1] * 256 + b[p];                 // nacteni signatury objektu
  p++; p++;
  stred_x = b[p + 1] * 256 + b[p];             // nacteni x polohy stredu
  p++; p++;
  stred_y = b[p + 1] * 256 + b[p];             // nacteni y polohy stredu
  p++; p++;
  sirka = b[p + 1] * 256 + b[p];               // nacteni sirky objektu
  p++; p++;
  vyska = b[p + 1] * 256 + b[p];               // nacteni vysky objektu
  p++; p++;                                    // p ukazuje na byte za koncem objektu
  kontrola2 = sig + stred_x + stred_y + sirka + vyska;  // vypocet kontrolniho souctu
  if (kontrola1 == kontrola2) {
    Serial.print(" sig = ");
    vypis(sig);                                // vypis signatury
    Serial.print("     stred x = ");
    vypis(stred_x);                            // vypis polohy objektu
    Serial.print("  y = ");
    vypis(stred_y);
    Serial.print("     sirka = ");
    vypis(sirka);                              // vypis sirky a vysky objektu
    Serial.print("     vyska = ");
    vypis(vyska);
    Serial.println();
  } else {
    Serial.println("Kontrolni soucet neodpovida!");
  }
}

void vypis (uint16_t cislo) {                  // vypis jednoho cisla s formatovanim
  if (cislo < 99)  Serial.print(" ");
  if (cislo < 9)  Serial.print(" ");
  Serial.print(cislo);
}

Scéna pro vyzkoušení obsahovala 5 objektů, zelené víčko od lahve (1), dva žluté papírky různých rozměrů (2), červené víčko od lahve (3) a kousek modrého plastu (4). Takhle to vypadalo na počítači s vyznačením detekovaných objektů.

pix12A takhle to odpovídá datům, které jsou předávána z Pixy do mikrokontroléru.

pix13Výpis v sériovém monitoru obsahuje pět objektů oddělených prázdným řádkem, u každého je uvedena signatura, poloha a rozměry. Pořadí, v němž se data z kamery předávají, jsou v první řadě podle signatury, a pokud je více předmětů stejně zařazených, pak se postupuje od největšího k nejmenšímu.

pix14Zdůrazňuji znovu, že uvedený způsob vyhodnocení není optimální pro Arduino, ale využívá jen ty prostředky, které lze snadno převést i na méně výkonné mikrokontroléry a do jazyků na nižší úrovni.


Sériová komunikace nemusí sloužit jen k přenosu informací z Pixy do mikrokontroléru, ale také v obráceném směru k řízení některých funkcí kamery. Například když pošleme do kamery povel 0xfe00 a po něm jeden byte hodnoty (například sekvenci 0x00, 0xfe, 0xf0), bere se to jako nastavení jasu kamery. V našem případě je vstupem pin 1 kamery a výstupem pin 3 Arduina. Opět platí, že nelze přenášet povely do kamery pokud je právě přenášen obraz z kamery přes USB do počítače (standardní zobrazení detekovaných objektů na černém pozadí nevadí)

Povel 0xfd00 ovládá barevnou LED, následují po jednom bytu intenzity světla pro složky RGB. Další program pro ukázku rozbliká LED střídavě základními barvami.

// ukazka ovladani RGB LED pres seriove rozhrani
#include <SoftwareSerial.h>                    // knihovna pro seriovou komunikaci Pixy/Arduino
SoftwareSerial mySerial(2, 3); // RX, TX       // Arduino pin 2 = Rx, pin 3 = Tx

void setup() {
  mySerial.begin(38400);                       // inicializace komunikace s Pixy
}

void loop() {
  mySerial.write(byte(0x00));                  // cervena barva
  mySerial.write(byte(0xfd));
  mySerial.write(byte(0xff));
  mySerial.write(byte(0x00));
  mySerial.write(byte(0x00));
  delay(1000);
  mySerial.write(byte(0x00));                  // yelena barva
  mySerial.write(byte(0xfd));
  mySerial.write(byte(0x00));
  mySerial.write(byte(0xff));
  mySerial.write(byte(0x00));
  delay(1000);
  mySerial.write(byte(0x00));                  // modra barva
  mySerial.write(byte(0xfd));
  mySerial.write(byte(0x00));
  mySerial.write(byte(0x00));
  mySerial.write(byte(0xff));
  delay(1000);
}

Příště se podíváme na připojení kamery přes další rozhraní a využití knihoven pro Arduino.