Hallo

In diesem Thread möchte ich euch zeigen, wie man unglaublich einfach und kostengünstig eine Kamera an den RP6 anschliessen kann.

Grundsätzlich geht es allerdings darum, mit einem 8MHz-ATMega ein Composite-Video-Signal auszuwerten. Ob das, wie in meinem Fall, eine ausgeschlachtete Überwachungskamera liefert, oder ein 15€-Teil vom großen c (art-nr: 150001-62) oder gar der Video-Ausgang einer Digicam oder Foto-Handys, ist völlig egal.

Die Hardware:

- Ein Kameramodul, das mit 5V funktioniert und ein Composite-Video-Signal liefert (5V, 1VSS/75Ohm)
- Ein Stecker, der in die RP6-XBUS1/2-Buchse passt
- Ein Cinch-Stecker, der das Kontrollsignal für einen Bildschirm auskoppelt.

Der Anschluß:

Die 5V-Kamera wird am Pin 3/5(Vcc) und 1/2(GND) angeschlossen. Der Vid-Ausgang der Kamera (oder des beliebigen Composite-Video-Lieferanten) kommt an Pin 8 (E_INT1). Dies ist, außer den User_ADCs der einzig freie ADC-Kanal, der zudem noch praktischerweise am XBus liegt. Außerdem hat er einen PullDown von 10K, die optimale Last für ein Composite-Signal. Der Chinch-Stecker wird zusätzlich zwischen Pin 8 (Signal) und Pin 1/2 (GND) angeschlossen. Der ist eigentlich nur zu Kontrolle gedacht, damit man sehen kann, was der RP6 sieht. Gam(ma) ist bei meiner Kamera nicht angeschlossen, sollte man aber als Jumper vorsehen.

Die Software:

Erste Hürde war der ADC und die damit möglichen Samplezeiten. Die im Datenblatt des ATMegas angegebenen 200kHz reichen für die 5MHz eines fbas-Signals bei weitem nicht aus. Aber er ist schnell genug, um wenigstens ein paar Daten einzulesen. Hier das Setup des ADC:

Code:
// ADC interne Referenz 2,56V, Ergebniss linksbündig, Kanal ADC4 (E_INT1)
	ADMUX = (1<<REFS1) | (1<<REFS0)  | (1<<ADLAR) | 4;
// setzte free running triggern
	SFIOR = (0<<ADTS2) | (0<<ADTS1) | (0<<ADTS0);
// kein interupt, Wandler einschalten, prescaller /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);
Referenzspannung sind die interenen 2,56V. Das ist recht praktisch, den ich lese das linksbündige Ergebniss als Byte-Variable ein und jeder Schritt entspricht nun 0,01V. (mein Signal hat 1,4V mit dem Multimeter gemessen) Im "8-Bit-Modus" ließt man nur die MSB der Ergebnisse, die der ADC bei "free running" mit 4MHz digitalisiert.

Nächste Hürde war das Timing des Signals. Mit der Samplerate kann ich das vsync-Signal gut erkennen, das hsync ist deutlich länger und markiert den Start des Halbbildes (Alle Fachbegriffe und Kentnisse habe ich von hier). Einige auf einander folgende Zeilen sehen etwa so aus:

http://radbruch.roboterbastler.de/rp...nc-signale.pdf

Man sieht deutlich die vsyncs, allerdings schaffe ich so nur ca. 60 Werte pro Zeile. Mehr geht einfach nicht mit C, vielleicht kann man mit Assembler hier noch etwas "rauskitzeln". Mein Code für das Einlesen eines Pixels:

do *pixelzeiger=ADCH; while (*pixelzeiger++ > 20);

Alternativen wie:

while (count_pixel) pixel[--count_pixel]=ADCH;
for (;count_pixel; pixel[--count_pixel]=ADCH);

wurden fast identisch übersetzt und brachten nur ca. 50 Lesungen pro Zeile. Mit

Code:
	while (line) // Zeile abwarten
	{
		while (ADCH > 20); while (ADCH < 30); line--;
	}
kann ich nun das vsync erkennen, hsync dauert bei mir ca. 47 Zyclen, also prüfe ich so:

Code:
	do // hsync abwarten
	{
		vsync=0; while (ADCH > 20); while (ADCH < 30) vsync++;
	} while (vsync < 40);
Da ich so vertikal "nur" ca. 60 Werte erfassen kann, aber horizontal alle Zeilen zwischen ca. 30 und 260 direkt ansteuern kann, habe ich meine Kamera zum Linienfolgen um 90 Grad gedreht. Zudem verwende ich nur die Pixel 10 bis 14, also einen recht kleinen Bereich der verfügbaren Daten:

