Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

Asembler Atmega regulator PI

as124 11 Maj 2007 23:52 2263 9
  • #1 11 Maj 2007 23:52
    as124
    Poziom 14  

    Buduję napęd silnika prądu stałego oparty na Atmedze8. I moim problemem jest implementacja algorytmu kaskadowego do sterowania silnikiem, a dokładniej regulatorów PI. (przepraszam za takie "mądre" określenie problemu, ale w ten sposób najłatwiej mi to opisać. Tak czy owak potrzebuje stworzyć program w asemblerze na Atmegę który wykona zadanie regulatora PI.
    Przechodząc do sedna sprawy muszę wykonać następujące operacje:

    1. Obliczyć błąd uchybu e=Y0-Y (gdzie: Y0 to wartość zadana (8bitów) Y wartość zmierzona (też 8bitów)
    2. X1 = e pomnożone razy stałą wartość K1 (8 bitów).
    3. X2= e pomnożone razy druga stała K2 (8 bitów)
    4. In = I(n-1) + X2 // I(n-1) to wartość In z poprzedniej pętli algorytmu
    5 U=X1+I(n-1)

    Wynik musi być 8bitowy.

    No i problem dla mnie stanowi rozwiązanie kwestii przepełnień przy dodawaniu i określenia znaku przy czym wartości Y0, Y, U są zapisane w zwykłym kodzie dwójkowym.
    No i pozostaje problem mnożeń. Skoro pomnożę 2 liczby 8bitowe wynik otrzymam 16 bitowy żeby tego uniknąć musiałbym na początku zamienić wszystkie bajty na (w jakiś sposób) odpowiadające im liczby w kodzie 1N.7Q wykonać obliczenia i spowrotem wrócić do normalnego kodu dwójkowego. Tylko nigdzie nie mogę znaleźć sposobu takiej konwersji:(.
    Jest wogóle jakiś sposób na przechodzenie między tymi sposobami reprezentacji liczb? (Zdaję sobie sprawę że chcę zamienić liczby z przedziału (0,255) na liczby z przedziału (-1,0.99) dlatego piszę o liczbach "w jakiś sposób odpowiadających".

    Dodano po 3 [godziny] 53 [minuty]:

    Narazie mam coś takiego, (pkt1 czyli obliczenie uchybu):

    ldi R16, 100 ; ladowanie przykladowych wartosci Y0
    ldi R17, 130 ; i Y

    cp R16, R17 ; porównanie odjemnej i odjemnika
    brlo e_ujemne
    sub R16, R17 ; jesli wynik jest dodatni
    mov R18, R16


    e_ujemne:
    sub R17, R16 ; jesli wynik jest ujemny
    mov R18, R17

    0 9
  • #2 12 Maj 2007 11:06
    adamusx
    Poziom 27  

    Witam.

    Wydaję mi się że obliczenia powinieneś przeprowadzać na liczbach co najmniej 16 bitowych, 8 bitów może być za mało.
    Czy realizujesz regulację prędkości czy położenia takiego silnika?
    Ja osobiście implementowałem kilka regulatorów na AVRach, ale pisane były one w języku C. Między innymi typowy regulator PID, jednak by uzyskać dość dobrą dokładność takiego regulatora ( np możliwośc ustalania prędkoścu silnika zo do ułamków obr/s, lub położęnia w stopniach na obrót) operacje przeprowadzałem na liczbach zmiennoprzecinkowych.

    0
  • #3 12 Maj 2007 13:07
    as124
    Poziom 14  

    Niestety w moim wypadku w grę wchodzi jedynie asembler. A jak widać wyżej najlepszym rozwiązaniem będzie rozwiązanie najprostsze (mam mało czasu i zbyt mało doświadczenia).

    Co do wymogów regulacji nie są one zbyt wygórowane.
    Regulatory będą pracowały w strukturze kaskadowej (podporządkowanego prądu i nadrzędej regulacji prędkości) właściwie ważniejszym parametrem będzie ostateczne pozycjonowanie się napędu. Dziś rano doszedłem do takiego rozwiązania dodawania - chyba lepszego. (wcześniejsze nie uwzględniało liczb ujemnych). Właśnie to jest dla mnie główny problem że nie mając doświadczenia nie wiem którą drogę wybrać. Wynik podawany jest na 8 bitowy PWM, więc zwiększanie precyzji obliczeń do 16 jest chyba zbęne. Co więcej zrobiłem nawet coś w rodzaju ograniczenia histerezowego w wypadku przepełnienia sądzę że to uprości dalsze obliczenia.

    Main:


    ldi R16, 100 ; przykładowa wartości (w kodzie U2)
    ldi R17, 30
    add R16, R17

    brvs wynik_niepoprawny ; skocz jesli wystapilo przepelnienie (V w SREG =1)
    jmp koniec_dodawania ; jesli nie to wynik jest prawidlowy

    wynik_niepoprawny:
    brlt max_ujemny ; skocz jesli wynik jest ujemny z przepelnieniem
    ldi R16, 0b01111111 ; jesli bylo przepelnienie ale wynik dodatni to ustaw 127
    jmp koniec_dodawania

    max_ujemny: ; jesli wynik jest ujemny i mniejszy od -128 to
    ldi R16,0b10000000 ; ustaw wynik na -128

    koniec_dodawania:
    mov R18, R16


    jmp Main

    0
  • #4 14 Maj 2007 07:47
    Ch.M.
    Poziom 27  

    Witaj
    Nie wiem w czym widzisz problem, mnozysz czy dodajesz rownie dobrze liczby 32bitowe takze w asemblerze. Troszke wiecej problemy jest gdy je dzielisz, ale to tez do przeskoczenia (zamiana na ułamki i mnożenie) Duza liczba bitopw jest potrzebna gdy mnożysz liczby z granic przedziałów, czy ułamki, a tak to wystarczy ci najstarsze 8bitów z wyników operacji
    Pozdrawiam

    0
  • #5 14 Maj 2007 10:48
    as124
    Poziom 14  

    Tak tylko przy takim podejściu nie starczy mi miejsca w rejestrach a nie bardzo mogę wykorzystywać pamięć (zostało mi już jej stosunkowo mało). Ostatecznie wynik też musi być 8bitowy więc wolałbym ograniczać wynik w każdym możliwym momencie, ale znów nie chce ciągle wyniku zaokrąglać bo boje się czegoś co chyba najlepiej określa termin "efekt motyla". Dlatego na wstępie pytałem o radę kogoś doświadczonego w tymtemacie - niestety nikt się jednoznacznie nie wypowiedział czy do sterowania silnikiem np. takie ciągłe zaokrąglanie może być użyte. ;( A ja zbyt dużego doświadczenia jeszcze w tym nie mam.:( Więc kombinuje pewnie "aż śmiesznie". Narazie najlepszym wydaje mi się takie odejmowanie: (reszta kodu powoli powstaje, będę go tutaj sukcesywnie umieszczał.

    ; R30 A B C D E F G H // bit=1 +, bit=0 - //dodatkowy rejestr "statusu"

    // bit A odpowiada za znak Y0 (odjemna)
    // bit B odpowiada za znak Y (odjemnik)
    // bit C odpowiada za znak e (czyli też za znaki wynikow mnozenia K1*e, K2*e dla K1,K2>0 )
    // bit D to 9 bit e


    // bit G=1 - do zmiany reszty znacznikow
    // bit H=0 - do zmiany reszty znacznikow
    ldi R30, 0b00000010

    ldi R16, 240 ; przykładowa wartość Y0, puzniej jest tutaj e
    ldi R17, 240 ; Y
    ldi R18, 114 ; przykladowe K1 (normalny)
    ldi R19, 80 ; przykladowe K2 (normalny)
    ldi R20, 5 ; MSB I(n-1)
    ldi R21, 45 ; LSB I(n-1)
    ldi R22, 0 ; MSB I(n)
    ldi R23, 0 ; LSB I(n)
    ldi R24, 0 ; MSB U
    ldi R25, 0 ; LSB U


    ///////////////////////////////////////////////////////////////////////////e=y0-y
    bst R30, A
    brts plus_e ; skocz jesli odjemna jest dodatnia (A=1)
    jmp minus_e ; skocz jesli odjemna jest ujemna (A=0)


    plus_e:
    bst R30, B
    brts plus_plus_e ; skocz jesli odjemnik jest dodatni (B=1)

    ; odjemna +, odjemnik -
    ; plus_minus_e:
    add R16, R17
    brcs plus_minus_e_c1
    bst R30, 0
    bld R30, D
    bst R30, 1
    bld R30, C
    jmp koniec_e
    plus_minus_e_c1:
    bst R30, 1
    bld R30, D
    bst R30, 1
    bld R30, C
    jmp koniec_e

    ; odjemna +, odjemnik +
    plus_plus_e:
    cp R16, R17 ; porównanie odjemnej i odjemnika
    brlo plus_plus_e_ujemne
    ;plus_plus_e_dodatnie
    sub R16, R17 ; jesli wynik jest dodatni
    bst R30, 1
    bld R30, C
    bst R30, 0
    bld R30, D
    jmp koniec_e
    plus_plus_e_ujemne:
    sub R17, R16 ; jesli wynik jest ujemny
    bst R30, 0
    bld R30, C
    bst R30, 0
    bld R30, D
    jmp koniec_e


    minus_e:
    bst R30, B
    brts minus_plus_e ; skocz jesli odjemnik jest dodatni (B=1)

    ; odjemna -, odjemnik -
    minus_minus_e:
    cp R17, R16 ; porównanie odjemnej i odjemnika
    brlo minus_plus_e_ujemne
    bst R30, 1
    bld R30, C
    bst R30, 0
    bld R30, D
    minus_plus_e_ujemne:
    bst R30, 0
    bld R30, C
    bst R30, 0
    bld R30, D

    ; odjemna -, odjemnik +
    minus_plus_e:
    add R16, R17
    brcs minus_plus_e_c1
    bst R30, 0
    bld R30, C
    bst R30, 0
    bld R30, D
    jmp koniec_e
    minus_plus_e_c1:
    bst R30, 0
    bld R30, C
    bst R30, 1
    bld R30, D
    jmp koniec_e


    koniec_e: ; kod jest obszerny, ale uwzględniając ominięcie wszystkich połapek wykonuje się w małej liczbie cykli zegara. Nie uwzględniam przypadku Y=Y0 bo wtedy regulacja jest "niepotrzebna".

    0
  • #6 14 Maj 2007 14:15
    Ch.M.
    Poziom 27  

    tak jak poprzednio pisałem wszystko zalezy od wielkości liczb na których operujesz i w zależnoścido nich wybierasz odpowiedni sposób ich zapisu i operacji na nich wykonywanych.
    Z tego co widzę to będą to liczby całkowite także ujemne ale 7mio bitowe (kod U2) ale pomysł zamieniania je na inny kod jest... troszke naciągany: po co? Program sobie poradzi i tak
    Odejmujesz 2 liczby 8bitowe - wynik 8bitów (komenda sbc)
    Mnozysz 2 osmiobitowe ( z czego jedna ze znakiem - komenda mulsu)
    Bierzesz najstarsze 8 bitów (liczba 7bitowa ze znakiem) i dzielisz przez 8bitowa (komenda - fmulsu)

    Zakładając, że uchyb nie był w granicach 5bitów to otrzymasz dosyć precyzyjny wynik. Jeśli był większy to też nie bedzie zle ale na pewno uzyskasz dokładność rzędu +/- 2-3bitów

    Przy dużych różnicach obrotów dokładność regulatora jest mała ale przecież regulator dąży do zmniejszenia uchybu i steruje tak by zmniejszyc uchyb.

    Musisz poexperymentować z parametrami i na pewno będziesz zadwolony ze swego dziełą, a silnik to takie wdzięczne bydle, które ma duzą bezwładność co ułatwia zycie :)
    Pozdrawiam

    0
  • #7 14 Maj 2007 16:13
    as124
    Poziom 14  

    Właściwie wszystkie "liczby" które wchodzą do regulatora są 8 bitowe w kodzie normalnym. Zamiana na kod U2 to już mój pomysł (który mi się nie podoba bo mam wtedy 2 razy mniejszą precyzję) dlatego stwierdziłem, że wszystko zrobię w normalnym formacie liczb rzeczywistych a znak umieszczę w innym rejestrze (więc operuję jakby na kodzie U2 z 9 bitowym słowem).
    Głównym założeniem jakie sobie postawiłem to żeby:
    - nie korzystać z pamięci a tylko z rejestrów (wewnątrz algorytmu regulatora)
    - unikać zaokrąglania (więc np. właśnie przejścia z normalnego kodu na U2 )
    (jedyne zaokrąglenie w momencie wyjścia z sygnałem U (bo on itak musi mieć 8 bitów)
    Wadą jest napewno długi kod i duża ilość skoków (ale sam algorytm chyba będzi działał szybko - właśnie nad tym teraz się głowie ile taktów zajmie maksymalnie)
    Odrazu zgadzam się z zarzutem, że to co uzyskałem to przerost formy nad treścią ale chcę żeby kod działał jak najlepiej no i był możliwie czytelny (skoki jmp których być nie musi).

    Efekt dotychczasowej pracy umieściłem tutaj http://bielan124.w.interia.pl/regPI.txt (wklejenie kodu tutaj przeszkadzało by w czytaniu)
    Napewno gdzieś mam jakieś błędy - jeśli ktoś ma cierpliwość i przeanalizuje kod proszę o opinie i wskazanie ewentualnych błędów (gorąco dziś więc literówek pewnie będzie sporo) a główną zasadą projektowania jest że własnych błędów się nie widzi:(.
    Jeśli ktoś się pokusi o sprawdzenie będę bardzo wdzięczny i dziękuję.

    PS: Odnośnie ilości cykli zegara potrzebnych do obliczeń ( parę razy odpaliłem program w symulatorze [ i choć nie sprawdziłem wszystkich mozliwości] wynika że algorytm regulatora zabiera ok.80 cykli zważywszy że w datasheet Atmega8 znalazłem czas przetwarzania przetwornika A/C od 65-260 µs (używam go do pomiaru prądu) to itak będę musiał w programie czekać na następny pomiar.

    0
  • #8 15 Maj 2007 10:28
    Ch.M.
    Poziom 27  

    Ja używam ADC nieco mocniej... 1 pomiar jakoś mnie nie satysfakcjonuje, szczególnie ze ten pierwszy jest z reguły do bani. Mierzę 255 razy na wejście (biore pod uwage 8MSB) i sumuje a następnie biorę 8MSB i mam bardzo stabilny pomiar
    Zajmuje to trochę czasu ale w koncu nie pilotuje samolotu tym kontrolerem :)
    Jak znajde czas wieczorkiem to popatrze to co zamieściłes w linku
    Pozdrawiam

    0
  • #9 15 Maj 2007 12:13
    Dr_DEAD
    Poziom 28  

    as124 napisał:

    No i pozostaje problem mnożeń. Skoro pomnożę 2 liczby 8bitowe wynik otrzymam 16 bitowy żeby tego uniknąć musiałbym na początku zamienić wszystkie bajty na (w jakiś sposób) odpowiadające im liczby w kodzie 1N.7Q wykonać obliczenia i spowrotem wrócić do normalnego kodu dwójkowego. Tylko nigdzie nie mogę znaleźć sposobu takiej konwersji:(.
    Jest wogóle jakiś sposób na przechodzenie między tymi sposobami reprezentacji liczb? (Zdaję sobie sprawę że chcę zamienić liczby z przedziału (0,255) na liczby z przedziału (-1,0.99) dlatego piszę o liczbach "w jakiś sposób odpowiadających".

    Chcąc stworzyć szybki i dokładny algorytm musisz zastosować dwa rozwiązania:
    1. Odpowiednio dobrany format Q.
    2. Zaokrąglanie liczb poprzez wyrzucanie najmłodszych bitów.
    Procesory DSP mają przystosowane mechanizmy i instrukcje do tego celu, Ty musisz sobie poradzić z tym zestawem co masz ale nie jest to dużą przeszkodą.
    Jeżeli chodzi o format Q czyli stały przecinek to działa ona na takiej samej zasadzie jak zmienny przecinek z tą tylko różnicą że dla danego parametru np. wartości prądu, zgóry ustalasz w kórym miejscu ten przecinek będzie a dobierasz to na podstawie przyjętej maksymalnej wartości parametru. Strasznie zagmatwałem sprawę ale chodzi o to że tak naprawdę format Q istnieje tylko i wyłącznie w głowie programisty i nie ma żadnych algorytmów przechodzenia z i na ten format bo dla procesora nie ma to żadnego znaczenia. Format Q przydaje sie wtedy gdy wiesz że jakiś parametr będzie sie zmieniał w przedziale od 0 do 4, wtedy zapisanie go normalnie daje okropną kwantyzację (0x00,0x01,0x02,0x03,0x04) czyli dokładność jest żadna. W takim wypadku stosujesz format Q np Q6 i wtedy:
    3,984375 = 0xFF w Q6
    1,71875 = 0x6E w Q6
    2,625 = 0xA8 w Q6
    i rozdzielczość w zakresie 0 - 4 masz normalnie 255.
    Jak sam widzisz format Q istnieje tylko w głowie programisty i nie ma żadnego wpływu na algorytm.

    0
  • #10 15 Maj 2007 12:57
    as124
    Poziom 14  

    Tak tylko jakiegoś przeliczenia muszę dokonać. Operując na rejestrach 8 bitowych i mając liczbę z zakresu 0-255 muszę ją podzielić przez 2 (stracić najmłodszy bit) żeby zrobić miejsce na znak mieszczący się w rejestrze podobnie jest z przejściem na format 1N7Q tutaj liczbę z zakresu 0-255 muszę zamienić na odpowiednią w zakresie -1 do 0.99 (przynajmniej w swojej głowie) a przy takiej operacji i zapisaniu jej w ośmiu znakach tracę zawsze dokładność.
    Mniej więcej na tym mój problem polegał. Przynajmniej ja tak rozumiem, że ten problem jest przy "przechodzeniu" między formatami.

    No ale ostatecznie jakiś tam kod już stworzyłem (link jest we wcześniejszym postcie). Jest może trochę "nieudaczny" ale teraz pracuje nad płytką i jeśli algorytm zadziała to itak będę z siebie dumny:). Oczywiście nie omieszkam się efektami podzielić na forum.

    0