Skocz do zawartości

Enkoder AS5040 - sygnał analogowy


Fifou

Pomocna odpowiedź

Można i tak, chociaż jest to łapanie się prawą ręką za lewe ucho. Skoro masz już sygnał PWM o dobrze opisanej charakterystyce, to dlaczego nie mierzysz go bezpośrednio? 1kHz to żadna częstotliwość a pomiar wypełnienia w zakresie 1/1025 do 1024/1025 za pomocą wejścia Input_Capture da Ci dokładność absolutną. Filtr analogowy a potem pomiar tego przetwornikiem wprowadzi opóźnienie wielu okresów PWM no i błędy kwantyzacji, zera, skali i liniowości ADC.

Link do komentarza
Share on other sites

Dobra sugestia. Tylko że wykorzystując wejście Input_Capture mogę zapisywać stan licznika reagując tylko na zbocze opadające. Jak wtedy wykrywać zbocze rosnące tak aby to wykrycie powodowało wyzerowanie licznika ?

[edit]

Wpadłem na pomysł, żeby w przerwaniu od wykrycia zbocza zmieniać rejestr decydujący o wykrywanym zboczu, i tak na zmianę. Wypali to ? I jeszcze pytanie czy reszta połączeń na schemacie jest ok ?

Link do komentarza
Share on other sites

Ponieważ oba kierunki są ważne, będziesz musiał przestawiać w każdej obsłudze przerwania stan bitu ICES na przeciwny - tak się dekoduje np. kody RC5. A potem no to już odczyt zatrzaśniętej wartości z ICR i w zależności od kierunku zbocza zapamiętanie jej jako wartości startowej początku stanu 1 lub obliczenie wypełnienia z jednoczesnym zapamiętaniem wartości startowej początku następnego okresu.

Rozwiązanie ma tę wadę, że czas reakcji rzędu 1us - bo taki jest tu najkrótszy mierzony impuls, będzie możliwy tylko w przypadku gdy AVR puszczony na 20MHz nie będzie obsługiwał żadnych innych przerwań. Krytyczny jest czas od pojawienia się zbocza do odczytu ICR i zmiany ICES. Potem obsługa przerwania może robić co chce, bo nawet jeśli w tym czasie przyjdzie kolejne zbocze, stan timera zostanie prawidłowo zatrzaśnięty w "uwolnionym" ICR a po wyjściu z obsługi procesor od razu wejdzie do kolejnej dzięki ustawionej fladze ICF. Procesor nie będzie zostawał w tyle za czasem rzeczywistym, bo następne przerwanie po takim spiętrzeniu przyjdzie dopiero za 1ms.

Bez takiego napinania czasowego można by to zrobić na xMega, która ma osobne łapanie obu zboczy lub na timerach w STM32. Nowsze/większe procesory mają bogatsze timery i takie funkcje jak badanie wypełnienia PWM są jednym z typowych trybów pracy.

EDIT: Pisaliśmy razem 🙂 Tak, reszta wygląda OK. No, może uważałbym na LEDy. Dopuszczalny prąd wyjść to 4mA więc wypadałoby przeliczyć oporniki szeregowe.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Da się pod kilkoma warunkami:

- puszczasz procesor na 20MHz,
- wszystkie inne przerwania oprócz tego od wejścia ICn są zablokowane,
- obsługę przerwania od ICn piszesz w asemblerze.

To pierwsze: wiadomo, do bicia rekordów trzeba mieć szybki pojazd. I tak masz tylko 20 cykli zegara CPU od pojawienia się zdarzenia w systemie przerwań (zsynchronizowanego zbocza z wejścia ICn) do odwrócenia bitu ICES.

Drugie: jeżeli całego czasu mamy 1us, to bijemy się o pojedyncze cykle zegara procesora a obsługa jakiegokolwiek przerwania w jednopoziomowym systemie przerwań AVR opóźnia nam wejście naszego przynajmniej o całe mikrosekundy. UART będzie musiał być obsłużony przez odpytywanie w pętli głównej.

