1-Wire. Algorytm wyszukiwania układów.

Każdy z układów z interfejsem 1-Wire posiada unikatowy 64-bitowy kod identyfikacyjny. Kod ten nosi nazwę „kod ROM” i może być utożsamiany z unikatowym adresem układu z interfejsem 1-Wire. Kod ten używany jest przez układ Master do wyboru układu w sieci. W związku z tym, że jest to kod unikatowy, to jeśli nie jest znana liczba układów Slave w sieci, może ona zostać określona przy jego użyciu przez zastosowanie funkcji przeszukiwania sieci. Algorytm jej działania oparty jest o zasadę przeszukiwania drzewa binarnego. Gałęzie przeszukiwane są do momentu aż zostanie odnaleziony koniec gałęzi lub pamięć ROM układu 1-Wire. Funkcja przeszukuje drzewo do momentu aż wszystkie numery ROM i wszystkie zakończenia zostaną odkryte.

Algorytm rozpoczyna się od wysłania rozkazu reset. Jeśli jego przesłanie zakończy się powodzeniem, to znaczy odpowiednio zareagują na niego układy dołączone do magistrali, wysyłana jest 1-bajtowa komenda zwana „search” o kodzie 0xF0 lub 0xFC. Komenda ta przygotowuje układy podłączone do magistrali do przeszukiwania.
Firma Dallas zaimplementowała dwa rodzaje komend przeszukujących. Najczęściej używane jest przeszukiwanie tzw. normalne (0xF0) sprawdzające wszystkie układy podłączone do linii. Innym rodzajem przeszukiwania jest tzw. warunkowe, które znajduje układy będące w stanie alarmowym, np. załączone termostaty czy timery, które sygnalizują zakończenie odmierzania czasu. Redukowany jest w ten sposób obszar przeszukiwania do tylko tych układów, które muszą zostać z jakiś powodów odczytane czy ustawione.
Po wysłaniu przez układ Master komendy przeszukiwania, układy Slave podłączone do magistrali 1-Wire (wszystkie, których dotyczy komenda) odpowiadają wysyłając jednocześnie mniej znaczący bit własnego adresu. Według standardu 1-Wire, układ Master inicjuje przesłanie każdego bitu zapisywanego czy odczytywanego z układu Slave. W związku ze specyfiką interfejsu, gdzie wszystkie układy podłączone są do wspólnej linii przesyłowej i na odebraną komendę przeszukiwania odpowiadają w tym samym czasie (jednocześnie i synchronicznie z sygnałem zegarowym wysyłanym przez układ Master), rezultat odczytu docierający do układu Master jest iloczynem logicznym stanów wyjść układów Slave (wired and).
Po tym jak układy Slave prześlą 1-szy bit swojego adresu, układ Master inicjuje odbiór następnego bitu, na który układy Slave odpowiadają wysyłając ponownie 1-szy bit adresu lecz tym razem jest to jego bit komplementarny. Następnie układ Master wysyła bit adresu do układów Slave. Jeśli urządzenie Slave odbiera bit, który zgadza się z bitem na pozycji adresu, pozostaje załączone i aktywne. Jeśli natomiast odebrany bit nie odpowiada temu z adresu, urządzenie Slave przechodzi do stanu oczekiwania i nie przesyła już żadnych danych. Stan ten trwa aż do odebrania następnego sygnału reset.
Opisywana wyżej procedura tzn. odbiór przez układ Master jednego bitu jako „normalny” i komplementarny a następnie przesłanie tego bitu adresu do układu Slave (jako „normalny”), powtarzana jest dla wszystkich 63 pozostałych bitów adresu. W takiej sytuacji urządzenie Slave wywołuje wszystkie układy dołączone do magistrali, lecz w danym momencie, po odbiorze bitu adresu, tylko jeden z nich przejdzie do stanu oczekiwania. Na końcu procedury znany jest adres ostatniego układu dołączonego do magistrali. W następnych przejściach procedury uwzględniana jest inna ścieżka, dotąd nie przeszukiwana.
Wyjaśnienia wymaga jeszcze jedna drobna nieścisłość opisu. Proszę zauważyć, że w tym opisie bitem 1-szym nazywam najmniej znaczący bit adresu układu, czyli bit na pozycji 0, natomiast bit 64 to bit na pozycji 63.
Jest oczywiste, że wszystkie uczestniczące w przeszukiwaniu układy mają bit o tej samej wartości na danej jego pozycji (tabela 1). Jeśli na przeszukiwanej gałęzi nie znajduje się

 

