Push & pop - psują multitasking w AVR

0

Witam.
Dzisiaj udało mi się wyklepać multitasking na procesorku atmega8(8MHz).
Zrobiłem go na timerze, którego częstotliwość wynosi ok 5kHz. Przedtem miałem 100Hz i 1kHz lecz jednak te 5kHz muszą zostać, ponieważ procesor musi obrabiać dość szybko pewne 2 sygnały.
Wszystko robie w asm.
Procedurka obsługi timera wygląda tak:


timer0:
  
  ;zapisanie rejestru r16 aby odjac ze stosu 2B
  sts tmp_r16, r16
  pop r16
  pop r16
  lds r16, tmp_r16
  
  push r16;zapisanie rejestrow procesu
  push r17
  push r18
  push r19
  push r20
  push r21
  push r22
  push r23
  push r24
  push r25
  push r26
  push r27
  push r28
  push r29
  push r30
  
  ldi r16,56
  out TCNT0, r16
  
  lds r16, timerp;taki bajer...
  inc r16
  sts timerp, r16

  clr r17
  lds r16, Proc_ID
  cp r16, r17
  brne SWITCH1
  SWITCH0:
    in r16, SPL				;save stack
	sts Proc2_Stack, r16

    lds r16, Proc1_Stack	;load stack
    out SPL, r16
	
    ldi r16, 1
	sts Proc_ID, r16
  rjmp timer_end
  SWITCH1:
    in r16, SPL				;save stack
	sts Proc1_Stack, r16

    lds r16, Proc2_Stack	;load stack
    out SPL, r16

    ldi r16, 0
	sts Proc_ID, r16
  timer_end:

  pop r30
  pop r29
  pop r28
  pop r27
  pop r26
  pop r25
  pop r24
  pop r23
  pop r22
  pop r21
  pop r20
  pop r19
  pop r18
  pop r17
  pop r16
reti

Przełączanie polega na zmianie stosów. Proces 1 ma stos 0xFF a proces 2 ma gdzies 0xBE.

Poniżej są 2 procesy które są wykonywane prawidłowo, lecz do czasu:

proc2:
  in r16, PORTD
  com r16
  out PORTD, r16
  rcall delay //pewne opoznienie
rjmp proc2

proc1:
///tutaj sa problemy

Chodzi o to że gdy operuje dużą ilością danych na stosach, np:

proc1:
push r16
push r16
push r16
push r16
push r16
push r16
push r16
push r16
push r16
push r16
pop r16
pop r16
pop r16
pop r16
pop r16
pop r16
pop r16
pop r16
pop r16
pop r16
rjmp proc1

To sygnał z proc2 zmniejsza swoją częstotliwość wprost proporcionalnie do ilości operacji na stosie :-[
Wiadomo rcall daje 2B na stos, więc większa ilość callowania zmieni sygnał procesu 2.
Wszystkie inne operacje bez użycia stosu nie wyływają na drugi proces.
Co jest nie tak? Wg mnie to dawanie na stos i ściąganie zabiera procesorowi troche taktów procesora i z tego są takie jaja.

0

Nie chce mi się sprawdzać tego dokładnie i analizować... czy timer masz na przerywanie pewnie ustawiony, prawda? Jeśli tak, to po wejściu do przerywania wyłącz je.

A poza tym fajnie będzie jak zamieścisz cały kod, bo widzę że nie jest cały i trudno podejrzeć coś więcej.

No mogłem, jeszcze zaznaczyć, że jak będzie cały kod, to chociaż sprawdę u siebie kompilując, bo tak w ciemno to...

0

Jest to mój pierwszy program w czystym asm dla tych procków, multitasking sam wymyślałem(bawiłem sie kiedys w pisanie os'ów) więc faktycznie coś mogłem namieszać :-)

.include "m8def.inc"; gospodarka pamieciowa dostosowana do Attiny26;]
;.include "tn26def.inc"
;.equ SPL = SP
;REZONATOR KWARCOWY 8MHz
.dseg
.org RAMSTART
Process:
  Proc_ID: .byte 1
  Proc1_Stack: .byte 1
  Proc2_Stack: .byte 1
  tmp_r16: .byte 1
  timerp: .byte 1
.cseg
.org 0x00
rjmp start
.org OVF0addr
rjmp timer0   

start:
;stack-dla atmegi
  clr r16
  out SPH, r16

;ports
  ser r16
  out DDRB, r16
  clr r16
  out PORTB, r16

;thread
  clr r16
  sts Proc_ID, r16
  
  ;proc1
  ldi r16, RAMSTART+0x3B+0x22
  out SPL, r16
  
  ldi r16, (proc1 & 0xFF)
  push r16
  ldi r16, (proc1 >> 8)
  push r16

  in r16, SPL
  ldi r17, 15 						; push r16, push r17, push r18.... push r30
  sub r16, r17
  sts Proc1_Stack, r16

  ;proc2
  ldi r16, RAMSTART+0x3B+0x22+0x22 ;pewnie zgubilem bajta;]
  out SPL, r16
  ldi r16, (proc2 & 0xFF)
  push r16
  ldi r16, (proc2 >> 8)
  push r16
  in  r16, SPL
  sts Proc2_Stack, r16