Trzecie: jeżeli naprawdę dobrze czujesz się w AVRGCC, może próbować napisać to w czystym C. Przede wszystkim musisz zrezygnować ze standardowych prologów i epilogów generowanych przez kompilator (atrybut ISR_naked). Potem musisz napisać własny prolog zachowujący na stosie tylko naprawdę niezbędne dwa-trzy rejestry, wczytać do nich (zmienna z atrybutem register) zawartość ICR (co nie zmienia flagów procesora), odwrócić stan ICES (a to już zmienia 🙁 i dlatego musisz wcześniej zachować rejestr SREG) i dopiero wtedy masz czas na zachowanie następnych rejestrów na stosie i konieczne obliczenia. Równie dobrze możesz już w tym miejscu wywołać normalną funkcję w C, która sama zadba o niszczone przez siebie rejestry. Epilog obsługi przerwania oraz RETI też musisz dopisać sam. W C da się, ale w asemblerze będzie to wszystko bardziej naturalne.

Mała pociecha: mega8 w odróżnieniu od swych większych sióstr używa szybkich, jednocyklowych instrukcji IN/OUT żeby dostać się do rejestrów Timera 1 - to kwestia adresu jaki ATMEL przeznaczył na te rejestry. Niektóre ATmegi mają tak wiele peryferiów, że dostały one adresy przekraczające możliwości adresowania typowych IN/OUT i wtedy trzeba używać dłuższych (2 cykle) LDS/STS.

  • Lubię! 1
Link do komentarza
Share on other sites

Pierwsze: Czyli dołączyć oscylator zewnętrzny ? Z dokumentacji Atmegi wynika że wewnętrzny zegar ma max 8 Mhz.

Drugie: Jak się blokuje przerwania ? Czy tu chodzi po prostu o brak wywołania ?

Trzecie: Czyli kod może być napisany w AVRGCC, ale samo przerwanie w assemblerze ? Jak korzystać z komend assemblera w c ?

Link do komentarza
Share on other sites

Ojej, jednak łatwo nie pójdzie..

1. Tak, musisz odpalić procesor z kwarcu 20MHz lub z zewnętrznego generatora.

2. System przerwań w AVR jest dość prosty: każde źródło (np. ADC, timer, wejście INTx) ma swoją flagę zezwolenia na zgłaszanie (się). Wszystkie zgłoszenia które przedarły się przez własne blokady (np. bit ADIE w ADC) są sumowane priorytetowo (żeby było wiadomo kto zwycięża przy jednoczesnych zgłoszeniach z kilku źródeł) a suma jest jeszcze przepuszczana przez globalną flagę I (rejestr SREG) manipulowaną instrukcjami SEI i CLI. Jeżeli sygnał przerwania przejdzie przez to wszystko, jest "zauważany" przez sekwencer procesora i wymusza skokową zmianę licznika instrukcji tak, by zacząć wykonywanie programu od tzw. wektora czyli adresu sprzętowo skojarzonego z danym źródłem przerwania. Masz więc możliwość wybiórczego blokowania każdego źródła osobno jak i globalnego wyłączania "na szybko" wszystkich przerwań. Jeżeli musisz- tak jak w omawianym przypadku - obsługiwać jedno i tylko jedno źródło, włączasz tylko je (ustawiasz bit TICIE1 w TIMSK) oraz oczywiście odblokowujesz system przerwań funkcją sei().

3. Tak, możesz mieszać kod przez wstawki asemblerowe do pliku .c lub przez stworzenie osobnego pliku .a z kodem tylko w asemblerze. Będzie to traktowane jak osobno kompilowany moduł, linkowany potem z resztą programu. Zacznij od czytania dokumentacji AVRGCC - to chyba nie jest miejsce na tak obszerne wyjaśnienia. Pierwszy sposób służy raczej do wstawiania prostych sekwencji kilku instrukcji. Obsługę przerwania napisałbym całą w osobnym pliku .a.

Czytanie możesz zacząć od tego:

http://people.ece.cornell.edu/land/courses/ece4760/GCC_asm/

http://www.nongnu.org/avr-libc/user-manual/group__asmdemo.html

Powodzenia 🙂

Link do komentarza
Share on other sites

Napisałem cały kod w assemblerze. Podczas debugowania dane z rejestru TCNT nie są przepisywane do ICR1. Trzeba to zrobić sztucznie w przerwaniu ? Czy oprócz tego kod jest ok ?

.include "m8def.inc"
.org 0x00
rjmp RESET
.org 0x05
rjmp TIM1_CAPT

