Informatyka 
 
Panel pomiarowy - program
częśc 1
Narcyz
W styczniowym i lutowym numerze Nowego Olimpu przedstawiliśmy panel pomiarowy który może być użyty zarówno na lekcjach jak i na pokazach. Część elektroniczna i mechaniczna została mniej więcej omówiona – teraz czas na oprogramowanie.

Panel pomiarowy zbudowano w oparciu o procesor Atmega8. Procesor ten ma wystarczająca moc obliczeniową by mierzyć napięcia oraz czas pomiędzy zdarzeniami jednocześnie obsługując wyświetlacz i mógłby się zajmować jeszcze wieloma innymi zadaniami, jednak trudno o tańszy i prostszy procesor a niewykorzystanie wszystkich możliwości nie jest specjalnym problemem. Zastosowany mikrokontroler posiada 1KB pamięci RAM oraz 8KB pamięci programu. Dodatkowo procesor ma 32 rejestry które mogą być użyte w większości poleceń. Ponieważ program obsługi naszego miernika jest stosunkowo prosty, większość informacji możemy umieścić bezpośrednio w rejestrach.

Nasz miernik będzie przystosowany do współpracy z wieloma przystawkami pomiarowymi. W zależności od podłączonego detektora, program powinien dostosować swoje zachowanie wywołując odpowiednie podprogramy i sterując procesem pomiaru i wyświetlania. Jednak niektóre procedury są wspólne i warto się nad nimi na początek zastanowić. Po pierwsze, będziemy potrzebowali programu sterującego wyświetlaczami. Pamiętajmy, że wyświetlacze podłączone są w taki sposób, że można zaświecić tylko jeden z nich, i aby panować nad wyświetlaniem wszystkich cyfr, trzeba cały czas przełączać informacje sterujące zarówno poszczególnymi segmentami jak i tym który wyświetlacz powinien być w danej chwili włączony. Odpowiednia procedura powinna być wołana kilkaset razy na sekundę.

Warto przygotować sobie kawałek programu który będzie wyświetlał zawartość trzech rejestrów i przy okazji powoli na wywołanie zadanej procedury 1000 razy na sekundę. Lub jeszcze lepiej – pozwoli na ustawienie jak często to procedura będzie wywoływana i od tego zaczniemy pisanie naszego programu. Warto przy okazji usystematyzować trochę kod i założyć, że każdy fragment naszego systemu będzie posiadał część inicjująca oraz wykonawczą. Procedury inicjujące będą miały etykiety zaczynające się od init. Procedury wykonujące pomiary będą zazwyczaj wykonywane w przerwaniach – na przykład zegarowych lub przerwań zewnętrznych będą miały także odpowiednie nazwy.

Program jest dość złożony i w chwili obecnej zawiera ponad 500 linii kodu. Dlatego będziemy omawiali go prezentując w jednym odcinku po jednej funkcjonalności.

Obsługę wewnętrznego timera oprzemy o wewnętrzny zegar nr 2. Będzie on zliczał dzieląc częstotliwość zegara procesora przez 2000, co przy zegarze 2MHz da nam jedno przerwanie na 1ms.:

initCron:		ldi	r16, 0b10001010		; CNT, fosc/8
		out	TCCR2, r16
		ldi	r16, 125			; divider
		out	OCR2, r16

		ldi	r16, 0
		out	TCNT2, r16

		in	r16, TIMSK
		ori	r16, 0b10000000		; interupt on compare
		out	TIMSK, r16

Po zainicjowaniu przerwań i ustawieniu częstotliwości warto zastanowić się nas ustawieniem dokładności przerwań które będą wywoływane. Licznik który będzie liczył co które przerwanie wołać procedurę pomiarową. Jego wartość umieścimy w parze rejestrów r11:r10. Ponieważ jednak wartość tego licznika będziemy ponownie ustawić po odliczeniu do 0 – musimy gdzieś zapamiętać wartość którą należy ustawić. Nie musi to być cała wartość – lecz wartość która pozwoli ją odtworzyć. Ponieważ potrzebujemy podzielniki 1, 10, 100 i 1000 (do uzyskania dokładności 1/1000s, 1/100s, 1/10s i 1s). Wartość podzielnika możemy więc zapamiętać na 2 bitach które umieścimy w rejestrze r8 na pozycjach 3 i 4.

Przygotujemy sobie procedurę która ustawi zawartości licznika:

cronResetAccuracy:	clr	r11
		mov	r16, r8
		andi	r16, 0b00011000
		breq	os_crac_00
		cpi	r16, 0b00001000
		breq	os_crac_01
		cpi	r16, 0b00010000
		breq	os_crac_10

os_crac_11:	ldi	r16, HIGH(1000)
		mov	r11, r16
		ldi	r16, LOW(1000)
		mov	r10, r16
		ret

