PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : RP6Base: Video Grabber 1



Dirk
02.05.2010, 13:14
Hallo Leute,

für die Experimentierplatine, die wir hier beschrieben haben:
https://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=53424
... und die hier:
http://www.rn-wissen.de/index.php/RP6_Kamera_-_Mitmach-Projekt
... gebaut wurde, hier ein erstes Programm für die RP6Base. Es funktioniert ab der Phase 3 des Aufbaus.
Ein 32x32 Pixel Bild wird auf dem Terminal ausgegeben. Das ursprüngliche Programm hat radbruch geschrieben.

Was habe ich geändert:
- Nutzung der Signale HSync und VSync des Sync-Separators auf der Exp

/*
* ************************************************** **************************
* RP6 ROBOT SYSTEM - ROBOT BASE TESTS
* ************************************************** **************************
* Example: Grab video pictures and show them on the terminal
* Author(s): radbruch, modified: Dirk
* ************************************************** **************************
* Description:
*
* With this program (written by radbruch) the picture of a CMOS camera
* (CONRAD 150001) can be displayed on the terminal.
* The camera is connected to this hardware project:
* http://www.rn-wissen.de/index.php/RP6_Kamera_-_Mitmach-Projekt
* The BAS video information (PG3) is connected to ADC4 (PG4).
* The synchronization signals are connected as follows:
*
* Signal RP6 Name Port Function
* VSync SCL PC0 SCL
* HSync SDA PC1 SDA
*
* If you use SCL and SDA this way, you can not use the I2C bus any more!
*
* ************************************************** **************************
*
* ACHTUNG: Den Jumper auf JP10 (Stellung H) nur aufstecken, wenn das Programm
* schon läuft! Bitte den Jumper entfernen, wenn kein Programm auf
* der RP6Base läuft! Der Bootloader würde sonst das zuletzt geladene
* Programm immer wieder neu starten!
*
* ################################################## ##########################
* The Robot does NOT move in this example! You can simply put it on a table
* next to your PC and you should connect it to the PC via the USB Interface!
* ################################################## ##########################
* ************************************************** **************************
*/

/************************************************** ***************************/
// Includes:

#include "RP6RobotBaseLib.h" // The RP6 Robot Base Library.
// Always needs to be included!

/************************************************** ***************************/
// Defines:

#define HSYNC (PINC & SDA)
#define VSYNC (PINC & SCL)
#define IRon() {statusLEDs.LED1 = false; updateStatusLEDs();}
#define IRoff() {statusLEDs.LED1 = true; updateStatusLEDs();}

/************************************************** ***************************/
// Variables:

uint8_t bildspeicher[1024], *bildzeiger; // 32*32=1KB * 8Bit Bildspeicher

/************************************************** ***************************/
// Functions:

void bild_einlesen(void)
{
uint8_t pixel[32],*pixelzeiger;
uint8_t i, zeilen, step, lines, rows, h_step, h_delay;

zeilen=32; // Das fertige Bild soll 32 Zeilen haben
step=7; // sichtbares TV-Bild ist ca. 30-260=230/32 ergibt Zeilensprung=7
rows=0; // Anzahl der Spalten (32x32, rechengünstig,aber verzerrt)

do
{
lines=zeilen; // Anzahl der einzulesenden Zeilen
pixelzeiger=&pixel[0]; // Zeiger auf Start Pixelspeicher
cli();
// VSync abwarten (Seitenanfang)
while(VSYNC);
// 40 Zeilen Austastzeit & ein Stück oberen Bildrand überlesen
h_step=40; while (h_step) { while (HSYNC); while (!HSYNC); h_step--; }

// Der Lesecursor befindet sich jetzt oben links im TV-Bild
// ab hier werden in step-Sprüngen in allen Zeilen jeweils das Pixel eingelesen,
// das sich im zeitlichen h_delay-Abstand vom linken TV-Bildrand befinden
// (= eine TV-Bildspalte)

while (lines--)
{
// auf die nächste gültige Zeile warten
h_step=step; while (h_step) { while (HSYNC); while (!HSYNC); h_step--; }
// mit h_delay steuern wir nun den Pixel an
// Nach dem sync fängt das Bild etwas verzögert an (Schwarzschulter), bei mir 10
// bei ca. 110 beginnt die 2.Schwarzschulter.
h_delay=10+3*rows; while (h_delay--) {nop();}

*pixelzeiger=ADCH; // letzten ADC-Wert auslesen und wegwerfen
*pixelzeiger++=ADCH; // aktuellsten ADC-Werte speichern
}
sei();

pixelzeiger=&pixel[0];
bildzeiger=&bildspeicher[32*rows];
for (i=0; i<32; i++) *bildzeiger++ = *pixelzeiger++;

} while (++rows < zeilen);
}