TIM1_CAPT:

;zapisanie czasu zdarzenia
in R20, ICR1L
in R21, ICR1H
;zerowanie timera
ldi r18, 0x00
out TCNT1L, r18
out TCNT1H, r18

;negacja bitu zbocza
ldi R17, 0b01000000
in R16, TCCR1B
eor R16, R17
out TCCR1B,R16

reti
RESET:


ldi R16,low(ramend) ;stos
out SPL, R16 ;do rejestru adujemy wartosc
ldi R16, high(ramend) ;
out SPH, R16 ;górna czesc rejestru Stack Pointer

;konfiguracja IC
ldi r16, (1<<TICIE1)
out TIMSK, r16
ldi r16,(1<<ICES1)|(1<<CS10)
out TCCR1B, r16

;konfiguracja UART
ldi r16, 129 ;f_osc=20Mhz, Baudrate=9600
out UBRRL, r16
ldi r16, 0 ;f_osc=20Mhz, Baudrate=9600
out UBRRH, r16
ldi r16, (1<<TXEN) ;tylko transmisja
out UCSRB, r16
ldi r16, (1<<UCSZ0)|(1<<UCSZ1) ;8 bitów
out UCSRC, r16

sei

LOOP: ętla nieskonczona

;najpierw ramka 0xFF (jeśli liczymy czas 1) lub 0x00 (jeśli liczymy czas 0), potem ICRL i ICRH
ldi r16, 0x00
in r22, TCCR1B
sbrs r22,6 
ldi r16, 0xFF
rcall WYSLIJ
mov r16,r20
rcall WYSLIJ
mov r16, r21
rcall WYSLIJ

rjmp LOOP

WYSLIJ:
sbis UCSRA,UDRE ; czekaj na wolny rejestr
rjmp WYSLIJ
out UDR,r16 ;dane do rejestru
ret
Link do komentarza
Share on other sites

Na początek nieźle, ale:

- Nie potrzebujesz skoku "rjmp TIM1_CAPT" bo przecież nie używasz żadnych innych wektorów przerwań i funkcja TIM1_CAPT może leżeć dokładnie w miejscu wektora przerwania.

- Nie musisz zerować timera. Przecież i tak za każdym razem łapiesz jego stan. Zapamiętuj czy jest to początek czy koniec impulsu i odpowiednio odejmuj wartości. Niech sobie timer chodzi w kółko. Odejmowanie działa prawidłowo nawet jeśli późniejsza chwila będzie złapana po przekręceniu się timera i będzie miała zapamiętaną mniejszą wartość niż wcześniejsza. Np. jeśli start impulsu dostał "znacznik czasu" 0xFF00 a potem koniec został złapany przy 0x0200 to: 0x0200-0xFF00=0x300 co jest wartości prawidłową, minęło przykładowe 0x0300 zegarów.

- Nie zrobiłeś żadnej synchronizacji między dwoma współbieżnymi procesami. Zastanów się co się stanie, gdy przerwanie wejdzie między wysłaniem pierwszego i drugiego bajtu przez UART.

- Moim zdaniem, po jak najszybszym odczytaniu zawartości ICR i odwróceniu stanu ICES (przy okazji: nie musisz za każdym razem przeładowywać R17, ustaw go sobie raz a dobrze a potem w przerwaniu z tego korzystaj) powinieneś wykonać obliczenia związane z długością impulsu (co drugie przerwanie) na podstawie wartości zapamiętanej jako początek i sprawdzić jakąś flagę. Jeżeli jest wyzerowana, możesz zapisać nową wartość wypełnienia do zmiennej globalnej. Program główny powinien z kolei sprawdzać stan tej flagi i odczytywać wartość wypełnienia tylko gdy jest ona ustawiona, wysyłać przez UART i dopiero wtedy kasować flagę. Typowa synchronizacja producenta i konsumenta danych. Zaważ, że przy 9600 wysyłanie kilku bajtów trwa więcej niż pomiar jednego okresu 1ms więc dane będą "produkowane" szybciej niż wysyłane. Albo nie będziesz mierzył każdego okresu albo nie będziesz wysyłał każdego pomiaru - i tak musisz się zdecydować, a synchronizacja może działać i tak i tak.

