Tym razem wracam do swojego poprzedniego projektu AVR-IplaTV-Box, aby zamienić zastosowany wcześniej zestaw Atnel ATB na Arduino Nano.
Wstęp
Ten post jest kontynuacją poprzedniego wątku, w którym przedstawiałem koncepcję urządzenia AVR-IplaTV-Box. Analizując komentarze jakie pojawiły się pod tamtym postem postanowiłem zamienić zastosowany wcześniej zestaw ATB na zewnętrzny moduł Arduino z wyświetlaczem LCD.
Migracja z zestawu ATB do Arduino
Proces migracji z ATB do Arduino był stosunkowo prosty, ponieważ głównym zadaniem tego zewnętrznego modułu jest odczytywanie komend IR z pilota i przesyłanie ich do komputera. Biorąc pod uwagę fakt, że każde Arduino jest wyposażone w układ pozwalający na komunikację UART<->USB, proces developmentu sprowadził się w głównej mierze do doboru bibliotek, kórych w sieci jest bardzo dużo.
Dodatkowa rozbudowa - LCD
Podczas tworzenia zewnętrznego modułu IR postanowiłem również zaimplementować wyświetlacz LCD 2x16 ze sterownikiem HD44780 w celu wyświetlania dodatkowych informacji o aktualnie oglądanym programie TV. Niestety nie posiadałem konwertera HD44780<->I2C, który pozwala na sterowanie tym wyświetlaczem przy użyciu 4 przewodów (w tym 2 zasilających). W rezultacie, w środku urządzenia znajduje się dużo przewodów połączeniowych.
Hardware
Jeśli chodzi o część sprzętową modułu, to składa się ona z Arduino Nano, wyświetlacza LCD HD44780, odbiornika IR TSOP31236 oraz kilku biernych elementów elektronicznych. Do budowy korpusu modułu wykorzystałem niepotrzebne etui na okulary (eco-friendly xD). Poniżej zamieszczam schemat elektryczny oraz połączeniowy modułu, który został stworzony przy użyciu darmowego programu Fritzing.
Dobór bibliotek Arduino
W projekcie zastosowałem Arduino Nano, które jest bardzo popularne wśród społeczności Arduino, więc znalezienie biblioteki do obsługi odbiornika podczerwieni TSOP31236 oraz wyświetlacza HD44780 było trywialnie proste.
Finalnie w projekcie wykorzystałem dwie poniższe biblioteki:
- LiquidCrystal by Arduino, Adafruit version 1.0.7
- IRremote by shirriff version 2.2.3
Dodatkowo postanowiłem napisać prostą funkcję do scrollowania tekstu w jednej linii, która domyślnie nie jest implementowana w bibliotekach do obsługi LCD ze sterownikiem HD44780.
Software
Sam program na Arduino jest stosunkowo prosty i łatwy do analizy. W głównej pętli programu wywoływane są funkcje do odczytu/aktualizacji stanu całej aplikacji oraz jednego przerwania, które nakręca softwarowy timer odpowiedzialny za scrollowanie tekstu na wyświetlaczu.
W oprogramowanie modułu można wyróżnić cztery główne stany:
- Odczyt komend z IR i wysłanie ich do komputera.
- Odczyt danych z komputera dot. aktualnie oglądanego programu TV.
- Aktualizacja danych do wyświetlenia na wyświetlaczu LCD - aktualizacja bufora.
- Wyświetlenie informacji na LCD - scrollowanie.
Scrollowanie tekstu odbywa się w jednym z dwóch trybów. Pierwszy (no force) wyświetla aktualny tekst z bufora, a sam bufor jest aktualizowany dopiero, gdy cała zawartość zostanie wyświetlona (płynne przejście). Drugi tryb (force) wymusza natychmiastową aktualizację bufora, a proces scrollowania zaczyna się od początku. Tryb ‘force’ jest wykorzystywany w momencie zmiany aktualnie oglądanego kanału, natomiast tryb ‘no force’ podczas aktualizacji informacji o aktualnie oglądanym programie (w głównej mierze chodzi o aktualizację postępu aktualnie oglądanego programu wyrażonego w procentach)
/*
AVR Ipla TV Box.
version: v1.1.0
author: DevTomek.pl
*/
#include <LiquidCrystal.h> // LiquidCrystal by Arduino, Adafruit version 1.0.7
#include <IRremote.h> // IRremote by shirriff version 2.2.3
// LED
const int INFO_LED = 13;
// IR
const int IR_SENSOR = 2;
IRrecv irrecv(IR_SENSOR);
decode_results results;
// LCD
const int SCREEN_WIDTH = 16;
const int SCREEN_HEIGHT = 2;
const int RS = 7, EN = 8, D4 = 9, D5 = 10, D6 = 11, D7 = 12;
LiquidCrystal lcd(RS, EN, D4, D5, D6, D7);
// Serial Read
const char READ_DATA_SEPARATOR = '|';
const uint8_t READ_BUFFER_SIZE = 255;
char readBuffer[READ_BUFFER_SIZE];
boolean hasNewData = false;
// Global Variables
String line1 = "AVR-Ipla-TV-Box";
String line1Buffer = line1;
unsigned int line1Length = 16;
String line2 = " DevTomek ";
String line2Buffer = line2;
int stringStart, stringStop = 0;
int scrollCursor = SCREEN_WIDTH;
volatile uint8_t timer1 = 0;
void setup() {
/* Based on: Arduino Timer Interrupts Calculator */
// TIMER 1 for interrupt frequency 100 Hz:
cli(); // stop interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCNT1 = 0; // initialize counter value to 0
// set compare match register for 100 Hz increments
OCR1A = 19999; // = 16000000 / (8 * 100) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS12, CS11 and CS10 bits for 8 prescaler
TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
sei(); // allow interrupts
Serial.begin(9600);
Serial.println("Starting initialization...");
pinMode(INFO_LED , OUTPUT);
lcd.begin(SCREEN_WIDTH, SCREEN_HEIGHT);
irrecv.enableIRIn();
Serial.println("Initialization done!");
}
void loop() {
readIRData();
readSerialData();
updateData();
if (timer1 > 25) {
scroll();
timer1 = 0;
}
}
ISR(TIMER1_COMPA_vect) {
timer1++;
}
void readIRData() {
if (irrecv.decode(&results)) {
if (results.value != 0xFFFFFFFF) {
digitalWrite(INFO_LED , HIGH);
Serial.println(results.value, HEX);
digitalWrite(INFO_LED , LOW);
}
irrecv.resume();
}
}
void readSerialData() {
while (hasNewData == false && Serial.available() > 0) {
static uint8_t ndx = 0;
char rc = Serial.read();
if (rc != '\n') {
readBuffer[ndx] = rc;
ndx++;
if (ndx >= READ_BUFFER_SIZE) {
ndx = READ_BUFFER_SIZE - 1;
}
} else {
readBuffer[ndx] = '\0';
ndx = 0;
hasNewData = true;
}
}
}
void updateData() {
if (hasNewData == true) {
// RX frame structure: [LINE_1_DATA|LINE_2_DATA|IS_FORCE_MODE]
line1Buffer = getValue(readBuffer, READ_DATA_SEPARATOR, 0);
line2Buffer = getValue(readBuffer, READ_DATA_SEPARATOR, 1);
boolean isForceMode = getValue(readBuffer, READ_DATA_SEPARATOR, 2) == "true";
if (isForceMode) {
updateLcdData();
}
hasNewData = false;
}
}
String getValue(String data, char separator, int index) {
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++) {
if (data.charAt(i) == separator || i == maxIndex) {
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i + 1 : i;
}
}
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}
void scroll() {
lcd.clear();
lcd.setCursor(scrollCursor, 0);
lcd.print(line1.substring(stringStart, stringStop));
lcd.setCursor(0, 1);
lcd.print(line2);
if (stringStart == 0 && scrollCursor > 0) {
scrollCursor--;
stringStop++;
} else if (stringStart == stringStop) {
updateLcdData(); // update the text as the previous one will be displayed
} else if (stringStop == line1Length ) {
stringStart++;
} else if (line1Length < SCREEN_WIDTH && stringStop == (SCREEN_WIDTH + line1Length) ) {
stringStart++;
stringStop = stringStart;
} else {
stringStart++;
stringStop++;
}
}
void updateLcdData() {
stringStart = stringStop = 0;
scrollCursor = SCREEN_WIDTH;
line1 = line1Buffer;
line2 = line2Buffer;
line1Length = line1.length();
}
Aktualny kod można również znaleźć na moim GitHub’ie w repozytorium AVR-Ipla-TV-Box i katalogu Arduino.
Podsumowanie
Na koniec chciałbym tylko zaznaczyć, że zastosowany na początku projektu zestaw Atnel ATB, był celowym wyborem, ponieważ projekt był wtedy w fazie rozwoju, więc zastosowanie zestawu uruchomieniowego miało jak najbardziej sens. Po długotrwałych testach okazało się, że cały projekt zakończył się sukcesem i wszystko działa zgodnie z moimi oczekiwaniami, co oznaczało, że trzeba przejść do kolejnego etapu, czyli stworzenie finalnej wersji urządzenia i zastąpienie zestawu uruchomieniowego, czymś w rodzaju zewnętrznego modułu IR.