/************************************************** ***************************/
// Main function - The program starts here:

int main(void)
{
initRobotBase(); // Always call this first! The Processor will not work
// correctly otherwise.

// ---------------------------------------
// Write messages to the Serial Interface:

writeString_P("\n\n _______________________\n");
writeString_P(" \\| RP6 ROBOT SYSTEM |/\n");
writeString_P(" \\_-_-_-_-_-_-_-_-_-_/\n\n");

writeString_P("################\n");
writeString_P("<<RP6 Base>>\n");
writeString_P("Video Grabber 1 \n");
writeString_P(" Version 1.10 \n");
writeString_P("################\n\n");
mSleep(2500);

setLEDs(0b111111); // Turn all LEDs on
mSleep(500); // delay 500ms
setLEDs(0b000000); // All LEDs off

// Initialize the M32 SDA pin (PC1) as input:
DDRC &= ~SDA; // ==> HSync
PORTC |= SDA; // Pullup on
// Initialize the M32 SCL pin (PC0) as input:
DDRC &= ~SCL; // ==> VSync
PORTC |= SCL; // Pullup on

// Switch the IR-LEDs off:
IRoff(); // IR LEDs off

uint16_t i, j;
extIntOFF(); // schaltet den E_INT1-Port auf Eingang für den ADC
// ADC interne Referenz 2,56V, Ergebniss linksbündig, Kanal ADC4
ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<ADLAR) | 4;
// setzte free running triggern
SFIOR = (0<<ADTS2) | (0<<ADTS1) | (0<<ADTS0);
// kein interupt, Wandler einschalten, prescaler /2
ADCSRA = (0<<ADIE) | (1<<ADEN) | (0<<ADPS2) | (0<<ADPS1) | (1<<ADPS0);
// Autotriggern bedeutet jetzt free running aktivieren, altes Flag löschen
ADCSRA |= (1<<ADATE) | (1<<ADIF);
// Initialisierung starten
ADCSRA |= (1<<ADSC);
// und noch die wohl eher unnötige Initiallesung
while (!(ADCSRA & (1<<ADIF)));
ADCSRA |= (1<<ADIF);

while(true)
{
bild_einlesen();

for (i=0; i<32; i++)
{
for (j=0; j<32; j++)
{
if (bildspeicher[j+32*i] > 55) writeString_P("*");
else writeString_P(" ");
}
writeInteger(i,DEC);
writeString_P("\n\r");
}
mSleep(200);
}
return 0;
}

/************************************************** ****************************
* Additional info
* ************************************************** **************************
* Changelog:
* - v. 1.1 (workout for the hardware project) 02.05.2010 by Dirk
* - v. 1.0 (initial release) 20.08.2007 by radbruch
*
* ************************************************** **************************
*/

/************************************************** ***************************/

Viel Spaß!

Dirk

radbruch
02.05.2010, 14:20
Hallo Dirk

Du legst dich ja mächtig ins Zeug.

