Port WaveForms SDK na Pascal
cykliczna akwizycja danych i histereza triggera

część 2

podstawowe informacje o urządzeniu i sterowanie zasilaczem
cykliczna akwizycja danych i histereza triggera
UART - Digital I/O i obsługa protokołów
I2C - Digital I/O i obsługa protokołów
SPI - Digital I/O i obsługa protokołów (mostek Wheatstone'a)
SPI - Digital I/O i obsługa protokołów (pomiar Iadj w LM317)
Digital I/O - ROM Mode, przerzutnik RS i dekoder do rosyjskiej VFD
Digital I/O - bezpośredni dostęp, wyświetlacz HDSP-2111 i termometr MCP9700
Analog OUT - woltomierz True RMS z przetwarzaniem sygnału

☘☘
dla ustalenia uwagi

Wielbiciele Analog Discovery 2 pewnikiem te stronki na pamięć znają, niemniej jednak profilaktycznie zaparkuję tu dwa arcyważne moim zdaniem linki - do schematów i opisu SDK:

➮ Analog Discovery 2 Reference Manual
➮ WaveForms SDK Reference Manual

Tematem dzisiejszej opowiastki będzie cykliczna akwizycja danych wyzwalana ustalonymi programowo parametrami badanego przebiegu. Czyli to, co robi pierwszy z brzegu sensowny oscyloskop. Przykładowe programiki od Digilent dla języków C/C++ oraz Python poruszają ten temat, ale co innego jest wyrzucić strumień liczb na konsolę tekstową, a co inne - jakoś te próbki pokazać w formie oscylogramu. Jest tu kilka drobiazgów do omówienia, a zatem po kolei...

Zaczniemy od prezentacji instalacji - pudełeczko AD2 w roli oscyloskopu, cukierkowy Rigol do weryfikacji co się dzieje na biurku, generator pracowicie produkuje testowe przebiegi.



czarcia zapadka

Prezentacja stabilnego przebiegu na ekranie, oprócz ustawienia parametrów czasowych i napięciowych akwizycji danych, wymaga jednej ważnej rzeczy - określenia miejsca przebiegu, które potraktujemy jako punkt zero na osi czasu, dzielący zestaw próbek na dwa zbiory - przed i po wyzwoleniu pomiaru. I to właśnie rola triggera, jego poprawne ustawianie to tak naprawdę clue obsługi oscyloskopu, ponieważ zapewnia w miarę stabilny obraz na ekranie, pozwala dowolnie długo kontemplować oscylogram a jednocześnie będzie to przebieg "żywy", nadążający kształtem za mierzonym sygnałem. Jak to robią profesjonalne rozwiązania to wszyscy wiemy, ale może dla przypomnienia krótki filmik:



Na rysunku poniżej jeszcze jedna migawka - czerwoną kropą zaznaczyłam punkt, w którym pracuje trigger - na poziomie 1V, w tym miejscu przyrządy centrują przebieg na ekranie.



Spróbujmy mniej więcej to samo zrobić, ale własnym sumptem przy pomocy Lazarusa i świeżo wyklutego modułku dwf.pas. Cała aplikacja simplescope1 dostępna jest w git, o tutaj:

➮ https://github.com/bienata/AnalogDiscovery2/tree/master/simplescope1

Kluczowe dla sukcesu całej zabawy jest poprawne zainicjowanie procesu zbierania danych przez AD2. Warto też mieć świadomość jak postępuje nasze pudełeczko z chomikowanymi próbkami, bo to akurat nie jest już takie intuicyjne. Zerknijmy na fragment kodu, przygotowujący AD2 do cyklicznej akwizycji przebiegu:

 main.pas
procedure TMainForm.PrepareSimpleScope;
begin
    // kanal 0 on
    FDwfAnalogInChannelEnableSet ( hAd2, 0, true );
    // offset na 0
    FDwfAnalogInChannelOffsetSet( hAd2, -1, 0 );
    // zakres 5Vpp
    FDwfAnalogInChannelRangeSet( hAd2, -1, 5.0 );
    // 100kHz, 100pt/div -> 1ms/div
    FDwfAnalogInFrequencySet( hAd2, 100E+3 );
    // buforek np 512 sampli
    FDwfAnalogInBufferSizeSet( hAd2, MAX_SAMPLES );
    // autotrigger na off
    FDwfAnalogInTriggerAutoTimeoutSet( hAd2, 0.0 );
    // wyzwalaj sygnalem analogowym
    FDwfAnalogInTriggerSourceSet( hAd2, trigsrcDetectorAnalogIn );
    // z kanalu 0
    FDwfAnalogInTriggerChannelSet( hAd2, 0 );
    // wyzwalanie zboczem
    FDwfAnalogInTriggerTypeSet( hAd2, trigtypeEdge );
    // poziom 1V
    FDwfAnalogInTriggerLevelSet( hAd2, 1.0 );
    // histereza 100mV
    FDwfAnalogInTriggerHysteresisSet ( hAd2, 0.1 );
    // narastajaco
    FDwfAnalogInTriggerConditionSet( hAd2, trigcondRisingPositive );
end;

Komentarze tłumaczą wszystko, a MAX_SAMPLES to stała określająca ile próbek będziemy sobie życzyć od naszego pudełeczka w każdym cyklu odczytu. No i tak na logikę - skoro dane składowane są w bufor w pamięci, to wydawałoby się naturalne, że zerowa próbka to wartość z chwili uruchomienia akwizycji, kolejne - to nasz przebieg magazynowany w RAM aż do wysycenia bufora. A tu zrobione jest inaczej, sprytniej. Jak zerkniemy sobie do dokumentacji SDK, do części "Analog In (Oscilloscope)" to na obrazku wyraźnie widać fazę Prefill, przed wyzwoleniem triggera. Ona zaczyna się zaraz po skonfigurowaniu parametrów i zapewnia dostęp do historycznych próbek względem momentu zadziałania triggera. A chytrość budowy AD2 polega na tym, że Prefill ustawia się na połowę rozmiaru zadanego bufora danych, inaczej mówiąc - na połowę widocznego fragmentu odciętych, określającą dziedzinę czasu, czy to w sekundach czy w próbkach. Zerknijmy na rysunek dla kilku wartości MAX_SAMPLES - jak widać, po zadziałaniu triggera na poziomie 1V przebieg jest ładnie "zaczepiony" w zadanym przez nas miejscu.



Oczywiście taka prezentacja bufora 0...N próbek wymaga drobnego zabiegu podczas ładowania danych do wykresu, a mianowicie oś X inicjujemy wartościami -N/2...N/2.

 main.pas
self.WaveLineSeries1.Clear;
for n := 0 to MAX_SAMPLES - 1 do
begin
    self.WaveLineSeries1.AddXY( n - round ( MAX_SAMPLES/2 ), waveform [ n ] );
end;

Finalny efekt będzie do złudzenia przypominał to, co pokazuje na ekranie Rigol czy aplikacja WaveForms. Filmik z działającym "discoveroskopem" poniżej:



Może dwa słowa o cyklicznym uruchamianiu zbiórki darów z przetwornika A/C, a mianowicie: po skonfigurowaniu parametrów uzbrajamy trigger wydając polecenie rozpoczęcia akwizycji:

 main.pas
FDwfAnalogInConfigure ( hAd2, false, true );
repeat
    FDwfAnalogInStatus( hAd2, true, @status );
    Delay (1);
until status = stsDone;
FDwfAnalogInStatusData( hAd2, 0, @waveform, MAX_SAMPLES );

AD2 zaczai się na zadany przez nas punkt w przebiegu, wykona serię pomiarów składując próbki w pamięci, gdy osiągnie koniec buforka - zwróci stsDone co będzie dla nas sygnałem, że można sobie zabrać dane funkcją FDwfAnalogInStatusData(). No i trzeba to biegiem zrobić, ponieważ musimy zdążyć przed następnym cyklem - armed/triggered/read/done. Ten fragment kodu wywoływany jest w handlerze obsługi aplikacyjnego czasomierza (obiekt Timer), szybkość jego pracy określa częstotliwość odświeżania przebiegu na wykresie TChart.

histeria i histereza

No, z histerią to koloryzuję deko, ja nie z takich, ale przyznam - wnerw mnie w pewnej chwili złapał, bo pracowicie wydziubana aplikacyjka nijak nie chciała pokazywać stabilnego przebiegu, tak jak to robił Rigol obok. Pomimo ustawienia triggera na narastające dodatnie zbocze, on łapał raz narastające, jak mu się chciało - to opadające, bez żadnej konsekwencji. A przecież wszystko zrobiłam tak jak w digilentowych przykładach, a tu lipa. Filmik z tej jazdy taki i od razu pokazuje w czym była rzecz:



Sztuczka polega na dodatkowym ustawieniu jednego drobnego parametru - faktycznych poziomów zadziałania triggera uwzględniających pewną histerezę wartości, to na okazję eliminacji szumów czy innych zakłóceń, mogących skutkować chybionymi wyzwoleniami. Histerezę ustawia się funkcją FDwfAnalogInTriggerHysteresisSet(), eksperymentalnie wybrałam sobie 100mV, dla triggera 1V to w miarę dobra wartość i voilà - przebieg trzyma się ekranu jak przyklejony, no super! Nie zmienia to faktu, że tu zadziałałam zupełnie intuicyjnie, przykłady nie wyjaśniają tej funkcji dokładnie, jest użyta jeno raz (/usr/share/digilent/waveforms/samples/py/AnalogIn_Record_Trigger.py) i bez żadnego komentarza. Jak widać, nie ma łatwo, ale tym przedniejsza ta cała zabawa.

#slowanawiatr, grudzień 2018

Natasza Biecek 2004-2019/~, e-mail