;timer
  ldi r16, CS02
  out TCCR0, r16 

  ;ldi r16, 0
  ;out TCNT0, r16 

  clr r16
  sts timerp, r16

  ldi r16, 1<<0
  out TIMSK, r16
  sei

stop:
rjmp stop

delay:
  ldi r20, 250
  ldi r21, 250
  ldi r22, 250
  Loop:
        ;nop
        dec r22
		brne Loop
      dec r21
      brne Loop
    dec r20
  brne Loop
ret


blink: ; r17 - mask pin 
  in r16, PORTB
  mov r18, r17 ;taki xor
  com r17
  and r17, r16
  com r16
  and r16, r18
  or r16,r17
  out PORTB, r16
ret

proc1:
  push r16; z tymi pushami i popami przebieg na oscyloskopie jest inny niz bez nich
  push r16
  push r16
  push r16
  push r16
  push r16
  push r16
  push r16
  push r16
  push r16
  pop r16
  pop r16
  pop r16
  pop r16
  pop r16
  pop r16
  pop r16
  pop r16
  pop r16
  pop r16

rjmp proc1

proc2:
  ldi r17, 2
  rcall blink; PORTB 0xb00000010 => PORTD.1 = ~PORTD.1
  rcall delay
  rcall delay
  rcall delay
  rcall delay
  rcall delay
  rcall delay
  rcall delay
rjmp proc2



timer0:
  sts tmp_r16, r16
  pop r16
  pop r16
  lds r16, tmp_r16
  
  push r16
  push r17
  push r18
  push r19
  push r20
  push r21
  push r22
  push r23
  push r24
  push r25
  push r26
  push r27
  push r28
  push r29
  push r30
  
  ldi r16,56; 255-155
  out TCNT0, r16
  
  lds r16, timerp
  inc r16
  sts timerp, r16

  clr r17
  lds r16, Proc_ID
  cp r16, r17
  brne SWITCH1
  SWITCH0:
    in r16, SPL				;save stack
	sts Proc2_Stack, r16

    lds r16, Proc1_Stack	;load stack
    out SPL, r16
	
    ldi r16, 1
	sts Proc_ID, r16
  rjmp timer_end
  SWITCH1:
    in r16, SPL				;save stack
	sts Proc1_Stack, r16

    lds r16, Proc2_Stack	;load stack
    out SPL, r16

    ldi r16, 0
	sts Proc_ID, r16
  timer_end:

  pop r30
  pop r29
  pop r28
  pop r27
  pop r26
  pop r25
  pop r24
  pop r23
  pop r22
  pop r21
  pop r20
  pop r19
  pop r18
  pop r17
  pop r16
reti

O ile pamietam RAMSTART nie bylo w m8def.inc, czyli
.equ RAMSTART=0x60
Upierdliwe to :/

//Do autorów serwisu, przy znacznikach < asm > </asm> psują sie w kodzie znaki & <<

0

Wiesz co, sorki, ale analiza i poszukiwania błędu w kodzie zawierającym takie teksty jak:

;pewnie zgubilem bajta;]
; z tymi pushami i popami przebieg na oscyloskopie jest inny niz bez nich

wskazuje na to, że program pisany był trochę na kolanie metodą prób i błędów, a to nie dobry styl do analizy.