Tab.1. Interpretacja bitów odebranych przez układ Master w czasie przeszukiwania

Bit rzeczywisty

Bit uzupełniony

Interpretacja

0

0

Na pozycji bitu znajdują się „0” i „1”; to jest sprzeczność

0

1

Na pozycji bitu znajduje się „0”

1

0

Na pozycji bitu znajduje się „1”

1

1

Brak urządzeń na przeszukiwanej gałęzi

 

żaden układ, przeszukiwanie jest zatrzymywane i rozpoczynane jest nowe, od wysyłania sygnału „1-Wire reset”. Sytuacja, gdy na pozycji bitu umieszczone jest zarówno „0” jak i „1”, nazywana jest sprzecznością i jest kluczem do określania kierunku i gałęzi przeszukiwania.
Opisywany algorytm wykrywa warunek sprzeczności w pierwszym przebiegu (bit/uzupełnienie = 0/0) i na jego podstawie wybiera gałąź „0” do kontynuacji pracy. To jest metoda przyjęta dla tego algorytmu. Możliwe jest stworzenie własnego, w którym do kontynuacji wybrana będzie gałąź „1”. Pozycja bitu, dla której ostatnio określony został warunek sprzeczności jest zapamiętywana dla następnych operacji wyszukiwania.

Tab. 2. Określanie kierunku przeszukiwań

Wynik porównania pozycji przeszukiwanego bitu z pozycją ostatnio odnalezionej sprzeczności

Obierana gałąź przeszukiwania

=

Wybierz gałąź „1”

<

Obierz tę samą gałąź co ostatnio (od ostatnio znalezionego adresu)

>

Wybierz gałąź „0”

 

Algorytm zapamiętuje także ścieżkę ostatnich sprzeczności, które wystąpiły w obrębie pierwszych 8 bitów adresu układu. Jest to kod rodziny układów. Jako rezultat takiego działania, układy odkryte podczas przeszukiwania grupowane są w rodziny. Ostatnio odkryty warunek sprzeczności w obrębie rodziny, może zostać użyty do przeszukiwania selektywnego danej rodziny układów (np. czujników temperatury czy pamięci).

algorytm dzialania

Rys. 1. Algorytm działania funkcji wyszukiwania układów 1-Wire (kliknij aby powiększyć)

Opisy zmiennych:
cmp_id_bit
– uzupełnienie jedynkowe bitu (w tym przypadku to negacja); należy pamiętać o fakcie, że do układu Master dociera iloczyn logiczny wszystkich bitów wysyłanych przez układy dołączone do magistrali 1-Wire
id_bit– pierwszy bit odczytany podczas sekwencji odczytu; uwaga jak powyżej
id_bit_number– pozycja bitu w adresie (numerze ROM) układu; zmienna zmienia się od 1 do 64, podczas gdy faktyczna pozycja (waga) bitu jest o 1 mniejsza (0..63)
LastDeviceFlag– zmienna wskazująca, że poprzednio odszukany układ był ostatnim w danej gałęzi
LastDiscrepancy– indeks wskazujący na bit, od którego bitu powinno się rozpocząć następne poszukiwanie sprzeczności (określanie gałęzi przeszukiwań)
LastFamilyDiscrepancy– indeks wskazujący na bit identyfikujący ostatnią sprzeczność wykrytą w obrębie 8-bitowego kodu rodziny układów
last_zero – pozycja bitu ostatnio zapisanego 0, dla której nie wykryto sprzeczności
ROM_NO– bufor o rozmiarze 8 bajtów, który zawiera numer ROM (adres) ostatnio wykrytego układu
search_direction– zmienna o rozmiarze 1 bitu wskazująca kierunek przeszukiwania drzewa; wszystkie układy znajdujące się na kierunku przeszukiwania są aktywne, reszta znajduje się w stanie oczekiwania na następne przeszukiwanie

