Elektroda.pl
Elektroda.pl
X
Elektroda.pl
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.
itemscope itemtype="https://schema.org/QAPage"

Szybki przetwornik A/C w Arduino UNO

bikemc 06 Lis 2018 00:28 879 22
  • Szybki przetwornik A/C w Arduino UNO

    #1
    Poziom 13  

    Cześć wszystkim!

    Mam pewien problem, a mianowicie potrzebuję szybki (1 kHz) przetwornik A/C, obsługą RTC i karty SD. Zbudowałem takowy na Arduino UNO, ale częstotliwość próbkowania jaką uzyskuję to max 49 Hz. A/C na pinie A0. Macie jakiś pomysł?

    Kod: c
    Zaloguj się, aby zobaczyć kod

    0 22
  • Pomocny post
    #2
    Poziom 25  

    1. Pisać kod na forum jak należy - przeczytaj instrukcję obsługi forum.
    2. Szybciej nie będzie przy takim programie. Sam delay ogranicza do poniżej 100hz następnie wolne operacje na SD i serial.

    Tak więc czego oczekujesz?

    1
  • #3
    Poziom 13  

    Potrzebuję rerejestrator napięcia lub prądu z zapisem na USB/SD a także z zegarek RTC o fs = 1 kHz. Mój wybór właśnie padł na Arduino UNO, choć mogę oprogramować inny. Ten zestaw z tym kodem jest zbyt wolny. Macie jakieś sugestie? :(

    0
  • #4
    Poziom 25  

    Jezeli bedziesz wstawiać delaye i wysyłać po serialu to nawet STM32H7 nie pomoże. Napisz program jak należy, to UNO sobie poradzi, ale jak zmuszasz go do wysyłania po UART-cie długich stringów + każesz czekać 10ms za każdym obrotem - to nie dajesz mu szans się wykazać.

    Jeżeli masz np. 32 znaki co 1ms to minimalna prędkość transmisji musi być >= 512.000 (bo procesor musi mieć czas na inne sprawy) Wyrzuć oczywiście wszelakie delay-e

    ADC wyzwalaj timerm co 1ms

    Na razie zrób to.

    0
  • #5
    Poziom 14  

    bikemc napisał:
    Ten zestaw z tym kodem jest zbyt wolny. Macie jakieś sugestie? :(

    Jeśli będziesz miał super szybkie arduino a pomiędzy pomiarami zrobisz
    delay(10);
    to i tak będziesz mierzył co 10ms. Kolega powyżej już pisał o tym.
    Mnóstwo informacji wywalasz na port szeregowy, to zajmuje czas.
    Tu masz przykład szybkiego arduino z procesorem dwurdzeniowym i pokazane jak korzystać z drugiego rdzenia.
    ESP32 Dual Core on Arduino
    W jednym tasku pomiary a w drugim zapis do SD.

    0
  • #6
    Poziom 22  

    Nie używaj analogRead() tylko normalnie obsłuż ADC. Taktowanie zegara ADC dla free running mode powinno wynosić 1Mhz. Daj więc rejestr ADPS na 100. Preskaler będzie na 16 i wtedy można wycisnąć do około 78kHz .

    0
  • #7
    Poziom 13  

    Usunąłem tą linię kodu z opóźnieniem 10ms. Fs= 41 Hz, więc cienizna :( W tym miejscu i tak kod się nie wykonuje 2 raz, bo potem jest nieskończona pętla. Jak w Arduino zmienić tryb ADC?

    Zauważyłem, że bardzo spowalnia działanie ukontrolera funkcja:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Czy jest możliwość jej zastąpienia?

    0
  • #8
    Poziom 27  

    Nie masz żadnych szans by w ciągu sekundy 1000 razy otworzyć plik z SD i coś do niego zapisać. Mógłbyś próbować umieszczać pomiary w ram i zapisywać je hurtem raz na 10s. Użyj funkcji analogicznej do millis(), tzn. micros() i sprawdź ile trwają poszczególne funkcje - przypisz wartość z funkcji do zmiennej przed i do drugiej po zakończeniu funkcji, różnicę wyrzuć na serial. Pomiar ADC nawet w Arduino trwa kilka us, obsługa karty po kilka ms (średnio, czasami może być i kilkaset ms). Może trochę przyspieszysz jak będziesz plik aktualizował np. co minutę. Kolejne przyspieszenie można uzyskać przez umieszczenie wszystkich danych do zapisu w jednym stringu i wrzucić go raz, a nie po literce.

    0
  • Pomocny post
    #9
    Poziom 22  

    Jak zmienić tryb ADC?
    Tak jak pisałem wcześniej, trzeba pogmerać w rejestrach.

    W setupie dopisz:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Będziemy akwizować dane do tablicy. Zamień więc definicję int sensorValue = 0, na uint16_t sensorValue[100] = 0;
    Tablica powinna być z dziesięć razy większa, ale ten procek ma za mało pamięci.

    Zadeklaruj więc globalnie zmienne:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Napisz też funkcję obsługi przerwania od ADC:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Jak zbierzemy 100 próbek, to poinformujemy o tym pętlę główną (flaga = true).
    W funkcji loop() zamiast pomiarCisnienia(); napisz:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    A funkcję pomiarCisnienia() zmień na:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Myślę, że w ten sposób zbierzesz wymagane 1000 próbek na sekundę.
    Nie uwzględniłem przeliczania wartości zmierzonej (*20.0/2024.0), bo sama obsługa tablic zajęła by około 45% pamięci i spali się procesor ogniem piekielnym.

    edit: w czasie wykonywania funkcji pomiarCisnienia() trzeba wyłączyć przerwania, bo wyniki mogą się zakłamywać. Przed for ... daj cli(); a po wyjściu z pętli sei();

    1
  • #10
    Poziom 13  

    Mózg masz niesamowity :) Nic tylko praca w NASA.
    Jest tylko ciekawostka. Nie ma stałego próbkowania. Gdy pomiary są równe 0 (brak podłączonego czujnika) to fs= ok. 2,2 kHz., Gdy podłącze czujnik to jest różnie, od 1,2 kHz do 1,5 kHz (pomiary ilości próbek w czasie 60 sekund).

    0
  • #11
    Poziom 13  

    Dlaczego tak się dzieje, że co 61 pomiar jest przerwa w zapisie danych na SD?

    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    0
  • #12
    Poziom 22  

    To już jest gotowy produkt?
    Zakomentuj Serial.
    Pokaż ostateczny kod.

    0
  • #13
    Poziom 13  

    Znalazłem błąd. Mam pytanie, jak na stałe ustawić częstotliwość próbkowania: np. na 128 Hz?



    Oto kod:

    Kod: c
    Zaloguj się, aby zobaczyć kod

    0
  • #14
    Poziom 22  

    Już dziś nie zajrzę do Twojego kodu (pewnie jutro wieczorem), ale jeśli chodzi o częstotliwość próbkowania, myślę że się pomyliłeś. Normalnie ATMEGA może zbierać prawie osiemdziesiąt tysięcy próbek na sekundę. Ja podałem kod na gwarantowane 1000 pps, a Ty chcesz spowolnić jeszcze ADC? To peryferium działa niezależnie od procesora i zmiana częstotliwości próbkowania nie wpłynie na wydajność programu. Możesz po prostu zbierać dane z ADC co którąś próbkę, jeśli masz taką potrzebę.
    Można opalić timer w trybie CTC z preskalerem 1024 i ICR ustawić na 122. Wtedy osiągniesz przepełnienie zegara miej więcej z częstotliwością 128 Hz. W przerwaniu od timera ustawiać flagę, a w przerwaniu od ADC wysyłać dane tylko gdy flaga jest ustawiona, a następnie ją resetować.

    0
  • #15
    Poziom 13  

    Interesuje mnie mnie próbkowanie 128 pps z zapisem na SD, ponieważ będę musiał skorelować dane. Czy znalazłbyś czas na przedstawienie kodu?

    0
  • #16
    Poziom 18  

    pimpuk napisał:
    Można opalić timer w trybie CTC z preskalerem 1024 i ICR ustawić na 122. Wtedy osiągniesz przepełnienie zegara miej więcej z częstotliwością 128 Hz. W przerwaniu od timera ustawiać flagę, a w przerwaniu od ADC wysyłać dane tylko gdy flaga jest ustawiona, a następnie ją resetować.

    Inaczej: włączyć Auto Triggering dla ADC, jako źródło wyzwalania Compare Match B lub Overflow timera1. Jeśli przerwanie timera nie jest potrzebne to zdefiniować jako puste (EMPTY_INTERRUPT(TIMER1_OVF_vect)) albo w ogóle nie definiować, ale wtedy w przerwaniu ADC należy kasować flagę przerwania od timera.

    1
  • #17
    Poziom 13  

    Rozumiem, a jak wyglądałby kod na ustawienie fs 128 Hz?

    0
  • #18
    Poziom 22  

    Jeśli chcesz mieć wyzwalanie ADC od porównania Timer1, to musisz dopisać w setupie:

    Kod: c
    Zaloguj się, aby zobaczyć kod

    No i puść zegar.
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Powinno wyzwalać ADC 128 razy na sekundę.
    Piszę na szybko w pracy, bez patrzenia w manual, więc mogłem coś pochrzanić.
    Wieczorem to sprawdzę.

    0
  • #19
    Poziom 13  

    Rozumiem. Jak wyglądałoby od strony pętli głównej?

    0
  • #20
    Poziom 22  

    bikemc napisał:
    Rozumiem. Jak wyglądałoby od strony pętli głównej?

    No nijak.
    ADC wyzwalany jest przerwaniem od timera. Po zakończeniu konwersji przerywany jest program i wywoływana jest funkcja ISR(ADC_vect).
    Po prostu chodzi o to, aby ADC był wyzwalany 128 razy na sekundę, czym zajmuje się nasz timer.

    0
  • #21
    Poziom 13  

    Coś mi nie działa :( Wynik programu to:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    Kod programu:

    Kod: c
    Zaloguj się, aby zobaczyć kod

    0
  • #22
    Poziom 9  

    pimpuk napisał:
    W setupie dopisz:
    Kod: c
    Zaloguj się, aby zobaczyć kod



    Próbuję dobrze zrozumieć co autor miał na myśli i proszę o potwierdzenie, że dobrze rozumuję, lub poprawienie mnie:
    1. PRR &= ~(1 << PRADC);
    PRADC - to stała binarna, która wskazuje, który bit odpowiada w rejestrze PRR za uśpienie ADC. JEDYNKA usypia ADC, ZERO nie-usypia. Dla pewności, aby ADC nie było uśpione, wpisujemy zero. "~" neguje nam wzkazaną jedynkę, czyli dla wszystkich pozostałych bitów mamy jedynki, a "&=" robi sumę z obecną zawartością rejestru PRR, a tym, co chcemy wpisać. W rejestrze PRR zostają więc stare wartości, tam gdzie podajemy "1" i wpisane zostaje "0" tam gdzie eskazuje PRADC.

    Zadziała dla UNO, ponieważ jest tam jeden rejestr PRR. Nie zadziała dla Arduino Mega, w uC Atmel 2560 są dwa rejestry PRR0 i PRR1, należało by wskazać ten odpowiedzialny za konkretny port na którym mamy ADC. Innych not katalogowych nie sprawdzałem.

    2. ADMUX = (1 << REFS0)  | (0 << ADLAR) | (0x00 << MUX0);
    Do rejestru ADMUX ustawiamy bity:
    REFS0 = 1
    z zapisu wynika, że REFS0, zostaje ustawiony na "0", co oznacza, że napięcie referencyjne pobieramy z zewnętrznego pinu AVCC, czyli nie korzystamy z żadnego napięcia referencyjnego, a zasilanie traktujemy jako napięcie referencyjne - AVcc jest podawane na AREF.

    ADLAR - ustawiamy na "zero" = dwa najstarsze bity wyniku z ADC są w rejestrze starszym ADCH, a pozostałe osiem młodszych bitów w rejestrze młodszym ADCL. Jedynka powoduje, że osiem starszych bitów wyniku ADC jest w ADCH, a dwa najmłodsze w ADCL. Jak wiadomo ADC jest 10-bitowe i wynik musi zająć 2 bajty. Nie dochodziłem, po co nam zmiana formy zapisu i chyba nie jest tutaj istotna. Zakładam, że podana wersje jest wersją defaultową.

    0x00 << MUX0 - cztery zera ustawia nam, że do ADC podłączamy wejście ADC0 (dla Uno sprawa jest prosta). Nie kumam, co oznacza dla 2560, w którym np. wartość 0b001000 oznacza, ze na "dodatnie wejście różnicowe" podajemy ADC0, a na "ujemne wejście różnicowe", podajemy to samo ADC0??? Rozumiem, że możemy takimi ustawieniami zmierzyć napięcie różnicowo pomiędzy dwoma wejściami ze wzmocnieniem x10, albo x200, albo bez wzmocnienia x1. Ale jaki jest sens podawania na oba wejścia różnicowe tego samego wejścia uC? Po to aby zmierzyć napięcie niezrównoważenia??

    I na koniec robimy sumę logiczną wszystkich bitów tego rejestru i wpisujemy do rejestru ADMUX.

    CDN. ale może ktoś się odniesie do moich pytań, puki jest ich mało. ;-)

    0
  • #23
    Poziom 22  

    1. PRR, jest to Power Reduction Register. Aby korzystać z ADC, bit PRADC musimy wyzerować.
    Nie wiem jakie rejestry są domyślnie ustawiane przy starcie Arduino. Nie wiem czy ADC startuje, czy może trzeba go uruchamiać jakimś magicznym pinMode.
    Na wszelki wypadek wyzerujemy sobie ten PRADC.
    Jeśli ustawienie bitu wygląda tak: PRR |= (1 << PRADC);
    To zerowanie wygląda tak: PRR &= ~(1 << PRADC);
    Dla mnie jest to najbardziej naturalna opcja i najłatwiej jest mi ten zapis zapamiętać oraz zrozumieć.
    Jak masz inne nawyki, to możesz pisać nawet tak: PRR = b00000001;

    JanuszKornas napisał:

    Zadziała dla UNO, ponieważ jest tam jeden rejestr PRR. Nie zadziała dla Arduino Mega,


    No w temacie jest mowa o UNO, więc w czym problem?

    2. ADMUX = (1 << REFS0) | (0 << ADLAR) | (0x00 << MUX0);

    REFS0 = 1, REFS1 = 0 napięcie referencyjne ustawione na AVcc.
    ADLAR ustawia kierunek konwersji, 0 z prawej do lewej, 1 na odwrót.
    Jak wyżej, nie wiem co robi Arduino i w jakich rejestrach grzebie, my musimy mieć wyzerowany ADLAR.
    0x00 << MUX0, tak jak napisałeś sprawa jest prosta.
    Jeśli chodzi o te "wejścia różnicowe", to pewnie masz na myśli komparator, bo to działa na zasadzie porównywania wartości na dwóch wejściach.
    Jeśli wartość odczytana na wejściu 1 przekroczy (lub spadnie poniżej, to zależy od ustawienia) wartość odczytaną na wejściu 2,
    to komparator wywołuje przerwanie od wektora ANA_COMP_vect.
    Te wejścia 1 i 2 są oczywiście umowne, bo tam jest ogólnie sporo ustawiania, ale to może innym razem.
    ADC może pracować w kilku trybach ustawianych w rejestrze ADCSRB.
    W naszym przypadku mamy: ADCSRB = (0x00 << ADTS0) | (0 << ACME).
    0x00 << ADTS0, mamy free running mode.
    0 << ACME, komparator analogowy jest wyłączony.
    Jeśli chcemy włączyć obydwa wejścia komparatora, to musimy wyłączyć ADC, czyli w rejestrze ADCSRA ustawić ADEN na 0, ADCSRA&=~(1<<ADEN)

    1