Jeśli masz już procesor, to w nim licz całkowitą długość okresu i długość stanu wysokiego - przecież obie wartości masz wprost z pomiaru - wystarczy odpowiednio odejmować złapane stany timera. Możesz nawet podzielić jedno przez drugie i dostać wprost wypełnienie i dopiero to wysyłać. Po co wysyłać jakieś gołe wartości z timera?

Link do komentarza
Share on other sites

Poniżej zmieniony kod. Przed wysyłaniem UARTem wyłaczam przerwania, tak żeby nie weszło pomiędzy wysyłane bajty. Po wysłaniu następuje pętla czekania ok. 2.5/ms, czyli ma czas na zmierzenie dwóch okresów. Wysyłam dalej gołe wartości z timera, ponieważ wolę operować na liczbach w c niż w asemblerze. Dane otrzymuje PC, który będzie je przetwarzać właśnie w c. Nie wiem do końca jak zrobić to odejmowanie czasów w asemblerze, także wolałbym zostać przy zerowaniu timera w przerwaniu, oczywiście jeśli nie powoduje to jakiś problemów. Dalej jest problem z brakiem przepisywania danych z TCNT do ICR podczas debugowania w AVR Studio. Według dokumentacji Atmega powinna automatycznie je przepisać po wystawieniu flagi ICF, czyli po wywołaniu przerwania.

[EDIT]

Chociaż dobrze by było liczyć już wypełnienie i wysyłać tylko to. Tylko jak np. dzielić 16-bitowe rejestry ?

.include "m8def.inc"
.org 0x00
rjmp RESET

.org 0x05
;zapisanie czasu zdarzenia
in R20, ICR1L
in R21, ICR1H
;zerowanie timera
ldi r18, 0x00
out TCNT1L, r18
out TCNT1H, r18
;negacja bitu zbocza
in R16, TCCR1B
eor R16, R17
out TCCR1B,R16
reti


RESET:


ldi R16,low(ramend) ;stos
out SPL, R16 ;do rejestru adujemy wartosc
ldi R16, high(ramend) ;
out SPH, R16 ;górna czesc rejestru Stack Pointer

;konfiguracja IC
ldi r16, (1<<TICIE1)
out TIMSK, r16
ldi r16,(1<<ICES1)|(1<<CS10)
out TCCR1B, r16

ldi R17, 0b01000000

;konfiguracja UART
ldi r16, 129 ;f_osc=20Mhz, Baudrate=9600
out UBRRL, r16
ldi r16, 0 ;f_osc=20Mhz, Baudrate=9600
out UBRRH, r16
ldi r16, (1<<TXEN) ;tylko transmisja
out UCSRB, r16
ldi r16, (1<<UCSZ0)|(1<<UCSZ1) ;8 bitów
out UCSRC, r16

sei

LOOP: ętla nieskonczona
ldi R29,0xF2
rcall POCZEKAJ
cli
;najpierw ramka 0xFF (jeśli liczymy czas 1) lub 0x00 (jeśli liczymy czas 0), potem ICRL i ICRH
ldi r16, 0xFF
in r22, TCCR1B
sbrs r22,6 
ldi r16, 0x00
rcall WYSLIJ
mov r16,r20
rcall WYSLIJ
mov r16, r21
rcall WYSLIJ
sei

rjmp LOOP

WYSLIJ:
sbis UCSRA,UDRE ; czekaj na wolny rejestr
rjmp WYSLIJ
out UDR,r16 ;dane do rejestru
ret

CZEKAJ:
inc R28
brne CZEKAJ
ret

POCZEKAJ:
rcall CZEKAJ
inc R29
brne POCZEKAJ
ret
Link do komentarza
Share on other sites

Przecież już samo wysyłanie jest opóźnieniem. Nie wiem po co dodałeś kolejne.