Ale że nie jestem straszny postanowiłem sam napisać coś od siebie. Poniżej znajdziesz najprawdopodobniej poprawny (testowałem i chyba jest dobrze, choć na uP nie sprawdzałem, bo nie chciało mi się :) ).
Oczywiście mój kod jest przykładowy. W praktyce pod uwagę musisz wziąć o wiele wiele wiele więcej rzeczy, bo póki wątki żyją same i nie korzystają z timerów to jest jeszcze dobrze. W momencie kiedy zaczną, to nie masz wyjścia i musisz dodatkowo robić zrzuty timerów, co nie jest proste, bo ich wartości przecież nie mają prawa "drgnąć" podczas zrzutów, tak by nie zaburzyć wątków (no chyba że timery też chodzą na przerywaniach, ale to zupełnie inna sprawa i jeszcze bardziej skomplikowana sytuacja).
W zasadzie powinieneś robić zrzut całej pamięci danych poniżej 0x60 a potem to przywracać. Szkoda, że nie da się przestawiać tego obszaru za jednym zamachem...

Ale póki nie ma timerów to jest w miarę prosto. Tylko należy jeszcze umiejętnie kontrolować stan portów lub robić ich zrzuty jeśli trzeba/można.

Co do kodu, myśle że nie jest skomplikowany. Jak coś to pisz, postaram się uzasadnić dlaczego tak a nie inaczej.

.include "m8def.inc"
.cseg
.org 0x00
rjmp RESET	;ustawiamy wektor dla resetu
.org 0x09 
rjmp timer0 ;ustawiamy wektor dla przerywania przeładowania TIMER0

.org 0x13
RESET:
	;*********************** sprawy wstępne ***********************

	;zapamiętujemy stos dla obu "wątków", czyli ustawiamy jego
	;początkowe wartości
	ldi r28, high(RAMEND>>1)
	sts stosadresA, r28
	ldi r28, low(RAMEND>>1)
	sts stosadresA+1, r28

	ldi r28, high(RAMEND)
	sts stosadresB, r28
	ldi r28, low(RAMEND)
	sts stosadresB+1, r28

	;ustawiamy kod startowy dla "wątku" (0 to pierwszy A, 1 to drugi B)
	;ustawiliśmy B bo A uruchomimy sami, więc przerwanie powinno wystartować B
	ldi r28, 1
	sts kod, r28


	;*********************** sprawy wstępne dla wątku B ***********************

	;ustawiamy stos "wątku" B i dodajemy do niego informacje zwrotne
	;dla wątku A nie dodajemy nic, ponieważ wątek ten uruchomimy przed wywołaniem przerwania
	;przerywanie dodaje do stosu informacje oraz ściąga ze stosu informacje. Ponieważ
	;wątek A uruchomimy ręcznie to stos nie musi zawierać żadnych informacji, natomiast
	;wątek B w momencie wywołania przerywania będzie traktowany tak, jakby już był raz przerzucany
	;czyli jego stos musi zawierać informacje o stanie wątku

	;odczytujemy wskaźnik stosu wątku B
	lds r28, stosadresB
	out SPH, r28
	lds r28, stosadresB+1
	out SPL, r28
	;zapisujemy adres zwrotny wątku B
	ldi r28, low(watekB)
	push r28
	ldi r28, high(watekB)
	push r28
	ldi r28, 0 
	;dla kompatybilności z przerywaniem musimy zapamiętać dokładnie 16 rejestrów (plus 1 zrzu SREG), tak
	;by stos miał odpowiedni kształt w momencie pierwszego uruchomienia wątku B
	;oczywiście w tym momencie nie ma znaczenia co zapamiętamy, równie dobrze moglibyśmy
	;odjąc 16 od wskaźnika stosu (plus 1 zrzut rejestru SREG), ale dla czytelności zrobiliśmy zrzut
	push r16
	push r17
	push r18
	push r19
	push r20
	push r21
	push r22
	push r23
	push r24
	push r25
	push r26
	push r27
	push r28
	push r29
	push r30
	push r31
	push r28	;WAŻNE, zapamiętujemy tutaj dowolną wartość dla SREG
	;zapisujemy nowy wskaźnik stosu
	in r28, SPH
	sts stosadresB, r28
	in r28, SPL
	sts stosadresB+1, r28


	;*********************** rozpoczynamy grę ***********************
	;*** podszywając się pod wątek A, który potem uruchomimy sami ***

	; ustawiamy domyślnie stos wątku A
	lds r28, stosadresA
	out SPH, r28
	lds r28, stosadresA+1
	out SPL, r28

	ldi r28, 0
	out TCNT0, r28
	; ldi r28, (1<<CS00) | (1<<CS02)	;dzielenie przez 1024
	ldi r28, (1<<CS00) 				 	;dzielenie przez 1
	out TCCR0, r28
	ldi r28, (1<<TOIE0)					;ustawiamy przerywanie przy przeładowaniu
	out TIMSK, r28
	sei
	rjmp watekA		;ręcznie uruchamiamy wątek A