os_crac_10:	ldi	r16, 100
		rjmp	os_crac_0A

os_crac_01:	ldi	r16, 10
		rjmp	os_crac_0A

os_crac_00:	ldi	r16, 1

os_crac_0A:	clr	r11
		mov	r10, r16
		ret

Program obsługi przerwania musi zapamiętać rejestry z których korzysta. Ba pewno musimy zapamiętać rejestr znaczników oraz rejestr r16 który używamy jako rejestr tymczasowy. Ponadto będzie nam potrzebny rejestr z służący do adresowania.

Poza pokazaniem kolejnego segmentu wyświetlacza, w procedurze zegarowej musimy jeszcze zmniejszyć licznik r11:r10 i gdy osiągnie zero – wywołać procedurę związana z obsługa aktualnie podłączonego urządzenia. Adres tej procedury będziemy pamiętać w rejestrach r0:r1. Przyda się nam też obsługa migania wyświetlacza. Czasem by wskazać błąd, będziemy migać całą zawartością wyświetlacza. Maskę która będziemy nakładać na wartość do wyświetlenia – umieścimy w rejestrze r7, a pierwszy bit rejestry r8 użyjemy jako flagę zezwalającą lub zabraniająca migotania:

timerTick:	push	r16
		in	r16, SREG
		push	r16
		push	zl
		push	zh

		; show next digit
		rcall	showSegment

		; increament tick counter
		inc	r9
		brne	os_ttick_01

		; each 256ms check blinking bit
		ldi	r16, $1
		and	r16, r8
		brne	os_ttick_bln

		ldi	r16, $ff
		cp	r16, r7
		brne	os_ttick_01
		
		; if set - change display state
os_ttick_bln:	mov	r16, r7
		clr	r7
		cpi	r16, $ff
		breq	os_ttick_01
		com	r7

os_ttick_01:	ldi	r16, 3
		
		dec	r10
		brne	os_ttick_02
		and	r11, r11
		breq	os_ttick_03
		dec	r11
		rjmp	os_ttick_02

os_ttick_03:	mov 	zl, r0
		mov	zh, r1
		icall
		rcall	cronResetAccuracy

os_ttick_02:	pop	zh
		pop	zl	
		pop	r16
		out	SREG, r16
		pop	r16
		reti

Pozostała nam jeszcze do napisania procedura wyświetlająca zawartość pamięci na wyświetlaczu. Na początek warto zainicjować wyświetlacz ustawiając kierunki na portach sterujących wyświetlaczem:

initDisplay:	; setup or port used by display
		ldi	r16, $ff
		out	DDRB, r16

		ldi	r16, 0
		out	PORTB, r16

		in	r16, DDRD
		ori	r16, $e0
		out	DDRD, r16

		in	r16, PORTD
		andi	r16, $1f
		out	PORTD, r16

		ret

Samo wyświetlanie pobiera wartości z rejestrów. Na początek sprawdzamy czy wyświetlacz został wygaszony sprawdzając zawartość rejestru r7. Jeśli tak – to wyłączamy wszystkie segmenty. Jeśli nie jest wygaszony – rejestr r7 zawiera informacje o tym który segment powinien zostać w danej chwili wyświetlony. Włączany wtedy odpowiedni bit portu D i ustawia na porcie B wartość z jednego z rejestrów r4:r6 zawierających stan wyświetlacza:

showSegment:	; shows the next segment
		push	r16
		mov	r16, r7

		cpi	r16, $ff
		brne	os_shseg_03

		clr	r16
		cbi	PORTD, 5
		cbi	PORTD, 6
		cbi	PORTD, 7
		rjmp	os_shseg_01

os_shseg_03:	andi	r16, 3
		breq	os_shseg_A
		cpi	r16, 1
		breq	os_shseg_B

os_shseg_C:	cbi	PORTD, 7
		out	PORTB, r6
		sbi	PORTD, 6

		clr	r7
		rjmp	os_shseg_01

os_shseg_B:	cbi	PORTD, 5
		out	PORTB, r5
		sbi	PORTD, 7

		rjmp	os_shseg_02

os_shseg_A:	cbi	PORTD, 6
		out	PORTB, r4
		sbi	PORTD, 5

os_shseg_02:	inc	r7

os_shseg_01:	pop	r16
		ret
 
Opinie
 
Facebook
 
  
33710 wyświetleń

numer 4/2015
2015-04-01

Od redakcji
Aktualności
Dla młodszych
Dydaktyka
Ekologia
Informatyka
Kącik poezji
Matematyka
Polityka
Prawo
Rozmaitości

nowyOlimp.net na Twitterze

nowy Olimp - internetowe czasopismo naukowe dla młodzieży.
Kolegium redakcyjne: gaja@nowyolimp.net; hefajstos@nowyolimp.net