Wyszukiwanie proste

Poprzez manipulację wartościami zmiennych LastDiscrepancy, LastFamilyDiscrepancy, LastDeviceFlag i wartością rejestru ROM_NO możliwe są do przeprowadzenia dwa podstawowe warianty wyszukiwania układów.

Szukanie pierwszego.
Pierwszy z nich to wyszukanie pierwszego adresu urządzenia. Jest to możliwe po ustawieniu LastDiscrepancy, LastFamilyDiscrepancy i LastDeviceFlag na wartość 0 i po tym wywołanie funkcji poszukiwania. Rezultat może zostać odczytany z rejestru ROM_NO. Jeśli żadne urządzenie nie jest podłączone do magistrali 1-Wire, wówczas przeszukiwanie jest przerywane. Jeśli znaleziono jakiś układ, jego adres można pobrać właśnie z tejże tabeli. Implementację tej funkcji można znaleźć na listingu 2 pod nazwą int one_wire_FIRST().

Szukanie następnego.
Drugi wariant to wyszukanie następnego układu, po pierwszym lub po kolejnym. Wykorzystywany jest po wywołaniu funkcji „szukaj pierwszego” układu lub jako kolejne w ciągu poleceń wyszukiwania następnych urządzeń. Rezultat działania zwracany jest w buforze ROM_NO. Jeśli poprzednio odszukano ostatnie urządzenie, zostanie ustawiona zmienna informująca o zakończeniu przeszukiwania a funkcja zwróci wartość FALSE. Implementację tej funkcji można znaleźć na listingu 2 pod nazwą int one_wire_NEXT().

Zaawansowane funkcje wyszukiwania

Inne możliwości stwarza tzw. przeszukiwanie zaawansowane, dzięki któremu można się odwołać do konkretnego układu lub grupy układów. Podobnie jak poprzednio, funkcje wywołuje się po uprzedniej manipulacji zawartością zmiennych.

Weryfikacja
Weryfikacja przeprowadza sprawdzenie, czy układ o znanym adresie  podłączony jest do magistrali 1-Wire. Można ją przeprowadzić podając adres układu w rejestrze ROM_NO oraz ustawiając wartość zmiennych: LastDiscrepency na 0x40 (64 dziesiętnie) i LastDeviceFlag na 0. Po opisanych nastawach należy wywołać funkcję wyszukiwania a po jej zakończeniu należy sprawdzić zawartość ROM_NO. Jeśli bufor zawiera adres, który był poszukiwany oznacza to, że urządzenie jest podłączone do magistrali 1-Wire. Implementację tej funkcji można znaleźć na listingu 2 pod nazwą int one_wire_VERIFY().

Wyszukiwanie w obrębie rodziny układów
Innym rodzajem operacji jest przeszukiwanie konkretnej rodziny układów. Jak zapewne pamiętamy z wcześniejszej lektury, każdy układ z interfejsem 1-Wire posiada własny unikatowy 64-bitowy kod, z czego pierwsze 8 bitów określa kod rodziny układów. Kod ten pozwala oprogramowaniu funkcjonującemu w układzie Master na orientację, czy pewne funkcje, związane z daną rodziną układów, są dostępne czy też nie. Jeśli dana sieć zawiera wiele różnych układów, to dobrą praktyką jest komunikacja z daną ich grupą, np. czujnikami temperatury.
Aby odwołać się do konkretnej grupy układów, należy pierwszy bajt bufora ROM_NO ustawić na wartość równą kodowi grupy a resztę bufora wypełnić wartością 0. Należy również ustawić wartość zmiennej LastDiscrepancy ustawić na 0x40 (64 dziesiętnie) a LastDeviceFlag i LastFamilyDiscrepancy na wartość 0. Jeśli funkcja poszukiwania znajdzie układ z rodziny podanym kodzie, wówczas jego adres zostanie umieszczony w ROM_NO. Jeśli natomiast brak jest układu z pożądanej grupy, to ROM_NO będzie zawierać adres pierwszego znalezionego układu. W związku z tym konieczne jest sprawdzenie wartości 1-go bajtu ROM_NO po zakończeniu pracy funkcji. Implementację tej funkcji można znaleźć na listingu 2 pod nazwą void one_wire_TSETUP(byte family_code).