; ************************ OBSŁUGA PRZERWANIA PRZEŁADOWANIA DLA TIMER0 ************************
timer0:
	;zapamiętujemy stan kilku rejestrów (w tym przypadku to połowa)
	push r16
	push r17
	push r18
	push r19
	push r20
	push r21
	push r22
	push r23
	push r24
	push r25
	push r26
	push r27
	push r28
	push r29
	push r30
	push r31

	;zapamiętujemy stan SREG, bo inaczej po powrocie z przerwania
	;stracimy informacje o flagach
	in r28, SREG
	push r28

	;sprawdzamy który kod uruchomić
	lds r28, kod
	cpi r28, 0
	breq uruchomwatekA

	;zapamiętujemy również aktualny adres stosu danego wątku
	;a ponieważ mamy uruchomić wątek B to zapamiętujemy adres dla wątku A
	in r28, SPH
	sts stosadresA, r28
	in r28, SPL
	sts stosadresA+1, r28

	;uruchamiamy wątek B
	;ustawiamy adres jego stosu
	lds r28, stosadresB
	out SPH, r28
	lds r28, stosadresB+1
	out SPL, r28

	;zmieniamy informacje o kodzie na kod A
	ldi r28, 0
	sts kod, r28
	rjmp koniectimer0
	

uruchomwatekA:
	;zapamiętujemy również aktualny adres stosu danego wątku
	;a ponieważ mamy uruchomić wątek A to zapamiętujemy adres dla wątku B
	in r28, SPH
	sts stosadresB, r28
	in r28, SPL
	sts stosadresB+1, r28

	;uruchamiamy wątek A
	;ustawiamy adres jego stosu
	lds r28, stosadresA
	out SPH, r28
	lds r28, stosadresA+1
	out SPL, r28

	;zmieniamy informacje o kodzie na kod B
	ldi r28, 1
	sts kod, r28

koniectimer0:
	;przywracamy jego rejestry danego wątku
	pop r28
	out SREG, r28
	pop r31
	pop r30
	pop r29
	pop r28
	pop r27
	pop r26
	pop r25
	pop r24
	pop r23
	pop r22
	pop r21
	pop r20
	pop r19
	pop r18
	pop r17
	pop r16
	push r28
	ldi r28, 0		;zerujemy TCNTO
	out TCNT0, r28
	pop r28
	sei
	ret



;************************************ WĄTEK A ************************************
watekA:
	;przykładowy kod wątku
	;ten wątek korzysta odlicza do 10 w rejestrach r20, r21, r22, czyli razem do 30
	;potem zwiększa rejestr r23 o jeden i jak ten dojdzie do 5 to zmienia stan bitu 0 w PORTB
	;i odlicza kilka razy operując na rejestrze r31
	ldi r23, 0
skokA1:
	ldi r20, 0
	ldi r21, 0
	ldi r22, 0
skokA2: 
	inc r20
	cpi r20, 10
	brne skokA2
skokA3: 
	inc r21
	cpi r21, 10
	brne skokA3
skokA4: 
	inc r22
	cpi r22, 10
	brne skokA4
	inc r23
	cpi r23, 5
	brne skokA1

	in r28, PORTB
	ldi r29, 1
	eor r28, r29
	out PORTB, r28
	
	ldi r31, 0
skokA5:
	dec r31
	brne skokA5
	rjmp watekA


;************************************ WĄTEK B ************************************
watekB:
	;przykładowy kod wątku (kopia wątku A, tylko z minimalnymi zmianami)
	;ten wątek korzysta odlicza do 5 w rejestrach r20, r21, r22, czyli razem do 15
	;potem zwiększa rejestr r23 o jeden i jak ten dojdzie do 10 to zmienia stan bitu 1 w PORTB
	;i odlicza kilka razy operując na rejestrze r31
	ldi r23, 0
skokB1:
	ldi r20, 0
	ldi r21, 0
	ldi r22, 0
skokB2: 
	inc r20
	cpi r20, 5
	brne skokB2
skokB3: 
	inc r21
	cpi r21, 5
	brne skokB3
skokB4: 
	inc r22
	cpi r22, 5
	brne skokB4
	inc r23
	cpi r23, 10
	brne skokB1

	in r28, PORTB
	ldi r29, 2
	eor r28, r29
	out PORTB, r28
	
	ldi r31, 0
skokB5:
	dec r31
	brne skokB5
	rjmp watekB


