Guten Tag, wir müssen als Semesterprojekt mit einem ATMega2560 eine VGA-Verbindung aufbauen und Bilder oder Text erstmal übertragen lassen. Es handelt sich um einen Informatik Studiengang und Elektronik ist eher am Rande abgedeckt. Deshalb entschuldigt bitte meine rudimentären Kenntnisse. Wir sitzen bereits seit vielen vielen Stunden an dem Projekt. Momentan sind wir noch an dem Punkt, dass wir das Horizontale und Vertikale Signal per C-Code nachstellen und mit dem Oszillator nachprüfen. Beim Einstellen des Timers haperts vermutlich. Wir haben ein paar wenige Ansätze im Internet gefunden, doch konnten diese bisher nicht korrekt abändern um das richtige Signal zu produzieren. Beim vertikalen Signal brauchen wir ca. 64µs eine 0 und dann 16.67ms eine 1. Und dann wieder von vorne. http://prntscr.com/f6j7ju Das bekommen wir schon nicht hin. Eventuell liegts an der Timer Konfiguration. Aus einem ähnlichen Projekt mit einem Arduino entnehmen wir, dass wir den Timer Mode 15 brauchen. Aber damit klappt es auch nicht so richtig. Habt ihr Vorschläge zur Timer Konfiguration? Über jegliche Hinweise wäre ich sehr dankbar.
Ja genau, daher stammt auch mein Screenshot. Das haben wir schon reichlich gelesen.
Ein konkreteres Beispiel findet ihr sogar hier im Forum. Inkl. Code und Schaltplan... Beitrag "AVR VGA Terminal" Habt ihr das auch schon angeschaut?
Und hier noch ein Beispiel für Arduino: https://forum.arduino.cc/index.php?topic=102181.0 Vielleicht ein bisschen mehr Mühe geben beim googeln ;-)
Ralf J. schrieb: > Ein konkreteres Beispiel findet ihr sogar hier im Forum. Inkl. Code und > Schaltplan... > > Beitrag "AVR VGA Terminal" > > Habt ihr das auch schon angeschaut? Ja, das haben wir uns schon angeschaut. War leider auch nur bedingt hilfreich. Ich schreib gleich nochmal einen Nachtrag, bei dem das Problem hoffentlich klarer wird.
Die Generierung des vertikalen Signales ist mir bereits gelungen. Dafür habe ich bei einem MCU Takt von 16Mhz den Prescaler 1024 verwendet, sodass jeder Takt 64µs andauert. Die Erste Flanke hat somit eine Länge von einem Takt und die zweite Flanke eine Länge von 260 Takten (16,64ms). Wir gehen nach diesen Informationen: https://www.uni-koblenz.de/~physik/informatik/ECC/vga.pdf Vertikal Synch: http://prntscr.com/f6j7ju Nun versuche ich mit einem Prescaler von 8 dasselbe Ergebnis für das horizontale Signal zu erreichen. Rechnung: 1 / 16 000 000 = 62,5ns 62,5ns * 8 = 0,5µs Siehe hier: http://prntscr.com/f6ls1d Leider werden mir die 0,5µs/Takt nicht am Oszilloskop angezeigt. Ich bekomme nun bei einem Timer im CTC Mode eine Flankenlänge von 2,2µs angezeigt. Ich habe versucht den Timer Mode zu wechseln, bislang leider ohne Erfolg. Keiner bringt den errechnetet Wert heraus. 0 32,8ms 1 256µs 2 512µs 3 1024ms 4 2,2µs 5 128µs 6 256µs 7 512µs 8 2,2µs 9 2,2µs 10 2,2µs 11 2,2µs 12 2,2µs 13 2,2µs 14 2,2µs 15 2,2µs
1 | #define F_CPU 16000000UL
|
2 | #include <avr/io.h> |
3 | #include <avr/interrupt.h> |
4 | |
5 | volatile int v_sync_timer = 0; |
6 | volatile int h_sync_timer = 0; |
7 | |
8 | int main( void ) |
9 | {
|
10 | |
11 | DDRL=0xff; |
12 | DDRA=0xff; |
13 | |
14 | PORTL = 0xFF; |
15 | PORTA = 0xFF; |
16 | // initialize Timer1
|
17 | cli(); // disable global interrupts |
18 | TCCR1A = 0; // set entire TCCR1A register to 0 |
19 | TCCR1B = 0; // same for TCCR1B |
20 | |
21 | // set compare match register to desired timer count:
|
22 | OCR1A = 0; |
23 | // turn on CTC mode:
|
24 | TCCR1B |= (1 << WGM12); |
25 | // Set CS10 and CS12 bits for 1024 prescaler:
|
26 | TCCR1B |= (1 << CS10); |
27 | TCCR1B |= (1 << CS12); |
28 | //TCCR1B |= (1 << CS11);
|
29 | // enable timer compare interrupt:
|
30 | TIMSK1 |= (1 << OCIE1A); |
31 | |
32 | |
33 | |
34 | TCCR3A = 0; // set entire TCCR1A register to 0 |
35 | TCCR3B = 0; // same for TCCR1B |
36 | |
37 | // set compare match register to desired timer count:
|
38 | OCR3A = 0; |
39 | // turn on CTC mode:
|
40 | TCCR3B |= (1 << WGM32); |
41 | |
42 | // Set Prescaler to 8
|
43 | TCCR3B |= (1 << CS31); |
44 | //TCCR3B |= (1 << CS32);
|
45 | |
46 | // enable timer compare interrupt:
|
47 | TIMSK3 |= (1 << OCIE3A); |
48 | |
49 | |
50 | sei(); // enable global interrupts |
51 | |
52 | |
53 | while( 1 ); |
54 | }
|
55 | |
56 | |
57 | ISR(TIMER1_COMPA_vect) |
58 | {
|
59 | //PORTL ^= 0xFF;
|
60 | |
61 | if (v_sync_timer == 0){ |
62 | PORTL ^= 0xFF; |
63 | v_sync_timer = v_sync_timer + 1; |
64 | }else if (v_sync_timer == 259) { |
65 | PORTL ^= 0xFF; |
66 | v_sync_timer = 0; |
67 | } else { |
68 | v_sync_timer = v_sync_timer + 1; |
69 | }
|
70 | }
|
71 | |
72 | ISR(TIMER3_COMPA_vect) |
73 | {
|
74 | PORTA ^= 0xFF; |
75 | /*
|
76 | if (h_sync_timer == 0){
|
77 | PORTA ^= 0xFF;
|
78 | h_sync_timer = h_sync_timer + 1;
|
79 | }else if (h_sync_timer == 63) {
|
80 | PORTA ^= 0xFF;
|
81 | h_sync_timer = 0;
|
82 | } else {
|
83 | h_sync_timer = h_sync_timer + 1;
|
84 | }
|
85 | */
|
86 | }
|
Ralf J. schrieb: > Und hier noch ein Beispiel für Arduino: > > https://forum.arduino.cc/index.php?topic=102181.0 > > Vielleicht ein bisschen mehr Mühe geben beim googeln ;-) Das haben wir auch versucht nachzubauen :) Siehe Nachtrag, da wird das Problem hoffentlich klarer.
Da gibts jede Menge: http://hackaday.com/2013/03/29/avr-vga-generator/ Als Videogame: http://avga.prometheus4.com/index.php?p=7-2 AVGA Basis Paket: http://avga.prometheus4.com/index.php
Hast du bedacht, dass Timer Interrupts niemals "sofort" aufgerufen werden, sondern mal mehr und mal weniger Takte verzögert werden? Ich bin ziemlich sicher, dass Interruptroutinen nicht geeignet sind, so kurze Zeiten hinreichend genau zu verarbeiten. Wo hast du eigentlich gemessen? Und wie hast du Dir denn gedacht, dass die Timer funktionieren sollen? Beschreibe mal, wie dein programm funktionieren SOLL, dann vergleichen wir das mit dienem Code und zeigen Dir, was du ändern musst. Momentan hast du uns nämlich lediglich ein Ratespiel präsentiert. Das Programm funktioniert nicht und ich kann auch nicht wirklich erkennen, was du den vorhattest, zu tun. Vergiss nicht, zu beschreiben, wie die die I/O Pins belegt hast.
Stefan U. schrieb: > Hast du bedacht, dass Timer Interrupts niemals "sofort" aufgerufen > werden, sondern mal mehr und mal weniger Takte verzögert werden? Hat er garantiert nicht. An dem Code ist doch rein garnichts selbst erdacht, es handelt sich um C&P-Code, an dem dann völlig ohne Sinn und Verstand ein wenig rumgestümpert wurde. > Ich bin ziemlich sicher, dass Interruptroutinen nicht geeignet sind, so > kurze Zeiten hinreichend genau zu verarbeiten. Sagen wir mal so: zumindest der Typ kriegt das ziemlich sicher nicht gebacken, aber möglich ist es natürlich durchaus. Es ist sogar ziemlich sinnvoll, denn es eröffnet die Möglichkeit, sehr leicht nutzbare zusätzliche Rechenzeit für den Teil des Gesamtsystems bereitzustellen, der mit der Imagegenerierung nichts zu tun hat. Und das besonders Schicke an dieser zusätzlichen Rechenzeit ist, dass sie relativ häufig verfügbar ist, nämlich einmal pro Scanline (wenn auch natürlich nur in entsprechend kleinen Stücken). Es reicht aber immerhin, um in main() noch relativ zeitnah auf äußere Ereignisse reagieren zu können und erspart so manchen konkurrierenden Interrupt, der sehr viel störendere Auswirkungen auf die Imagegenerierung hätte... Wenn es also nicht nur darum geht, VGA-kompatible Synchronsignale zu generieren, sondern am Ende in die Frames auch selber Bildinhalte zu packen, ist es wohl i.d.R. die beste Variante, dies mit einem Scanline-Interupt zu machen. Durch Vergleich mit dem Wert des Timers, der ihn anstößt, kann man die variable Interruptlatenz kompensieren, dann Bildinhalte raus, dann HSYNC raus, Verwaltung des Vertikalkrams und Ende. Der Rest der Zeilenzeit, also ein großer Teil des HBLANK steht main() als Rechenzeit zur Verfügung. Und, wie immer bei zeitkritischen Anwendungen, ist das alles in Assembler sehr viel einfacher umzusetzen, weil man die "Taktverbrauchsanzeige" quasi live im Quelltext hat und zumindest ein Teil (Kompensation der variablen Latenz) auch bei einer C-Lösung sowieso zwingend in Assembler zu programmieren wären...
Stefan U. schrieb: > Hast du bedacht, dass Timer Interrupts niemals "sofort" aufgerufen > werden, sondern mal mehr und mal weniger Takte verzögert werden? Macht sich das erst bei einer höheren Frequenz bemerkbar? Das vertikale Signal wird mir ziemlich sauber angezeigt. > Ich bin ziemlich sicher, dass Interruptroutinen nicht geeignet sind, so > kurze Zeiten hinreichend genau zu verarbeiten. > > Wo hast du eigentlich gemessen? > Und wie hast du Dir denn gedacht, dass die Timer funktionieren sollen? > > Beschreibe mal, wie dein programm funktionieren SOLL, dann vergleichen > wir das mit dienem Code und zeigen Dir, was du ändern musst. Momentan > hast du uns nämlich lediglich ein Ratespiel präsentiert. Das Programm > funktioniert nicht und ich kann auch nicht wirklich erkennen, was du den > vorhattest, zu tun. > > Vergiss nicht, zu beschreiben, wie die die I/O Pins belegt hast. ISR(TIMER1_COMPA_vect) soll durch OCR1A = 0; jeden Takt aufgerufen werden. Im ersten Takt dreht das Programm PORTL von 0xFF auf 0x00 um. 0x00 bleibt nun solang auf 0x00, bis v_sync_timer die Zahl 259 erreicht hat. Da jeder Takt 64µs andauert, haben wir nun eine Zeit von 16,576ms, wo der PORTL auf 0x00 steht. Danach wird PORTL wieder umgedreht. 0x00 wird zu 0xFF. v_sync_timer wird wieder auf 0 gesetzt. Das heißt der Zustand 0xFF hält ganz genau einen Takt, also 64µs an, bis PORTL wieder umgedreht wird. Dadurch entsteht ein horizontales Signal, das 64µs auf 0xFF und 16ms auf 0x00 steht.
>ISR(TIMER1_COMPA_vect) soll durch OCR1A = 0; jeden Takt aufgerufen >werden. Du Simpel, die ISR muss auch von der CPU abgearbeitet werden. Alleine um einen Interrupt nur zu "initialisieren" bedarfs es schon ein paar CPU-Takte. Beispiel aus der Luft gegriffen und vereinfacht: Sagen wir du hast eine 1MHz CPU, pro Takt ein Befehl, eine ISR braucht 2 Takte fürs Betretten und 2 Takte fürs Verlasen. Die ISR braucht also 4 Takte für sich selbst, ohne das dein USer-Code mitzählt. Sagen wir dein USer-Code braucht 10 Takte, sind also 14 Takte ins. für die ISR. Wenn du nun rechnest: 1.000.000 / 14 = 71429. Du kannst diese ISR also maximal 71429 mal in der Sekunde aufrufen. Ohne das dabei Rechenzeit in der Hauptschleife übrig bliebe, also ein theoretisches Beispiel. Du willst die ISR 16.000.000 mal in nur 16.0000.000 Takten aufrufen...merkst du das Problem? Also aufhören mit copy&paste und selber mal nachdenken...im Hinblick auf deine spätere Arbeit bestimmt keine verkehrte Methodik. Gruß J
Hi >Du Simpel, die ISR muss auch von der CPU abgearbeitet werden. Alleine um >einen Interrupt nur zu "initialisieren" bedarfs es schon ein paar >CPU-Takte. Er meint Timer-Takte, also fcpu/1024. Allerdings verstehe ich nicht warum da ein Interrupt gebraucht wird. Da langt fast PWM im Timermode 14 oder 15. Top mit OCR1 oder OCR1A auf die 16,..ms gestellt und OCR1A oder OCR1B auf die 64µs
Hi Mist aus Versehen auf Absenden geraden. .... auf die 64µs gestellt. Das Ganze noch mit den COM-Bits auf invertierende PWM gestellt und fertig. Mit einem Prescaler von 8 an Stelle von 1024 bekommt man das auch noch genauer hin. MfG Spess
Vielen Dank an euch alle für die Hinweise und Tipps. Ich habe zwischenzeitlich deutliche Fortschritte dank euch gemacht. Ich habe es jetzt geschafft ein sauberes VGA-Signal mit Farben auszugeben. Jedoch immer nur eine Farbe auf einmal und dann den ganzen Bildschirm oder einen Teil des Bildschirms, aber keine einzelnen Pixel. Wie im oberen Beitrage beschrieben, werden die Signale durch den Fast-PWM am Timer 0 & Timer 1 erstellt. Nun will ich noch auf die ISR-Funktionen der Timer zugreifen. Scheinbar werden diese aber nicht mehr getriggert, da PWM ja nicht primär mit diesen arbeitet. Erst wenn ich sei() in den Quellcode mit einfüge, funktionieren die ISR-Funktionen, aber die PWM-Signale werden dadurch nicht mehr ausgegeben. Ich wollte nun fragen, ob es möglich ist, *dass man einen Fast-PWM und einen Timer-Overflow-Interrupt* (und dann mit der entsprechenden ISR-Funktion arbeiten) auf dem gleichen Timer konfigurieren kann. Nur zur Verdeutlichung, damit ihr wisst, wohin das Ganze führen soll: Das Endziel sollte irgendwann sein, dass ich farbige einfach Grafiken darstellen kann. Wie z.B. eine Sonne oder die Uhrzeit oder Ähnliches. Wenn die Grafiken dann nur 120x60 Pixel haben wäre das auch in Ordnung. Unser aktueller Code:
1 | #define F_CPU 16000000UL
|
2 | #include <avr/io.h> |
3 | #include <avr/interrupt.h> |
4 | |
5 | |
6 | int main( void ) |
7 | {
|
8 | |
9 | DDRH=0xff; |
10 | DDRB=0xff; |
11 | DDRL=0xFF; |
12 | PORTL = 0x00; |
13 | DDRE = 0xff; |
14 | |
15 | TCCR2A = 0; |
16 | TCCR2B = 0; |
17 | |
18 | // Timer Mode 7
|
19 | TCCR2A |= (1 << WGM20) | (1 << WGM21); |
20 | TCCR2B |= (1 << WGM22); |
21 | |
22 | //Clear B on Compare Inverted
|
23 | TCCR2A |= (1<< COM2B1|(1<< COM2B0)); |
24 | |
25 | //Clock Prescaler 8
|
26 | TCCR2B |= 2; |
27 | |
28 | OCR2A = 63; |
29 | OCR2B = 7; |
30 | |
31 | TIFR2 = (1 << TOV2); |
32 | TIMSK2 = (1 << TOIE2); |
33 | |
34 | //Configuration Timer 1
|
35 | TCCR1A = 0; |
36 | TCCR1B = 0; |
37 | |
38 | //Timer Mode 15
|
39 | TCCR1A |= (1<<WGM10) | (1<<WGM11); |
40 | TCCR1B |= (1<<WGM12) | (1<<WGM13); |
41 | |
42 | //Clear B on Compare Inverted
|
43 | TCCR1A |= (1<< COM1B1|(1<< COM1B0)); |
44 | |
45 | // Clock Prescaler 1024
|
46 | TCCR1B |= 5; |
47 | |
48 | OCR1A = 259; |
49 | OCR1B = 0; |
50 | |
51 | TIFR1 = (1 << TOV1); |
52 | TIMSK1 = (1 << TOIE1); |
53 | |
54 | //sei();
|
55 | |
56 | while( 1 ); |
57 | }
|
58 | |
59 | ISR (TIMER1_OVF_vect){ |
60 | |
61 | PORTL = 0xFF; |
62 | |
63 | }
|
Da die letzte Anschlussfrage das ursprüngliche Thema etwas sprengt, habe ich einen neuen Beitrag veröffentlicht. Beitrag "Fast-PWM und ISR Funktionen gleichzeitig nutzen"
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.