Arduino a displeje I.
Ve článcích o displejích si ukážeme různé způsoby grafického výstupu z Arduina. V prvním dílu si předvedeme, jak používat maticové LED displeje. Také se zaměříme na platformu Rainbowduino a ukážeme si i pár příkladů využití.
Obsah článku:
Úvod
U velké části projektů se dostaneme do situace, kdy se nám hodí mít možnost něco zobrazit. Ať už jde o vykreslování grafu, zobrazení řetězce, nebo celé grafické rozhraní programované aplikace či hry. Nepočítáme-li segmentové displeje, konstrukčně nejjednodušší jsou maticové LED displeje. Není to nic jiného, než vhodně rozmístěné a spojené LED diody.
Maticové LED displeje
anglicky matrix display
Tento typ displejů využívá k zobrazování klasické LED diody. Jedna dioda slouží většinou jako jeden pixel výsledného obrazu. Slovo matice v názvu napovídá, že budou srovnané do mřížky. V této mřížce mají vždy jeden vývod (anoda/katoda) společný pro řádek a druhý pro sloupec. Můžeme se také setkat s displeji, které mají více než jednu barvu. Potom může mít každá z LED diod i tři nebo více vývodů (většinou společná anoda, nebo katoda a pro každou barvu jeden zbývající vývod). Do matice jsou vývody diod spojeny stejným způsobem jako u jednobarevných, jenom buďto řádky, nebo sloupce mají více vývodů (pro každou barvu jeden). Také se můžeme setkat s různou velikostí displejů. Časté jsou k vidění velikosti 5×7, 8×8 a další.
Teorie řízení
Schéma jednobarevného a trojbarevného maticového displeje. Rozložení LED je stejné, jako při pohledu na displej zepředu.
V tomto článku budeme používat tento RGB maticový displej, jehož schéma můžete vidět na obrázku vpravo. Na začátek si ukážeme, jak pracovat pouze s jednou barvou a poté si vysvětlíme princip práce s celým RGB spektrem. Ze schématu je jasné, že náš displej má společnou anodu. Všechny anody jsou spojené v řádcích. Sloupce tedy připadají na katody. Jelikož je náš displej RGB, má každý sloupec tři vodiče pro ovládání barev. V první části příkladu budeme pracovat pouze s jednou barvou (například zelenou). Na začátek se musíme podívat ještě na to, jak jsou piny rozmístěny ve skutečnosti. Na zadní straně našeho displeje nalezneme 32 pinů (8 pro každou barvu + 8 pro společnou anodu). Orientačním prvkem je číslo jedna natištěné u jednoho z rohových pinů.
Na obrázku můžete vidět popis pinů podle datasheetu.
Než se pustíme do programování, musíme si trošku objasnit princip řízení. Pokud chceme rozsvítit LED na pozici [1,1], musíme připojit pin 17 na + a 1 na GND. Pokud bychom ale chtěli rozsvítit zároveň bod [1,1] a [2,2], je situace trošku komplikovanější. Kdybychom totiž připojili 17 a 18 na + a 1 i 2 na GND současně, rozsvítil by se nám celý čtverec ([1,1], [2,1], [1,2], [2,2]). Z tohoto důvodu probíhá ovládání tak, že se vždy pracuje jen s jednou řadou (ať už jde o řádek, nebo o sloupec), rozsvítí se všechny body, které se mají zobrazit, poté se napájení řady vypne a to samé se opakuje se všemi dalšími řadami. Pokud toto „překreslování“ probíhá dostatečně rychle, lidské oko si ničeho nevšimne (kvůli jeho setrvačnosti). Anody jsou vypnuté, pokud je na jejich pinu stav LOW, u katod je tomu naopak – vypnuté jsou při stavu HIGH.
Zapojení
Nyní už se můžeme pustit do spojení Arduina s displejem. V této části pracuji s Arduinem Leonardo. Budeme používat zelenou barvu RGB displeje. Také bychom měli použít 330 ohm resistor na řádky nebo sloupce (jedno z nich). Jaký pin zapojíme kam je pouze na nás, jen tomu musíme přizpůsobit kód. My použijeme následující zapojení.
Číslo sloupce | Pin Arduina | Pin displeje | Číslo řádku | Pin Arduina | Pin displeje |
1 | 2 | 28 | 1 | A3 | 17 |
2 | 3 | 27 | 2 | A2 | 18 |
3 | 4 | 26 | 3 | A1 | 19 |
4 | 5 | 25 | 4 | A0 | 20 |
5 | 6 | 24 | 5 | 10 | 29 |
6 | 7 | 23 | 6 | 11 | 30 |
7 | 8 | 22 | 7 | 12 | 31 |
8 | 9 | 21 | 8 | 13 | 32 |
Programování
Začneme tím, že si do prvních dvou polí (radky, sloupce) vypíšeme piny, na které jsou připojeny. Mějme na paměti, že se zde posunou souřadnice pinů kvůli indexování v poli od nuly. Bodu [1,1] na displeji budou tedy odpovídat sloupce[0] a radky[0]. Také si můžeme všimnout dvojrozměrného pole obrazek[][], ve kterém jsou informace o požadovaném obrázku. Pokud má vybraná LED svítit, bude na jí odpovídajícím políčku 1. Princip by měl být zřejmý z komentářů v kódu.
byte sloupce[] = {2,3,4,5,6,7,8,9}; byte radky[] = {A3,A2,A1,A0,10,11,12,13}; //dvojrozměrné pole pro obrázek byte obrazek[8][8] = {{1,1,1,1,1,1,1,1}, {1,0,0,0,0,0,0,1}, {1,0,1,1,1,1,0,1}, {1,0,1,0,0,1,0,1}, {1,0,1,0,0,1,0,1}, {1,0,1,1,1,1,0,1}, {1,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1}}; void setup(){ for(int i = 0; i < 8; i++){ //nastavíme piny pinMode(sloupce[i], OUTPUT); pinMode(radky[i], OUTPUT); //zajistíme vypnutí displeje digitalWrite(sloupce[i], HIGH); digitalWrite(radky[i], LOW); } } void loop(){ for(int i = 0; i < 8; i++){ //zapneme řádek i digitalWrite(radky[i], HIGH); //dále pracujeme s jednotlivými sloupci for(int j = 0; j < 8; j++){ //pokud je ve vybraném políčku 1, rozsvítí se LED if(obrazek[i][j] == 1){ digitalWrite(sloupce[j], LOW); } } delay(1); //chvíli počkáme, aby byl obraz dostatečně jasný //vypneme všechny sloupce for(int j = 0; j < 8; j++){ digitalWrite(sloupce[j], HIGH); } //vypneme řádek i digitalWrite(radky[i], LOW); } }
A výsledek?
Tímto jsme si představili práci s libovolným jednobarevným displejem. Co když ale chceme použít všechny dostupné barvy RGB displeje?
RGB teoreticky
Princip řízení zůstává stále stejný. Teď asi zklamu majitele menších desek. K ovládání totiž potřebujeme celkem 32 pinů. Takový počet ale najdeme jenom u větších desek. Menší desky to zvládnou pouze s použitím nějakého přídavného hardware (shift registr, řadiče…). Pokud bychom k ovládání používali jenom funkce digitalWrite(), „namícháme“ celkem 7 různých barev (dá se říci, že osm, když započítáme i stav, kdy LED nesvítí vůbec). Při použití funkce analogWrite() je teoretické maximum 2563 barev, což je úctyhodných 16777216 možností. Musíme ale brát v úvahu možné nepřesnosti.
Poznámka: Také existuje celá řada displejů s řadičem, který umožňuje ovládání po sériové lince. Jedním z nich je například tento.
Rainbowduino
S tématem maticových displejů velmi úzce souvisí Rainbowduino. Jedná se o klon Arduina, který je přímo určen k ovládání LED diod. Může ovládat buďto maticový displej 8×8, nebo dokonce RGB kostku 4x4x4 (oboje se společnou anodou). My si je předvedeme se stejným displejem jako v předchozí části. V tomto případě nepotřebujeme žádný jiný hardware, než Rainbowduino a displej. Nastavíme přepínač na desce do stavu USB a poté najdeme zdířku s označením 1 BLUE. Displej zasuneme do zdířky tak, aby byl v této zdířce zasunut pin displeje s označením 1.
Funkce
V první řadě musíme Arduino IDE naučit s deskou Rainbowduino pracovat. To uděláme jednoduše – stáhneme knihovnu (pro IDE 1.0+) ze stránek výrobce a umístíme ji do složky Libraries. Do kódu ji poté vložíme známým příkazem #include <Rainbowduino.h>. Abychom nemuseli dělat vše manuálně, nabízí se nám několik funkcí.
Název | Popis |
Rb.init() | Inicializuje driver pro Rainbowduino. Většinou umístěna v setup(). |
Rb.setPixelXY(x,y,r,g,b) | Nastaví barvu pixelu určeného souřadnicemi. Pixel může mít souřadnice od 0 do 7. Když si desku natočíme tak, aby USB bylo dole, je bod [0,0] v levém spodním rohu. |
Rb.blankDisplay() | Vypne všechny LED diody. |
Rb.drawChar(znak, x, y, RGBbarva) | Vypíše na displej znak na daných souřadnicích. Barva musí být zadána ve 32 bitovém formátu. To může být například 0xFF00FF, což je číslo v hexadecimální soustavě, kdy první dva znaky za 0x udávají hodnotu pro červenou, druhé dva pro zelenou a poslední dva pro modrou. Každý znak zabere obvykle pole 6×8, kdy souřadnice jsou udány pro pravý horní roh. V šířce znaku je započítána i jednosloupcová mezera před každým z nich. Pozor! Tato funkce má otočenou soustavu souřadnic o 90° proti směru hodinových ručiček. |
Rb.drawCircle(x,y,pol,RGBbarva) | Nakreslí „kruh“ se středem v [x,y] o poloměru pol. Barva se zde zadává stejně jako u funkce pro zobrazení znaku. |
Rb.drawLine(x0, y0, x1, y1, RGBbarva) | Vykreslí úsečku zvolené barvy z bodu [x0,y0] do bodu [x1, y1] |
Rb.drawRectangle(x, y, vyska, sirka, RGBbarva) | Zobrazí obdélník (nebo čtverec), kde bod [x,y] je pravý horní roh. |
Rb.fillRectangle(x, y, vyska, sirka, RGBbarva) | Funguje stejně jako předchozí funkce, pouze bude výsledný tvar vyplněný. |
Po připojení k PC spustíme IDE a v menu Boards nastavíme typ desky na Arduino Duemilanove w/ ATmega 328.
Jako ukázku použijeme příklad, který zobrazí znak zaslaný po sériové lince.
#include <Rainbowduino.h> char a; void setup(){ Rb.init(); Serial.begin(9600); } void loop(){ while(Serial.available() > 0){ a = Serial.read(); Rb.blankDisplay(); Rb.drawChar(a,0,1,random(0xFFFFFF)); delay(500); } }
Možná jste si také všimli, že se na jednom z okrajů desky nachází několik neosazených pinů (konkrétně D2, D3, A1, A2, A3, A6 a A7). To jsou plnohodnotné piny, které můžeme používat pomocí nám dobře známých funkcí. Další užitečnou věcí jsou piny určené k propojování více desek Rainbowduino k sobě. Z jejich popisků zjistíme, že se jedná o napájení, piny pro sériovou komunikaci a také piny i2c sběrnice (SDA, SCL).
Propojujeme Rainbowduina
I když by bylo možné komunikovat mezi deskami po sériové lince, my použijeme i2c sběrnici, se kterou jsme se poprvé setkali v minulém článku. Pro náš účel je vhodnější, protože umožňuje přímé adresování. Tentokrát nepotřebujeme jiný hardware, než dvě (nebo více) desek. Princip si ukážeme na příkladu. V něm propojíme dvě desky Rainbowduino. Jedna z nich (master) bude přijímat zprávy po sériové lince a rozsvítí diodu s danými souřadnicemi a barvou. Displeje si umístíme tak, aby master byl dole a slave nad ním. Osa x bude mít tedy maximální hodnotu 7 a osa y 15. Adresu slave zařízení si nastavíme například na 100. Master bude po sériové lince čekat příkaz ve tvaru: reset(0/1):x:y:r:g:b, tedy například: 0:1:0:255:0:0. V příkladu jsou použity uživatelem definované funkce clearDisplay(adresa) a setPixel(adresa). První z nich odešle příkaz ke zhasnutí všech diod na displeji dané adresy a druhá na displeji dané adresy nastaví barvu a pozici pixelu.
Kód pro master zařízení
//master #include <Wire.h> #include <Rainbowduino.h> byte c,x,y,r,g,b; void setup(){ Rb.init(); Serial.begin(9600); Wire.begin(); } void loop(){ while(Serial.available() > 0){ c = Serial.parseInt(); //mají se zhasnout dosud svítící LED? x = Serial.parseInt(); y = Serial.parseInt(); r = Serial.parseInt(); g = Serial.parseInt(); b = Serial.parseInt(); if(x >= 8 || y >= 16 || r >= 256 || g >= 256 || b >= 256){ Serial.println("Chybna data, nebo neplatny rozsah."); } else{ if(c == 1){ Rb.blankDisplay(); clearDisplay(100); } if(y < 8){ //toto se zobrazí na master zařízení Rb.setPixelXY(x,y,r,g,b); } else if(y >= 8){ //toto se musí před zobrazením odeslat do slave zařízení //před odesláním ještě převedeme souřadnice na rozsah 0 - 7 y = y%8; setPixel(100); } } } } void clearDisplay(int addr){ Wire.beginTransmission(addr); Wire.write(1); //pouze zhasne svítící LED Wire.write(0); Wire.write(0); Wire.write(0); Wire.write(0); Wire.write(0); Wire.endTransmission(); } void setPixel(int addr){ Wire.beginTransmission(addr); Wire.write(c); Wire.write(x); Wire.write(y); Wire.write(r); Wire.write(g); Wire.write(b); Wire.endTransmission(); }
Kód pro slave zařízení
//slave #include <Wire.h> #include <Rainbowduino.h> byte c,x,y,r,g,b; void setup(){ Rb.init(); Wire.begin(100); Wire.onReceive(priPrijmu); } void loop(){ if(x >= 8 || y >= 16 || r >= 256 || g >= 256 || b >= 256){ Serial.println("Chybna data, nebo neplatny rozsah."); } else{ if(c == 1){ Rb.blankDisplay(); } if(y < 8){ Rb.setPixelXY(x,y,r,g,b); } } } void priPrijmu(int c){ while(Wire.available() > 0){ c = Wire.read(); x = Wire.read(); y = Wire.read(); r = Wire.read(); g = Wire.read(); b = Wire.read(); } }
Zobrazení obrázku z PC
V této části propojíme Arduino Leonardo se čtyřmi displeji Rainbowduino. Na nich si poté zobrazíme obrázek, který odešleme z PC. Zpracování obrázku provedeme v prostředí Processing, kde ho rozkouzkujeme na jednotlivé pixely a informace o nich odešleme po sériové lince do Arduina Leonardo. To bude mít za úkol informace zpracovat a rozeslat je do jednotlivých displejů po i2c sběrnici. Na obrázku vidíte rozmístění displejů a jejich adresy.
V tomto příkladu budeme potřebovat ještě několik vodičů na spojení jednotlivých komponent. Nabízí se nám více způsobů propojení – první z nich je naznačený červenými liniemi. Leonardo je zde připojeno k jednomu sloupci a ten je připojen k dalšímu. Ve druhém způsobu (modře) jsou s Arduinem spojeny oba sloupce. Tyto dva způsoby jsou funkčně ekvivalentní, ale praktičtější je ten druhý. Asi nejjednodušší způsob je si propojovací vodiče vyrobit. Mě se osvědčil plochý vícežilový kabel se zdířkami na jedné straně a piny na druhé. Piny i dutinky jsou k dostání zde. Pro i2c komunikaci potřebujeme čtyři vodiče – SDA, SCL, +5V a GND. Výsledek může vypadat třeba takto – takovéto propojky budeme potřebovat dvě.
Teď už se můžeme pustit do programu pro Processing. Ten bude mít za úkol pomocí funkcí pro práci s obrázkem načíst vybraný obrázek a rozebrat ho na jednotlivé pixely. Z nich zjistit jejich barvu a souřadnice a vše odeslat po sériové lince. Odesílaná data mají stejnou podobu jako v minulém příkladu. Obrázek musí být 16×16 pixelů a může to být třeba obyčejný smajlík:
Princip jednotlivých částí je popsán v kódu.
import processing.serial.*; PImage obr; int r,g,b,x,y, last; Serial port; void setup(){ if(Serial.list().length > 0){ println(Serial.list()); port = new Serial(this, Serial.list()[0], 19200); obr = loadImage("7.jpg"); //načte obrázek obr.loadPixels(); //vytvoří jednorozměrné pole pixelů //pole obr.pixels obsahuje jednotlivé pixely obrázku if(obr.pixels.length != 256){ //test rozměrů size(200,20); text("Obrázek není 16x16px.",10, height/2); } else{ while(millis() - last < 100){ //chvilku počká } size(16,16); last = millis(); for(int i = 0; i < obr.pixels.length; i++){ //tyto tři funkce (red()...) načtou informace o jednotlivých barvách pixelu r = int(red(obr.pixels[i])); g = int(green(obr.pixels[i])); b = int(blue(obr.pixels[i])); //z indexu pixelu v poli určíme souřadnice x = i % 16; //sloupec y = (i - i%16)/16; //řádek y = 15 - y; //otočení osy y while(millis() - last < 5){ //chvilku počká } port.write(0+":"+x+":"+y+":"+r+":"+g+":"+b+":"); println(x+","+y+","+r+","+g+","+b); last=millis(); } fill(0); image(obr,0,0); } } else{ println("Nenalezeno žádné zařízení"); } } void draw(){ }
Data nám tedy už z PC chodí. Teď musíme zajistit, aby byla správně zpracována a rozeslána – to má u nás na starost Arduino Leonardo. Jistě si povšimnete, že je princip skoro stejný, jako u master zařízení v minulém příkladu.
//master - Leonardo #include <Wire.h> byte c,x,y,r,g,b; void setup(){ Serial.begin(19200); Wire.begin(); } void loop(){ while(Serial.available() > 0){ c = Serial.parseInt(); //mají se zhasnout dosud svítící LED? x = Serial.parseInt(); y = Serial.parseInt(); r = Serial.parseInt(); g = Serial.parseInt(); b = Serial.parseInt(); if(x >= 16 || y >= 16 || r >= 256 || g >= 256 || b >= 256){ Serial.println("Chybna data, nebo neplatny rozsah."); } else{ if(c == 1){ clearDisplay(100); clearDisplay(101); clearDisplay(102); clearDisplay(103); } if(x < 8 && y < 8){ //levý spodní setPixel(100); } else if(x < 8 && y >= 8){ //levý horní y = y%8; setPixel(101); } else if(x >= 8 && y < 8){ //pravý spodní x = x%8; setPixel(102); } else if(x >= 8 && y >= 8){ //pravý horní x = x%8; y = y%8; setPixel(103); } } } } void clearDisplay(int addr){ Wire.beginTransmission(addr); Wire.write(1); //pouze zhasne svítící LED Wire.write(0); Wire.write(0); Wire.write(0); Wire.write(0); Wire.write(0); Wire.endTransmission(); } void setPixel(int addr){ Wire.beginTransmission(addr); Wire.write(c); Wire.write(x); Wire.write(y); Wire.write(r); Wire.write(g); Wire.write(b); Wire.endTransmission(); }
Poslední a nejdůležitější částí jsou displeje. Kód všech bude téměř totožný – bude se lišit pouze v i2c adrese.
//slave - Rainbowduino #include <Wire.h> #include <Rainbowduino.h> byte c,x,y,r,g,b; void setup(){ Rb.init(); //tuto adresu musíme nastavit zvlášť pro každou desku Wire.begin(100); Wire.onReceive(priPrijmu); } void loop(){ } void priPrijmu(int c){ while(Wire.available() > 0){ c = Wire.read(); x = Wire.read(); y = Wire.read(); r = Wire.read(); g = Wire.read(); b = Wire.read(); } if(c == 1){ Rb.blankDisplay(); } if(x < 8 && y < 8){ Rb.setPixelXY(x,y,r,g,b); } }
A výsledek by mohl vypadat třeba takto.
V případě jakýchkoliv dotazů či nejasností se na mě neváhejte obrátit v komentářích.
- Sledovač slunce s Arduinem - 23.3.2022
- Programovatelný kytarový pedál s Arduinem - 26.2.2020
- Arduino infračervený teploměr vytištěný na 3D tiskárně - 11.2.2020
One Comment on “Arduino a displeje I.”
Napsat komentář
Pro přidávání komentářů se musíte nejdříve přihlásit.
René Češka
21.5.2016 at 23:47Dobrý den, mohl byste mi prosím pomoct s jedním programem? Jde o to, že si chci na vlastnoručně postaveném 5×5 LED maticovém displeji zahrát hru, kde se budu pomocí potenciometru vyhýbat světýlkům, co se přibližují k potenciometrem ovládané diodě. Dostal jsem se do části, kdy už z části funguje „padání“ LED diod, ale je problém, že LED dioda tak třikrát „spadne“ a přestane se cokoli zobrazovat. Předem děkuji za odpověď, jsem úplný začátečník, s Arduinem a vůbec s veškerou touhle elektronikou a programováním jsem začal před dvěma měsíci. S pozdravem René
Program:
byte sloupce[] = { 9,10,11,12,13};
byte radky[] = {8,7,6,5,4};
//potencimetr
byte led = 11; //pin s LED diodou
byte pot = A0; //pin s připojeným potenciometrem
int val; //proměnná připravená k uchování hodnot
int t = 0;
int f = 4;
int z = 0;
int h = 100;
int cislo = 2;
//dvojrozměrné pole pro obrázek
byte obrazek[5][5] = {{0,0,0,0,0},
{0,0,0,0,0},
{0,0,0,0,0},
{0,0,0,0,0},
{0,0,0,0,0}};
void setup(){
for(int i = 0; i < 5; i++){
//nastavíme piny
pinMode(sloupce[i], OUTPUT);
pinMode(radky[i], OUTPUT);
//potencimetr
Serial.begin(9600);
//zajistíme vypnutí displeje
digitalWrite(sloupce[i], HIGH);
digitalWrite(radky[i], LOW);
}
randomSeed(analogRead(A3));
}
void loop(){
//vykreslování
for(int x = 0; x < 5; x++){
for(int i = 0; i < 5; i++){
//zapneme řádek i
digitalWrite(radky[i], HIGH);
//dále pracujeme s jednotlivými sloupci
for(int j = 0; j < 5; j++){
//pokud je ve vybraném políčku 1, rozsvítí se LED
if(obrazek[i][j] == 1){
digitalWrite(sloupce[j], LOW);
}
}
delay(1); //chvíli počkáme, aby byl obraz dostatečně jasný
//vypneme všechny sloupce
for(int j = 0; j = 50){
int randNumber = random(0,4);
cislo = randNumber;
h=0;
}
//hra
//pohyb
if(val90){
obrazek[0][0] = 0;
obrazek[0][1] = 0;
obrazek[0][2] = 1;
obrazek[0][3] = 0;
obrazek[0][4] = 0;
}
if(val145 ){
obrazek[0][0] = 0;
obrazek[0][1] = 0;
obrazek[0][2] = 0;
obrazek[0][3] = 1;
obrazek[0][4] = 0;
}
if(val62){
obrazek[0][0] = 0;
obrazek[0][1] = 1;
obrazek[0][2] = 0;
obrazek[0][3] = 0;
obrazek[0][4] = 0;
}
if(val0){
obrazek[0][0] = 1;
obrazek[0][1] = 0;
obrazek[0][2] = 0;
obrazek[0][3] = 0;
obrazek[0][4] = 0;
}
if(val174 ){
obrazek[0][0] = 0;
obrazek[0][1] = 0;
obrazek[0][2] = 0;
obrazek[0][3] = 0;
obrazek[0][4] = 1;
}
//nepřátelé patro1
if(cislo == 0){
obrazek[z][0] = 0;
obrazek[f][0] = 1;
}else if(cislo == 1){
obrazek[z][1] = 0;
obrazek[f][1] = 1;
}else if(cislo == 2){
obrazek[z][2] = 0;
obrazek[f][2] = 1;
}else if(cislo == 3){
obrazek[z][3] = 0;
obrazek[f][3] = 1;
}else if(cislo == 4){
obrazek[z][4] = 0;
obrazek[f][4] = 1;
}
//potenciometr
val = analogRead(pot)/4;
//čtení hodnoty na A0 a úprava rozsahu
//generování PWM
val = analogRead(pot)/4;
//čtení hodnoty na A0 a úprava rozsahu
Serial.println(val);
//generování PWM
}