logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[mega16][asm AvrStudio] rcall przeszkadza USARTowi ?

liba 22 Sie 2008 17:18 1503 5
REKLAMA
  • #1 5463400
    liba
    Poziom 14  
    Witam kolegów,
    mam dość dziwny kłopot. Poniższy program zawiera jedynie procedury służące do komunikacji Atmega16 z PC. Jest tu obsługa wejściowego i wyjsciowego bufora kołowego o pojemności 255znaków. Pętla główna jest w zasadzie pusta.

    W celu przetestowania każdy przychodzacy znak jest dodawany do bufora wyjściowego i odsyłany (echo). Program testuje Bray terminalem wysyłając paczki po kilkadziesiąt znaków. Wszystko działało bez zarzutu, dopóki nie dodałem skoku rcall w pętli głównej programu Kiedy pętla główna składa się wyłącznie z kilku nop paczki wracają bez błędów, a kiedy jest taki krótki rcall lub call jak w przykładzie to ok.50% danych wraca wykrzaczona. Błędów jest tym więcej im mniej nop w pętli głównej. Umieszczanie w petli głównej innych rozkazów, skoków rjmp, jmp, add, or nie powoduje błędów.

    Co Wy na to? Nie ma to chyba znaczenia ale ustawiłem 9600bps przy kwarcu 16MHz. Uzywam przejściówki USB-RS232 na Atmega8 wg Osama Tamury.

    Oto program:
    
     	.nolist	 	;
     	.include	 "m16def.inc"	;
     	.list	 	;
     	.listmac
    ;===== definicje
    ;komunikacja, bufory wejściowy i wyjściowy
    .equ outBufferStart = $0100
    .equ outBufferLength = 250
    .def outBufferReadPos = R2
    .def outBufferWritePos = R3
    .def SendChar = R22
    .equ inBufferStart = $0200
    .equ inBufferLength = 250
    .def inBufferReadPos = R5
    .def inBufferWritePos = R6
    
    ;--- wektor przerwań
    .cseg
    .org $000 jmp RESET ; Reset Handler
    .org $002 reti ;jmp EXT_INT0 ; IRQ0 Handler
    .org $004 reti ;jmp EXT_INT1 ; IRQ1 Handler
    .org $006 reti ;jmp TIM2_COMP ; Timer2 Compare Handler
    .org $008 reti ;jmp TIM2_OVF ; Timer2 Overflow Handler
    .org $00A reti ;jmp TIM1_CAPT ; Timer1 Capture Handler
    .org $00C reti ;jmp TIM1_COMPA ; Timer1 CompareA Handler
    .org $00E reti ;jmp TIM1_COMPB ; Timer1 CompareB Handler
    .org $010 reti ;jmp TIM1_OVF ; Timer1 Overflow Handler
    .org $012 reti ;jmp TIM0_OVF ; Timer0 Overflow Handler
    .org $014 reti ;jmp SPI_STC ; SPI Transfer Complete Handler
    .org $016 jmp USART_RXC ; USART RX Complete Handler
    .org $018 reti ;jmp USART_UDRE ; UDR Empty Handler
    .org $01A jmp USART_TXC ; USART TX Complete Handler
    .org $01C reti ;jmp ADC ; ADC Conversion Complete Handler
    .org $01E reti ;jmp EE_RDY ; EEPROM Ready Handler
    .org $020 reti ;jmp ANA_COMP ; Analog Comparator Handler
    .org $022 reti ;jmp TWSI ; Two-wire Serial Interface Handler
    .org $024 reti ;jmp EXT_INT2 ; IRQ2 Handler
    .org $026 reti ;jmp TIM0_COMP ; Timer0 Compare Handler
    .org $028 reti ;jmp SPM_RDY ; Store Program Memory Ready Handler
    
     RESET:
    ldi r16, high(RAMEND) ; Main program start
    out SPH, r16 ; Set Stack Pointer to top of RAM
    ldi r16, low(RAMEND)
    out SPL, r16
    ;komunikacja
    ldi R17, 0
    ldi R16, 103	;9600bps przy 16MHz
    out UBRRH, r17
    out UBRRL, r16
    ldi r16, (1<<RXCIE)|(1<<TXCIE)|(1<<RXEN)|(1<<TXEN)
    out UCSRB,r16
    ldi r16, (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1) ; Set frame format: 8data, disabled parity, 1stop bit
    out UCSRC,r16
    ser R16	;ustawia 255
    mov outBufferReadPos, R16
    mov outBufferWritePos, R16
    mov inBufferWritePos, R16
    mov inBufferReadPos, R16
    
    ldi SendChar, RReset
    rcall AddToOutBuffer
    
    sei ; Enable interrupts
    
    ;###### główna pętla programu #######
    MainLoop:
    
    ;rcall CzytajRozkaz
    nop
    nop
    nop
    nop
    nop
    nop
    
    rjmp MainLoop
    
    CzytajRozkaz:
      ret
    
    ;--- dodaje do bufora wejściowego przysłany USARTem znak
    USART_RXC:
      push R18
      in R18, SREG
      push R18
      ;sprawdzmy czy nie stracono danych
      sbic UCSRA, DOR ;Data OverRun
        sbi ErrorPort, ErrorPin
      ;ustalmy pozycje do zapisu
      inc inBufferWritePos
      ;zapis do bufora
      ldi XH, high(inBufferStart)
      ldi XL, low(inBufferStart)
      add XL, inBufferWritePos
      in R18, UDR
      st X, R18
      mov SendChar, R18
      rcall AddToOutBuffer	;echo
      ;koniec
      pop R18
      out SREG, R18
      pop R18
    reti
    
    
    ;--- dodaje do bufora wyjściowego znak z rejestru SendChar
    ; brak obsługi błędów przepełnienia, zakładam, że bufor jest wystarczająco duży
    AddToOutBuffer:
      cli
      in R18, SREG
      push R18
      ;ustalenie pozycji do zapisu, czy nie ma przepełnienia
      inc outBufferWritePos
      ;zapis do bufora
      ldi XH, high(outBufferStart)
      ldi XL, low(outBufferStart)
      add XL, outBufferWritePos
      st X, SendChar
      ;jeśli nic nie jest akurat wysyłane to wyślijmy
      sbic  UCSRA,UDRE
        rcall USART_TXC
    	cli
      ;koniec
      pop R18
      out SREG, R18
    ret
    
    
    ;--- wysyła dane z bufora przez USART
    USART_TXC:
      push R18
      in R18, SREG
      push R18
      ;czy w buforze jest coś jeszcze do wyslania?
      cp outBufferReadPos, outBufferWritePos
      breq UTXC_end ;bufor jest pusty
        ;jest coś do wysłania, ustalmy adres
    	inc outBufferReadPos
    	;wysyłamy
        ldi XH, high(outBufferStart)
        ldi XL, low(outBufferStart)
        add XL, outBufferReadPos
        ld R18, X
    	out UDR, R18
      UTXC_end:
      pop R18
      out SREG, R18
      pop R18
    reti
    


    Proszę o pomoc
  • REKLAMA
  • #2 5475516
    rfhzcx
    Poziom 14  
    W procedurze AddToOutBuffer rozkaz rcall do obsługi USART_TXC "gryzie" się z końcowym reti w procedurze USART_TXC.
    Najlepiej zmień ten rcall USART_TXC na bezpośrednie linie kodu - kod jest krótki a te cli, reti są mało czytelne.
    Pozdro
    AK
  • REKLAMA
  • #3 5482538
    liba
    Poziom 14  
    Dzięki za odpowiedź,

    rzeczywiście lepiej jest umieścić te kilka linijek kodu bezpośrednio w AddToOutBuffer i tak zrobiłem, ale problem nie ustąpił. Poprzedni kod, przynajmniej pod względem skoków (rcall) i powrotów (ret/reti), był raczej poprawny i to nie to jest chyba przyczyną problemu.

    Licze na dalsze rozważania...
  • REKLAMA
  • Pomocny post
    #4 5503276
    mm_pawel
    Poziom 11  
    Witam

    rcall nie przeszkadza USARTowi...
    i całe szczęście, bo musielibyśmy wyrzucić wszystkie AVRy do kosza ;)

    Problem jak zwykle tkwi w programie.

    W procedurze obsługi przerwania USART_TXC założyłeś, że możesz włożyć do UDR kolejny bajt, bez sprawdzania, czy jest on pusty. Poniekąd jest to założenie słuszne, ale nie w Twoim programie. A problem polega na tym, że również inny fragment programu (i również działający w przerwaniu) potrafi wpisać bajt do UDR.
    Wyobraź sobie taką sekwencję zdarzeń:
    - przychodzi przerwanie RXC - zaczyna się jego obsługa
    - w międzyczasie przychodzi przerwanie TXC - i zaczeka na koniec obsługi RXC
    - w wyniku działania przerwania RXC jest włożony bajt do UDR
    - przerwanie RXC kończy swoją pracę
    - uaktywnia się przerwanie TXC i bez sprawdzenia wpisuje kolejny bajt do UDR

    W dokumentacji nie jest dokładnie opisane, kiedy nastąpi przepisanie rejestru UDR do rejestru szeregowego, z którego następuje wysyłanie bajtu, ale jest to synchronizowane przez zegar nadajnika USART - a więc znacznie wolniej niż zegar mikrokontrolera. Tak więc jest duża szansa (a w zasadzie pewność), że jeden bajt zostanie nadpisany przez drugi.

    W ten sposób powstaje konflikt w nadajniku USART który skutkuje błędami w transmisji.

    Najprostsza poprawka w Twoim programie, to dołożenie sprawdzenia flagi UDRE w procedurze USART_TXC tak jak jest to w procedurze AddToOutBuffer.

    A teraz najciekawsze - dlaczego tylko przy rcall ujawnia sie ten błąd?

    To już będą tylko moje domysły, choć wydają mi się dość prawdopodobne.
    Odebranie bajtu skutkuje jego wysyłaniem z powrotem. Możemy założyć, że USART odbiera kolejny bajt z komputera synchronicznie nadając jeden z poprzednich bajtów. Skutkuje to prawie równoczesnym zgłoszeniem przerwań TXC i RXC. Ale przyjmijmy, że TXC jest zgłaszane przez USART trochę wcześniej np. jeden-dwa cykle zegarowe. Po zakończeniu wykonywania NOPa, program zaczyna obsługe przerwania TXC. Przerwanie RXC przychodzi chwilę później i jest obsługiwane w drugiej kolejności. Taka kolejność obsługi przerwań nie powoduje błędów.

    A co się dzieje, gdy dołożymy rcall?
    NOP jest wykonywany w jednym cyklu, więc decyzja o rozpoczęciu procedury przerwania jest prawie natychmiastowa.
    RCALL to 3 cykle, a RET to aż 4 cykle. Jeżeli przerwanie TXC trafi w jeden z tych rozkazów, to musi zaczekać na jego zakończenie. Jeżeli w tym czasie zgłosi się również przerwanie RXC, to po zakończeniu rozkazu (rcall,ret) nastąpi wykonanie przerwania, zgodnie z ich priorytetami.
    A RXC ma wyższy priorytet niż TXC. Więc przerwania zostaną wykonane w sekwencji RXC i TXC - a to powoduje błąd.

    Na koniec jeszcze uwaga do sekwencji reti i cli - którą już podobno usunąłeś. To nie jest tylko mało czytelne, ale też trochę groźne - włącza na moment flagę zezwolenia na przerwanie. W ten sposób Twoje przerwanie RXC mogło być przerwane przez inne przerwanie. Tu nie było to groźne, ale radzę unikać takich prowokacji - zawsze się kiedyś zemszczą.

    Pozdrawiam
    Paweł
  • REKLAMA
  • #5 5504620
    liba
    Poziom 14  
    HA, to jest dopiero rzetelna i wyczerpująca odpowiedź! Bardzo dziękuje.

    Miałeś racje, rzeczywiście sekwencja zdarzeń, którą opisujesz była powodem kłopotów. Po poprawce kodu i poradzeniu sobie ze sporadycznymi resetami (które przedtem pewnie też się zdarzały, ale informacji o tym nie dostrzegałem w gąszczu krzaczków :-) wszystko działa. Jeszcze raz dziękuje.

    Co do niebezpieczeństwa użycia rozkazu cli zaraz po reti... Nie mam w tej chwili dostępu do AVRstudio i nie sprawdziłem tego, ale gdy pisałem swój kod zasugerowałem sie tym zdaniem z manuala Atmegi16:
    Cytat:
    No interrupt will be executed after the CLI instruction, even if it occurs simultaneously with the
    CLI instruction

    Możliwe zatem, że jednak nie ma niebezpieczeństwa o którym mówisz... ale nie jestem pewien.
  • #6 5507660
    mm_pawel
    Poziom 11  
    Witam

    Co do RETI i CLI to rzeczywiście masz rację. Zacytuję inny fragment dokumentacji, o którym faktycznie zapomniałem, a który w połączeniu z Twoim cytatem opisuje to jednoznacznie:
    Cytat:
    When the AVR exits from an interrupt, it will always return to the main program and execute one more instruction before any pending interrupt is served.


    Mimo to, ja bym unikał takiego pisania programu ;)
    Pozdrawiam
REKLAMA