Jak myślisz co się stanie gdy tak po prostu zablokujesz przerwania? Zbocze na IC1 i tak się pojawi, timer zostanie zatrzaśnięty w ICR1 a flaga zgłoszenia zostanie ustawiona. Timer dalej będzie liczył, kolejne wartości timera będą nadpisywać poprzednie aż wreszcie zrobisz sei() i zauważysz zgłoszenie. Natychmiast wejdziesz do czekającego zgłoszenia, odczytasz jakąś dziwną, ostatnią wartość z ICR1 i powiesz: procesor mi się popsuł bo liczba wygląda na przypadkową. Zamiast na szybko zmieniać kod wg pomysłów ad hoc włóż ręce pod.. krzesło i zacznij myśleć. Masz dwa procesy, musisz obsługiwać każde przerwanie żeby nie stracić synchronizacji ze zdarzeniami zewnętrznymi dzięki czemu możesz liczyć długość każdego okresu i długość każdego stanu wysokiego a więc i każde wypełnienie. Teraz pomyśl jak tak skrupulatnie policzony wynik bezpiecznie przesłać do programu głównego lub - z innego punktu widzenia - jak bezpiecznie go odczytać. Na tym etapie nie potrzebujesz nowego kodu asemblerowego tylko chwilę zastanowienia i opisania tego własnymi słowami.

Przy okazji: nie wystarczy dzielić 16/16 bitów, bo jaki wynik dostaniesz gdy czas stanu wysokiego zmierzysz np. na 780 a okres na 1001(us)? Podziel na int-ach 780/1001. Dlatego proszę o chwilę zastanowienia.

Jeżeli nie wiesz jak odejmować 16 bitów, spojrzyj do kodu wynikowego kompilatora. W C odjęcie dwóch uint16_t to przecież jedna linijka kodu. Zobacz jak kompilator to rozwija i zrób tak samo. Akurat odejmowanie (w odróżnieniu od dodawania) w AVR jest proste.

Jeżeli nie chcesz dzielić w asemblerze (nie dziwię Ci się), możesz wysyłać dwie liczby: długość okresu i długość stanu wysokiego zmierzone za ten sam okres sygnału wejściowego. PC sobie policzy sobie co potrzebuje a przynajmniej dane będą spójne.

Link do komentarza
Share on other sites

Opóźnienie, podczas którego przerwania są włączone, dodałem po to, żeby procesor "zaktualizował" czasy. Pierwszy pomiar będzie, tak jak napisałeś, "dziwną, ostatnią wartością z ICR", ale następny będzie już dobry (ponieważ czas, kiedy włączone są przerwania trwa trochę więcej niż 2 okresy PWM enkodera). Wtedy, jeśli przerwanie będzie na zmianę zapisywało czas stanu niskiego i stanu wysokiego w różnych rejestrach (czas stanu wysokiego R26:R25, czas stanu niskiego R24:R23), będę mógł po wyłączeniu przerwań spokojnie wysłać oba czasy i będą to czasy z tego samego okresu, ewentualnie z dwóch kolejnych jednak w moim przypadku zmiany są na tyle małe, że nie robi to różnicy przy pomiarze co 1 ms. Mając oba czasu bez problemu obliczę wypełnienie.

Przerwanie by wyglądało wtedy tak:

.org 0x05
;zapisanie czasu zdarzenia

in R19, ICR1L
in R20, ICR1H
;zerowanie timera
ldi r18, 0x00
out TCNT1L, r18
out TCNT1H, r18

;zapis do odpowiedniego rejestru
in r22, TCCR1B
sbrc r22,6
mov r23,r19
sbrc r22,6
mov r24, r20
sbrs r22,6
mov r25, r19
sbrs r22,6
mov r26, r20


;negacja bitu zbocza
in R16, TCCR1B
eor R16, R17
out TCCR1B,R16
reti
Link do komentarza
Share on other sites

Trochę to prymitywne, ale przyjmuję taki sposób. Jeżeli rzeczywiście nie musisz mieć każdego okresu zmierzonego, Twój pomysł zadziała 🙂

Teraz skup się nad minimalizacją czasu zbocze IC1 → odczyt ICR i odwrócenie ICES, bo przecież od tego wszystko się zaczęło. Dopiero te dwie operacje kończą krytyczną sekcję obsługi przerwania i dalej możesz robić obliczenia i co tam trzeba. Spróbuj przenieść odwracanie bitu tuż za odczyt rejestru. I pamiętaj, że operacje arytmetyczne i logiczne zmieniają stan bitów stanu procesora. Jeżeli będziesz chciał robić jakąś arytmetykę w programie głównym, musisz w obsłudze przerwania zachowywać rejestr SREG.

Zerowanie timera możesz wykonywać z użyciem stałej przechowywanej "na stałe" w R18.

Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.