Aufgeschreckt durch dein Mitmachprojekt (http://www.rn-wissen.de/index.php/RP6_Kamera_-_Mitmach-Projekt) habe ich ja auch einen erneuten Anlauf mit der Kamera unternommen (BMP erzeugen (https://www.roboternetz.de/phpBB2/zeigebeitrag.php?p=495235#495235)) und ein paar neue Erkenntnisse zum Thema Auflösung gefunden.

Wie schon mehrfach von verschiedenen Seiten angemerkt kann der ADC bei 4MHz pro Zeile maximal 16 Werte hintereinander samplen. Das läßt sich bei dieser Betriebsart (Dauerlauf) nicht ändern und auch die Startverzögerung am Anfang der Zeile (die ich bisher ja auch verwendete um die Auflösung zu "Erhöhen") ändert daran nichts. Es wird immer nur auf einen dieser 16 Werte zugegriffen.

Abhilfe schafft hier das Anhalten und erneute Starten des ADCs. Sobald der Zeilensync erkannt ist, wird der ADC angehalten. Dann startet die Verzögerung für den gesuchten Zeilenpixel und erst dann wird der ADC erneut gestartet. Nachdem der erste eingelesene Wert (diesmal legal ermittelt mit Warten auf das Flag:) abgespeichert wurde, läuft alles wie gewohnt mit dem Warten auf den nächsten Sync weiter. Nun dauert das erste Samplen zwar einige Zyklen mehr als im Dauerlauf, aber wir lösen uns damit von den sturen 16 Werten die wir bisher erhalten hatten:


while(zeile--) {while (ADCH > 20); while (ADCH < 30);}

ADCSRA = (1<<ADATE)|(0<<ADEN)|(1<<ADIF)|(0<<ADSC)|(1<<ADPS0); // ADC stoppen
while(i--); // Pixel ansteuern
ADCSRA = (1<<ADATE)|(1<<ADEN)|(1<<ADIF)|(1<<ADSC)|(1<<ADPS0); // ADC wieder starten
while (!(ADCSRA & (1<<ADIF))); // 26 ADC-Takte warten bis Wandlung fertig
*bildzeiger++=ADCH; // das sind ca. 6,5µs
ADCSRA |= (1<<ADIF);


Zur weiteren Steigerung der Auflösung habe ich noch an der Verzögerung rumoptimiert. Die while(i--)-Verzögerungeschleife benötigt ca. 3 Takte pro Durchlauf mit Sprung, deshalb lese ich zwei nacheinanderliegende Pixel mit einer "halben" Verzögerungsstufe ein:


ADCSRA = (1<<ADATE)|(0<<ADEN)|(1<<ADIF)|(0<<ADSC)|(1<<ADPS0); // ADC stoppen

nop(); nop(); // nächste Spalte zwei NOPs später einlesen

while(i--); // Pixel ansteuern
ADCSRA = (1<<ADATE)|(1<<ADEN)|(1<<ADIF)|(1<<ADSC)|(1<<ADPS0); // ADC wieder starten


Obwohl ich mit dieser Technik nun horizontal über 100 Pixel ansteuern könnte, funktioniert das in Phase1 noch nicht optimal. Das liegt wieder an den nur 16 Werten pro Zeile im Dauerlauf. Ich kann so nicht erkennen in welchem Takt das Syncsignal erkannt wurde. Diese kleine Ungenauigkeit beim Start der Pixelverzögerung führt in Phase1 dazu, dass die Pixel nicht ganz genau getroffen werden. Dass der ADC nicht syncron mit dem Bild läuft, verstärkt diesen Effekt auch noch.

Ganz anders sind aber die Möglichkeiten der Phase3 mit ihrer Hardwaresyncerkennung. Da hierbei der Sync digital ausgewertet wird fällt die Wandelzeit des ADCs weg und das Ende des Sync kann nahezu auf den Takt genau erkannt werden. Dadurch sollte das gesuchte Pixel viel besser getroffen werden können. (Ich kann's nicht testen weil ich immer noch Phase1 verwende)

Funktioniert es mit dem M32 eigentlich auch mit ADC-Prescaler /2, also 8MHz am ADC?

Gruß

mic

Dirk
02.05.2010, 16:45
Hallo mic,

also: Aufschrecken wollte ich dich eigentlich nicht! [-o<

... habe ich ... ein paar neue Erkenntnisse zum Thema Auflösung gefunden.
Das klingt gut, wobei ich mich erst mal da rein fuchsen muss, was du da machst. Wenn ich das so sehe, sind wir noch nicht am Ende der Fahnenstange mit Auflösung und Bildqualität angekommen. Gute Aussichten!

... kann der ADC bei 4MHz pro Zeile maximal 16 Werte hintereinander samplen. Das läßt sich bei dieser Betriebsart (Dauerlauf) nicht ändern und auch die Startverzögerung am Anfang der Zeile (die ich bisher ja auch verwendete um die Auflösung zu "Erhöhen") ändert daran nichts. Es wird immer nur auf einen dieser 16 Werte zugegriffen.

Das ist mir nur teilweise klar. Sicher bekomme ich free running mit Übertaktung des ADC auf 4 MHz nur 16 Werte pro Zeile. Allerdings ist das ja egal, wenn wir Spalten lesen, weil wir da ja eh nicht in Eile sind. Die Startverzögerung hat ja die Zeit von der steigenden Flanke von HSync bis zum Zeilenanfang (hintere Schwarzschulter) überbrückt. Aber sie hat ja nicht die Auflösung erhöht! Wenn wir ab Zeilenanfang 32 "Zeitmarken" setzen wollten, dann war das, was du mit h_delay bis jetzt gemacht hast, doch ganz ok, oder?
(Anmerkung: Ich konnte das nur mit Oszi für die M32-Version einjustieren, wie hast du das eigentlich gemacht???)

Funktioniert es mit dem M32 eigentlich auch mit ADC-Prescaler /2, also 8MHz am ADC?
Ich war zu feige, es zu probieren. :-#

Gruß Dirk

P.S.:
Noch eine Frage: Wenn ich dein ursprüngliches Programm auf meiner Base laufen lasse, dreht sich rhythmisch die linke Kette mit. Das Programm funktioniert aber normal. Auch nach dem Umschreiben auf die Phase 3 Version war das noch genau so und ich habe mir einen Wolf gesucht, den Fehler zu finden. Läuft bei dir gel. auch die linke Kette? Ich konnte das schließlich nur durch Löschen der Timer 1 Einstellungen der RP6BaseLib (TCCR1A/B = 0) abstellen, ohne es verstanden zu haben. Hast du eine Idee?

radbruch
02.05.2010, 19:20
Hallo Dirk

Nein, mit der Kamera sind wir noch lange nicht am Ende der Fahnenstange :)

Das Kettenproblem wird durch einen Zeigerüberlauf verursacht:
https://www.roboternetz.de/phpBB2/viewtopic.php?p=456037#456037

Es gibt im Dauerlauf nur diese 16 Werte. Es ist dabei völlig egal, wann wir diese Werte aus dem ADC auslesen.

Mit der Verzögerung am Zeilenanfang warten wir auf das auszulesende Pixel. Da wir aber nicht wissen, wie "alt" der zuletzt gelesene ADC-Wert ist, lesen wir mehrere nebeneinander liegende Pixel aus dem selben Sample des ADC! Die Sequenz

*pixelzeiger=ADCH; // letzten ADC-Wert auslesen und wegwerfen
*pixelzeiger++=ADCH; // aktuellsten ADC-Werte speichern

ist eigentlich schon der Hinweis auf das Problem.

Was wurde am Ende der Verzögerung zuletzt gesamplet? In ADCH steht immer der Wert der letzten Digitalisierung, also einer der 16 Werte. Das ändert sich nicht, solange der ADC nicht gestoppt und wieder gestartet wurde. Denn dann kann man den ADC mit dem Kamerasignal für diese eine Zeile genauer als in 16 Schritten syncronisieren. Ohne diese Synchrionisation läuft der ADC-Takt quasi neben dem Videotakt mit einer anderen Schrittgröße (blöder Vergleich?).

Der "Dauerlauf" des ADC ist die schnellste Methode die Werte einzulesen. Das war nötig um die Syncs zu erkennen. Mit der Phase3 ist das nicht mehr nötig. Nun könnte man den ADC entschärfen und generell nur noch einmalige Samples einlesen: Bildstart, Zeilenstart, Pixelverzögerung, ADC starten und Wert, nach Fertigmeldung des ADC, einlesen, warten auf nächstes Sync. Dann könnten wir uns auch von den 4MHz verabschieden, allerdings würde vermutlich die Einlesezeit im Gesamten deutlich steigen

Hier scheiden sich wohl letzlich auch die Wege:
Auf das Projekt zugeschnittene Auswertung oder universelle Anwendung?

Gruß

mic

[Edit]
Zufällig drübergestolpert: Das Edit in diesem Beitrag schreit quasi nach einem jumperbaren 75Ohm-Widerstand:
https://www.roboternetz.de/phpBB2/viewtopic.php?p=456159#456159

Dirk
02.05.2010, 21:15
Hi mic,

Ohne diese Synchrionisation läuft der ADC-Takt quasi neben dem Videotakt mit einer anderen Schrittgröße (blöder Vergleich?).
Ja, das war ein Nachteil. Aber könnte man nicht auch beim free running ADIF auswerten und z.B. in einer ISR den Wert sichern, wenn er wirklich fertig ist?

Mit der Phase3 ist das nicht mehr nötig. Nun könnte man den ADC entschärfen und generell nur noch einmalige Samples einlesen: Bildstart, Zeilenstart, Pixelverzögerung, ADC starten und Wert, nach Fertigmeldung des ADC, einlesen, warten auf nächstes Sync. Dann könnten wir uns auch von den 4MHz verabschieden, allerdings würde vermutlich die Einlesezeit im Gesamten deutlich steigen
Die Frage wäre, ob man die "legal" möglichen 4 Lesungen (tatsächlich verteilen sich aber nur 3 "Lesungszeitpunkte" auf die 52 µs) nutzen kann, um Einlesezeit zu sparen. Immerhin bekäme man auf einmal mind. 3 Punkte pro Zeile.

... Beitrag schreit quasi nach einem jumperbaren 75Ohm-Widerstand
Ja, sollte man machen. Ich habe deine Empfehlung schon in Phase 2 aufgenommen.

Gruß Dirk