Pomijanie grupy układów
Inną dostępną operacją jest – można by powiedzieć odwrotna do powyżej opisanej – funkcja pomijania danej rodziny układów. Polega ona na pomijaniu rodziny układów, do której należy poprzednio odszukany układ. Można w ten sposób znacznie przyspieszyć wyszukiwanie, ograniczając się do konkretnej grupy układów, na której w danym momencie nam zależy, pomijając wszystkie pozostałe. Funkcja może być wywołana wyłącznie po przeprowadzonym poprzednio szukaniu, np. pierwszego układu. Aby ją uruchomić należy wartość LastFamilyDiscrepancy skopiować do zmiennej LastDiscrepancy i wywołać wyszukiwanie. Następne szukanie odnajdzie pierwszy z układów po podanym kodzie rodziny. Jeśli dana rodzina należała do ostatniej grupy układów, to wówczas zmienna LastDeviceFlag przyjmie wartość „prawda”. Implementację tej funkcji można znaleźć na listingu 2 pod nazwą void one_wire_FAMILYSKIP().


Tab. 3. Podsumowanie opisu funkcji wyszukujących 1-Wire

Zmienna

 

Opis funkcji

Last-Discrepancy

LastFamily Discrepancy

LastDeviceFlag

ROM_NO

Szukaj pierwszego

0

0

0

adres układu

Szukaj następnego

nie zmieniać

nie zmieniać

nie zmieniać

adres układu

Weryfikacja

0x40

bez znaczenia

0

należy wpisać adres

poszukiwanego układu a następnie

sprawdzić rezultat

Szukaj układów z danej rodziny

0x40

0

0

należy ustawić 1-szy bajt na kod rodziny układów a następnie sprawdzić rezultat

Omiń układy z danej rodziny

pobierz z ostatniego

wyszukiwania

0

0

nie zmieniać

 

Jako ciekawostkę warto wspomnieć, że układ DS2480B posiada zaimplementowany sprzętowo niemal identyczny algorytm. Zwalnia to użytkownika od konieczności powtórnej jego implementacji i oszczędza czas konieczny na zbudowanie urządzenia i oczywiście – co w dzisiejszych czasach nie ma już tak wielkiego znaczenia – pamięć programu mikrokontrolera.

 

Jacek Bogusz
j.bogusz@easy-soft.net.pl

 


 

pseudokod

Szukaj pierwszego:
LastDiscrepancy= LastDeviceFlag = 0
sygnalizacja 1-Wire reset
czekaj na odpowiedź układów, jeśli brak odpowiedzi to koniec pracy
id_bit_number= 1, last_zero = 0
wyślij komendę przeszukiwania układów (0xF0)
odczytaj pierwszy bit id_bit
[urządzenie (A = 1)] and [urządzenie (B = 0)] and [urządzenie (C = 1)] = 0
odczytaj bit komplementarny cmp_id_bit
[urządzenie (A = 0)] and [urządzenie (B = 1)] and [urządzenie (C = 0)] = 0
jeśli id_bit_number>LastDescrepancy to search_direction=0, last_zero=1
wyślij na magistralę 1-Wire bit search_direction równy 0 (urządzenia A i C przejdą w stan oczekiwania na sygnał reset)
zwiększ id_bit_number (zmienna przyjmie on wartość 2)
odczytaj drugi bit id_bit (będzie on miał wartość 0, bo odpowie urządzenie B)
czytaj drugi bit komplementarny cmp_id_bit (będzie on miał wartość 1 z powodu jak wyżej)
bit i bit komplementarny mają różną wartość: search_direction = id_bit
wyślij na magistralę 1-Wire search_direction równy 0, urządzenie B jest określane przez zawartość ROM_NO i jest wybrane (aktywne)
LastDescrepancy = last_zero

Szukaj następnego:
sygnalizacja 1-Wire reset
czekaj na odpowiedź układów, jeśli brak odpowiedzi to koniec pracy
id_bit_number= 1, last_zero = 0
wyślij komendę przeszukiwania układów (0xF0)
odczytaj pierwszy bit id_bit
[urządzenie (A = 1)] and [urządzenie (B = 0)] and [urządzenie (C = 1)] = 0
odczytaj bit komplementarny cmp_id_bit
[urządzenie (A = 0)] and [urządzenie (B = 1)] and [urządzenie (C = 0)] = 0
ponieważ id_bit_number = LastDescrepancy to zmiennej search_direction nadaj wartość 1
wyślij na magistralę 1-Wire bit search_direction równy 1 (urządzenie B przejdzie w stan spoczynkowy)
zwiększ id_bit_number (przyjmie on wartość 2)
odczytaj drugi bit id_bit (będzie on miał wartość 0, bo [urządzenie (A = 0) and [urządzenie (C = 1)] = 0)
czytaj drugi bit komplementarny cmp_id_bit (będzie on miał wartość 0, bo [urządzenie (A = 1) and [urządzenie (C = 0)] = 0)
ponieważ id_bit_number > LastDescrepancy, to search_direction = 0 i last_zero = 2
wyślij na magistralę 1-Wire search_direction równy 0, urządzenie A jest określane przez zawartość ROM_NO i jest wybrane (aktywne), urządzenie C przejdzie w stan spoczynkowy
LastDescrepancy = last_zero

Szukaj następnego:
sygnalizacja 1-Wire reset
czekaj na odpowiedź układów, jeśli brak odpowiedzi to koniec pracy
id_bit_number= 1, last_zero = 0
wyślij komendę przeszukiwania układów (0xF0)
odczytaj pierwszy bit id_bit
[urządzenie (A = 1)] and [urządzenie (B = 0)] and [urządzenie (C = 1)] = 0
odczytaj bit komplementarny cmp_id_bit
[urządzenie (A = 0)] and [urządzenie (B = 1)] and [urządzenie (C = 0)] = 0
ponieważ id_bit_number < LastDescrepancy to search_direction = ROM_NO = 1
wyślij na magistralę 1-Wire bit search_direction równy 1 (urządzenie B przejdzie w stan spoczynkowy)
zwiększ id_bit_number (przyjmie on wartość 2)
odczytaj drugi bit id_bit (będzie on miał wartość 0, bo [urządzenie (A = 0) and [urządzenie (C = 1)] = 0)
czytaj drugi bit komplementarny cmp_id_bit (będzie on miał wartość 0, bo [urządzenie (A = 1) and [urządzenie (C = 0)] = 0)
ponieważ id_bit_number =LastDescrepancy, to search_direction = 1
wyślij na magistralę 1-Wire search_direction równy 1, urządzenie C jest określane przez zawartość ROM_NO i jest wybrane (aktywne), urządzenie A przejdzie w stan spoczynkowy
LastDescrepancy =last_zero, które jest równe 0 tak więc LastDeviceFlag = prawda

Szukaj następnego:
zmienna LastDeviceFlag ma wartość „prawda”, więc koniec pracy
LastDiscrepancy =LastDeviceFlag = 0


Rys. 2. Schemat i pseudokod funkcjonowania opisywanego algorytmu dla adresu 2-bitowego

http://www.tomaszbogusz.blox.pl/

ZałącznikWielkość
Program opisywany w artykule (1-Wire search.7z)3.02 KB