1 | ;GPS Uhr
|
2 | ;2008 Björn Wieck
|
3 | ;Dieses Programm liest aus dem Datenstrom eines NMEA-GPS-Empfängers die UTC-Uhrzeit und das
|
4 | ;Datum aus und zeigt beides auf einem LCD-Display an.
|
5 | ;Das Programm sucht nach der Zeichenfolge $GPRMC und wertet die Enthaltenen Daten aus.
|
6 | ;
|
7 | ;Typische Zeichenfolge für NMEA:
|
8 | ;$GPRMC,191410,A,4735.5634,N,00739.3538,E,0.0,0.0,181102,0.4,E,A*19
|
9 | ; ^^^^^^ ^^^^^^ ^^^
|
10 | ; Uhrzeit Datum Ende+Prüfsumme
|
11 |
|
12 |
|
13 |
|
14 | .include "m8def.inc"
|
15 |
|
16 | .def temp = R16
|
17 | .def temp1 = R17
|
18 | .def temp2 = R18
|
19 | .def temp3 = R19
|
20 | .def temp4 = R20
|
21 |
|
22 | .equ F_CPU = 3685400 ; Systemtakt in Hz
|
23 | .equ BAUD = 4800 ; Baudrate
|
24 |
|
25 | ; Berechnungen
|
26 | .equ UBRR_VAL = ((F_CPU+BAUD*8)/(BAUD*16)-1) ; clever runden
|
27 | .equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1))) ; Reale Baudrate
|
28 | .equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000) ; Fehler in Promille
|
29 |
|
30 | .if ((BAUD_ERROR>10) || (BAUD_ERROR<-10)) ; max. +/-10 Promille Fehler
|
31 | .error "Systematischer Fehler der Baudrate grösser 1 Prozent und damit zu hoch!"
|
32 | .endif
|
33 |
|
34 |
|
35 | ;IRQ-Sprungtabelle
|
36 | .org 0x00
|
37 | rjmp main ; Reset Handler
|
38 | reti ;rjmp EXT_INT0 ; IRQ0 Handler
|
39 | reti ;rjmp EXT_INT1 ; IRQ1 Handler
|
40 | reti ;rjmp TIM2_COMP ; Timer2 Compare Handler
|
41 | reti ;rjmp TIM2_OVF ; Timer2 Overflow Handler
|
42 | reti ;rjmp TIM1_CAPT ; Timer1 Capture Handler
|
43 | reti ;rjmp TIM1_COMPA ; Timer1 CompareA Handler
|
44 | reti ;rjmp TIM1_COMPB ; Timer1 CompareB Handler
|
45 | reti ;rjmp TIM1_OVF ; Timer1 Overflow Handler
|
46 | reti ;rjmp TIM0_OVF ; Timer0 Overflow Handler
|
47 | reti ;rjmp SPI_STC ; SPI Transfer Complete Handler
|
48 | rjmp USART_RXC ; USART RX Complete Handler
|
49 | reti ;rjmp USART_UDRE ; UDR Empty Handler
|
50 | reti ;rjmp USART_TXC ; USART TX Complete Handler
|
51 | reti ;rjmp ADC ; ADC Conversion Complete Handler
|
52 | reti ;rjmp EE_RDY ; EEPROM Ready Handler
|
53 | reti ;rjmp ANA_COMP ; Analog Comparator Handler
|
54 | reti ;rjmp TWSI ; Two-wire Serial Interface Handler
|
55 | reti ;rjmp SPM_RDY ; Store Program Memory Ready Handler
|
56 |
|
57 | ; Hauptprogramm
|
58 |
|
59 | main:
|
60 | cli
|
61 | ; Stackpointer initialisieren
|
62 |
|
63 | ldi temp, LOW(RAMEND)
|
64 | out SPL, temp
|
65 | ldi temp, HIGH(RAMEND)
|
66 | out SPH, temp
|
67 |
|
68 | ;IO-Ports initalisieren
|
69 | ldi temp,0b11111111 ;PortB, Bit 0-7 auf Ausgang stellen
|
70 | out DDRB, temp
|
71 | ldi temp,0b00000000 ;PortD, Bit 0-7 auf Eingang stellen
|
72 | out DDRD, temp
|
73 |
|
74 |
|
75 | ; Baudrate einstellen
|
76 |
|
77 | ldi temp, HIGH(UBRR_VAL)
|
78 | out UBRRH, temp
|
79 | ldi temp, LOW(UBRR_VAL)
|
80 | out UBRRL, temp
|
81 |
|
82 | ; Frame-Format: 8 Bit
|
83 |
|
84 | ldi temp, (1<<URSEL)|(3<<UCSZ0)
|
85 | out UCSRC, temp
|
86 |
|
87 | sbi UCSRB, RXCIE ;Interrupt bei Empfang
|
88 | sbi UCSRB, RXEN ;RX (Empfang) aktivieren
|
89 |
|
90 | rcall lcd_init ;LCD initalisieren
|
91 | rcall lcd_clear ;LCD löschen
|
92 |
|
93 | sei ;Interrupts global aktivieren
|
94 |
|
95 | loop:
|
96 | ;Uhrzeit parsen und auf lcd ausgeben
|
97 |
|
98 | ldi r30,LOW(GPRMC) ;den Z-Pointer mit dem Start der GPRMC Bytes laden
|
99 | ldi r31,HIGH(GPRMC)
|
100 | ldd temp1, Z + 0x01 ;Stunde links
|
101 | rcall lcd_data
|
102 | ldd temp1, Z + 0x02 ;Stunde rechts
|
103 | rcall lcd_data
|
104 | ldi temp1, ':' ;trenner
|
105 | rcall lcd_data
|
106 | ldd temp1, Z + 0x03 ;Minute links
|
107 | rcall lcd_data
|
108 | ldd temp1, Z + 0x04 ;Minute rechts
|
109 | rcall lcd_data
|
110 | ldi temp1, ':' ;trenner
|
111 | rcall lcd_data
|
112 | ldd temp1, Z + 0x05 ;Sekunde links
|
113 | rcall lcd_data
|
114 | ldd temp1, Z + 0x06 ;Sekunde rechts
|
115 | rcall lcd_data
|
116 | ldi temp1, 0b11000000 ;LCD umschalten auf Zeile 2
|
117 | rcall lcd_command
|
118 |
|
119 |
|
120 |
|
121 | ;Datum parsen und auf LCD ausgeben
|
122 | ;dieser Teil ist aufwendiger weil eine $GPRMC-Zeile nicht immer gleich lang ist
|
123 | ;die Anzahl der Kommas ist immer gleich.
|
124 |
|
125 | ldi temp4,0 ;Kommazähler löschen
|
126 | ldi r30,LOW(GPRMC) ;den Z-Pointer mit dem Start der GPRMC Bytes laden
|
127 | ldi r31,HIGH(GPRMC)
|
128 | dateloop1:
|
129 | ld temp, Z+ ;Byte holen
|
130 | cpi temp, ',' ;und mit (ASCII) Komma vergleichen
|
131 | brne dateloop1 ;wenn kein Komma dann nächstes Byte holen
|
132 | inc temp4 ;Komma gefunden, Kommazähler erhöhen
|
133 | cpi temp4, 9 ;wenn 9 Kommas gezählt sind
|
134 | breq readdate ;Datum auslesen
|
135 | rjmp dateloop1
|
136 | readdate:
|
137 | ld temp1, Z+ ;Tag links
|
138 | rcall lcd_data
|
139 | ld temp1, Z+ ;Tag rechts
|
140 | rcall lcd_data
|
141 | ldi temp1, '.' ;Trenner einfügen
|
142 | rcall lcd_data
|
143 | ld temp1, Z+ ;Monat links
|
144 | rcall lcd_data
|
145 | ld temp1, Z+ ;Monat rechts
|
146 | rcall lcd_data
|
147 | ldi temp1, '.' ;trenner einfügen
|
148 | rcall lcd_data
|
149 | ld temp1, Z+ ;Jahr links
|
150 | rcall lcd_data
|
151 | ld temp1, Z+ ;Jahr rechts
|
152 | rcall lcd_data
|
153 | ldi temp1, 0b10000000 ;LCD umschalten auf Zeile 1
|
154 | rcall lcd_command
|
155 | ldi temp1, 0b00000010 ;LCD Cursor home
|
156 | rcall lcd_command
|
157 |
|
158 | ; etwas Pause damit die Anzeige stabiler ist.
|
159 |
|
160 | rcall delay5ms
|
161 | rcall delay5ms
|
162 | rcall delay5ms
|
163 | rcall delay5ms
|
164 | rcall delay5ms
|
165 | rjmp loop
|
166 |
|
167 |
|
168 |
|
169 | ;Interruptroutine: wird ausgeführt sobald ein Byte über UART empfangen wurde
|
170 |
|
171 | USART_RXC:
|
172 | push temp ; temp auf dem Stack sichern
|
173 | in temp, sreg ; SREG sichern
|
174 | push temp
|
175 |
|
176 | in temp, UDR ; UART Daten lesen
|
177 | cpi temp, '$' ; empfangenes Byte mit '$' vergleichen
|
178 | brne int_rxc_ende ; wenn nicht gleich, dann zu int_rxc_ende
|
179 | rxloop1:
|
180 | sbis UCSRA, RXC
|
181 | rjmp rxloop1
|
182 | in temp, UDR ; UART Daten lesen
|
183 | cpi temp, 'G' ; empfangenes Byte mit 'G' vergleichen
|
184 | brne int_rxc_ende ; wenn nicht gleich, dann zu int_rxc_ende
|
185 | rxloop2:
|
186 | sbis UCSRA, RXC
|
187 | rjmp rxloop2
|
188 | in temp, UDR ; UART Daten lesen
|
189 | cpi temp, 'P' ; empfangenes Byte mit 'P' vergleichen
|
190 | brne int_rxc_ende ; wenn nicht gleich, dann zu int_rxc_ende
|
191 | rxloop3:
|
192 | sbis UCSRA, RXC
|
193 | rjmp rxloop3
|
194 | in temp, UDR ; UART Daten lesen
|
195 | cpi temp, 'R' ; empfangenes Byte mit 'R' vergleichen
|
196 | brne int_rxc_ende ; wenn nicht gleich, dann zu int_rxc_ende
|
197 | rxloop4:
|
198 | sbis UCSRA, RXC
|
199 | rjmp rxloop4
|
200 | in temp, UDR ; UART Daten lesen
|
201 | cpi temp, 'M' ; empfangenes Byte mit 'M' vergleichen
|
202 | brne int_rxc_ende ; wenn nicht gleich, dann zu int_rxc_ende
|
203 | rxloop5:
|
204 | sbis UCSRA, RXC
|
205 | rjmp rxloop5
|
206 | in temp, UDR ; UART Daten lesen
|
207 | cpi temp, 'C' ; empfangenes Byte mit 'C' vergleichen
|
208 | brne int_rxc_ende ; wenn nicht gleich, dann zu int_rxc_ende
|
209 | rjmp read_gprmc ;$GPRMC-String wurde erkannt
|
210 |
|
211 | int_rxc_ende:
|
212 |
|
213 | pop temp
|
214 | out sreg, temp ; SREG wiederherstellen
|
215 | pop temp ; temp wiederherstellen
|
216 | reti
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | ;$GPRMC-String wurde erkannt, String einlesen und ins SRAM kopieren
|
222 |
|
223 | read_gprmc:
|
224 |
|
225 | ldi r30,LOW(GPRMC) ; den Z-Pointer mit dem Start der GPRMC Bytes laden
|
226 | ldi r31,HIGH(GPRMC)
|
227 | rxloop6:
|
228 | sbis UCSRA, RXC ; UART Daten lesen
|
229 | rjmp rxloop6
|
230 | in temp, UDR
|
231 | cpi temp, '*' ;empfangenes Byte mit '*' (Stringende) vergleichen
|
232 | breq int_rxc_ende ;wenn gleich, dann zu int_rxc_ende
|
233 | st Z+,temp ;empfangenes Byte im SRAM ablegen und Z erhöhen
|
234 | rjmp rxloop6
|
235 |
|
236 |
|
237 |
|
238 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
239 | ;; LCD-Routinen ;;
|
240 | ;; ============ ;;
|
241 | ;; (c)andreas-s@web.de ;;
|
242 | ;; ;;
|
243 | ;; 4bit-Interface ;;
|
244 | ;; DB4-DB7: PB0-PB3 ;;
|
245 | ;; RS: PB4 ;;
|
246 | ;; E: PB5 ;;
|
247 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
248 |
|
249 |
|
250 |
|
251 | ;sendet ein Datenbyte an das LCD
|
252 | lcd_data:
|
253 | mov temp2, temp1 ;"Sicherungskopie" für
|
254 | ;die Übertragung des 2.Nibbles
|
255 | swap temp1 ;Vertauschen
|
256 | andi temp1, 0b00001111 ;oberes Nibble auf Null setzen
|
257 | sbr temp1, 1<<4 ;entspricht 0b00010000
|
258 | out PORTB, temp1 ;ausgeben
|
259 | rcall lcd_enable ;Enable-Routine aufrufen
|
260 | ;2. Nibble, kein swap da es schon
|
261 | ;an der richtigen stelle ist
|
262 | andi temp2, 0b00001111 ;obere Hälfte auf Null setzen
|
263 | sbr temp2, 1<<4 ;entspricht 0b00010000
|
264 | out PORTB, temp2 ;ausgeben
|
265 | rcall lcd_enable ;Enable-Routine aufrufen
|
266 | rcall delay50us ;Delay-Routine aufrufen
|
267 | ret ;zurück zum Hauptprogramm
|
268 |
|
269 | ;sendet einen Befehl an das LCD
|
270 | lcd_command: ;wie lcd_data, nur ohne RS zu setzen
|
271 | mov temp2, temp1
|
272 | swap temp1
|
273 | andi temp1, 0b00001111
|
274 | out PORTB, temp1
|
275 | rcall lcd_enable
|
276 | andi temp2, 0b00001111
|
277 | out PORTB, temp2
|
278 | rcall lcd_enable
|
279 | rcall delay50us
|
280 | ret
|
281 |
|
282 | ;erzeugt den Enable-Puls
|
283 | lcd_enable:
|
284 | sbi PORTB, 5 ;Enable high
|
285 | nop ;3 Taktzyklen warten
|
286 | nop
|
287 | nop
|
288 | cbi PORTB, 5 ;Enable wieder low
|
289 | ret ;Und wieder zurück
|
290 |
|
291 | ;Pause nach jeder Übertragung
|
292 | delay50us: ;50us Pause
|
293 | ldi temp1, $42
|
294 | delay50us_:dec temp1
|
295 | brne delay50us_
|
296 | ret ;wieder zurück
|
297 |
|
298 | ;Längere Pause für manche Befehle
|
299 | delay5ms: ;5ms Pause
|
300 | ldi temp1, $21
|
301 | WGLOOP0: ldi temp2, $C9
|
302 | WGLOOP1: dec temp2
|
303 | brne WGLOOP1
|
304 | dec temp1
|
305 | brne WGLOOP0
|
306 | ret ;wieder zurück
|
307 |
|
308 | ;Initialisierung: muss ganz am Anfang des Programms aufgerufen werden
|
309 | lcd_init:
|
310 | ldi temp3,50
|
311 | powerupwait:
|
312 | rcall delay5ms
|
313 | dec temp3
|
314 | brne powerupwait
|
315 | ldi temp1, 0b00000011 ;muss 3mal hintereinander gesendet
|
316 | out PORTB, temp1 ;werden zur Initialisierung
|
317 | rcall lcd_enable ;1
|
318 | rcall delay5ms
|
319 | rcall lcd_enable ;2
|
320 | rcall delay5ms
|
321 | rcall lcd_enable ;und 3!
|
322 | rcall delay5ms
|
323 | ldi temp1, 0b00000010 ;4bit-Modus einstellen
|
324 | out PORTB, temp1
|
325 | rcall lcd_enable
|
326 | rcall delay5ms
|
327 | ldi temp1, 0b00101000 ;noch was einstellen...
|
328 | rcall lcd_command
|
329 | ldi temp1, 0b00001100 ;...nochwas...
|
330 | rcall lcd_command
|
331 | ldi temp1, 0b00000100 ;endlich fertig
|
332 | rcall lcd_command
|
333 | ret
|
334 |
|
335 | ;Sendet den Befehl zur Löschung des Displays
|
336 | lcd_clear:
|
337 | ldi temp1, 0b00000001 ;Display löschen
|
338 | rcall lcd_command
|
339 | rcall delay5ms
|
340 | ret
|
341 |
|
342 |
|
343 |
|
344 | .DSEG
|
345 | GPRMC: .BYTE 200 ;$GPRMC-String im SRAM
|