Sterownik do HD44780 na I2C

Po długiej walce ze zdobywaniem wiedzy na temat pisania sterowników pod magistralę I2C udało mi popełnić pierwszy. Jest nieco ułomny, bo poza sterowanie podświetleniem oraz dostępem do dwóch linii, nie udostępnia nic ciekawego. Planuję sukcesywną rozbudowę oczywiście. Dodanie obsługi ioctrl\’i, dodanie urządzenia znakowego i rozbudowę do obsługi innych wyświetlaczy zgodnych z HD44780. Ale to później…

Hardware… Oryginalny interfejs HD44780 to 12 linii. 8 linii danych, linia RS (rozkaz/dane), linia CS (zatrzaskiwanie danych z magistrali), linia RW (odczyt/zapis) oraz opcjonalnie linia sterująca podświetleniem, w zależności od konstrukcji wyświetlacza. Wyświetlacz może pracować także w trybie 4-bitowej linii danych, wtedy oszczędzamy trochę, ale za to dokładamy czas związany z transmisją danych. Nasz system musi posiadać 8 wolnych linii GPIO, które poświęcimy na wyświetlacz. Raspberry Pi dysponuje wystarczającą ilością linii GPIO na złączu, ale po co je marnować.

Projekt Arduino przychodzi nam z pomocą. Do tej platformy różni producenci produkują różne moduły, w tym konwerter hd44780 na magistralę I2C.

Schemat konwertera, który kupiłem wygląda mniej więcej tak… Mniej więcej, gdyż nie posiada diody led. Sercem układu jest ekspander linii gpio PCF8574T. Jest to de facto rejestr dwukierunkowy posadzony na magistrali. Jego obsługa jest trywialna, bo wystarczy pisać bądź czytać dane z adresu magistrali (urządzenia), dzięki temu, że PCF nie posiada żadnych adresów wewnętrznych, a dane które wystawia (bądź pobiera) na magistralę są bezpośrednio związane z jego portami wejścia/wyjścia. Jego trywialna obsługa jest czasami wrzodem… Nie można skonfigurować niezależnie portów jako wejście lub wyjście. Albo wszystkie porty są wejściem, albo wyjściem. W związku z tym nie możemy np odczytać ze scalaka aktualnego stanu wejść, które przed chwilą ustaliliśmy (np nie można odczytać czy linia sterowania podświetleniem, jest włączona, czy wyłączona, gdyż odczyt zmieni stan linii). Jeżeli chcemy mieć taką funkcjonalność musimy ją sami zrealizować w systemie.

Sterownik jest napisany pod RPi, więc jeżeli będzie potrzeba uruchomienia go na innym systemie, trzeba będzie zrewidować kilka miejsc. Kod źródłowy jest tutaj: github.

W sterowniku jest jeden „dirty hack” związany z inicjalizacją urządzenia. Łańcuch jest taki: urządzenie –> magistrala –> adapter –> klient –> sterownik. Na magistrali I2C urządzenia się nie zgłaszają same swojej obecności. Zatem system musi wiedzieć, że pod danym adresem znajduje się jakieś urządzenie, w przeciwnym razie nie będzie „klienta” i driver nie zostanie podczepiony pod urządzenie. Są w ogólności dwa sposoby na przekazanie tej informacji:

  • drzewo urządzeń (ang. device tree) – wiadomo o co chodzi (chyba)
  • „pliki płyty” (ang. board specyfic files) – pliki *.c które opisują hardware danego systemu, dla którego konfigurowane jest jądro.

Zarówno jeden jak i drugi sposób wymaga zabawy z jądrem, a tego chciałem uniknąć. Większość przykładów jednak podaje powyższe sposoby. Jest trzeci, ale jego znacznie trudniej znaleźć, a jeżeli ktoś nie ma doświadczenia, to z dokumentacji jądra nie wynika on bezpośrednio. Odpowiedzią jest funkcja i2c_new_device, która tworzy klienta i podłącza go pod odpowiedni adapter… Wystarczy stworzyć odpowiednią strukturę opisującą urządzenie i wskaźnik do klienta:

struct i2c_board_info info = {
.type = "hd44780_i2c",
.addr = 0x27,
};

static struct i2c_client *client;

A następnie w funkcji inicjującej moduł jądra wywołać funkcję:

client = i2c_new_device(adapter, &info);

przed momentem dodania sterownika (przed funkcją i2c_add_driver). Po stworzeniu klienta i dodaniu sterownika, system wykonuje funkcję probe… I w sumie o to chodzi. Ten manewr wymaga jeszcze dodatkowej rzeczy. Napisane własnej funkcji „init” i „exit” dla modułu. W przykładach załatwia to makro „module_driver”.

Sterownik jest stosunkowo prosty. Po załadowaniu tworzy w systemie plików sysfs odpowiednie wpisy.

pi@raspberrypi ~/raspberry_pi/modules/lcd $ tree /sys/bus/i2c/devices/1-0027
/sys/bus/i2c/devices/1-0027
├── backlight
├── content
├── driver -> ../../../../../bus/i2c/drivers/hd44780_i2c
├── modalias
├── name
├── power
├── subsystem -> ../../../../../bus/i2c
└── uevent

Interesują nas dwa pliki. Backlight i content. Zapis do pliku backlight „0” lub 0 spowoduje wyłączenie podświetlenia. Zapis innej wartości włączenie podświetlenia. Zapis do pliku content dwóch linii spowoduje wyświetlenie ich zawartości na wyświetlaczu. Zapis jest stosunkowo prymitywny, więc zapisywane linie nie mogą zawierać więcej niż jednego znaku końca linii. Jeżeli jedna z linii jest dłuższa niż 16 znaków, zostanie przycięta.

Dwa wyświetlacze w akcji: