W ramach Mirkowego konkursu, dotyczącego API dla ATB-USBasp 4.2, powstał program SQP-I2Cscan
napisany w C# z wykorzystaniem biblioteki "
LibUsbDotNet C# USB Library", która jest do pobrania ze strony
https://sourceforge.net/projects/libusbdotnet/.
Jest to mały i prosty program - jak chodzi o główną funkcjonalność skanowania I2C, to tylko kilkanaście linijek
. Znaczna część kodu to GUI z dodatkami, które powstawały, bo Mirek przeciągał termin zakończenia konkursu
. Zasadniczo realizuje on tylko jedną praktyczną funkcję - skanuje magistralę I2C wykorzystując ATB-USBasp 4.2. Dla uprzyjemnienia korzystania, program realizuje autodetekcję podłączenia / odłączenia ATB-USBasp oraz graficznie prezentuje postęp oraz wyniki skanowania. Dodatkowo poprzez dostępny suwak można regulować prędkość magistrali I2C.
Wynik skanowania I2C prezentowany jest w tabeli podając adresy znalezionych urządzeń w formacie dziesiętnym i szesnastkowym oraz w konwencjach adresów 7-bitowego i 8-bitowego. Dodatkowo dodawana jest informacja o możliwych typach układów posiadających wykryty adres. Informacja o możliwych urządzeniach pobierana jest z pliku
i2c_table.txt, dzięki czemu istnieje możliwość samodzielnej definicji nazw układów prezentowanych po ich wykryciu.
Dzięki temu, że biblioteka LibUsbDotNet dostępna jest zarówno dla Windows jak i Linux, powstała możliwość stworzenia aplikacji poprawnie kompilującej się, bez jakichkolwiek przeróbek, zarówno pod Windows jak i pod Linux (wykorzystując środowisko Mono). Co ciekawe, to ten sam plik wykonywalny SQP-I2Cscan.exe można uruchomić pod Windows jak i pod Linux
i nawet nie ma znaczenie gdzie go skompilujemy
.
Cały kod udostępniam na licencja GNU GPL v. 3.0, czyli dostępny jest kod źródłowy z pełnymi prawami do samodzielnej modyfikacji i rozpowszechniania.
Kod programu znajduje się w wątku konkursowym
https://forum.atnel.pl/topic20920.html#p209345.
Dla lubiących narzędzia do wersjonowania, to ostateczną wersję wrzuciłem też do publicznego repozytorium
https://bitbucket.org/rskup/sqp-i2cscan/.
Skończy z marketingiem i przejdźmy do szczegółów programowania
Jako, że nie lubię korzystać kobyły Microsoftu, to do programowania w C# używam środowiska SharpDevelop. Dlatego nie będę opisywał szczegółów tworzenia programu w GUI aplikacji (bo te w środowisku od MS może trochę się różnić) a skupię się tylko kilka kluczowych punktach.
Podstawową rzeczą jest dodanie biblioteki LibUsbDotNet do naszego stworzonego projektu. W moim środowisku wystarczy wejść w menu Project (Projekt) i wybrać Manage Packages... (Zarządzanie Pakietami) i w nim wyszukać w liście proponowanych dodatkowych pakietów
LibUsbDotNet, następnie kliknąć Add (Dodaj) i po chwili mamy dodaną tę biblioteką do naszego projektu
Niestety przy korzystaniu z biblioteki LibUsbDotNet pojawia się drobny problem. W bibliotece mamy kilka metod do obsługi urządzeń libusb i niestety nie działają one zamiennie jak to chyba teoretycznie miało być
. Dla niektórych wersja driverów libusb poprawnie działa wywołanie części funkcji a dla innych działają inne
.
Najprościej jest pod Linuxem, bo tam działają te zbliżone nazwami do natywnych funkcji libusb. Te same funkcje działają pod Windows, ale tylko gdy mamy drivery pobierane bezpośrednio z projektu libusb-win32. Ale już prawie ten sam driver (te same wersje dll-ek) w paczce z podpisem cyfrowym, czyli ten sam co instaluje mkAVRCalculator (driver ten ma dodany dodatkową bibliotekę dedykowaną dla Windows libusbK.dll) wymaga używania funkcji z grupy UsbRegistry. Więc będziemy czasami mieli w kodzie sprawdzenie jaki mamy system
.
Dla ułatwienia i zwiększenia czytelności podefiniowałem sobie w programie kilka stałych:
- stałe z informacjami identyfikującymi ATB-USBasp
- nazwę pliku w którym przechowywane są opisy urządzeń I2C
- zakres adresów I2C do skanowania
- potrzebne komendy pochodzące z API ATB-USBasp
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
No to zacznijmy coś konkretnego pokodować
Na samy początku program, w celu wykrycia czy podpięty jest do komputera ATB-USBasp, wywołujemy funkcję FindATBUSBaspDevice(), która to przegląda wszystkie urządzenia libusb i na podstawie VID i PID oraz nazwy i serial stringa ustala czy jest podpięte właściwe urządzenie. Niestety w Linux tą metodą nie jest zwracana nazwa i serial, więc one są tam nie weryfikowane. Czyli pod Windows wykrycie urządzenia jest z dokładnością do ATB-USBasp a pod Linuxem każda wersja USBasp zostanie uznana za prawidłową (oczywiście jest możliwość dokładnego kolejnego sprawdzenia czy to jest na pewno ATB-USBasp, ale ja już tego nie zrobiłem
):
W przypadku wykrycia ATB-USBasp funkcja oprócz zwrócenia tego (poprzez wartość true), aktualizuje zmienną globalną MyUsbRegistry w której przechowujemy wskazanie na nasze urządzenie.
Czyli przy starcie programu wykonujemy:
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Zmienna bool ATBUSBasp przechowuje informację czy mamy podłączone ATB-USBasp, a funkcja SetATBUSBaspStatus() ustawia odpowiednio GUI i informacje prezentowane na nim. Funkcja FindATBUSBaspDevice() realizuje właściwe sprawdzenie istnienia urządzenia ATB-USBasp wygląda następująco:
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Ewentualne dalsze zmiany urządzeń libusb zrzucimy na karb zaimplementowanych w bibliotece procedur DeviceNotifier. Podpinamy naszą funkcję OnDeviceNotifyEvent(), by była wywoływana na zdarzeniach zmian stanów urządzeń:
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Nasz funkcja sprawdza czy zdarzenie dotyczy naszego VID, PID i czy jest typ zdarzenia nas interesujący (pojawienie się urządzenia =>
DeviceArrival oraz usunięcie urządzenia =>
DeviceRemoveComplete):
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Sprawdzanie w przypadku pojawienia się urządzenia, czy pracujemy pod Linuxem, spowodowane jest tym, że drugie wywołanie funkcji FindATBUSBaspDevice() w tym środowisku powoduje błędne informacje, więc pomijamy to i bazujemy tyko na VID i PID ze zdarzenia.
Mamy więc wykrywanie i informowanie użytkownika (poprzez modyfikacje GUI) o podpięciu lub usunięciu urządzenia ATB-USBasp. Pora więc zacząć się z nim komunikować
.
Jak to bywa przy komunikacji ze wszystkimi urządzeniami, to pierwszą funkcją jaką należy wykonać jest otwarcie urządzenia. Realizowane jest to, za każdym razem po wyzwoleniu skanowania I2C, poprzez funkcję OpenATBUSBaspDevice(), która przy okazji przypisuje nam identyfikator urządzenia do zmiennej globalnej MyUsbDevice.
Oczywiście tutaj także musimy użyć różnych funkcji dla Windows i Linux, bo wspominany wcześniej problem wersji sterowników ma tutaj znaczenie.
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Zamknięcie połączenia z urządzeniem (wykorzystywane po skończeniu skanowania urządzeń na I2C) realizowane jest w funkcji CloseATBUSBaspDevice():
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Po otwarciu urządzenia zostaje nam tylko zacząć wysyłać do niego komendy zgodnie z API.
Samo wysłanie danych do urządzenia realizujemy w programie poprzez funkcję SendToATBUSBaspDevice(), która korzysta z funkcji biblioteki MyUsbDevice.ControlTransfer(). API ATB-USBasp zwraca status wykonania większości komend poprzez zwrócenie stringa "OK" lub "ERROR". Sprawdzamy to, ale dla uproszczenia (bo po co porównywać ciągi
), patrzymy tylko na długość zwróconego ciągu
. Dzięki takiemu podejściu od razu dla komend które nic nie zwracają mamy także interpretację OK
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Do wywołania funkcji konieczne jest zdefiniowanie wartości zmiennych wejściowych (zgodnie z API ATB-USBasp), co jest banalne
:
Ustawienie prędkości zegara SCL wygląda następująco:
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
A sprawdzenie obecności urządzenia na szynie I2C wygląda następująco:
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Teraz pozostaje złożyć części kodu w całość i dorzucić trochę iteracji z GUI, by użytkownik lubi jak się zmieniają różne rzeczy jak program działa a szczególnie jak coś klika
Podsumowując, po kliknięciu przycisku skanowania I2C, gdy mamy podłączone ATB-USBasp, musimy otworzyć urządzenie (korzystając z funkcji OpenATBUSBaspDevice()), następnie ustawić wymaganą przez użytkownika prędkość dla I2C (poprzez funkcję i2cSetSCL()) i przystąpić do odpytywania kolejnych adresów (korzystając z funkcji i2cCheck()). Jeżeli funkcja zwróci prawdę, to znaczy że mamy urządzenie o takim adresie na szynie I2C, wtedy dodajemy informacje o tym urządzeniu do wyświetlanej tabelki i skanujemu kolejne adresy. Na koniec skanowania musimy zamknąć połączenie z urządzeniem i powiadomić użytkownika o zakończeniu działań.
Jak we wszystkich bibliotekach i poradnikach Mirka, także w API ATB-USBasp stosowany jest adres w notacji 8 bitowej (wartość łącznie z bitem R/W na najmłodszej pozycji), dlatego przy skanowaniu nie wysyłamy kolejnych wartości, tylko z przeskokiem co dwa.
Ale ja, na przekór
, lubię stosować notację 7-bitową, i taką używam w programie. Dlatego wartości zakresów adresów do skanowania zdefiniowane w stałych są 7 bitowe (I2C_7BIT_ADDRESS_MIN / I2C_7BIT_ADDRESS_MAX) a wartość wysyłana poprzez API jest przesunięta o jeden bit w lewo
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Zgodnie ze specyfikacją I2C, nie wszystkie adresy przeznaczone są dla normalnych urządzeń. Dlatego nie skanujemy całego zakresu a tylko jego część. Początek i koniec jest zdefiniowany we wspominanych wcześniej stałych I2C_7BIT_ADDRESS_MIN (najmniejszy adres) oraz I2C_7BIT_ADDRESS_MAX (największy adres).
Program posiada możliwość pokazywania w tabeli, oprócz adresów znalezionych urządzeń, także dodatkowych informacji o możliwym typie tego urządzenia. Realizowane jest po poprzez wczytanie takich informacji zaraz po uruchomieniu programu z pliku o nazwie zdefiniowanej w stałej programu
I2C_DESCRIPTION_FILE (domyślnie jest to plik i2c_table.txt) do zdefiniowanej listy:
język csharp
Musisz się zalogować, aby zobaczyć kod źródłowy. Tylko zalogowani użytkownicy mogą widzieć kod.
Wczytanie pliku odbywa się poprzez funkcję readI2CDeviceDescriptionFile(). Gdy plik zostanie załadowany ustawiana jest zmienna I2CDescriptionInfo, która to informuje czy mamy skąd próbować podawać nazwę urządzenia po jego wykryciu na I2C.
Informacje w plik mogą być modyfikowane przez użytkowników, o ile zachowana zostanie jego struktura (w źródłach dołączony jest przykładowy drugi plik i2c_table_long.txt, zawierający więcej informacji o urządzeniach).
Skanowanie urządzeń na I2C jest bardzo szybkie. Stosowanie normalnego sterowania progress bar-a (paska postępu) powoduje, że gdy pojawia się już komunikat o zakończeniu skanowania to progress bar jeszcze jest w połowie i sobie powoli jeszcze cały czas rośnie. Spowodowane jest to stosowaniem animacji progress bara przez Windowsowy interfejs Aero. Niestety nie ma opcji wyłączającej tę animację
. Aby animacja się nie wykonywała to wymagane jest zastosowanie triku przy wpisywaniu kolejnych wartości do progress bar-a (dlatego prograss bar sterowany nie jest bezpośrednio a poprzez dodaną funkcję SetProgressNoAnimation()).
Dziwne dodane wpisy na końcu w funkcji MainFormFormClosing() spowodowane są tym, że biblioteka LibUsbDotNet ma chyba jakieś błędy i pod Linuxem są problemy z zamykaniem programu - przy eleganckim zwalnianiem zasobów. A w ten sposób robimy to trochę mniej elegancko
.
--
Pozdrawiam,
Robert