.dseg
.org 0x60
 stosadresA: .DW 0	; zapamiętujemy stan stosu dla wątku A
 stosadresB: .DW 0	; zapamiętujemy stan stosu dla wątku B
 kod:  .DB 0		; informacja o tym, który wątek wystartować

</quote>

oczywiście miało być
nie dobry -> niedobry

Dalej nie czytam, bo znów wyjdzie 5 postów :P

0

Zgadza sie. Napisałem go w kilka minut aby po prostu uruchomić te wątki, i później wg tego kodu napisać już coś porządnego.
Zapomniałem o flagach i może dla tego mój kod jest troche wadliwy. Jak przyjde ze szkoły to się pobawie.
Dzięki za kod [browar]

2min i mój kod poprawiony :-D
Wszystko przez to ze zapomniałem właśnie o flagach, po prostu ich nie zapisywałem na stos.
Wiem jak to jest analizować kogoś kod, lepiej już samemu napisać - coś niemoge się doszukać jak poradziłeś sobie z problemem; gdy wywoływana jest funkcja przerwania timera, na stos jest dawany adres powrotny 2B, u mnie w kodzie na początku dodawałem 2B do wartości stosu i zapisywałem do zmiennych, lecz zmieniłem to na:

timer0:
  sts tmp_r16, r16;aby nie stracic wartosci rejestru
  pop r16
  pop r16
  lds r16, tmp_r16

RR nie wiem w czym pisałeś to, troche błędów masz. Ja poskładałem sobie własne stanowisko na windowsa z pliczka avrasm32 i edytora pn ;-)
O ile czytałem w dokumentacjach i tych pierdołach

.dseg
.org 0x60
 stosadresA: .DW 0        ; zapamiętujemy stan stosu dla wątku A
 stosadresB: .DW 0        ; zapamiętujemy stan stosu dla wątku B
 kod:  .DB 0                ; informacja o tym, który wątek wystartować

w .dseg stosuje się np. "label: .byte 2" gdzie 2 to ilość zarezerwowanych bajtów, chyba ze masz inną wersje

main.asm(xx): warning: A .db segment with an odd number of bytes is detected. A zero byte is added.
main.asm(xx) : error : .DB and .DW can't be used in the data segment

RR można prosić o kontakt? jakieś gg albo cuś masz :>

0

Podaj swojego maila to się odezwę.

Korzystam z AvrStudio4. Kurs asm:
http://www.edw.com.pl/ea/asm_avr.html

Jeśli chodzi o DW i DB to nie wiem jak dokładnie tego używać i jak to wygląda normalnie... na AvrStudio kompilacja przeszła poprawnie, DW oznaczało 2 bajty, DB jeden bajt. Po kompilacji mam 5 bajtów SRAM zajęte... gdyby nie działało to bym tego nie wpisał. Natomiast jeśli coś jest nie tak u ciebie to zupełnie inna sprawa. W końcu jesteś chyba inteligentny więc możesz poprawić :P

PS. AVRStudio też pracuje na avrasm32. Kompilowałem dodatkowo teraz w środowisku VMLAB i też na avrasm32 poszło bezbłędów.

0

Nie chciało mi się rejestrować na stronce atmela, czy tam szukać tego bajera. Program do asemblacji łatwo było znaleźć, na początku miałem jakiś niemiecki badziew :-D

sony-5(at)wp,pl

0

Wiesz co... nie pisz takich bzdur hehe. AVRStudio jest darmowe. Jest też środowisko VMLAB też darmowe. Więc jaki problem z rejestracją? A VMLAB możesz mieć bez rejestracji.
http://www.amctools.com/download.htm

0

RR, weż się może jednak regnij, będziesz szczęsliwszy z możliwością edycji swoich postów, powiadomieniem o odpowiedziach i... ułatwieniem życia moderatorom :-)

nie no, żartuję, tych dwóch czy trzech zmienionych\połączonych postów się nawet nie zauważa :-)

0

deus ja już dawno bym to zrobił, tyle że sprawa wygląda tak, że ja sobie pourzęduje tutaj jeszcze tydzień może dwa... właściwie to ja tutaj wpadam tylko dlatego, że mam tyle roboty, że nie wiem od czego zacząć (ilość mnie w dodatku przeraża)... ale jak zacznę to będę musiał przestać wpadać :) lub będę robił to rzadko no i moderatorzy odpoczną :).

no ale może się zarejestruje, jak tylko będę miał wenę...

1 użytkowników online, w tym zalogowanych: 0, gości: 1