Code:
#include "RP6RobotBaseLib.h"

#define mpower 50
#define spur 145

// Achtung! Die PWM-Werte werden hier OHNE Rampe verändert!
void setMotorPWM(uint8_t power_links, uint8_t power_rechts)
{
extern uint8_t mleft_ptmp, mright_ptmp;

	if(power_links > 210) power_links = 210;
	if(power_rechts > 210) power_rechts = 210;
	mleft_power=mleft_ptmp=power_links;
	mright_power=mright_ptmp=power_rechts;

	OCR1BL = power_links;
	OCR1AL = power_rechts;

	if(power_links || power_rechts)
		TCCR1A = (1 << WGM11) | (1 << COM1A1) | (1 << COM1B1);
	else
		TCCR1A = 0;
}

uint16_t get_line(uint16_t line)
{
	uint8_t pixel[256],*pixelzeiger, vsync;
	uint16_t temp=0;

	pixelzeiger=&pixel[0]; // Zeiger auf Start Pixelspeicher
	cli();
	do // hsync abwarten
	{
		vsync=0; while (ADCH > 20); while (ADCH < 30) vsync++;
	} while (vsync < 40);

	while (line) // Zeile abwarten
	{
		while (ADCH > 20); while (ADCH < 30); line--;
	}

	do *pixelzeiger=ADCH; while (*pixelzeiger++ > 20); // Zeile einlesen
	sei();
	*pixelzeiger=0; // Endekennung der Daten

	pixelzeiger=&pixel[10]; // Summe der Pixel 10-14 bilden
	while (pixelzeiger < &pixel[15]) temp+=*pixelzeiger++;
	return (temp);
}

int main(void)
{
	uint16_t zeile;
	uint16_t gamma, i;
	uint16_t strich_links, strich_rechts, strich_mitte, strich_breite;
	uint8_t pow_l,pow_r;

	initRobotBase();
	extIntOFF(); // schaltet den E_INT1-Port auf Eingang für den ADC
	//powerON();
// ADC interne Referenz 2,56V, Ergebniss linksbündig, Kanal ADC4 (E_INT1)
	ADMUX = (1<<REFS1) | (1<<REFS0)  | (1<<ADLAR) | 4;
// setzte free running triggern
	SFIOR = (0<<ADTS2) | (0<<ADTS1) | (0<<ADTS0);
// kein interupt, Wandler einschalten, prescaller /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);

	gamma=0; i=0;
	for (zeile=30; zeile<260; zeile+=5)
	{
		gamma+=get_line(zeile);
		i++;
	}
	gamma=gamma/i;
	gamma-=gamma/3;
	writeInteger(gamma,DEC);
	writeString_P("\n\r");
	
	setMotorDir(BWD,BWD);
	pow_l=pow_r=mpower;
	setMotorPWM(pow_l/2,pow_r/2);
	mSleep(mpower);
	setMotorPWM(pow_l,pow_r);

	do
	{
	   strich_links=strich_rechts=0;
		zeile=30;
		do
		{
		   if (!strich_links && (get_line(zeile)<gamma))
			{
				strich_links=zeile;
				zeile+=5;
			}
		   if (strich_links && (get_line(zeile)>gamma)) strich_rechts=zeile;
			zeile+=10;
		} while ((zeile<260) && !strich_rechts);
		if (!strich_rechts) strich_rechts=260;

		strich_mitte=(strich_links+strich_rechts)/2;
		strich_breite=strich_rechts-strich_links;

		if ((strich_links < spur) && (strich_rechts > spur))
			pow_l=pow_r=mpower;
		else
		{
			if (strich_links > spur) {pow_l=pow_l/2; pow_r=mpower;}
			if (strich_rechts < spur) {pow_l=mpower; pow_r=pow_r/2;}
		}
		setMotorPWM(pow_l,pow_r);

		//writeInteger(spur,DEC);
		//writeString_P(" - ");
		writeInteger(strich_mitte,DEC);
		writeString_P(" - ");
		writeInteger(strich_breite,DEC);
		writeString_P("\n\r");
 		//mSleep(100);
	} while (strich_breite < 100);
	//setMotorDir(FWD,FWD);
	setMotorPWM(pow_l/2,pow_r/2);
	mSleep(mpower);
	setMotorPWM(0,0);
	//mSleep(500);
	while (1);
	return(0);
}
Für OCR oder das Erkennen von Personen dürfte diese Lösung nicht taugen. Noch fehlt die KI, aber dafür haben wir ja andere Spezialisten.

Gruß

mic