PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Minimallösung: Kamera für den RP6



radbruch
17.08.2007, 00:00
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 (http://de.wikipedia.org/wiki/Composite_Video) auszuwerten. Ob das, wie in meinem Fall, eine ausgeschlachtete Überwachungskamera liefert, oder ein 15€-Teil vom großen c (http://www.conrad.de)(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 (https://www.roboternetz.de/phpBB2/viewtopic.php?t=33070). Die im Datenblatt des ATMegas angegebenen 200kHz reichen für die 5MHz eines fbas-Signals (http://de.wikipedia.org/wiki/FBAS) bei weitem nicht aus. Aber er ist schnell genug, um wenigstens ein paar Daten einzulesen. Hier das Setup des ADC:


// 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 (http://de.wikipedia.org/wiki/FBAS)). Einige auf einander folgende Zeilen sehen etwa so aus:

http://radbruch.roboterbastler.de/rp6/liniensuche1/sync-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


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:


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:


#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

Xtreme
17.08.2007, 08:48
Wow, echt beeindruckend was du da geschafft hast! Ich komm zur Zeit einfach zu nichts...
Wie sieht es mit der Stromaufnahme der Kamera aus? Man könnte auf diese Weiße einen Laserscanner verwirklichen. Schau mal auf die Homepage von toemchen...

radbruch
17.08.2007, 11:47
Hallo

Die Stromaufnahme der Kamera wird bei Conrad mit 10mA angegeben.

Natürlich könnte man damit auch solch einen Laserscanner (http://www.robofriend.de/laserscanner.htm) realisieren, der ohne unterstützenden PC funktioniert.

Das funktioniert übrigends nicht nur mit dem RP6, sondern mit allen 8MHz-ATMegas mit freiem ADC-Kanal. Mehr als 8Mhz ist kein Problem, weniger ist kritisch, weil der vsync nur 4,7us dauert.

Anbauvorschlag für den asuro:

Wenn man den Widerstand R12(12k) der Batteriemessschaltung entfernt, kann man das Kamerasignal am ADC5 anschließen. Der R13(10k) kann drin bleiben und bildet wie bei meinem RP6 eine Grundlast für das Signal(PullDown). Plus der Kamera ins freie Loch vom R12 (Vbat), Minus an den R13(GND). Mit einem kleinen Stecker könnte man noch wahlweise die Kamera oder den R12 einstecken. Wobei ich davon ausgehe, dass die Kamera auch mit 4,8V funktioniert. Einzige Programmänderung wäre dann ein anderer Kanal im ADMUX-Register des ADC.

Noch ein paar Pics:
http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170008klein.jpg (http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170008.jpg) http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170009klein.jpg (http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170009.jpg) http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170005klein.jpg (http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170005.jpg) http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170003klein.jpg (http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170003.jpg)

Gruß

mic

radbruch
19.08.2007, 03:15
Hallo

Da ich die Kamera vorerst zum Linienfolgen einsetzen möchte, reicht es, wenn der RP6 schwarz und weis unterscheiden kann. Deshalb verwende ich nur noch die oberen 4Bit der Ergebnisses. Da dann nur 16 Werte auftreten, kann man locker in Echtzeit eine Gammaberechnung machen und nun erkennt der RP6 die Linie ziemlich gut und vor allem recht unabhängig vom Fremdlicht:

http://img.youtube.com/vi/G2F4jhsRtF4/1.jpg (http://www.youtube.com/watch?v=G2F4jhsRtF4)
(Bild anklicken für youtube-Video)

In dieser Version kann der RP6 über 40 verschiedene Positionen des Strichs von links-raus nach rechts-raus erkennen. Das sind bei diesem Abstand einige Zentimeter. Strichbreite und vor allem Strichmitte kann er auch schon berechnen.

Gruß

mic

vklaffehn
19.08.2007, 11:37
Hallo!
Sehr schöne Idee, da werd ich mich wohl von meiner Gameboycamera wieder lösen und es mal damit versuchen. Wenn Du bei 8MHz ca.60Pixel pro Zeile schaffst, komme ich mit 16MHz auch in die Nähe von 120, bei voller Zeilenauflösung, da bin ich besser als die GBCAM mit 128*128 Pixeln und hab nicht den ganzen Stress mit Belichtung und Registerkonfiguration.... Vielen Dank für die Inspiration, werde bei Gelegenheit berichten, wie es dann bei mir aussieht.

MfG
Volker

Sommer
19.08.2007, 14:10
Hi,

Du wertest also die Spannungen zwischen V und HSync aus oder wie kann ich das verstehen? So ganz bin ich bei deiner Auswertung noch nicht mitgekommen :-) Auch deine Bitaufteilung verstehe ich noch nicht ganz? Bitte um aufklärung... mich würds einfach näher Interessieren...

Gruß Ulli

radbruch
19.08.2007, 14:31
Hallo Volker

Mit der GB-Kamera wollte ich's auch mal versuchen, nur leider hatte ich keine rumliegen.

Mit der 8Mhz funktioniert es eigentlich schon recht gut. Man kann natürlich keine Wunder erwarten. Ein großes Problem ist auch die Datenmenge, mehr Pixel bedeutet eben auch mehr Daten.

In der ersten Variante habe ich auf den vsnyc (und den vertikalen Strahlrücklauf) gewartet, er dauert bei meiner Lesegeschwindigkeit ca. 47 Lesungen. Dann habe ich die hsyncs gezählt um die gewünschte Zeile anzusteuern (wegen der Datenmenge nur jeder Xte Zeile, das ergibt die vertikale Auflösung). Bei Erreichen der gewünschten Zeile habe ich dann so schnell wie möglich die Daten der Zeile eingelesen und komme so auf ca. 60 Pixel/Zeile (horizontale) Auflösung. Aus Speichermangel habe ich dann die Zeile verarbeitet und dann erst die nächste eingelesen. Deshalb benötigt ein kompletter Scan des Bildes pro eingelesene Zeile mindestens ein Halbbild für das eigentliche scannen und noch ein paar weitere fürs verarbeiten. In Summe deutlich zu lange.

Gegenüber diesen ersten Versuchen sieht das Einlesens nun wesentlich anders aus. Ich lese pro Scan nun nur noch einen (beliebigen) horizontalen Pixel pro Zeile, den dafür aber pro Halbbild in allen gewünschten vertikalen Linien auf einen Schlag. Das Bild wird dadurch Spaltenweise eingelesen. Der sichtbare Bereich meines Bildes liegt zwischen den Zeilen 30 und 260, also habe ich so eine maximal mögliche vertikale Auflösung von 230 Pixeln.

Da ich nun horizontal nur noch einen Pixel/Zeile einlesen muss, ist es nun nicht mehr kritisch, wie schnell man das kann. Man muss nur den Pixel finden. Und das mache ich in dieser Variante so:

Wie gewohnt warten auf den Start des Halbbildes, dann 30 Zeilen "Austastlücke für BTX" abwarten, jetzt beginnt das eigentliche Einlesen. Und hier nun die wesentliche Änderung, nach dem hsync wird erstmal eine kurze Zeit abgewartet:

h_delay=50; while (h_delay--);

Das ist der x-Versatz des Pixels, das ich einlesen will. Über die Dauer der Verzögerung kann ich unabhängig von der Lesegeschwindigkeit des ADCs erst den Lesezeitpunkt innerhalb einer Zeile festlegen (max für h_delay weiß ich noch nicht). Dann löscht eine Dummy-Lesung das alte ADC-Resultat und startet damit ein erneutes Sampeln des Signals. Nun lese ich das ADCH nochmal aus und habe damit den gewünschten Pixel. Jetzt bleibt noch genug Zeit um das Ende der aktuellen Zeile abzuwarten. Nach dem nächsten vsync kann das Pixel in der nächsten (oder Xten) Zeile eingelesen werden. Somit dauert das Lesen einer kompletten Spalte so lange wie ein Halbbild.

Die horizontale Auflösung entspricht den möglichen Werte für das h-delay, also etwas mehr als 0 bis Ende der Zeile. Das habe ich noch nicht gemessen, denn eigentlich reicht es, nur an bestimmten Punkten eine Spalte zu scannen um einen Punkt, Strich oder gar ein einfaches Muster zu erkennen. Deshalb ist meine Kamera auch um 90° gedreht, der Strich kommt von links ins Bild und die Lage des Strichs entspricht dadurch den Zeilen des Bildes. Dann reicht ein Scan einer Spalte, um den Strich zu erkennen. Mit 2 Scans in verschiedenen Spalten kann man dann schon die Richtung des Striches auswerten...

Hier noch mein aktueller, ungeschminkter Arbeitscode als Anregung:


#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;
}

void get_lines(uint16_t delay, uint8_t zeilen)
{
uint8_t pixel[256],*pixelzeiger;
uint8_t i, step, lines, h_step, h_sync, h_delay;
uint16_t gamma[16],g_hell, g_dunkel, g_sprung;
uint16_t strich_links, strich_rechts, strich_mitte, strich_breite;

step=(260-35)/zeilen; // 30-260 sind die sichtbare Zeilen
lines=zeilen; // Anzahl der einzulesenden Zeilen
pixelzeiger=&pixel[0]; // Zeiger auf Start Pixelspeicher

cli();
do // vsync abwarten
{
h_sync=0; while (ADCH > 20); while (ADCH < 30) h_sync++;
} while (h_sync < 40);

h_step=35;
while (h_step) // 30 Zeilen Austastzeit überlesen
{
while (ADCH > 20); while (ADCH < 30); h_step--;
}

while (lines--)
{
h_step=step;
while (h_step) // auf die nächste gültige Zeile warten
{
while (ADCH > 20); while (ADCH < 30); h_step--;
}
h_delay=delay;
while (h_delay--); // auf richtiges Pixel warten
*pixelzeiger=ADCH; // letzten ADC-Wert auslesen und wegwerfen
*pixelzeiger++=ADCH; // aktuellsten ADC-Werte speichern
}
sei();
//*pixelzeiger=0; // Endekennung der Daten

for (i=0; i<16; gamma[i++]=0); // 16 gammawerte zählen
pixelzeiger=&pixel[0];
for (lines=0; lines < zeilen; lines++) gamma[*pixelzeiger++ >> 4]++;

g_sprung=5;
g_dunkel=0;
for (i=1; i<16; i++)
{
if (gamma[i] > gamma[i-1])
if ((gamma[i] - gamma[i-1]) > g_sprung)
if (!g_dunkel) g_dunkel=i;
}
g_hell=0;
for (i=14; i; i--)
{
if (gamma[i] > gamma[i+1])
if ((gamma[i] - gamma[i+1]) > g_sprung)
if (!g_hell) g_hell=i;
}
/*
for (i=0; i<16; i++)
{
if (i) writeString_P("-");
writeInteger(gamma[i],DEC);
}
writeString_P("\n\r");
writeInteger(g_dunkel, DEC);
writeString_P("-");
writeInteger(g_hell, DEC);
writeString_P("\n\r");
*/
strich_links=0;
strich_rechts=0;
for (i=0; i<zeilen; i++)
{
if ((pixel[i] >> 4) == g_dunkel) writeString_P("*");
else writeString_P(" ");
if (((pixel[i] >> 4) == g_dunkel) && ((g_hell-g_dunkel)>1))
{
if (!strich_links) strich_links=i;
else strich_rechts=i;
}
}
strich_mitte=(strich_links+strich_rechts)/2;
strich_breite=strich_rechts-strich_links;
writeString_P(" | ");
writeInteger(strich_links, DEC);
writeString_P("-");
writeInteger(strich_rechts, DEC);
writeString_P("-");
writeInteger(strich_mitte, DEC);
writeString_P("\n\r");
}

int main(void)
{
// 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);

while (1){
get_lines(50,48);
mSleep(50);
}
/*
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);
}

Der Funktion get_line() wird der Wert für den h_delay und die Anzahl der gewünschten Pixel/Spalte übergeben (das Pixelfeld hat 256 Elemente, ich lese aber nur 48 Pixel ein).

Nach dem Stetzen der Startbedingungen wird eine Spalte in pixel[] eingelesen, dann ein Hystogramm für die oberen 4Bit von pixel[] in gamma[] berechnet. Dann wird in gamma[] eine "Kante" von mindestens g_sprung gesucht, von dunkel her ist das die Linie, von hell aus gesucht das Blatt. Jetzt folgt noch die (Test-)Ausgabe der erkannten Strich-Pixel und die Berechnung der Strichdaten Kante-links/-rechts, Strich-Breite und Mitte. Strichmitte soll übrigens mal der Rückgabewert dieser Funktion werden.

@Sommer
Vielleicht reichen dir die Erklärungen schon.

Gruß

mic

Sommer
19.08.2007, 16:33
Hi,

ja denke vorerst mal schon ;-)
Jetzt werden alle mal selber probieren ;-)

Auf jedenfall eine Interessante Low Cost Lösung wenns funktioniert wie du schreibst. Kannst auch mal ein Video posten wo RP6 eine Linie entlangfährt?

Gruß Ulli

MartinFunk
19.08.2007, 19:57
Hi radbruch,

Ich kapier das nicht richtig.
kannst du den code mal so abspecken das er das bild in ein array schreibt?

MfG Martin

vklaffehn
19.08.2007, 20:52
Hallo!
Danke für die ausführliche Erläuterung, meine Kamera auch 90° gedreht, das ganze soll, wie irgendwo in dem Thread hier schon erwähnt, als Laserscanner arbeiten, d.h. ich brauche quasi nur zeilenweise auswerten, allerdings wollte ich später noch ein wenig mehr machen, quasi einfachste Mustererkennung und so, mein Mega32 hat auch schon 32KB SRAM zur Seite, da kann man notfalls das eine oder andere kleine Bildchen zum vergleichen abspeichern, ich weiß leider nur noch nicht, wann ich mal wieder Zeit zum basteln habe.... :-)
MfG
Volker

radbruch
20.08.2007, 01:35
Hallo

Es wächst und gedeiht, hier mein einfacher Linienfolger:


#include "RP6RobotBaseLib.h"

#define mpower 50
#define spur 20

// 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;
}

uint8_t get_line(uint16_t delay, uint8_t zeilen)
{
uint8_t pixel[256],*pixelzeiger;
uint8_t i, step, lines, h_step, h_sync, h_delay;
uint16_t gamma[16],g_hell, g_dunkel, g_sprung;
uint16_t strich_links, strich_rechts, strich_mitte, strich_breite;

step=(260-35)/zeilen; // 30-260 sind die sichtbare Zeilen
lines=zeilen; // Anzahl der einzulesenden Zeilen
pixelzeiger=&pixel[0]; // Zeiger auf Start Pixelspeicher

cli();
do // vsync abwarten
{
h_sync=0; while (ADCH > 20); while (ADCH < 30) h_sync++;
} while (h_sync < 40);

h_step=35;
while (h_step) // 30 Zeilen Austastzeit überlesen
{
while (ADCH > 20); while (ADCH < 30); h_step--;
}

while (lines--)
{
h_step=step;
while (h_step) // auf die nächste gültige Zeile warten
{
while (ADCH > 20); while (ADCH < 30); h_step--;
}
h_delay=delay;
while (h_delay--); // auf richtiges Pixel warten
*pixelzeiger=ADCH; // letzten ADC-Wert auslesen und wegwerfen
*pixelzeiger++=ADCH; // aktuellsten ADC-Werte speichern
}
sei();

for (i=0; i<16; gamma[i++]=0); // 16 gammawerte zählen
pixelzeiger=&pixel[0];
for (lines=0; lines < zeilen; lines++) gamma[*pixelzeiger++ >> 4]++;

g_sprung=5;
g_dunkel=0;
for (i=1; i<16; i++)
{
if (gamma[i] > gamma[i-1])
if ((gamma[i] - gamma[i-1]) > g_sprung)
if (!g_dunkel) g_dunkel=i;
}
g_hell=0;
for (i=14; i; i--)
{
if (gamma[i] > gamma[i+1])
if ((gamma[i] - gamma[i+1]) > g_sprung)
if (!g_hell) g_hell=i;
}

strich_links=0;
strich_rechts=0;
for (i=0; i<zeilen; i++)
{
if (((pixel[i] >> 4) == g_dunkel) && ((g_hell-g_dunkel)>1))
{
if (!strich_links) strich_links=i; // wenn linker Rand gefunden,
else strich_rechts=i; // weitersuchen bis rechter Rand
}
}
strich_mitte=(strich_links+strich_rechts)/2;
strich_breite=strich_rechts-strich_links;
return(strich_mitte);
}

int main(void)
{
uint8_t i;
uint8_t pow_l, pow_r, dir_l, dir_r;
uint8_t strich, keine_strich, out_l, out_r, abweich;

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);

out_l=true;
out_r=false;
keine_strich=0;

dir_l=dir_r=BWD;
setMotorDir(dir_l,dir_r);
pow_l=pow_r=mpower;
setMotorPWM(pow_l/2,pow_r/2);
mSleep(mpower);
setMotorPWM(pow_l,pow_r);

do
{
strich=get_line(50,48);
abweich=2*abs(spur-strich);

if (strich)
{
out_l=out_r=false;
if (strich < (spur-15)) out_l=true;
if (strich > (spur+15)) out_r=true;

if (strich < spur)
{
pow_l=mpower+abweich;
pow_r=mpower-abweich;
}
else
{
pow_l=mpower-abweich;
pow_r=mpower+abweich;
}
}
else
{
if (out_l) {pow_l=mpower; pow_r=0;}
if (out_r) {pow_l=0; pow_r=mpower;}
}

setMotorPWM(pow_l,pow_r);

writeString_P("\n\r");
writeInteger(strich, DEC);
writeString_P("-");
writeInteger(abweich, DEC);
mSleep(100);
} while (1);
return(0);
}

und ein paar Videos der Tests. Zuerst die RN-Teststrecke:

http://img.youtube.com/vi/txMYl7aKTBA/default.jpg (http://www.youtube.com/watch?v=txMYl7aKTBA)
(http://www.youtube.com/watch?v=txMYl7aKTBA)

Dann ein USB-Kabel, das am Boden liegt:

http://www.youtube.com/watch?v=NFQz49vDDfg

Selbes Kabel auf einem anderen Untergrund:

http://www.youtube.com/watch?v=paXpHySWn7g
http://www.youtube.com/watch?v=qVBPT5tGpjg

Ganz nett, wenn man anschaut, wie es mit der KI dieses Linienfolgers bestellt ist. Zudem scanne ich den Strich nur von einer Seite...



Ich kapier das nicht richtig.
kannst du den code mal so abspecken das er das bild in ein array schreibt?
Euch ist hoffentlich klar, dass dies alles hier keine "Ready to use"-Lösung ist. Die Kamera ist nur das sehr einfache Auge. Wie der Roboter das Gesehene interpetiert, ist eure Aufgabe:


#include "RP6RobotBaseLib.h"

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

void bild_einlesen(void)
{
uint8_t pixel[32],*pixelzeiger;
uint8_t i, zeilen, step, lines, rows, h_step, h_sync, 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();
// h_sync abwarten (syncsignal länger 40 bedeutet Seitenanfang)
do { h_sync=0; while (ADCH > 20); while (ADCH < 30) h_sync++; } while (h_sync < 40);

// 30-35 Zeilen Austastzeit überlesen (der Rest des hsyncs+nicht darstellbare BTX-Infos)
h_step=35; while (h_step) { while (ADCH > 20); while (ADCH < 30); 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 (ADCH > 20); while (ADCH < 30); 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 20
// bei ca. 150 beginnt die 2.Schwarzschulter. Bei 150-20 möglichen Bildpunkten
// ergibt sich eine maximale Auflösung von 128 horizontal. Zusammen mit der
// vertikalen Auflösung von 230 kämen wir dann bei einem 8MHz-ATMega auf stolze
// 128*230 Bildpunkte.
h_delay=20+4*rows; while (h_delay--);

*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);
}

int main(void)
{
uint16_t i, j;

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);

while(1)
{
bild_einlesen();

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

return(0);
}

Damit macht man z.B. 32x32-"Bilder", wenn das jemand braucht. Die Ausgabe dazu findet ihr im Anhang.
Ein kleines Filmchen zeigt, wie es funktioniert:

http://img.youtube.com/vi/5PCvCAti1RY/1.jpg (http://www.youtube.com/watch?v=5PCvCAti1RY)
(http://www.youtube.com/watch?v=5PCvCAti1RY)

Hier wird übrigens nur per Wert > 90 ein Punkt gesetzt oder nicht. Quasi nur die Rohdaten ohne Bearbeitung.

Gruß

mic

Sommer
20.08.2007, 20:39
Hi Radbruch,

das sieht ja alles recht gut aus...
Aber wenn ich in sDatenblatt schaue, sehe ich das der Atmega32
für eine ADC Wandlung im bestenfall 13µS benötigt. 200Khz ist ja nur die Taktfrequenz des ADC Wandlers! Normale Video ADC Arbeiten mit Taktfrequenzen von 35Mhz...

Wenn also unser ADC 13µS benötigt verpasst er auf jedenfall einige H_Syncs. bis er loslegt. Das ende findet er ja dann auch nicht immer...

Erklärungsnotstand :-)

radbruch
20.08.2007, 22:46
Hallo Sommer


Aber wenn ich ins Datenblatt schaue, sehe ich das der Atmega32
für eine ADC Wandlung im bestenfall 13µS benötigt.

Darüber (https://www.roboternetz.de/phpBB2/viewtopic.php?t=33070) habe ich mich auch schon gewundert. Allerdings steht das nur oben im Datenblatt. Bei der Beschreibung des ADCs wird dann immer nur von 13,5 "Taktzyklen" gesprochen. Und die verwendet er auch, ich habe keinen Weg gefunden, um das Samplen nach weniger als 10Bit abzubrechen. Mit der Zyklenrechnerei hab ich's nicht so, aber der Ansatz wäre wohl (Bei 8MHZ ATMega und perscaler /2): 1/4000000Hz Clock*14 Zyklen oder 3,5us. Dann würde ich das 4,7us-hsync mindestens einmal treffen. Wenn man genau hinschaut, sieht man im Beitrag oben im Diagramm (http://radbruch.roboterbastler.de/rp6/liniensuche1/sync-signale.pdf) die Schwarzschultern und die hsyncs. Allerdings ist das blöderweise spiegelverkehrt, weil ich die Werte rückwärts ausgegeben hatte. Hier ein Code der 256 Werte in Zeile 100 einliest und tabellengerecht an den PC sendet:


// Liest ab der 100. Zeile 256 Werte am Stück ein
// und sendet die Daten als Tabellenvorlage zum PC.

#include "RP6RobotBaseLib.h"

int main(void)
{
uint8_t pixel[256],*pixelzeiger, *endezeiger;
uint8_t vsync, lines;

initRobotBase();
extIntOFF();
//powerON();
// interne Referenz 2,56V, linksbündig, Kanal ADC4
ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<ADLAR) | 4;
// free running triggern
SFIOR = (0<<ADTS2) | (0<<ADTS1) | (0<<ADTS0);
// kein interupt, einschalten, prescaller /2
ADCSRA = (0<<ADIE) | (1<<ADEN) | (0<<ADPS2) | (0<<ADPS1) | (1<<ADPS0);
// free running aktivieren, altes Flag löschen
ADCSRA |= (1<<ADATE) | (1<<ADIF);
// Initialisierung starten
ADCSRA |= (1<<ADSC);

while (!(ADCSRA & (1<<ADIF)));
ADCSRA |= (1<<ADIF);

pixelzeiger=&pixel[0];
endezeiger=&pixel[255];
lines=100;
cli();

do // vsync abwarten
{
vsync=0;
while (ADCH > 20);
while (ADCH < 30) vsync++;
}while (vsync < 40);

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

// 256 Werte am Stück einlesen und als Basis für ein Diagramm senden
do *pixelzeiger=ADCH; while (pixelzeiger++ < endezeiger);
sei();

writeString("------------------------\n\r");
lines=0;
do
{
writeInteger(lines, DEC);
writeString_P("; ");
writeInteger(pixel[lines], DEC);
writeString_P("; ");
writeString("\n\r");
}while (++lines);

while (1)
return(0);
}

Damit schafft man mit einem 8MHz-ATMega "nur" knapp 50 Lesungen pro Zeile, weil sich die Formulierung des Einlesebefehls und damit der Code geändert hat. Im Anhang die Ausgabe des Programms mit einem schwarzen Strich vor der Kameralinse.

Wie gut sich meine Kamera an das BAS-Timeing (http://de.wikipedia.org/wiki/FBAS) hält, weiß ich allerdings nicht. Ich sollte mal eine alternative Signalquelle testen (oder Abwarten, bis es einer von euch nachgebaut hat).

Das zeilenweise Einlesen des TV-Signals ist inzwischen "Schnee von gestern". Meine aktuellen Programme lesen das Bild spaltenweise ein, mit je einem h_delay zwischen hsync und Lesen des Pixels pro Zeile. Damit schaffe ich nun, wie im Quellcode der Smilieerkennung zu lesen ist, locker 150 unterschiedliche Lesepositionen innerhalb einer BAS-Zeile.

Gruß

mic

[Edit]
Wenn ich das so lese, stelle ich auch fest, dass da was nicht stimmen kann. Wenn eine Zeile (laut Wiki) 64us dauert, kann ich mit 3,5us pro Lesung niemals auf 50 oder gar 60 Werte/Zeile kommen. Da passt etwas noch nicht, aber trotzdem funktioniert es irgendwie. 8-[

radbruch
21.08.2007, 22:14
Hallo

Das scheint eine eierlegende Wollmilchsau zu sein. Auch die Abstandsmessung (im Ansatz nach der hier vorgeschlagenen Laserscanner-Methode) funktioniert. Natürlich muss man sich das mit viel massgeschneidertem Code erkaufen, aber Entwicklungszeit kostet uns ja nichts und macht uns schlauer.

Auf der Basis des Smiliebildes ermittelt dieses Programm die Helligkeitsverteilung im 32x32-Bild (nur die oberen 4 Bit des Wertes) und sucht dann im Bild den Bereich mit der größten Helligkeit. Die erste Zeile mit mehr als 10 heller-als-Durchschnitt Punkten ist dann der Abstand (wir wollen es ja nicht zu kompliziert machen). Das ist der von meinen LEDs (ich habe leider keinen Laserpointer) erzeugte Lichtfleck. In der Ausgabe zum PC steht die Zeilennummer und die Nummer der ersten Zeile mit geforderter Helligkeit zur Diagnose und Einstellung:


#include "RP6RobotBaseLib.h"

#define power 50
#define rampe 50

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

// 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;
}

void bild_einlesen(void)
{
uint8_t pixel[32],*pixelzeiger;
uint8_t i, zeilen, step, lines, rows, h_step, h_sync, 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();
do { h_sync=0; while (ADCH > 20); while (ADCH < 30) h_sync++; } while (h_sync < 40);
h_step=35; while (h_step) { while (ADCH > 20); while (ADCH < 30); h_step--; }

while (lines--)
{
// auf die nächste gültige Zeile warten
h_step=step; while (h_step) { while (ADCH > 20); while (ADCH < 30); h_step--; }
h_delay=20+4*rows; while (h_delay--);

*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);
}

int main(void)
{
uint16_t i, j;
uint16_t gamma[16],g_hell, g_dunkel, g_hell_count;
uint8_t lichtpunkt, abstand;
uint8_t dir_l, dir_r, 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);

dir_l=dir_r=FWD;
setMotorDir(dir_l,dir_r);
pow_l=pow_r=power;
setMotorPWM(pow_l,pow_r); mSleep(rampe);

while(1)
{
bild_einlesen();

for (i=0; i<16; gamma[i++]=0); // 16 gammawerte zählen
bildzeiger=&bildspeicher[0];
for (i=0; i < 1024; i++) gamma[*bildzeiger++ >> 4]++;

g_hell=g_dunkel=0;
for (i=1; i<16; i++)
{
if (gamma[i] > g_hell) { g_dunkel=g_hell; g_hell=gamma[i]; }
else if (gamma[i] > g_dunkel) g_dunkel=gamma[i];
}

if (g_hell < g_dunkel)
{
i=g_hell;
g_hell=g_dunkel;
g_dunkel=i;
}

for (i=1; i<16; i++)
{
if (g_dunkel == gamma[i]) g_dunkel=i;
if (g_hell == gamma[i]) g_hell=i;
}


for (i=0; i<16; i++)
{
writeInteger(gamma[i], DEC);
if (gamma[i] == g_hell) writeString_P("H");
else if (gamma[i] == g_dunkel) writeString_P("D");
else writeString_P(" ");
}
writeString_P("\n\r");
writeInteger(g_dunkel, DEC);
writeString_P("-");
writeInteger(g_hell, DEC);
writeString_P("\n\r");

g_hell_count=0;
lichtpunkt=0;
for (i=0; i<32; i++)
{
for (j=0; j<32; j++)
{
//if (bildspeicher[j+32*i]>>4 == g_dunkel) writeString_P(" ");
//if (bildspeicher[j+32*i]>>4 > g_hell+1) writeString_P("*");
//else writeString_P(" ");
if (bildspeicher[j+32*i]>>4 > g_hell+1) g_hell_count++;
}
if ((!lichtpunkt) && (g_hell_count > 10)) lichtpunkt=i;
else g_hell_count=0;
writeInteger(i,DEC);
if (lichtpunkt) { writeString_P("-"); writeInteger(lichtpunkt, DEC);}
writeString_P("\n\r");
}

abstand=18;
if (lichtpunkt)

{
pow_l=pow_r=power/2;
setMotorPWM(pow_l,pow_r); mSleep(rampe);
if (lichtpunkt > abstand)
{
if (dir_l == BWD)
{
setMotorPWM(pow_l/2,pow_r/2); mSleep(rampe);
setMotorPWM(0,0); mSleep(rampe);
dir_l=dir_r=FWD;
setMotorDir(dir_l,dir_r);
setMotorPWM(pow_l/2,pow_r/2); mSleep(rampe);
setMotorPWM(pow_l,pow_r); mSleep(rampe);
}
}

if (lichtpunkt < abstand)
{
if (dir_l == FWD)
{
setMotorPWM(pow_l/2,pow_r/2); mSleep(rampe);
setMotorPWM(0,0); mSleep(rampe);
dir_l=dir_r=BWD;
setMotorDir(dir_l,dir_r);
setMotorPWM(pow_l/2,pow_r/2); mSleep(rampe);
setMotorPWM(pow_l,pow_r); mSleep(rampe);
}
}

if (lichtpunkt == abstand)
{
setMotorPWM(0,0); mSleep(rampe);
}
} // ende lichtpunkt != 0
else
{
if (dir_l == BWD) dir_l=dir_r=FWD;
setMotorDir(dir_l,dir_r);
if (pow_l < power)
{
pow_l=pow_r=power;
setMotorPWM(pow_l,pow_r); mSleep(rampe);
}
}
}

return(0);
}

Bei 32 Zeilen beträgt die messbare Distanz ca. 27 (Lichtpunkt hebt sich bei Tageslicht nicht mehr auf weisem Blatt ab oder Sehne des Kreissegment des Lichtpunkts kommt am unteren Bildrand nicht mehr auf 10)) und 3 (zu dicht an der Kamera). Das hängt natürlich sehr vom Aufbau ab, die Werte gelten bei Tageslicht und meinem Aufbau:

https://www.roboternetz.de/phpBB2/album_pic.php?pic_id=1837

Es fehlt noch das "Feintuning", auf diesem Video "sieht" er gelegentlich den Lichtreflex auf den Fliesen:

http://img.youtube.com/vi/KVcep1v9yT4/default.jpg (http://www.youtube.com/watch?v=KVcep1v9yT4)
(http://www.youtube.com/watch?v=KVcep1v9yT4)

Und noch ein weiteres Video meiner Tests. Wie man sieht, die Kamera ist ein robuster und katzenresistender Sensor:

http://img.youtube.com/vi/qWIY-yKWcD4/default.jpg (http://www.youtube.com/watch?v=qWIY-yKWcD4)
(http://www.youtube.com/watch?v=qWIY-yKWcD4)

Ich vermute, hier sieht er in erster Linie den Lichtpunkt am Boden. Ohne Kamera klappt es deutlich besser. *schwört* Wenn man da etwas Zeit investiert, kann man sicher viel damit anstellen.

Gruß

mic

vklaffehn
21.08.2007, 23:31
Moin!
Das sieht doch alles sehr vielversprechend aus!! Leider hatte ich ncoh keine Zeit, selbst zu basteln, dafür hab ich einen kleinen Linienlaser ;-) Ich hoff mal, am WE kann ich auch die ersten Bilder (Achtung, Wortspiel!!!) liefern. Weiter so!!
MfG
Volker

robo.fr
24.08.2007, 04:23
Hallo radbruch,

sehr interessante Idee, die Kamera so ganz ohne Elektronik an den Atmega32 anzuschließen.
Vor einiger Zeit habe ich das mal mit einem Atmega8 gemacht, um die Dynamik des Videosignals voll auszunutzen, musst ich mir aber einen kleinen Verstärker basteln.

Hier das Projekt (http://www.roboterclub-freiburg.de/AtmelAVR/Hardware/AtmegaKamera/AtmegaKamera.html)

Gruß,
robo

radbruch
24.08.2007, 13:11
Hallo robo,

das ist auch ein hübsches Projekt. Da ich den ADC im Dauerlauf betreibe, kann ich die Syncs auch ohne zusätzlichen Vergleicher erkennen. Mein Kameramodul liefert 1,4V am Multimeter, das messe ich direkt ohne Verstärker. Dadurch erhalte ich bei der internen 2,56V-Referenz zwar nur Werte bis ca. 140 (na sowas), da ich aber sowieso nur mit 4-Bit-Werten weiterrechne, stört das nicht sonderlich.

Der Ablauf ist aber gleich: Warten auf neue Seite, Zeilen zählen bis gewünschte Zeile erreicht ist, kurze Verzögerung um den Pixel in der Zeile anzusteuern, Wert einlesen.

32x32 (=1024Byte) erscheint mir auch als optimale Bildgrösse, allerdings wird's dann beim ATMega8 schon sehr eng. Für ein einfaches Linienverfolgen reicht aber auch ein Bruchteil der Zeilen aus. Auch einfache Mustererkennung sollte möglich sein, einen netten Ansatz dazu habe ich hier (http://www.elektronik-projekt.de/thread.php?postid=3541#post3541) gefunden. Minimalste Bildverarbeitung und pfiffige Speichermethoden reduzieren dabei den Platzbedarf für ein Bild auf 64 Bytes. Damit könnte man dann mehrere aufeinanderfolgende Bilder speichern und vergleichen, das ist die Grundlage für Bewegungserkennung und den optischen Fluss. Dafür reichen aber meine Mathekenntnisse noch nicht aus. Auch für neuronale Netze, das eigentliche Thema dieses Threads, bin ich noch nicht reif genug. 8-[

Gruß

mic

roboterheld
04.09.2007, 21:58
eine tolle leistung mit der camera.

vklaffehn
03.10.2007, 02:13
Moin!
Ich wecke den Thread mal wieder, ich bin auch grad dabei, das hier umzusetzen, allerdings bin ich etwas verwirrt. Ich benutze einen Mega8 mit 16MHz, aber Deine ADC-Konfig verstehe ich nicht so ganz, lt. Datenblatt wird beim Mega8 für den ADC nix im SFIOR-Register konfiguriert? Also im Prinzip schaltest Du den Prescaler auf 2, den ADC in den Free Running Mode (das ist bei mir ADFR in ADCSRA?), liest fröhlich ADCH, um die Syncs zu erkennen, wartest ab dem Sync eine fixe Zeit, um einen bestimmten Pixel der Zeile zu erwischen? Soweit richtig? Ich habe bei mir das Problem, daß ich 'Ausreißer' bei den Messungen habe, oder die Syncs nicht erwische, dabei müßte ich doch bei 16MHz eher mehr Werte prü Zeile bekommen? Morgen(naja.... nachher) werd ich hier mal ein paar gesampelte Daten+meinen Code hinpacken, evtl. ist's einfach zu spät dafür...

MfG Volker

radbruch
03.10.2007, 02:38
Hallo,

der Ablauf ist genau richtig beschrieben. Mit dem SFIOR-Register wird beim ATMega32 der Free-Runing-Modus als Triggerquelle ausgewählt.

Wichtig ist noch die 2,56V-Referenz für den ADC. Mit den "Schwarzschultern" dauert eine Zeile bis der Wert unter 20 ist. Dann folgt der Zeilensync, dessen Ende ein Wert über 30 (Ende der Schwarzschulter) kennzeichnet. Möglicherweise weichen die Pegel etwas ab. Ein schwarzes Bild (Objektiv geschlossen) sollte Bildpunktwerte von ca. 30 (=schwarz) haben.

Zwischen Zeilensuchen und Einlesen des Wertes sollten die Interrupts gesperrt sein. Ob der Prescaller /2 bei 16 Mhz auch noch funktioniert konnte ich noch nicht testen. Ich nehme zwar an, dass sich die Werte nicht mehr wesentlich verfälschen, aber sicherheitshalber kannst du auch mal mit /4 testen.


Morgen ... werd ich hier mal ein paar gesampelte Daten+meinen Code hinpacken
...und vielleicht noch eine Anschlußsizze.

Gruß

mic

robo.fr
03.10.2007, 07:40
Hallo Volker,

wenn Du die Synchronisation mit der Schaltung (http://www.roboterclub-freiburg.de/AtmelAVR/Hardware/AtmegaKamera/AtmegaKamera.html ) über den Komperatoreingang machst, wird die Synchronisation noch präzisser.
Bei einigen Programmen zur TV-Ausgabe mit einem Atmega wird als Trick der Prozessor in den Sleep-Modus versetzt, dann wird die Interrupt-Response Time systemtaktgenau ( t=1/16MHz ).

Gruß,
robo

vklaffehn
03.10.2007, 17:31
Moin!
Ah, ein Mega32... irgendwie war ich von einem Mega8 ausgegangen und habe mich auch über den 1024 Bytes großen Bildspeicher gewundert....
Ich bin ein wenig schlauer geworden, ich habe einfach mal einen 10K Pulldownwiderstand an mein Videosignal gehängt, nun sieht das irgendwie besser aus. Wie es scheint, kann ich nun den Anfang einer Zeile erkennen und ca. 80 Werte sampeln, allerdings scheint der ADC zu langsam, jeder Wert ist mindestens doppelt, was aber nicht wirklich ein Problem ist. Allerdings habe ich noch nicht durchschaut, wo und wie Du eigentlich den Beginn eines neuen Bildes erkennst.....

Ich habe mir schon einen Wolf gegooglet, ich finde aber irgendwie nur Signalverläufe einer einzelnen Zeile, kann mir jemand einen Tipp gebenm wo ich mal einen Verlauf eines BAS-Bildanfanges oder eines ganzen Bildes inkl. Synchronisationssygnales finde???

MfG Volker

radbruch
03.10.2007, 18:48
Hallo,

mein RP6 hat eben einen ATMega32 und der von mir verwendete Eingang ist mit einem 10K-PullDown beschaltet.

Meine Infos zum BAS-Signal habe ich von hier (http://de.wikipedia.org/wiki/FBAS). Der Start einer neuen Zeile ist der Sync-Impuls. Ein neues (Halb-) Bild beginnt mit einem extrem langen Sync.-Impuls:


Um eine Unterscheidung zwischen Zeilen- und Vertikalimpuls zu erreichen, ist letzterer 2,5 Zeilen (2,5 × 64 Mikrosekunden) lang.

Das ist im Programm folgender Abschnitt:


do { h_sync=0; while (ADCH > 20); while (ADCH < 30) h_sync++; } while (h_sync < 40);

Aufgedröselt sieht das so aus:


do
{
h_sync=0; // Synclängenzähler rücksetzen
while (ADCH > 20); // Ende aktueller Zeile abwarten
while (ADCH < 30) // Solange ein Sync-Pegel erkannt wird
{
h_sync++; // wird der Zähler erhöht
}
} while (h_sync < 40); // Zähler größer 40 bedeutet: Start neues Halbbild
wird solange ausgeführt, bis 40 mal Sync.-Pegel erkannt wurde. Bei 16Mhz müßte dieser Wert allerdings deutlich größer als 40 sein. Das ist aber egal solange ein Zeilensync nicht länger als 40 ist.

Mit dem kleineren Speicher des ATMega8 must du Abstriche bei der Auflösung des Bildes machen, das ist aber nicht dramatisch. Mein aktueller Linienfolger liest z.B. auch nur eine Zeile mit 32 Werte ein. Das genügt völlig um eine Linie zu erkennen und ist deutlich mehr als eine LED/Fototransistor-Sensorik liefert.

Wenn du deinen Code und deine Messwerte postest, kann man dir etwas helfen ohne raten zu müssen.

Gruß

mic

vklaffehn
03.10.2007, 19:14
So, ich bin, galube ich, ein Stück weiter. Wie es scheint, liefern die Zeilensyncs bei mir so ungefähr 31 zurück, und eben scheine ich einen Bildanfang erwischt zu haben, einen haufen '31' und dazwischen ziemlich konstant '60', danach schienen nach den '31' spikes Zeilendaten zu kommen, ich werde das jetzt mal genauer untersuchen.

Übrigens mal am Rande, die Beschaltung ist im Prinzip folgende:

Atmega8 mit 16MHz, Conrad S/W Kameramodul an ADC3, Pulldown von 10KOhm am Videosignal
Den ADC initialisiere ich so


void adc_init()
{
// ADC interne Referenz 2,56V, Ergebniss linksbündig, Kanal ADC3
ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<ADLAR) | 3;

// Wandler einschalten, prescaler /2
ADCSRA = (1<<ADEN) | (0<<ADPS2) | (0<<ADPS1) | (1<<ADPS0);

// free running
ADCSRA |= (1<<ADFR);

// Initialisierung starten
ADCSRA |= (1<<ADSC);

// und noch die wohl eher unnötige Initiallesung
while (!(ADCSRA & (1<<ADIF)));
ADCSRA |= (1<<ADIF);
}


Die Daten lese ich so


for (cnt=0;cnt<512;cnt++)
{
*zeiger=ADCH;
*zeiger++=ADCH;
}


Danach schubse ich die Daten per RS232 in eine Excel Tabelle

radbruch
03.10.2007, 21:53
Hallo


for (cnt=0;cnt<512;cnt++)
{
*zeiger=ADCH;
*zeiger++=ADCH;
}

Warum liest du den Wert zweimal ein? Wir verwenden nur das High-Byte des Ergebnisses der A/D-Wandlung. Deshalb reicht ein *zeiger++=ADCH; pro Wert. Der Zeiger sollte auf ein Byte-Feld zeigen, dieses solltest du sicherheitshalber zuvor löschen (mit 0 füllen).

Deine Daten zeigen selten Werte unter 30, hier scheinen die Pegel der Kameras zu streuen. Da wir auf Werte größer 20 prüfen um das Zeilenende zu erkennen, ist das kritisch. Günstiger wäre hier auf 30 zu prüfen. Versuch doch mal folgende Kombination:

while (ADCH > 30); while (ADCH < 40)

Solange der Wert größer 30 ist, befinden wir uns in einer Zeile, Start der nächsten Zeile ist am Ende des syncs wenn der Pegel wieder über 40 ist.

Deine Daten scheinen auch noch einen anderen Fehler zu haben, ich weiß aber noch nicht, was da nicht stimmt. Versuche doch mal mit einem weisen Blatt vor der Kameralinse eine Datenreihe zu erzeugen (200 Werte reichen vorerst), dann sollte man den Bereich "Sync" vom Bereich "Daten" besser unterscheiden können. Das selbe mit verdeckter Linse zeigt den Unterschied zwischen Sync-Pegel und Schwarz (~30). Meine Kamera passt automatisch ihren Weisabgleich an, möglichst parallel auf einem TV-Bildschirm das Kamerabild kontrollieren.

Deine Codestückchen sind zu kurz! Ich kann weder erkennen, wie du zum Seiten-/Zeilenanfang findest, ob die Interrupts gesperrt sind oder ob sonst noch Fehler erkennbar sind. Noch ganz wichtig ist folgendes: Nach dem Seitenanfang folgen erst einige Zeilen "Schrottpixel" (bei meiner Kamera bis ca. 35ste Zeile). Diese Zeilen muss man überlesen, das eigentliche Bild beginnt erst hier. Ebenso endet das Bild auch schon vor der letzten Zeile.

Gruß

mic

vklaffehn
03.10.2007, 22:43
Moin!
Bin wohl noch nicht ganz wach :-)
Vielen Dank für's schauen, ich fasse nochmal zusammen :
Das doppelte Einlesen war nur ein Unfall, eigentlich soll das so aussehen:



void grab()
{
unsigned int cnt;
unsigned char pixel;
uint8_t daten[512],*zeiger;
zeiger=&daten[0];

cli();

while (ADCH > 30); while (ADCH < 40); //Zeile abwarten


for (cnt=0;cnt<512;cnt++)
{
*zeiger++=ADCH;
}

for (cnt=0;cnt<512;cnt++)
{
pixel=daten[cnt];
txd(pixel);
}
}


Also ich warte im Moment eigentlich den Anfang einer (beliebigen) Zeile ab und erfasse danach einfach 512 bytes, das müßten ja ein paar Zeilen sein. Diese will ich mir erst einmal am PC (also in Rohform) anschauen.
Interrupts brauche ich im Moment nicht, die Funktion 'txd()' ist ein Mini Software UART, der einfach das übergebene Byte an 'nem Pin rausschreibt und irgendwas anderes macht der µC im Moment auch erstmal nicht.

Automatic Gain Control kann man bei dem Modul mit einem Pin an und abschalten, zur Zeit ist das deaktiviert, als Kontrolle lasse ich mir das Bild über den Video-In von meinem Compi hier anzeigen, das sieht auch richtig aus. Den Beginn einer Zeile kann ich auch erkennen, aber den Anfang des Bildes krieg ich nicht raus.
Folgendermaßen habe ich mal versucht, dahinterzusteigen :


void synctest()
{
uint8_t h_sync;
uint8_t daten[512];
unsigned int cnt;
cli();

//auf bildanfang warten
for (cnt=0;cnt<512;cnt++)
{
h_sync=0;
while (ADCH > 50); // Zeile abwarten
{
while (ADCH < 50) h_sync++; //Syncs zählen
}
daten[cnt]=h_sync;
}

//debugging...
for (cnt=0;cnt<512;cnt++)
{
txd(daten[cnt]);
}
}

Damit zähle ich ja quasi die Dauer der Sync-signale, und bei 512 Messungen sollte doch eigentlich immer mindestens ein Bildrücklauf vorhanden sein, richtig? Naja, da ich morgen arbeiten muß, werde ich mich den Daten wohl morgen Abend wieder widmen.

Vielen Dank für die Ausführungen!!

MfG Volker

radbruch
03.10.2007, 23:14
Hallo

Versuche es mal so:


/*
Solange Zeilen abwarten bis das Sync-Signal länger als 40 Lesezyklen ist
(CPU-Takt abhängig!) An dieser Stelle beginnt ein neues Halbbild.
*/
do { h_sync=0; while (ADCH > 20); while (ADCH < 30) h_sync++; } while (h_sync < 40);

/*
Ab jetzt zählen wir die Zeilensyncs um die gewünschte Zeile anzusteuern
(mindestens 35 um die ersten Schrottzeilen zu überspringen, 100-200 wäre
im Teststadium sinnvoll)
*/
zeile=35; while (zeile) { while (ADCH > 20); while (ADCH < 30); zeile--; }



Schwellwerte für Bild/Sync (der Strahlrücklauf IST das Syncsignal!) must du anpassen. while (h_sync < 40); macht das selbe wie bei dir, es zählt die Lesezyklen und misst damit die Zeit des Syncs. Bei meinem CPU-Takt bedeutet mehr als 40 den vertikalen Strahlrücklauf(=Bildanfang). Was bei dir jetzt noch fehlt ist das Ansteuern einer Zeile mit Bildinformationen. Ansonsten guter Testcode, das muss man auch mal loben.

Gruß

mic

[Edit]
Ursache für die verschiedenen BAS-Pegel könnte auch die interne Spannungsreferenz des ADC sein. Irgendwo habe ich mal was von "ein paar 0,1V" Ungenauigkeit gelesen. Mein Kalibrierungstest mit einer 1,5V-Batterie ergab allerdings den selben Wert wie die Messung mit einem Multimeter. Mit dieser "Festspannungsquelle" habe ich übrigends auch im Vorfeld die Messfehler bei unterschiedlichen Prescallerwerten getestet. Hierbei gab es auch beim Faktor /2 im HighByte keinen Messfehler, im LowByte waren die Bits 0-3 verfälscht. Möglicherweise steigt der Fehler bei höherem CPU-Takt. Wenn ich das M32-Board habe, werde ich das mal testen...

vklaffehn
03.10.2007, 23:38
Moin!
Hmmm, bin doch noch hier ;-)
Das obere habe ich schon versucht, auch an den Schwellwerten ein wenig rumgedreht, leider scheint mein Programm dann immer stehenzubleiben, daher mein Versuch, die Längen der Syncpulse zu debuggen, da sollten ja kurze Impulse der Zeilensynchronisation auftauchen und jedes Halbbild ein langer Strahlenrücklauf. Morgen werde ich auch mal nach der Genauigkeit meines ADC schauen und evtl. mal eine andere Camera nehmen. Sobald das dann hinhaut, brauche ich auch nicht komplette Bilder speichern, sondern nur pro Spalte den jeweils hellsten Wert je zweimal, einmal ohne, einmal mit Laserlinie aufgenommen. Naja, wie gesagt, morgen geht's weiter.....
Brauch doch was für den Robotik Treff Niedersachsen am Samstag ;-)

P.S.:Danke für das Lob, ist, ehrlich gesagt, nicht das erste Programm, was ich schreibe ... ;-) Hab schon Assembler auf dem C64, Turbo Pascal, C++ mit MFC unter Windows, ein wenig C# sowie PHP/MySQL hinter mir, aber alles eher als fortgeschrittener Amateur.... Außerdem räum ich den Code immer vorher auf, bevor ich ihn poste, normalerweise sind da Millionen auskommentierte Zeilen von irgendwelchen Versuchen drin.

radbruch
04.10.2007, 20:44
Was is nun mit der Kamera? *ungeduldigmitdenfingerntippelt*

Zu deinem P.S.:
C64 war doch Mist, Z80 (https://www.roboternetz.de/phpBB2/zeigebeitrag.php?p=238949&highlight=#238949) war Top!

mic

vklaffehn
04.10.2007, 21:34
Moin!
Hab zu viel versprochen, heut keine Zeit gehabt. Was den C64 angeht, der gehörte meinem Papa, was anderes hatten wir halt nicht, mit dem Z80 hat mein großer Bruder später rumgebastelt, 4MHz mit Klingeldraht!!!
Back to Topic: Ich denke mal, morgen Abend klappt vielleicht noch was, ansonsten am Samstag, wenn die anderen durch die Robotik-Abteilung der TU Braunschweig wandern... ;-)

MfG Volker

vklaffehn
05.10.2007, 21:19
Moin!
Ich glaube, ich hab mein Problem entdeckt, die Zeilensyncs scheinen bei mir so bei 31 rum zu liegen, der Vertikalsync besteht ja anscheinend aus 5 Syncs hintereinander mit Schwarzpegel dazwischen (und drumrum diese Trabanten), d.H. um einen V-Sync zu erkennen, muß ich ja nicht einfach nur einen langen Sync erkennen, sondern eigentlich einen Weißpegel, gefolgt von 5 Syncs mit Schwarzpegel und anschließen wieder Weißpegel?

Oder ist gemeint, dass während des Strahlrücklaufes 5 Pulse kommen, die JEWEILS 2,5mal so lang sind wie die Zeilensynchronpulse?
Fragen über Fragen....
MfG Volker


Ah, hab gerade folgendes entdeckt :
http://www.rickard.gunee.com/projects/video/pic/howto.php
sowie
http://kabelwalter.de/DasFernsehen/videosignal.htm
Also kommen da 5 lange Syncs, habe ich das jetzt richtig gesehen? Das würde nämlich auch erklären, das mein Synctest immer mehrere längere Syncs zwischen den Zeilenimpulsen entdeckt hat....

radbruch
05.10.2007, 21:51
Hallo

Also im Wiki (http://de.wikipedia.org/wiki/FBAS) wird das so beschrieben:


Um eine Unterscheidung zwischen Zeilen- und Vertikalimpuls zu erreichen, ist letzterer 2,5 Zeilen (2,5 × 64 Mikrosekunden) lang.
(hab ich oben schon zitiert)

Da eine komplette Zeile genau 64 Millisekunden dauert, interpretiere ich 2,5*64 als V-Syncronimpulslänge von 150 Mikrosekunden (also Wert kleiner als "schwarz"). Der H-Sync dauert 4,7 (mit Schwarzschultern etwas über 10) Mikrosekunden. Da ich im Schnitt den H-Sync 2-3 mal erkenne, ist bei mir mehr als 40 Syncpegel in Folge ein V-Sync. Bei 150/4,7*2 komme ich auf mindestens 60 Lesungen mit Pegel<schwarz. Dann starte ich die Zeilenzählung und überlese dabei schon bei der ersten Zeile alle eventuell noch nicht gelesenen restlichen V-Syncs und vermutlich auch die übrigen Seitensycndaten. Das ist wohl der "Schrott" in den ersten 30 Zeilen meiner Bilder. Aber das werde ich überprüfen, denn es sind wohl die "Trabanten".

Gruß

mic

vklaffehn
05.10.2007, 22:15
Moin!
Da machen sich wohl die 16MHz meines Mega8 bemerkbar, ich habe mal folgendes zusammenprogrammiert:


void synctest()
{
uint8_t h_sync;
unsigned int cnt;
uint8_t daten[512];
cli();

//auf bildanfang warten
for (cnt=0;cnt<512;cnt++)
{
h_sync=0;
while (ADCH > 50); //Zeile abwarten
{
while (ADCH < 35) h_sync++; //Syncdauer zählen, Schwarzschulter bei 48, sync bei 31
}
//if (h_sync > 20) PORTC ^= (1<<Laser);
daten[cnt]=h_sync;
}

//debugging...
for (cnt=0;cnt<512;cnt++)
{
txd(daten[cnt]);
}
}


und im Anhang mal die Dauer der Syncs, ich würde sagen, ich erwische alle 5, damit kann ich doch jetzt endlich mal was anfangen!!!!
Die Nacht ist ja noch jung.
MfG Volker

Ich stelle gerade fest, das die Pegel zwischen den Kameramodulen stark abweichen, bei meinem Farbmodul liegen die Syns bei 31 und der Schwarzpegel bei 48, bei meinem S/W Modul sind die Syncs bei 56 und der Schwarzpegel bei 96.......
da macht es evtl. Sinn, einfach vorher mal den Minimalwert zu ermitteln und als Referenz für den Sync abspeichern.....

robo.fr
22.12.2007, 18:17
Hier eine kleine Diskussion im MC-Netz zu dem Thema:
http://www.mikrocontroller.net/topic/86833

roboterheld
22.12.2007, 18:35
auszug von fachleuten aus diesem forum : http://www.mikrocontroller.net/topic/86833


.....Wenn man sich den Code anschaut, dann merkt man dass da ziemlich
gepfuscht wurde (mangels Wissen). Der Programmierer verwechselt
andauernd horizontal und vertikal und dies hier ist der Code:
do *pixelzeiger=ADCH; while (*pixelzeiger++ > 20);

Ob der ADC fertig ist, interessiert nicht, es wird trotzdem gespeichert.
Von den 60 Messwerten sind daher nur etwa etwa 20 real (sieht man auch
deutlich an der dort geposteten PDF). Das macht sagenhafte 16 Pixel pro
Zeile. Theoretisch sind bei 16MHz bis zu 33 Pixel möglich, allerdings
läuft der ADC dann mit 8MHz statt maximal 250kHz.

vklaffehn
22.12.2007, 20:30
Moin!
Ach, wirklich? ist doch schön, wenn da auch mal Fachleute drübergucken ;-) Mir ist durchaus klar, daß ich zwar (in meinem Falle) 96 mal den ADC pro Zeile auslese, aber die tatsächliche Auflädung geringer ist, da der ADC zu langsam ist, allerdings bekomme ich, wenn ich vorher prüfe, zwar nur tatsächlich ferig gewandelte Pixel, aber sehr viel weniger, da nehm ich lieber dese Methode und lebe mit doppelten Pixeln, zudem mir tatsächlich durchaus eine Auflösung von 16 Pixeln pro Zeile für meinen Zweck genügen würde..... Und beim ursprünglichen Programmierer ist auch die erfolgreiche Anwendung seiner Idee zu sehen. Außerdem war das ganze für mich eigentlich nur ein Einstieg in die ADC-programmierung, da ich das vorher halt noch nicht wirklich gemacht habe. Und das der ADC sträflich übertaktet ist, steht hier glaube ich auch schon irgendwo, funktioniert aber trotzdem erstaunlich gut. Außerdem gibt es da jetzt eine schöne Stelle, die ich aus dem selben Thread mal zitieren möchte :


...
> Meiner
> Meinung ist es eine reife Leistung, auf die Idee zu kommen, eine Kamera
> an einen AVR ohne Zusatzhardware anzuschließen.

Das sehe ich genauso. Viele hatten die Idee schon, einige Ideen
funktionieren auch mehr oder wenige, nur sind die meisten in der Praxis
eher nicht zu gebrauchen (so auch meine, denn wann hat man schon ein
Standbild und möchte 30s warten ? Wie man sieht bin ich nicht der erste
der diese Idee hat.)
...


Um auf den Punkt zu kommen : Klar ist das halt eine 'Bastellösung', die aber für meine Zwecke super reicht und aus meinen vorhandenen Restbeständen aufgebaut werden konnte. Es geht hier auch nicht darum, ein Video in super Qualität zu digitalisieren (leider hab ich meinen DVD-Brenner noch nicht an meinem Mega8 zum laufen bekommen, und die Kodierung in Mpeg2 dauert leider auch noch viel zu lange :-) ), sondern markante informationen aus einem Videosignal auszuwerten, wie z.B. Helligkeitsunterschiede o.ä. .
Wenn man z.B. schaut, was die 'klassischen' Sharp IR-Abstandssensoren kosten, dann ist die Idee, ein Kameramodul + LED zu verwenden, gar nicht mal so abwegig, wenn man nicht die Genauigkeit der IR-Sensoren benötigt.

Mir nicht so ganz klar, was genau Du mit Deinem Beitrag hier bezweckst? Bist Du deren Meinung oder unserer oder Deiner??? Einfach so ein Zitat in den Raum stellen hilft nicht wirklich.
Daß meine Kenntnisse der Atmel Controller noch nicht perfekt sind, weiß ich auch ohne daß jemand ein paar Fachleute zitiert.

Mein Modul hat mir auch schon die ersten Bilder geliefert, wenn das ganze Ding fertig ist, werd ich es hier gern mal in Zusammenhang mit meiner Verwendung vorstellen.....

MfG
Volker

Und auch sonst ein frohes Fest und guten Rutsch!!

radbruch
22.12.2007, 20:55
Hallo,

ich finde es prima, dass meine eigentlich schon recht alte Idee nun doch noch aufgegriffen wird. Ich dachte damals, ich hätte einen genialen Billigsensor "erfunden", aber die schwache Resonanz hat mich dann doch enttäuscht. Bin mal gespannt, was die echten Könner daraus machen. @robo.fr: Danke für den Hinweis und den Link.

Dass ich damals v- und h-syncs verwechselt habe, weiß ich natürlich inzwischen auch. Dass der ADC mehr kann als im Datenblatt steht kann jeder überprüfen, der es selbst mal versucht. Und dass manche Effekte nicht schlüssig zu erklären sind, habe im Verlauf dieses Thread ja auch geschildert. Wenn ich in der Zwischenzeit nicht über viele andere interessante Themen gestolpert wäre, hätte ich aus der Kamera auch noch mehr rausgekitzelt. Aber was nicht ist kann ja noch werden.

Gruß

mic

roboterheld
22.12.2007, 22:08
....dann ist die Idee, ein Kameramodul + LED zu verwenden, gar nicht mal so abwegig......

die hohe auslastung des atmegas , nur durch diesen einen sensor macht ihn nicht sehr attraktiv. wenn ich dann den preis für eine zusatz atmegaplatine rechne, weil ja auch noch andere zeitkritische aufgaben bewältigt werden müssen ist er nicht produktiv im gesamteinsatz.

vklaffehn
22.12.2007, 22:41
Naja, Ziel meines Senors ist es, über die gesamte Breite meines Robots, ca. 30 cm, Hindernisse zu erkennen, inklusive eines (groben) Abstandswertes und Position (da haben wir sie wieder, die 16 bis 32 Pixel :-) ) Um Mißverständnissen vorzubeugen : Ich verwende einen Mega8 exklusiv für diese Aufgabe, der per I2C später seine Meßwerte ausgibt. Dieses Prinzip verwenden meines Wissens auch die US-Sensoren der SFR-Reihe, da ist ein PIC drauf, glaube ich. Die Kosten für die Hardware liegen ca. bei 13 € + 6-13€ für einen Linienlaser, ein US-Sensor kostet genausoviel/mehr und ist zwar bei der Entfernung genauer, kann mir aber nicht sagen, wo seitlich das Hindernis ist, ein IR-Sensor ist zwar günstiger, aber mit einem kann ich die Strecke nicht bzw. evtl. mit Servo nicht so schnell, abtasten, somit ist diese Lösung für mich (!) durchaus produktiv.

MfG Volker

purebasic
12.01.2008, 09:52
hier ist eine basprogramm mit den zeitplan aus diesem forum, dazwischen können die adc-werte evtl rausgezogen werden. man muss die adc-routinen nur geschickt dort einbauen :




$regfile = "m32def.dat"
$framesize = 32
$swstack = 32
$hwstack = 64
$crystal = 8000000
$baud = 19200


Declare Sub Tv_asm()

Config Pinb.7 = Output
Config Pind.7 = Output

Sync Alias Portb '-[ 1,2K ]- Sync Ausgang
Video Alias Portd '-[ 560 ]- Video Ausgang
Const Syn = 7
Const Vid = 7

Disable Interrupts

Waitms 200

Call Tv_asm()

End

Sub Tv_asm()
$asm

ldi r16,168 'bei internem RC Takt, unbedingt
Out Osccal , R16 'Kallibrieren !!!

'**************** Hauptschleife **************************************
' bei 8 MHz ( 1 Takt = 0,125µs )
' die Zeiten im Hauptprogramm zählen immer von "cbi sync,syn" (Sync auf Low)
' bis "sbi sync,syn" (Sync auf High) oder eben von High nach Low
'************************************************* ********************


' __ ___ ___
' V-Sync |_______| |_______| | ---> die ersten 2,5 Zeilen
' 27,5 4,5 27,5 4,5

Main:

ldi r16,5
Syn_0:

cbi sync,syn
cbi video,vid

ldi r23,$49 '27,5 µs
Syn_1:
dec r23
brne syn_1

sbi sync,syn

ldi r23,12 '4,5 µs
Syn_2:
dec r23
brne syn_2
nop

dec r16
brne syn_0 'Schleife 5 x 32µs = 160µs


' __ ________ _________
' Nachtrabanten |__| |__| | ---> wieder 2,5 Zeilen
' 2,25 29,75 2,25 29,75

ldi r16,5
Sync2:

cbi sync,syn 'Nachtrabanten
cbi video,vid

ldi r23,6 '2,25 µs
Syn_3:
dec r23
brne syn_3

sbi sync,syn

ldi r23,$4f '29,75 µs
Syn_4:
dec r23
brne syn_4

dec r16
brne sync2 'Schleife 5 x 32µs = 160µs
'zusammen 320µs = 5 Zeilen

'************************ Zeile 6-156 *******************************
'
' _______________________
' __ ____| |___ ---> 307 Zeilen mit Bildinhalt
' H-Sync |____|
' 4,5 6 52µs Bildinhalt 1,5 µs
'
'************************************************* *******************

ldi r16,151 'Schleife für 151 Zeilen
Hsyn_0:

cbi sync,syn
cbi video,vid

ldi r23,12 '4,5 µs
Hsyn_1:
dec r23
brne hsyn_1

sbi sync,syn
cbi video,vid

ldi r23,$9e '59,5 µs ( 6 + 52 + 1,5µs )
Hsyn_2: 'da die Pegel für Bildinhalt schwarz sind
dec r23 'wurde es hier zusammnegefasst
brne hsyn_2
nop
nop

dec r16
brne hsyn_0

'************************ Zeile 157 *********************************

nop
Hsyn_3:
cbi sync,syn '1 Zeile
cbi video,vid

ldi r23,12 '4,5 µs
Hsyn_4:
dec r23
brne hsyn_4

sbi sync,syn
cbi video,vid

ldi R23, 16 '6,0 µs
Hsyn_44:
dec R23
brne Hsyn_44

sbi sync,syn
sbi video,vid

ldi r23,$8a '52,0 µs Bild
Hsyn_5:
dec r23
brne hsyn_5
nop
nop

sbi sync,syn
cbi video,vid

ldi r23,4 '1,5 µs Nachsync
Hsyn_6:
dec r23
brne hsyn_6

'************************Zeile 158-312 *******************************

ldi r16,155 'Schleife für 155 Zeilen
Hsyn_8:

cbi sync,syn

ldi r23,12 '4,5 µs
Hsyn_9:
dec r23
brne hsyn_9

sbi sync,syn

ldi r23,$9e '59,5 µs ( 6 + 52 + 1,5µs )
Hsyn_10: 'da die Pegel für Bildinhalt schwarz
dec r23 'bleiben wurde es zusammnegefasst
brne hsyn_10
nop
nop
dec r16
breq ende 'der Sprung zu "ende" spart 5 Takte
nop
nop
nop 'Ausgleichs nop's, da der
nop 'Sprung zu main mehr Takte benötigt
nop
nop
nop
nop
nop
nop
brne hsyn_8
Ende:
jmp main
$end Asm
End Sub**

radbruch
25.09.2008, 19:17
Hallo

Mein aktuelles Projekt bringt mich wieder zurück zur einfachen Kamera:
http://i1.ytimg.com/vi/XTYzLqgKMLM/default.jpg (http://www.youtube.com/watch?v=XTYzLqgKMLM)
http://www.youtube.com/watch?v=XTYzLqgKMLM

Das sieht zwar schon recht schick aus, aber die Ergebnisse mit den Phototransistoren sind einfach unbefriedigend wenn man die Leistung der Kamera kennt ;)

Im Moment mache ich noch Tests mit dem RP6 (8MHz Mega32) als Steuerung, geplant ist das aber eigentlich für einen tiny13 mit 9,6MHz. Deshalb muss der Code deutlich schlanker werden, was zur Folge hat, dass alles auch etwas übersichtlicher ist. Zur allgemeinen Erheiterung zeige ich euch deshalb mal den aktuellen Stand meiner Vorversuche. Ziel ist dabei u.a. möglichst wenig Ram zu verbraten denn der tiny hat davon nicht wirklich viel:

// cd-racer mit cam (erste Tests mit RP6) 25.9.2008 mic

#include "RP6RobotBaseLib.h"

uint8_t get_line(void)
{
uint8_t i;

cli();

// auf Bildanfang warten
do
{
i=0; while (ADCH > 30); while (ADCH < 50) i++;
} while (i < 40);

// die ersten 50 Zeilen überlesen
i=50;
while (i--)
{
while (ADCH > 30); while (ADCH < 50);
}

// warten auf hell-dunkel-Übergang
while (ADCH > 110) i++; // Zeile einlesen bis Wert deutlich Richtung dunkel
sei();
return(i);
}

int main(void)
{
uint8_t linie;
uint16_t i, servo=1550, dummy;

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;
// setze 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);

// SCL (Pin10) auf Ausgang und Low zur Servoansteuerung
DDRC |= 1;
PORTC &= ~1;

while (1)
{
linie=get_line(); // linke Kante der Linie suchen

if((linie > 0) && (linie < 70)) // Linie im "Bild"?
{
if(linie > 36 && servo <2000) servo+=(linie-35)*2; // Servoposition
if(linie < 34 && servo > 675) servo-=(35-linie)*2; // korrigieren

i=servo; // Servo blockierend auf Position stellen
cli();
PORTC |= 1;
while(i--) dummy ^= i;
PORTC &= ~1;
sei();
i=15000;
while(i--) dummy ^= i;
}
}
return(0);
}
Wie gehabt hängt die Kamera wieder direkt an einem ADC-Pin. Der ADC rennt wieder im Dauerlauf mit kleinstem Prescaler und die Werte werden auch wieder ohne Rücksicht auf den ADC-Status linksbündig als 8bit-Wert eingelesen. Der Ablauf ist zu Beginn ebenfalls wie gehabt: Warten auf neues Bild und dann Zeilen zählen bis der Start der gewünschten Zeile erreicht ist. Dann kommt eine kleine Änderung: Ich zähle wie oft der eingelesene Wert einen (im Moment noch festen) Wert überschreitet (=heller als Linie) und interpretiere diesen Zählwert dann als linke Kante der Linie. So erhalte ich Werte zwischen 0="Linie links aus dem Bild" bis 69="Linie rechts aus dem Bild".

Mit diesem Wert steuere ich dann ein Servo (ohne großen Schnickschnack über eine Zählschleife mit gesperrten Interrupts) und halte so die Kamera bei einem Wert zwischen 34 und 36:
http://i1.ytimg.com/vi/46ts6GH04NI/1.jpg (http://www.youtube.com/watch?v=46ts6GH04NI)
http://www.youtube.com/watch?v=46ts6GH04NI

Weil das so wunderbar funktioniert werde ich wohl nochmals einen kleinen Versuch mit einem liniefolgenden RP6 unternehmen ;)

Gruß

mic

carlitoco
26.09.2008, 12:51
Ich habe da so eine Idee, aber leider noch nicht soo viel Ahnung vons Janze, wie das halt öfter so ist : ) .

Wäre es möglich mit der erweiterung M32 ein 8-Bit Graustufenblid zu erkennen?

Die Idee dahinter ist folgende.
Ich dachte mir man könnte das Bild als Raster berechnen, um Bewegung/-en in den verschiedenen Sektoren des Rasters zu erkennen.
Anschliesend lässt man den RP6 immmer in die Richtung der Sektoren fahren, welche sich wenig verändern.
Stellt man sich nun einen raum, z.B. sein Zimmer vor, fährt der RP6 immer in die Tiefe des Abblides von dem Raum, bzw. zu den unveränderten Sektoren. Sollte es so sein, das er auf eine weisse Wand zufährt gibt es dafür noch andere Sensoren.

Ich hoffe es ist einigermaßen nachzuvollziehen was ich meine.

Was haltet ihr davon ?

gruss toco

radbruch
26.09.2008, 17:14
Wäre es möglich mit der erweiterung M32 ein 8-Bit Graustufenblid zu erkennen?
Ein 32x32Pixel-Bild würde dann 1k Ram belegen. Der Mega32 hat davon aber nur 2k zur Verfügung. Für echte Bildverarbeitung wie man sie vom PC/Mac kennt sind die AVRs wohl nicht geeignet, auch wenn manche Typen noch deutlich mehr Ram anbieten können.

RP6conrad
26.09.2008, 18:34
Macht das kein Sinn om einen LM1881 zu nutzen für Erkennung von H-sync und V-sync ? Dan wird die Auslastung von mega32 etwas geringer soll ich denken, und moglich die Erkennung genauer. Den IC selbst soll billig sein und besteht auch in DIP Gehause.

radbruch
26.09.2008, 20:09
Macht das kein Sinn om einen LM1881 zu nutzen...
Das würde schon Sinn machen, aber ich finde, der besondere Reiz liegt eben im Verzicht auf weitere Hardware. Nur die Kamera und der AVR und ein paar Codezeilen und die Sache funzt. Deshalb auch der Threadtitel: "Minimallösung" :)

blenderkid
26.09.2008, 21:07
hi, da das mit dem Atmega32 nicht so gut geht, wäre das mit dem AT7000 besser zu bewerkstelligen, würde das dann vllt auch mit Farbe funtionieren, es würden auch 32x32 Pixel reichen.

Wäre toll so Farben zu verfolgen.

MfG, blenderkid

carlitoco
26.09.2008, 21:09
Aber für was ließe sich die "minimallösung" einsetzen, wenn ich einen autonomen roboter bauen will?

mfg carlitoco

radbruch
27.09.2008, 00:27
Hallo

Der RP6 ist einfach zu fett für den engen RN-Kurs:

http://i1.ytimg.com/vi/h7bdUD3mJfg/1.jpg (http://www.youtube.com/watch?v=h7bdUD3mJfg) http://i4.ytimg.com/vi/sm3WrykPj4M/default.jpg (http://www.youtube.com/watch?v=sm3WrykPj4M) http://i4.ytimg.com/vi/3r_m2rGgXLk/default.jpg (http://www.youtube.com/watch?v=3r_m2rGgXLk)
http://www.youtube.com/watch?v=h7bdUD3mJfg
http://www.youtube.com/watch?v=sm3WrykPj4M
http://www.youtube.com/watch?v=3r_m2rGgXLk

Aber als Test für mein Projekt reicht mir das erstmal. Andere Anwendungen wären z.B. Objekt-, Muster- und Hindernisserkennung, Barcodelesen, Abstandsmessung (mit Laserpointer) und möglicherweise auch Licht-/IR-Kommunikation. Vielleicht fällt mir noch was nettes ein, vorerst bleibe ich mal beim Liniensuchen. Hier noch der Code:


// RP6 folgt einer Linie die mit der Kamera erkannt wird 26.9.2008 mic

#include "RP6RobotBaseLib.h"

#define mitte 1550

// 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;
}

uint8_t get_line(void)
{
uint8_t i;
cli();
// auf Bildanfang warten
do { i=0; while (ADCH > 30); while (ADCH < 50) i++; } while (i < 40);
// die ersten 50 Zeilen überlesen
i=50; while (i--) { while (ADCH > 30); while (ADCH < 50); }
// warten auf hell-dunkel-Übergang
i=0; while (ADCH > 100) i++;
sei();
return(i);
}

int main(void)
{
uint8_t linie;
uint16_t i, servo=mitte, dummy;

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;
// setze 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);

// SCL (Pin10) auf Ausgang und Low zur Servoansteuerung
DDRC |= 1;
PORTC &= ~1;
setMotorDir(BWD,BWD);
//setMotorPWM(100,100);

while (1)
{
linie=get_line(); // linke Kante der Linie suchen

if((linie > 0) && (linie < 70)) // Linie im "Bild"?
{
if(linie > 36 && servo <mitte+800) servo+=(linie-35)*2; // Servoposition
if(linie < 34 && servo >mitte-1000) servo-=(35-linie)*2; // korrigieren

i=servo; // Servo blockierend auf Position stellen
cli();
PORTC |= 1;
while(i--) dummy ^= i;
PORTC &= ~1;
sei();
i=15000;
while(i--) dummy ^= i;

if(servo < mitte) setMotorPWM(100, 100-(mitte-servo)/10);
if(servo > mitte) setMotorPWM(100-(servo-mitte)/10, 100);

if(servo < mitte-300)
{
setMotorDir(BWD,FWD);
setMotorPWM(100, (mitte-servo)/10);
}
else if(servo > mitte+300)
{
setMotorDir(FWD,BWD);
setMotorPWM((servo-mitte)/10, 100);
}
else setMotorDir(BWD,BWD);

} // else setMotorPWM(0,0);
}
return(0);
}
Übrigends ist die hier vorgestellte Anwendung bei weitem nicht so zeit- und rechenintensiv wie allgemein angenommen ;)

Gruß

mic

ikarus_177
06.04.2009, 14:34
Hi,

ich hab mich auch man an die (Bascom-) Signalerkennung/verarbeitung "gewagt", und mir ein 15€ - Kameramodul vom C zugelegt. Das Kamerasignal wird auch schon recht brav ausgewertet, der schwarze Strich recht zuverlässig erkannt: http://www.youtube.com/watch?v=3WcBV3fSolg

Vielen Dank an radbruch für die gute Erklärung der Auswertung in C (wie man sieht, funktionierts auch in Bascom recht passabel ;-))!

In so einer "einfachen" Kamera steckt sicher noch viel Potential, z.B.: ein Laser-Entfernungsmesser,...

Viele Grüße
ikarus_177

sechsrad
10.04.2009, 19:23
Sieht gut aus.

Wie sieht der Bascomcode aus?

mfg

radbruch
10.04.2009, 19:33
Hallo

Die Bascom-Variante:

https://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=47232

Gruß

mic

ikarus_177
10.04.2009, 19:45
Hi,

mein Code ist vom Prinzip her eigentlich genau gleich wie der von radbruch.

Wird ein neues Bild erkannt, wird zuerst auf eine bestimmte Zeile gewartet, danach alle Werte der Zeile eingelesen, bis der Strich erkannt wird. Danach wird der Abstand des Striches vom linken Kamerarand am Terminal mit Leerzeichen "simuliert", danach der Strich ausgegeben.

Mich wunderte es selber, wie einfach eigentlich eine Kamera auszuwerten ist - faszinierend ;-)

Viele Grüße

radbruch
10.04.2009, 23:23
Mich wundert vielmehr dass bei inzwischen über 10000 Views so wenig Resonanz auf diesen Thread zu verzeichnen ist. Vielleicht sollte ich ihn etwas mehr puschen... ;)

sechsrad
11.04.2009, 10:59
Vielleicht könnte man das eine oder andere noch in Inline-Asm setzen?

mfg

thewulf00
11.04.2009, 11:07
Das ist nicht ungewöhnlich für einen Thread, der 1,5 Jahre als ist. Ich selbst habe ihn aber auch erst gestern gefunden. Und ich finde, wie alle anderen auch, Deine Pionierarbeit fantastisch!

proevofreak
29.06.2009, 19:25
hallo leute, bin auch daran interessiert meine hindernisserkennung des RP6 mit hilfe einer kamera zu unterstützen. dazu würde ich gerne die CMOS kamera vom großen C mit der Artikelnummer: 150001 - 62 nehmen.

jetzt weiß ich nur bislang noch relativ wenig über die auswertung bzw. ansteuerung des kamerasignals.
irgendwo in diesem thread ist von einem rn wikiartikel über dieses thema zu lesen.
nur leider konnte ich diesen trotz ausgiebiger suche bislang nicht finden.

wer kann mir weiterhelfen bzw. sagen ob eine hindernisserkennung mit diesem kameramodul möglich ist?

danke schon mal im voraus

proevofreak
02.07.2009, 18:01
ich weiß nicht warum mir keiner in diesem thread antwortet. scheinbar ist mein beitrag in der menge der vielen unterschiedlichen beiträge der letzten tage untergegangen.

aber die bisherige resonanz in diesem thread zeigt, wie viele user sich mit dem thema kamera am rp6 beschäftigen bzw. beschäftigt haben.

deshalb wäre ich sehr dankbar, wenn mir jemand auf meine oben gestellten fragen antworten würde.

mfg

radbruch
02.07.2009, 20:00
Hallo

Gleich vorweg: Nur die wirklich nette Kamera bringt mich dazu auf solche Drängelei zu antworten:
http://conrad.de/goto.php?artikel=150001
(Das Datenblatt (http://www2.produktinfo.conrad.com/datenblaetter/150000-174999/150001-an-01-sl-Artikel.pdf) ist ja krass :)

Meine Erfahrungen mit Kameras beschränken sich im Wesentlichen auf diesen Thread. Wie man das Bild einlesen kann habe ich hier schon gezeigt. Zur Auswertung der Bilder bin ich aber bisher noch nicht gekommen.

Gruß

mic

proevofreak
04.07.2009, 12:45
@radbruch: ich weiß ja nicht welche drängelei du mir vorwerfen willst. meinst du das, dass ich 3 tage nach meinem ersten post noch mal das selbe poste?

hast du schon mal dran gedacht, dass das vielleicht den grund hat, dass auf meinen ersten post niemand antwortet, weil er in der fülle der verschiedenen posts der letzten tage komplett unterging.

nur damit wir uns verstehen: also, ich wollte niemanden drängeln, sondern einfach nur eine antwort auf meine frage. darum auch der 2. post.

jetzt werd ich mir mal noch genauer in diesen thread einlesen. woher hast du eigentlich dein wissen über die ansteuerung von cmos kameras? im internet konnte ich nämlich bisher keine wirklich geeigneten seiten finden.

mfg andi

radbruch
04.07.2009, 13:07
woher hast du eigentlich dein wissen über die ansteuerung von cmos kameras?Über die Kameras weiß ich auch nicht viel. Das ist für diese Anwendung auch nicht wichtig. Es geht in erster Linie um das samplen des Videosignals nur mit AVR-Bordmitteln.

Ich habe bisher lediglich markante Helligkeitsunterschiede in den Bildern zur Linienerkennung ausgewertet. Zu weiteren Versuchen bin ich noch nicht gekommen. Ein kleines Tutorial aus dem RN-Wiki lies mich letztlich kapitulieren: http://www.rn-wissen.de/index.php/Bildverarbeitung_Tutorial

Gruß

mic

carlitoco
06.08.2009, 03:44
Habe mir das jetzt auch mal zusammen geschustert...

mit dem smily wills noch nicht so recht klappen...

auch giebt es problemem mit dem licht, da meine cammera recht viel davon braucht scheinbar ... oder ich müsste die empfindlichkeit irgendwie verändern

gruss

radbruch
06.08.2009, 10:35
Hallo

Beim Smily-Programm (erste Seite des Threads) wird hier die Helligkeit des Punktes überprüft und entsprechend ein Stern oder ein Leerzeichen geschrieben:

if (bildspeicher[j+32*i] >90) writeString_P("*");
else writeString_P(" ");
Alle Werte über 90 sind Sternchen. Mit <90 würde die Ausgabe übrigens invertiert werden.

Meine Kamera hat, wie auch die oben gezeigte 15€-Kamera, einen Gain-Eingang mit dem man die Verstärkung (=Lichtempfindlichkeit?) der Kameras einstellen kann. Bei meinen Versuchen war Gain unbeschaltet, das bedeutet Gain-Aus, eine Brücke zu GND schaltet Gain ein?

Als Videoquelle kann man auch andere Geräte mit Videoausgang verwenden, z.B. Digicam oder Fotohandy.

Gruß

mic

carlitoco
06.08.2009, 16:15
bei meiner Kammera (alt funkkammera) sind 2 ausgänge GND & Signal
das Gain macht sie automatisch (die sitzt auf nem chip).

Versuch:
Mache ich licht im dunklen Zimmer aus und nehme die Taschenlampe um zu sehen ob es überblendet => klar = überblendet reguliert sich jedoch automatisch und ich habe wieder Sicht.

gruss

carlitoco
08.08.2009, 16:53
Hmm die Smily Version gibt mir folgenden error gruss


Compiling: RP6Base_Cam.c
avr-gcc -c -mmcu=atmega32 -I. -gdwarf-2 -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=RP6Base_Cam.lst -I../../RP6Lib -I../../RP6Lib/RP6base -I../../RP6Lib/RP6common -std=gnu99 -MD -MP -MF .dep/RP6Base_Cam.o.d RP6Base_Cam.c -o RP6Base_Cam.o
RP6Base_Cam.c: In function ‘main’:
RP6Base_Cam.c:91: error: expected declaration or statement at end of input
make: *** [RP6Base_Cam.o] Fehler 1

carlitoco
08.08.2009, 17:05
Sorry das compilieren geht ... nun aber ein anderes Problem.

Der RP6 dreht seine linke Kette in regelmäßigen abständen, jedoch immer stärker und schneller- am Terminal gibt er folgendes aus.



0

1

2..31 0 1 2 ...31

radbruch
08.08.2009, 17:22
Ähm, nun wäre ein günstiger Zeitpunkt um uns dein Programm zu zeigen ;)

carlitoco
08.08.2009, 20:25
lustiger weise ist es dein programm. Ich meine das von dem du hier sprichst:

"Beim Smily-Programm (erste Seite des Threads) wird hier die Helligkeit des Punktes überprüft und entsprechend ein Stern oder ein Leerzeichen geschrieben:"

radbruch
08.08.2009, 20:37
Hallo,

kann es sein, dass wir aneinander vorbeireden?


...nun aber ein anderes Problem.

Der RP6 dreht seine linke Kette in regelmäßigen abständen, jedoch immer stärker und schneller- am Terminal gibt er folgendes aus.
Und hier das (mein) Smiliy-Programm von der ersten Seite des Threads:

#include "RP6RobotBaseLib.h"

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

void bild_einlesen(void)
{
uint8_t pixel[32],*pixelzeiger;
uint8_t i, zeilen, step, lines, rows, h_step, h_sync, 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();
// h_sync abwarten (syncsignal länger 40 bedeutet Seitenanfang)
do { h_sync=0; while (ADCH > 20); while (ADCH < 30) h_sync++; } while (h_sync < 40);

// 30-35 Zeilen Austastzeit überlesen (der Rest des hsyncs+nicht darstellbare BTX-Infos)
h_step=35; while (h_step) { while (ADCH > 20); while (ADCH < 30); 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 (ADCH > 20); while (ADCH < 30); 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 20
// bei ca. 150 beginnt die 2.Schwarzschulter. Bei 150-20 möglichen Bildpunkten
// ergibt sich eine maximale Auflösung von 128 horizontal. Zusammen mit der
// vertikalen Auflösung von 230 kämen wir dann bei einem 8MHz-ATMega auf stolze
// 128*230 Bildpunkte.
h_delay=20+4*rows; while (h_delay--);

*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);
}

int main(void)
{
uint16_t i, j;

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);

while(1)
{
bild_einlesen();

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

return(0);
}
Keinerlei Befehle zur Ansteuerung der Motoren. Irgendwie passt das nicht, oder?

Wenn ich das richtig deute bekommst du nur Leerzeichen gesendet. Das bedeutet, die Schwelle von 90 wird nie überschritten. Spiele etwas mit den Werten. Mit diesem Programm werden 256 Bytes ab Zeile 100 eingelesen und zum Terminale gesendet. Kleine Werte sind Sync-Inpulse, große Werte sind hell:

// Liest ab der 100. Zeile 256 Werte am Stück ein
// und sendet die Daten als Tabellenvorlage zum PC.

#include "RP6RobotBaseLib.h"

int main(void)
{
uint8_t pixel[256],*pixelzeiger, *endezeiger;
uint8_t vsync, lines;

initRobotBase();
extIntOFF();
//powerON();
// interne Referenz 2,56V, linksbündig, Kanal ADC4
ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<ADLAR) | 4;
// free running triggern
SFIOR = (0<<ADTS2) | (0<<ADTS1) | (0<<ADTS0);
// kein interupt, einschalten, prescaller /2
ADCSRA = (0<<ADIE) | (1<<ADEN) | (0<<ADPS2) | (0<<ADPS1) | (1<<ADPS0);
// free running aktivieren, altes Flag löschen
ADCSRA |= (1<<ADATE) | (1<<ADIF);
// Initialisierung starten
ADCSRA |= (1<<ADSC);

while (!(ADCSRA & (1<<ADIF)));
ADCSRA |= (1<<ADIF);

pixelzeiger=&pixel[0];
endezeiger=&pixel[255];
lines=100;
cli();

do // vsync abwarten
{
vsync=0;
while (ADCH > 20);
while (ADCH < 30) vsync++;
}while (vsync < 40);

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

// 256 Werte am Stück einlesen und als Basis für ein Diagramm senden
do *pixelzeiger=ADCH; while (pixelzeiger++ < endezeiger);
sei();

writeString("------------------------\n\r");
lines=0;
do
{
writeInteger(lines, DEC);
writeString_P("; ");
writeInteger(pixel[lines], DEC);
writeString_P("; ");
writeString("\n\r");
}while (++lines);

while (1); // ;)
return(0);
}

Gruß

mic

carlitoco
09.08.2009, 01:06
Okay so verrückt es klingen mag ... ich mache
$make all
nach dem speichern des .hex
dreht sich der motor.
Wie beschrieben spuckt auch das terminal nur zahlen von 1 bis 31 aus.
gruss carlitoco

carlitoco
15.08.2009, 15:15
Also um nochmal meine problematik zu schildern:
dieser Code:

#include "RP6RobotBaseLib.h"

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

void bild_einlesen(void)
{
uint8_t pixel[32],*pixelzeiger;
uint8_t i, zeilen, step, lines, rows, h_step, h_sync, 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();
// h_sync abwarten (syncsignal länger 40 bedeutet Seitenanfang)
do { h_sync=0; while (ADCH > 20); while (ADCH < 30) h_sync++; } while (h_sync < 40);

// 30-35 Zeilen Austastzeit überlesen (der Rest des hsyncs+nicht darstellbare BTX-Infos)
h_step=35; while (h_step) { while (ADCH > 20); while (ADCH < 30); 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 (ADCH > 20); while (ADCH < 30); 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 20
// bei ca. 150 beginnt die 2.Schwarzschulter. Bei 150-20 möglichen Bildpunkten
// ergibt sich eine maximale Auflösung von 128 horizontal. Zusammen mit der
// vertikalen Auflösung von 230 kämen wir dann bei einem 8MHz-ATMega auf stolze
// 128*230 Bildpunkte.
h_delay=20+4*rows; while (h_delay--);

*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);
}

int main(void)
{
uint16_t i, j;

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);

while(1)
{
bild_einlesen();

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

Verursacht, dass der RP6 folgendes am Terminal ausgiebt:

[READY]
0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31


und sich die linke Kette dreht in pulsierenden immer intensieveren stößen.

Es ist mir schleierhaft wie das zustande kommt... Oder wird ein anderer pin als INT1 (pin8 XBUS) für den anschluss des Signals der Cam verwendet ?

gruss carlitoco

SlyD
15.08.2009, 15:32
Da wird wohl ein Pointer ausbüchsen und in irgendwelche anderen Speicherbereiche schreiben die für die Motorsteuerung zuständig sind.
Und die position dieser Speicherbereiche kann sich sogar von avr-gcc zu avr-gcc Version unterscheide.

Dein PC schmeisst Dir bei sowas ne Schutzverletzung um die Ohren - auf dem RP6 gibts aber kein Betriebssystem ;)




*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++;


ich wette hier bei den ganzen Zeiger operationen liegt irgendwo der Fehler begraben - aber ich habe keine Zeit das selbst zu testen.

MfG,
SlyD

radbruch
15.08.2009, 20:01
Hallo

Ich vermute, weil ich es im Moment nicht testen kann, dass hier der Fehler liegt:

}while (rows++ <zeilen);

Die innere while(lines--)-Schleife wird 32 mal durchlaufen, aber die Spaltenschleife 33 mal! row startet zwar mit 0 im ersten Durchgang, beendet aber nicht nach row==31 sondern führt noch einen Durchgang mit row==32 aus. Somit werden 32*33 Bytes eingelesen und die Zeiger schiessen aus dem 1K-Array. Warum ich das nicht selbst bemerkte ist mir schleierhaft, vielleicht liegt das wirklich am unterschiedlichen Speicheraufbau der GCC-Versionen. Oder an einem anderen Unterschied der meine Motorvariablen ausserhalb des Gefahrenbereichs anlegt. Lösung wäre einfach:

}while (++rows <zeilen);

Gruß

mic

carlitoco
16.08.2009, 09:55
also ich habe das direkt mal getestet... nun giebt mir das Terminal nur noch die Zahlenkolone aus, die linke Kette ist nun ruhig.

Aber ein "bild" bekomme ich noch nicht.

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

radbruch
16.08.2009, 11:41
Hallo

Das sieht doch nicht schlecht aus. Scheinbar hängt er sich nicht mehr auf und die Einlesefunktion wird komplett ausgeführt sonst würde er wohl nicht zur Ausgabe gelangen. Die noch fehlenden Sternchen sollten nach dem Anpassen des Schwellwerts (90) erscheinen. Da bei der Zeilensuche im Wechsel auf >20 und <30 geprüft wird scheint das Signal der Kamera anzukommen. Abhängig vom Objekt (dicker schwarzer Strich auf weisem Papier wäre günstig) sollte nun der Schwellwert irgendwo zwischen 30 und 90 liegen. Am einfachsten vielleicht in einer Schleife ausprobieren:


uint16_t i, j, k;

...

while(1)
{
bild_einlesen();

for (k=30; k<90; k+=10)
{
writeString_P("Schwelle ");
writeInteger(k,DEC);
writeString_P(":\n\r");

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

}
mSleep(200);
}


Natürlich auch wieder nicht geteset, ich hoffe, es funzt so.

Gruß

mic

carlitoco
16.08.2009, 14:05
Nun spuckt er das aus, nehme mal an das meine Cam andere Werte hat, was die licht sensorik angeht ?


[READY]
Schwelle 30:

*********************** ********0

********************************1

********************************2

********************************3

********************************4

********************************5

********************************6

********************************7

********************************8

********************************9

********************************10

********************************11

********************************12

********************************13

********************************14

********************************15

********************************16

********************************17

********************************18

********************************19

********************************20

********************************21

********************************22

********************************23

********************************24

*************************** ****25

********************************26

********************************27

********************************28

********************************29

********************************30

********************************31

Schwelle 40:

*********************** ****** *0

********************************1

********************************2

********************************3

********************************4

********************************5

********************************6

********************************7

********************************8

********************************9

****** ****** *****************10

********************************11

********************************12

****** ****** ******************13

********************************14

********************************15

************** ****** ****** ***16

********************************17

********************************18

************* ****** ****** ****19

********************************20

********************************21

********************************22

********************************23

***** **************************24

*************************** ****25

********************************26

********************************27

********************************28

********************************29

***** ****** ****** ************30

********************************31

Schwelle 50:

*********************** ****** *0

********************************1

********************************2

********************************3

****** ****** *****************4

********************************5

********************************6

****** ****** ******************7

********************************8

********************************9

****** ****** ****** ****** ***10

********************************11

********************************12

****** ****** ****** ****** ****13

********************************14

********************************15

************** ****** ****** ***16

********************************17

***** ****** ****** ************18

************* ****** ****** ****19

********************************20

**** ****** ********************21

********************************22

********************************23

***** ****** ****** ****** *****24

*************************** ****25

***** ****** *******************26

**** ****** ****** ****** ******27

********************************28

**** ***************************29

***** ****** ****** ****** *****30

********************************31

Schwelle 60:

*** ****** ** ****** ** ** *0

**** ***** ** ** *** * * **1

****** ******** ** ******** *2

**** ** ****** ** ** *** ***3

**** * ** ** ** *** ** * *4

****** *** ****** * ****** *5

** ** ** *** ** * **** * ** 6

** ** ** ** *** * * **** * *7

* * **** * ** ****** *** ** 8

** *** * ** * *** ** * *9

* *** ** * ***** * * ** ***10

* * ****** ** ****** ** ** *11

* ** ***** * * ** *** * **** 12

** ** *** ** * **** * * * ****13

** * ** ** ** *** ** * ***14

* ** * **** * ** * **** ** ** 15

***** ****** ** ** *** * * *16

** ** ** ** * **** * * *17

* ** ** ** *** * * ** * * **18

***** *** ** *** * * ** * *19

*** ** * ***** ** ** *** 20

** *** * * ** * *** ** ***21

** ** *** ******** ** ********22

*** ***** *** ** *** * * ** 23

* ** ***** * **** *** * **** 24

** ****** **** ******** * ****25

*** * *** ** ** ****** * ***26

*** * **** *** * **** * ** 27

***** *** ** *** ****** *** *28

** ** ** *** ** * **** * * *29

* ** ** ** *** * * **** * ** 30

** ** *** ****** *** ****** *31

Schwelle 70:

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

Schwelle 80:

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18


[RP6BOOT]

habe jetzt die werte auf

danke und gruss
for (k=60; k<70; k+=10) verändert um mal eine "Essenz" des Ganzen zu erhalten

[edit]
spannend zu beobachten ist, dass die schwell-werte sich verändern bei anschluss der cammera an ein TV. D.h. sie leigen ohne TV ca. bei 60 mit TV angeschlossen darunter.

radbruch
16.08.2009, 15:23
for (k=60; k<70; k++)

Was wird da eigentlich fotografiert?

carlitoco
16.08.2009, 16:04
schwarzer balken auf hellem untergrund

radbruch
16.08.2009, 17:06
Seltsam, der Balken lässt sich nicht mal erahnen. Irgendwas stimmt noch nicht. Ein paar Ausreisser sind normal, aber deine Werte scheinen mir fast zufällig zu sein. Versuche mal eine halb schwarze, halb weise Fläche, Kante waagrecht oder senkrecht. Oder versuche das 256-Bytes-am-Stück mit schwarzer und mit weiser Fläche:

// Liest ab der 100. Zeile 256 Werte am Stück ein
// und sendet die Daten als Tabellenvorlage zum PC.

#include "RP6RobotBaseLib.h"

int main(void)
{
uint8_t pixel[256],*pixelzeiger, *endezeiger;
uint8_t vsync, lines;

initRobotBase();
extIntOFF();
//powerON();
// interne Referenz 2,56V, linksbündig, Kanal ADC4
ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<ADLAR) | 4;
// free running triggern
SFIOR = (0<<ADTS2) | (0<<ADTS1) | (0<<ADTS0);
// kein interupt, einschalten, prescaller /2
ADCSRA = (0<<ADIE) | (1<<ADEN) | (0<<ADPS2) | (0<<ADPS1) | (1<<ADPS0);
// free running aktivieren, altes Flag löschen
ADCSRA |= (1<<ADATE) | (1<<ADIF);
// Initialisierung starten
ADCSRA |= (1<<ADSC);

while (!(ADCSRA & (1<<ADIF)));
ADCSRA |= (1<<ADIF);

pixelzeiger=&pixel[0];
endezeiger=&pixel[255];
lines=100;
cli();

do // vsync abwarten
{
vsync=0;
while (ADCH > 20);
while (ADCH < 30) vsync++;
}while (vsync < 40);

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

// 256 Werte am Stück einlesen und als Basis für ein Diagramm senden
do *pixelzeiger=ADCH; while (pixelzeiger++ < endezeiger);
sei();

writeString("------------------------\n\r");
lines=0;
do
{
writeInteger(lines, DEC);
writeString_P("; ");
writeInteger(pixel[lines], DEC);
writeString_P("; ");
writeString("\n\r");
}while (++lines);

while (1); // ;)
return(0);
}

carlitoco
16.08.2009, 19:44
folgende ausgabe:
[code]

_|Alex|_
13.03.2010, 19:24
Erstmal Sorry das ich diesen alten Beitrag ausgrabe. :mrgreen:

Aber ich habe mal ne Frage undzwar habe ich das Kamera Modul 1 vom großem C wie auch im ersten beitrag beschrieben und möchte es direkt an meinen RP6 anschließen nun finde ich den E_INT1 nicht warscheinlich mein Fehler ](*,) und nun noch eine Frage undzwar brauche ich dafür noch etwas außer dem Code oder klapt das dan so?

RobbyMartin
13.03.2010, 20:46
hey

guck mal auf der erweiterungs platine da steht int 1 drauf


lg
martin

_|Alex|_
13.03.2010, 20:52
danke den hab ich auch schon gahabt dachte aber das wär ein anderer,
den da das "E" davor nich is ich bin jezt am INT1 nur irgendwie kommt das Signal nich richtig an.

lg Alex

Sceloporus
14.03.2010, 18:11
Hallo,

toller Thread.

Ich will das mit dem Linien-Folgen nachprogrammieren, aber scheitere an meinen Hardware-Unkenntnissen.

Wo schließt ihr den Chinch-Stecker an, so dass ihr das Bild der Kamera auf dem PC-Monitor seht? Welche Zusatzsoftware nutzt ihr?

Kann man auch einen VGA-Stecker anschließen und ihn an den VGA-Anschluss eines Monitors anschließen?

Danke für Hinweise!

_|Alex|_
14.03.2010, 19:18
Wo schließt ihr den Chinch-Stecker an


Den schließt du an GND und Signal an Signal is bei meiner Kamera vom großen C vout.

Hier nochmal ein Bild
http://www.abload.de/img/cinchyoyv.jpg (http://www.abload.de/image.php?img=cinchyoyv.jpg)

1. ist Signal
2. Masse(GND)

lg Alex

Sceloporus
14.03.2010, 22:54
Danke, das hat auch weitergeholfen, meine eigentliche Frage war eine andere... nämlich wohin ihr die Kamera anschließt, damit ihr ein Bild von der Kamera (zur Prüfung) seht?

Also wohin geht das Signal?

_|Alex|_
15.03.2010, 09:08
Das Frage ich mich auch 8-[
Ich habe das auch noch nich richtig hinbekommen ](*,) und hoffe das wir es nochmal erklärt bekommen.


lg ALex

trek_star
22.03.2010, 09:01
hm... ich kann euch da nicht Helfen.

radbruch
22.03.2010, 16:25
Hallo


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. (Ein Zitat aus dem Startbeitrag dieses Threads.)

Links die Kamera, in der Mitte der Stecker für den XBUS und rechts unten der Chinchstecker zum Anschluss an den Fernseher:
http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170008klein.jpg (http://radbruch.roboterbastler.de/rp6/liniensuche1/P8170008.jpg)

Das Videosignal hat nominale 1V an 75Ohm. Ohne Kontrollmonitor (Fernseher) ist aber nur der PullDown am E_INT1 angeschlossen. Die gemessenen Werte sind daher höher als mit Kontrollmonitor. Um diesen Effekt etwas zu dämpfen habe ich bei späteren Anwendungen einen jumperbaren 100Ohm-Widerstand als zusätzlichen PullDown eingesetzt der im Betrieb ohne Kontrollmonitor die Signalquelle mehr belastet.

FBAS auf VGA passt nicht wirklich: http://lmgtfy.com/?q=fbas+to+vga

Gruß

mic

_|Alex|_
24.03.2010, 15:15
komisch ich finde den E_INT1 irgendwie nicht auf meinem RP6

radbruch
24.03.2010, 15:29
E_INT1 ist der Anschluß PA4 am Mega32 und der Pin8 am XBUS:


#define UBAT (1 << PINA7) // ADC7 (Input)
#define MCURRENT_L (1 << PINA6) // ADC6 (Input)
#define MCURRENT_R (1 << PINA5) // ADC5 (Input)
#define E_INT1 (1 << PINA4) // INT1 (input per default... can be output)
#define LS_L (1 << PINA3) // ADC3 (Input)
#define LS_R (1 << PINA2) // ADC2 (Input)
#define ADC1 (1 << PINA1) // ADC1 (Input)
#define ADC0 (1 << PINA0) // ADC0 (Input)
(Aus RP6RobotBase.h)

_|Alex|_
25.03.2010, 09:28
Cool Danke ich werds mal versuchen \:D/

Lg Alex

radbruch
29.12.2010, 19:13
Hallo

Trotz Mitmachprojekt (https://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=53424) finden sich immer noch keine Anwendungen für diese einfache und kostengünstige Kameralösung. Weil ich die Kamera aber für mein RA2-Projekt verwenden möchte und mir gleichzeitig eher zufällig ein zur Kamera passendes sehr grobpixeliges Display (https://www.roboternetz.de/phpBB2/viewtopic.php?t=57205) in die Hände gefallen ist, habe ich eine Variante für einen 8MHz-ATMega8 geschrieben. Aufgrund der Erkenntnisse aus dem Mitmach-Thread verwende ich als Basis das einfache 24x16 Pixel-Programm (https://www.roboternetz.de/phpBB2/zeigebeitrag.php?p=500285#500285). An das Display angepasst werden nur 12 der 16 möglichen x-Pixel angezeigt, die y-Auflösung wurde auf 10 Zeilen reduziert. Der ADC rennt zwar immer noch mit Prescaler /2 im Dauerlauf, aber da nun auf das Ende der AD-Wandlung gewartet wird, ist das Einlesen eines Bildes so entspannt, dass man dazu nicht mal mehr die Interrupts sperren muss:


uint8_t bild[4][160];

uint8_t Bild_aufnehmen(void)
{
static uint8_t bildnummer=0;
uint8_t *bildzeiger;
uint8_t zeile, sync, c;

bildnummer = (bildnummer+1) & 3; // Bildnummer von 0 bis 3
zeile=30; // 30 Zeilen am Bildanfang überlesen
bildzeiger = &bild[bildnummer][0];

do{sync=0;while (ADCH > 20);while (ADCH < 30) sync++;}while (sync < 40);
for(c=0; c<10; c++) // 10 Zeilen einlesen
{
sync=15; // 15 Werte sollen am Stück gelesen werden
while(zeile--){while (ADCH > 20);while (ADCH < 30);} // auf Zeile warten
*bildzeiger++=ADCH; // erster Wert!
ADCSRA |= (1<<ADIF);
while(sync--) // Werte 2-16 einlesen
{
while(!(ADCSRA & (1<<ADIF)));
*bildzeiger++=ADCH;
ADCSRA |= (1<<ADIF);
}
zeile=24; // 24 Zeilen überlesen (30+ 24*10 = 270 Zeilen)
}
return(bildnummer);
}


Wie gehabt wird der ADC mit Prescaler /2, rechtsbündig und mit interner 2,56V-Referenz betrieben. Nach der Erkennung des Bildstarts werden 30 Zeilen überlesen, dann werden in einem Rutsch die 16 Pixel einer Zeile eingelesen. Nach jeweils einem Zeilensprung von 24 Zeilen werden dann schließlich die restlichen 9 Zeilen eingelesen. Das alles passiert innerhalb eines Halbbildes und mit freigegebenen Interrupts!

Der Bildspeicher ist 16x10=160 Bytes groß, ich verwende davon vier Stück die ich nacheinander mit den Kameradaten fülle. Die Einlesefunktion verwendet die Bildspeicher als Ringspeicher. Die Auswertefunktion bildet das Mittel aus den einzelnen Bildspeichern und stellt die Pixel auf dem Display dar:

http://i3.ytimg.com/vi/ngim6DzIcGQ/2.jpg (http://www.youtube.com/watch?v=ngim6DzIcGQ)
http://www.youtube.com/watch?v=ngim6DzIcGQ

Mein ungeputzter Arbeitscode:


// Kamera und acht Helligkeitsstufen mic 29.12.2010

#include <avr/wdt.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <inttypes.h>

#define colors 8 // Anzahl der Farbebenen (8 ist Maximum!)

// Variablen ------------------------------------------------------------------
volatile uint8_t ebene=0, col = 0; // Bildaufbau der LED-Matrix mit Timer2-ISR
uint8_t x, y, z, bildspeicher[colors][15];
uint8_t bild[4][160];
uint16_t counter=0;
uint8_t attrib=1;

prog_uint8_t font[5][17]={ // Zeichensatz für 3x5 7-Segmentanzeige 0 bis F und Blank (Speicherplatz im Flash)
{ 0b111, 0b001, 0b111, 0b111, 0b100, 0b111, 0b100, 0b111, 0b111, 0b111, 0b111, 0b100, 0b111, 0b001, 0b111, 0b111, 0b000},\
{ 0b101, 0b001, 0b001, 0b001, 0b101, 0b100, 0b100, 0b001, 0b101, 0b101, 0b101, 0b100, 0b100, 0b001, 0b100, 0b100, 0b000},\
{ 0b101, 0b001, 0b111, 0b111, 0b111, 0b111, 0b111, 0b001, 0b111, 0b111, 0b111, 0b111, 0b100, 0b111, 0b111, 0b111, 0b000},\
{ 0b101, 0b001, 0b100, 0b001, 0b001, 0b001, 0b101, 0b001, 0b101, 0b001, 0b101, 0b101, 0b100, 0b101, 0b100, 0b100, 0b000},\
{ 0b111, 0b001, 0b111, 0b111, 0b001, 0b111, 0b111, 0b001, 0b111, 0b001, 0b101, 0b111, 0b111, 0b111, 0b111, 0b100, 0b000} \
};
prog_uint8_t * fnt = (prog_uint8_t *) font; // Zeiger für den Zugriff auf den Zeichensatz (für plot_byte())

// Funktionen -----------------------------------------------------------------
void init(void);
void cls(void);
void plot_byte(uint8_t x, uint8_t y, uint8_t ziffer); // Ziffer von 0 bis F
void write_byte(uint8_t a, uint8_t z); // z=0 ist oben, z=1 ist unten
uint8_t Bild_aufnehmen(void);
void Bild_darstellen(uint8_t level);
void Bild_Info(void);
// Einen Bildpunkt an x, y setzen. Werte für c: 0 ist aus, 1 ist dunkel, 4 ist hell
void set(uint8_t x, uint8_t y, uint8_t c);
// Potiwert P2 an ADC6 einlesen als Byte! Kanal 7 ist die Cam
uint8_t readADC6(void);

// WatchDog beim Initialisieren ausschalten
// https://www.roboternetz.de/phpBB2/viewtopic.php?p=531597#531597
void kill_WD(void) __attribute__((naked)) __attribute__((section(".init3")));
void kill_WD(void) { MCUSR = 0; wdt_disable(); }

// Hauptschleife --------------------------------------------------------------
int main(void)
{
init();
for(x=0; x<12; x++)
for(y=0; y<10; y++)
set(x, y, (colors-((x+y)/3)%colors)); // Helligkeitsstufen anzeigen
_delay_ms(2000);

cls();

while(Bild_aufnehmen()); // Bildspeicher füllen
// Bild_Info(); // min - max - mitte - durchschnitt
while(1)
{
if(!col)
{
z=readADC6();
if(attrib)
{
for(y=5; y<10; y++)
for(x=0; x<12; x++) set(x, y, z/29); // 255/29 ergibt 8,8
}
else
{
while(Bild_aufnehmen());
Bild_darstellen(z);
}
if(z==0) if(attrib) { attrib=0; Bild_Info(); } // Bit0: Zeile
if(z==255) if(!attrib) { attrib=1;};
if(attrib) write_byte(z, attrib);
}
counter++;
}
return (0);
}

// Definitionen der Funktionen ------------------------------------------------
uint8_t Bild_aufnehmen(void)
{
static uint8_t bildnummer=0;
uint8_t *bildzeiger;
uint8_t zeile, sync, c;

bildnummer = (bildnummer+1) & 3; // Bildnummer von 0 bis 3
zeile=30; // 30 Zeilen am Bildanfang überlesen
bildzeiger = &bild[bildnummer][0];

do{sync=0;while (ADCH > 20);while (ADCH < 30) sync++;}while (sync < 40);
for(c=0; c<10; c++) // 10 Zeilen einlesen
{
sync=15; // 15 Werte sollen am Stück gelesen werden
while(zeile--){while (ADCH > 20);while (ADCH < 30);} // auf Zeile warten
*bildzeiger++=ADCH; // erster Wert!
ADCSRA |= (1<<ADIF);
while(sync--) // Werte 2-16 einlesen
{
while(!(ADCSRA & (1<<ADIF)));
*bildzeiger++=ADCH;
ADCSRA |= (1<<ADIF);
}
zeile=24; // 24 Zeilen überlesen (30+ 24*10 = 270 Zeilen)
}
return(bildnummer);
}

void Bild_darstellen(uint8_t level)
{
uint8_t x, y, temp;
uint8_t bild_temp[160];
uint16_t temp16;

for(x=0; x<160; x++)
{
temp16 = bild[0][x] + bild[1][x] + bild[2][x] + bild[3][x];
bild_temp[x] = temp = temp16/4;
}

if(level)
{
for(y=0; y<10; y++)
for(x=0; x<12; x++)
{
temp= bild_temp[16*y+x+2];
if(temp<level) set(x, 9-y, colors); else set(x, 9-y, 0);
}
}
else
for(y=0; y<10; y++)
for(x=0; x<12; x++)
{
temp= bild_temp[16*y+x+2];
set(x, 9-y, (temp-20)/8);
}
}

void Bild_Info(void)
{
uint8_t min=225, max=0, temp;
uint16_t durchschnitt=0, temp16;

for(x=0; x<160; x++)
{
temp16 = bild[0][x] + bild[1][x] + bild[2][x] + bild[3][x];
temp = temp16/4;
if(temp > max) max=temp;
else if(temp>20) if(temp < min) min=temp;
durchschnitt += temp;
}
durchschnitt /= 160;

cls();
write_byte(min, 1);
_delay_ms(1000);
cls();
write_byte(max, 0);
_delay_ms(1000);
cls();
write_byte((min+max)/2, 1);
_delay_ms(1000);
cls();
write_byte(durchschnitt, 0);
_delay_ms(1000);
}

void cls(void)
{
uint8_t x, y;
for(y=0; y<colors; y++)
for(x=0; x<15; x++)bildspeicher[y][x] = 0;
}
void set(uint8_t x, uint8_t y, uint8_t c)
{
uint8_t ebene;

y = 9-y; // Koordinatennullpunkt unten links
if(y < 8) // y 9 bis 2
for(ebene=0; ebene<colors; ebene++)
if(c>ebene) bildspeicher[ebene][x] |= (1 << y);
else bildspeicher[ebene][x] &= ~(1 << y);
else // y 1 und 0
for(ebene=0; ebene<colors; ebene++)
if(c>ebene) bildspeicher[ebene][12+(x>>2)] |= (1<<((x%4)*2+(y&1)));
else bildspeicher[ebene][12+(x>>2)] &= ~(1<<((x%4)*2+(y&1)));
}
void plot_byte(uint8_t x, uint8_t y, uint8_t ziffer)
{
uint8_t c, f1=colors, f2=0, fontbyte=0;

for(c=0; c<5; c++)
{
fontbyte = pgm_read_byte_near(fnt+17*c+ziffer); // Zeichensatz ist im Flash
if(fontbyte & 4) set(x , y-c, f1); else set(x , y-c, f2);
if(fontbyte & 2) set(x+1, y-c, f1); else set(x+1, y-c, f2);
if(fontbyte & 1) set(x+2, y-c, f1); else set(x+2, y-c, f2);
if(fontbyte & 0) set(x+3, y-c, f1); else set(x+3, y-c, f2);
}
}
void write_byte(uint8_t a, uint8_t z) // z=0 ist oben, z=1 ist unten
{
uint8_t c, temp=1;

c=0;
while(a >= 100) {a-=100; c++; }
if(c) plot_byte(0, 9-5*z, c); else { plot_byte(0, 9-5*z, 16); temp=0; }
c=0;
while(a >= 10) {a-=10; c++; }
if(c || temp) plot_byte(4, 9-5*z, c); else plot_byte(4, 9-5*z, 16);
c=0;
while(a >= 1) {a-=1; c++; }
plot_byte(8, 9-5*z, c);
}
uint8_t readADC6(void)
{
uint8_t adc6, dummy;
ADCSRA = (0 << ADEN);
dummy=ADCH;

// A/D Conversion (aus der asuro-Lib)
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (0 << ADPS1) | (1 << ADPS0); // clk/32
ADMUX = (1<<ADLAR) | (1 << REFS0) | (6);// AVCC reference with external capacitor
ADCSRA |= (1 << ADSC); // Start conversion
while (!(ADCSRA & (1 << ADIF))); // wait for conversion complete
adc6=ADCH;
ADCSRA |= (1 << ADIF); // clear ADCIF
ADCSRA = (0 << ADEN);
dummy=ADCH;
// rechtsbündig, interne 2,56V-Referenz (0:1 5V, 1:1 2,56V) , Kanal 7
ADMUX = (1<<ADLAR) | (1<<REFS1) | (1<<REFS0) | 7;
// enable, start conversion, freerunning, prescaler /2
ADCSRA = (1<<ADEN) | (1<<ADSC) | (1<<ADFR) | (0<<ADPS2) | (0<<ADPS1) | (1<<ADPS0);

return(adc6);
}
void init(void)
{
cli();

DDRB = 0xff;
DDRC = 0x0f;
DDRD = 0xf0;

TCCR2 = (1<<CS21) | (0<<CS20); // 8-bit Timer mit 1/8 Vorteiler
TCCR2 |= (1<<WGM21) | (1<<WGM20); // Fast PWM
TCCR2 |= (0<<COM21) | (0<<COM20); // no OC2-Pin
OCR2 = 100; // 0=dunkel, 255=hell
TIFR = (1<<OCF2) | (1<<TOV2); // Clear old flags
TIMSK |= (1<<TOIE2) | (1<<OCIE2); // overflow and compare interrupt

// rechtsbündig, interne 2,56V-Referenz (0:1 5V, 1:1 2,56V) , Kanal 7
ADMUX = (1<<ADLAR) | (1<<REFS1) | (1<<REFS0) | 7;
// enable, start conversion, freerunning, prescaler /2
ADCSRA = (1<<ADEN) | (1<<ADSC) | (1<<ADFR) | (0<<ADPS2) | (0<<ADPS1) | (1<<ADPS0);

sei(); // Interrupts erlauben
}

// ISR ------------------------------------------------------------------------
SIGNAL (SIG_OUTPUT_COMPARE2)
{
OCR2 = (1<<ebene)-1; // hihi
PORTC &= ~0x0f; // Die Pins der Displaymatrix werden wieder auf Low gesetzt
PORTD &= ~0xf0;
PORTB &= ~0x03;
}
SIGNAL (SIG_OVERFLOW2)
{
uint8_t ledval, portb;

// Spalten
if(col) PORTB |= (1<<4); /* Danach Einsen hinterherschicken (PB4 = 1) */
else PORTB &= ~(1<<4); /* Bei der ersten Spalte eine 0 ausgeben (PB4 = 0) */
PORTB |= (1 << 3); /* PB3 = 1 (cl) */
PORTB &= ~(1 << 3); /* PB3 = 0 (!cl) */
PORTB |= (1 << 2); /* PB2 = 1 (str) */
PORTB &= ~(1 << 2); /* PB2 = 0 (!str) */

// Zeilen
ledval = bildspeicher[ebene][12+(col>>2)]; // y 1 und 0
portb = (ledval >> (col%4)*2) & 0x03;
ledval = bildspeicher[ebene][col]; // y 9 bis 2
PORTC |= ledval & 0x0f;
PORTD |= ledval & 0xf0;
PORTB |= portb;

col++;

if(col>11)
{
col=0;
ebene++;
if(ebene == colors) ebene=0;
}
}


Gruß

mic

Dirk
30.12.2010, 19:28
Hi mic,

das ist ja mal wieder genial!

Ich hole mir dieses PingPong erst mal vom großen C.

Ich wußte gar nicht, dass da ein M8 drin sitzt...

Gruß Dirk

Mons
09.06.2011, 17:28
Gibt es einen aktuelleren Quellcode?
Ich möchte dieses Projekt für mein nächstes verwenden.

radbruch
09.06.2011, 22:53
Das ist der aktuellste Quellcode. Der ADC wird zwar auch hier gnadenlos übertaktet, aber beim Einlesen einer Bildzeile wird nicht mehr stur eingelesen sondern auf den Status des ADC geachtet. Es ist die aktuell schnellste Lösung, alle Pixel werden innerhalb eines Halbbildes eingelesen. Basis ist diese Version:

https://www.roboternetz.de/phpBB2/zeigebeitrag.php?p=500285#500285

Bedeutend höhere Auflösungen sind auch möglich, allerdings wird so pro Halbbild nur ein Pixel pro Zeile eingelesen:

https://www.roboternetz.de/community/showthread.php?47457-Hardware-Mitmach-Projekt-Kamera-f%FCr-den-RP6&p=458253&viewfull=1#post458253
https://www.roboternetz.de/community/showthread.php?47457-Hardware-Mitmach-Projekt-Kamera-f%FCr-den-RP6&p=457599&viewfull=1#post457599

Was soll die Kamera bei deinem Projekt erkennen?

Mons
10.06.2011, 11:41
Die Kamera soll drei IR Laser Orten um die Landung eines Quadrocopters autonom erfolgen zu lassen. Wenn ich fertig bin werde ich mal ein Video posten (wahrcheinlich erst in den Sommerferien).
Danke für den aktuellen Quellcode. Mit was hast du den denn Compiliert?

LG Matthias

Dunkle Macht
27.07.2011, 14:19
Hallo,

ist es mit der von dir beschriebenen Methode denn auch möglich Farben zuverlässig zu unterscheiden?
Wie kalibrierst du die Kamera? Machst du das manuell, indem du werte im Programm änderst oder geht das automatisch?

Gruß

radbruch
27.07.2011, 17:04
Hallo

Mit Farberkennung habe ich noch keine Versuche unternommen. Eventuell kann man mit Filterscheiben arbeiten?

Da die Kamera einen automatischen Weisabgleich macht, braucht man eigentlich nichts kalibrieren. Zur Zeit spiele ich mal wieder mit einem Linienfolger, diesmal aber mit dem asuro und in Bascom:
http://www.youtube.com/watch?v=usS3a3Ud_vo

Es klappt zwar noch nicht so gut, aber es ist auch eher als Einstieg in eine echte Bildverarbeitung gedacht.

Gruß

mic

radbruch
28.07.2011, 16:25
Hallo

Gleich vorweg die Warnung: Dieser Beitrag ist nur für die wirklich harten Kamera-Fans geeignet.

Zur Zeit versuche ich die Kamera auch mit Bascom in den Griff zu bekommen. Das klappt auch ganz gut:


http://www.youtube.com/watch?v=sr-7kZ68qDo

Basis ist der eigentlich schon getestete Code für den RP6. Wie üblich hangelt sich der Code an den Syncs zum Bildanfang und dann zur Zielzeile, in meinem Programm zur 50. Zeile. Dann wird laufend geprüft, ob die Helligkeit größer einer bestimmten Schwelle ist. Die Anzahl der Prüfungen wird mitgezählt und das Ergebnis ist dann der Wert für die Position der Kante der Linie:


Do ' Auf Bildanfang warten
B = 0
While Adch > 30 ' solange kein sync
Wend
While Adch < 50 ' solange sync
Incr B ' syncs zählen
Wend
Loop Until B > 40 ' mehr als 40 bedeutet Bildanfang
B = 50 ' die zu lesende Zeile
While B > 0 ' Zeilen zählen
While Adch > 30 ' solange kein sync
Wend
While Adch < 50 ' solange sync
Wend
Decr B
Wend ' Zeile gefunden
B = 0
While Adch > 65 ' Hell/Dunkel-Schwelle
Incr B ' warten und zählen bis Schwellwert erreicht wird
Wend
Linie = B ' Rückgabewert kopieren

Wichtig ist nun diese kleine und eigentlich unscheinbare Funktion:

B = 0
While Adch > 65 ' Hell/Dunkel-Schwelle
Incr B ' warten und zählen bis Schwellwert erreicht wird
Wend
Was mache ich da? Der Pegel des Videosignals in ADCH ist beim Sync deutlich unter 30, alles was größer als 50 ist, wird als Bildinformation interpretiert. Alle Werte über der Schwelle von 65 werden als "Hell" gewertet, ein Wert unter der Schwelle bedeutet die Kante der Linie ist gefunden.

Eigentlich hätte ich nun erwartet, dass bei einem 8MHz-RISC-Kontroller der Zähler sofort in die Höhe schießt, denn die Ausführung der kleinen Scheife dauert nur ein paar Takte. Dem ist aber nicht so! Wenn ich mit Adch > 50, quasi alles was nicht Syncpegel ist, als Bildbereich gelten lassen, komme ich auf Zählerstände von maximal 27. Das ist auch das, was man im Video oben sieht und hört: Wenn der Linienwert >26 ist wird keine Linie erkannt, weil sie außerhalb des Sichtbereichs der Kamera ist. Aber warum ist der Wert so unerwartet niedrig? Ich vermute, Bascom wartet intern bis der ADC mit der Wandlung fertig ist bevor der Wert von ADCH gelesen werden kann. Das würde also 26 echte Positionen bedeuten, was allerdings zu den in C ermittelten maximalen 16 nicht ganz passt. Hier muss man noch etwas weiter forschen, aber ich schweife auch ab....

Wirklich spannend ist nun folgendes: Wenn man die Schwelle nicht als absoluten Wert angibt sondern als Variable, dann verändern sich die gemessenen Werte:

Dim Schwelle As Byte

...
Adch > Schwelle
...

So komme ich nur noch auf Maximalwerte von ca. 23! Irgendwas an der Art der Zuweisung scheint so länger zu dauern als in der Variante mit Konstanten. Sehr seltsam. Was Basom da übersetzt sollte man mal disassemblieren.

Gruß

mic

radbruch
08.08.2011, 22:30
Hallo

Da ich mal wieder an meiner ewigen Kamerabaustelle arbeite, kann ich nun quasi die Version 2.0 der Minimalkamera als Beta vorstellen.

Bisher hatten wir das Problem, dass wir zwar die Syncs gefunden haben, aber die Flanken wurden nicht richtig erkannt. Deshalb "zitterte" der Zeilenanfang beim Einlesen der Bilddaten. Das sieht man deutlich bei den HiRes-Bildern:

http://radbruch.bplaced.net/robot/cam2bmp/ocb.bmp (http://radbruch.bplaced.net/robot/cam2bmp/ocb.bmp) http://radbruch.bplaced.net/robot/cam2bmp/domino1.bmp (http://radbruch.bplaced.net/robot/cam2bmp/domino1.bmp) http://radbruch.bplaced.net/robot/cam2bmp/domino2.bmp (http://radbruch.bplaced.net/robot/cam2bmp/domino2.bmp)
(Bilder aus https://www.roboternetz.de/community/showthread.php?47457-Hardware-Mitmach-Projekt-Kamera-f%FCr-den-RP6&p=458253&viewfull=1#post458253)

Nun bin ich schon vor einiger Zeit im Datenblatt der AVRs über diesen Satz in der Beschreibung des analogen Komparators (Analog Comparator) gestolpert:


The output of the Analog Comparator is synchronized and then directly connected to
ACO. The synchronization introduces a delay of 1 - 2 clock cycles.Frei übersetzt: Der Status der Komparators wird innerhalb von 1 bis 2 Taktzyklen (!) im ACO-Bit dargestellt.

Blöderweise sind aber die Pins des Komparators bei meinen Fertigrobotern schon belegt, deshalb habe ich diesen Ansatz bisher nicht weiterverfolgt. Eher zufällig ist mir nun aufgefallen, dass mein asuro-probot genau diese Pins nicht verwendet, wenn ich die Sensorplatine nicht einstecke. Dadurch kann ich nun AIN0 und AIN1 nutzen. Als zusätzliche Hardware erzeuge ich mit einem 10k-Poti eine einstellbare Spannung an AN0 als Schwelle für den Sync-Pegel, die Erweiterung der Software beschränkt sich auf das Umschalten zwischen ADC- und AC-Betrieb und der geänderten Sync-Erkennung:


Sub Readline(byval Zeile As Word)
Local Znr As Word

' AC einschalten Vergleich zwischen AIN0 (PD6) und ADC-Kanal 4 (PC4)
Reset Adcsr.aden ' ADC dissable
Reset Acsr.acd 'enable analog comparator ACSR bit 7 = 0

Portd.4 = Acsr.aco ' Status zeigen: Warten auf Bildstart
Do ' Auf Bildanfang warten
Znr = 0
While Acsr.aco = 0 ' solange kein sync (PC4 > PD6)
Wend
While Acsr.aco = 1 ' PC4 < PD6 = Sync
Incr Znr ' syncs zählen
Wend
Loop Until Znr > 60 ' mehr als 60 (mit AC!) bedeutet Bildanfang

Znr = Zeile ' die zu lesende Zeile
While Znr > 1 ' Zeilen zählen
While Acsr.aco = 0 ' solange kein sync
Wend
While Acsr.aco = 1 ' solange sync
Wend
Decr Znr ' eine Zeile vor dem Ziel...
Wend
While Acsr.aco = 0 ' solange kein sync
Wend

' ADC einschalten
Set Adcsr.aden ' ADC Enable
Set Adcsr.adsc ' ADC Start Conversion
Portd.4 = Acsr.aco ' Status zeigen: Zeile gefunden

Set Adcsr.adif ' ADIF-Bit rücksetzen !!! Nur schreibend zugreifen !!!
While Adcsr.adif = 0 ' warten bis ADC fertig mit Wandeln
Wend
Znr = Adch ' erste Lesung ist noch Sync
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(01) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(02) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(03) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(04) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(05) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(06) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(07) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(08) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(09) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(10) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(11) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(12) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(13) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(14) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(15) = Adch
Set Adcsr.adif
While Adcsr.adif = 0
Wend
Linienspeicher(16) = Adch
Set Adcsr.adif
End Sub


Da der ADC wie gehabt im Dauerlauf gestartet wurde, schaltet die Funktion zuerst den ADC aus und den AC ein. Dann hangelt sich die Funktion durch die Syncs über den Bildanfang bis zu der Zeile, die sich vor der gesuchten Zeile befindet. Wenn am Ende dieser Zeile der Sync erkannt wird, schaltet die Funktion von AC auf ADC zurück und startet die erste Wandlung des ADC noch während des Syncs. So wird der Zeilenanfang mit einer bisher noch nicht gekannten Genauigkeit getroffen. Dieses einfache Demo


While X = X
Readline 50

For X = 1 To 16
Print Linienspeicher(x) ; " ";
Next X
Print

Waitms 300
Wend


erzeugt diese Ausgabe:


Bascom-Spielereien mit der Kamera
96 102 103 103 103 103 99 79 57 99 99 96 96 46 6 35
96 102 103 103 103 102 99 56 57 99 97 96 96 15 3 35
96 103 103 103 103 102 99 56 60 99 97 96 96 9 3 35
96 102 103 103 103 102 99 71 57 99 99 96 96 39 6 35
96 102 103 103 103 102 99 57 57 97 97 96 96 39 6 35
96 102 103 103 102 102 99 57 57 97 97 96 96 19 3 35
96 102 103 103 103 102 99 57 56 99 97 96 96 39 6 35
96 102 103 103 103 103 99 63 57 99 99 96 96 39 6 35
96 102 103 103 103 102 99 56 56 97 97 96 96 39 6 35
96 102 103 103 103 102 99 76 56 99 99 96 96 41 6 35
97 103 103 103 103 102 99 60 60 99 97 96 96 15 3 35
88 102 102 103 102 102 99 79 56 99 99 96 96 54 6 35
97 103 103 103 103 102 99 56 62 99 97 96 96 9 3 35
87 103 103 103 103 102 99 60 60 99 97 96 96 30 4 35
96 102 103 103 103 103 99 76 56 99 99 96 96 44 6 35
86 102 103 103 103 102 99 70 57 99 99 96 96 39 6 35
96 102 103 103 103 102 99 78 56 99 99 96 96 44 6 35
96 103 103 103 103 102 99 57 57 99 97 96 96 19 3 35
96 102 103 103 103 102 99 56 57 99 97 96 96 14 3 35
Werte<70 sind der Strich, <ca. 40 ist der Sync am Ende der Zeile. Es sind also ca. 13 Werte pro Zeile die gelesen werden.

Gruß

mic

hsc123
12.08.2012, 21:53
Hallo Radbruch

Gigantisch was du da gemacht hast. Funktionier das auch mit einer Farbkamera zb. 156967 - 62 vom grossen C ? Muss man da etwas besonderes beachten oder kann ich die Kamera wie oben beschrieben anschliessen ?

Danke und Grüße
HSC123

radbruch
12.08.2012, 22:45
Hallo

Obwohl ich es nie mit einer Farbkamera versucht habe, gehe ich davon aus, dass es mit dieser Kamera auch funktioniert. 5V Versorgung und 1V Signal scheint ideal, nur der Preis ist etwas happig. Bevor du dich in Unkosten stürzt, sollte dir klar sein, das es keine "ready to use"-Lösung für diese Anwendung einer Kamera gibt.

Für den Fall, dass du das noch nicht gefunden hast, hier der Link zum "Mitmach-Projekt" (das softwaremäßig leider in den Startlöchern stecken geblieben ist). Großes Lob an dieser Stelle an Dirk:

www.roboternetz.de/community/threads/47457-Hardware-Mitmach-Projekt-Kamera-für-den-RP6

Gruß

mic

RolfD
13.08.2012, 02:11
Hallöle,
man muss vielelicht dazu sagen, das eine cam Lösung weniger an irgendwelchen technischen Problemen scheitert - eagl ob SW oder das schwieriger zu dekodierende FBAS Farbsignal - als viel mehr am verfügbaren Ram um das Image dann auch im Speicher abzubilden und auszuwerten. Die meisten mir bekannten Lösungen schieben daher das Bild raw auf den PC welcher das Image in Folge bearbeitet und Steuersequenzen an den Mega32 sendet. Auf dem Mega32 ist nicht viel mehr als z.B. ein einfacher Linienfolger machbar - und der muss nicht bunt sein. Ein Farbbild braucht im günstigsten Fall "nur" 3 mal mehr Ram als ein greyscale gleicher Bittiefe.
Möchte man dann aber noch Einstellungen an der cam vornehmen (capture/exposure time usw.), ist man mit einer i2c-cam eh besser bedient. Allerdings löst das auch nicht das allgegenwärtige Speicherproblem.
Wenn du sinnvoll mit einer Cam spielen möchtest, leg dir daher lieber gleich nen anständiges ARM7/9 Board mit satt Speicher und USB Cam zu oder versuch die PC-lösung... z.B. mit der neuen WLAN Karte.

Selbst das Minibild einer Gameboy cam in greyscale verlangt schon 8-16k Ram.... je nach bittiefe
http://www.angelfire.com/de3/juliprograms/amr/gbcam.htm
Und das Ding ist wenigstens dank Artifical Retina Image Sensor einstellbar.
(äm.. womit ich nicht das Projekt hier kritisieren möchte... aber es hat eben Grenzen...)

Gruß Rolf

lasergehirn
13.08.2012, 10:38
Hi,

man muss aber nicht das ganze Bild im RAM halten für einige bestimmte algos.
z.B. hier:
http://www.jrobot.net/Projects/AVRcam.html

farbige Objekte verfolgen und Statistiken mit nem kleinen MEGA8.
Das geht natürlich nicht mit der analogen Cam das ist klar.
Bei den analogen Cams geht natürlich auch noch einiges mehr an Rechenzeit für das erfassen des Bildes ansich drauf...

DarkSoldier
18.11.2012, 22:12
Hallo,
vielleicht liest jemand das hier noch der hier in dem Thread aktiv ist /war oder jemand der mir trotzdem weiterhelfen kann (:.
Ich finde das das hier eine super Idee ist. Deswegen möchte ich das nun auch nachbauen. Nur hat Conrad dummerweise das Kamera Modul 1 aus dem Programm genommen. Und jetzt suche ich nach einer Kamera die ich ersatzweise benutzen kann finde aber keine die genau wie diese 5 Pole hat... Deswegen wollt ich fragen was für eine Kamera dafür benutzbar ist (:. Weil die im Mitmach Projekt auch die alte Kamera verwendenhilft mir das uch nicht weiter. Ich hättee aber gerne auch noch so eine Kamera mit 5 Polen. Also mit Vcc (am besten +5V), GND, Gamma, VIDEO-IN, Gain).

Danke im Vorraus,


DarkSoldier

radbruch
18.11.2012, 22:34
Hallo

Ja, leider ist die Kamera nicht mehr lieferbar. Grundsätzlich funktionieren alle Kameras mit AV-Ausgang, denn deren Signal hat immer 1V. Schwieriger ist es eine passende Eingangsspannung zu finden. Bei den 12V-Typen vermute ich einen internen Spannungsregler der möglicherweise die von uns gewünschten 5V erzeugt:
http://www.pollin.de/shop/dt/NTM5OTE0OTk-/Haustechnik/Sicherheitstechnik/Kameras/Mini_Kamera_S_W.html

Vermutlich funktioniert auch eine Farbkamera , denn die SW-Fernseher konnten ja auch ein Farbbild in Graustufen darstellen:
http://www.conrad.de/ce/de/product/156967/

Aber das habe ich selbst noch nicht getestet. (Oje: https://www.roboternetz.de/community/threads/29906-Minimall%C3%B6sung-Kamera-f%C3%BCr-den-RP6?p=556795&viewfull=1#post556795)

Gruß

mic

DarkSoldier
20.11.2012, 20:55
Hallo,
Danke erstmal für deine schnelle Antwort.
Ich habe mir überlegt das ich mir die Kamera vom Großen C besorgen werde die du als Link geschrieben hattest.
Ich habe mir auch in den letzten Stunden gedanken über die Programmierung gemacht. Da hab ich dieses Bild hier gefunden: http://ires.roboterbastler.de/images/BAS_Zeilensignal_unmoduliert.PNG
Das ist doch dann 1 einzelnes Pixel oder nicht? Welches dann vermutlich in einem Byte abgespeichert werden kann oder hab ich das noch nicht richtig verstanden?
Und da gibts dann sozusagen die "ankündigung" für ein neues Pixel, dann wird die Spannung auf 0V gezogen für 4,7us und dann wird sie für 5,8us auf 0,3 V erhöht. Worauf dann für 13us dass signal für schwarz kommt und je nach höhe der spannung ist das pixel mehr oder weniger schwarz, dann dass selbe jeweils für grau, hellgrau und weiß. Dann kommt noch eine "endsequenz".
Hab ich das jetzt richtig verstanden oder habe ich da einen Grundlegenden Denkfehler?

Danke für deine/eure Hilfe
DarkSoldier

Dirk
20.11.2012, 21:24
Hi DarkSoldier,

nein, das Bild zeigt eine ganze Bildzeile.
Du kannst dir vielleicht die Grundlagen bei dem RP6 Kamera Mitmach-Projekt ansehen: http://www.rn-wissen.de/index.php/RP6_Kamera_-_Mitmach-Projekt#Die_Synchronisations-Signale

DarkSoldier
20.11.2012, 22:26
Hallo,
also ich habe mich da jetzt mal durchgekämpft ;)

Da gibt es ja ein Csync und ein Hsync die aber durchaus zu gleichen Zeiten unterschiedliche Werte haben. Deswegen stellt es mir grade die Frage wie man das alles durch ein Kabel jagen kann :O . Ich möchte anfangs erstmal auf jedenfall nur das Hsync der kamera auslesen und "auswerten". Und das Csync ignorieren, dass läuft doch in der Zeit ab in der das Hsync Pause macht oder nicht? Und alles geht durch das gleiche Kabel? (:

Entschuldigt bitte doppelt und 3 fache fragen usw.. aber über ein soo großes elektrotechnik wissen verfügt man in der 10. Klasse eines bayrischen gymnasiums leider noch nicht :( da macht man ja leider GAR NIX zu solchen interessanten themen... das muss man sich alles seber beibringen ;)

danke an euch für eure hilfe ;)

DarkSoldier

- - - Aktualisiert - - -

Hallo nochmal,

jetzt wo ich mir den wikipedia artikel dazu nochmal angesehen hab ist mir vermutlich der sinn der farbverteilung aufgefallen ;) die eingezeichneten graphen geben die spannung wieder die durch das einzige kabel durchfliesst. bei einem fernseher mit zb 4 farbstufen gibt es also 4 verschiedene spannungen von denen jede einzelne "ihre farbe" zugewiesen bekommen hat also weiß hat immer ca. 1 volt. usw. unt vor jeder zeile gibt es eine art "vorsequenz" die sagt, dass da jetzt eine neue zeile anfängt. und danach auch.
Hab ich das jetzt richtig verstanden? ;)
Danke,
Darksoldier

Dirk
21.11.2012, 19:54
Ja, so ähnlich.
Da wird wirklich in Form eines Spannungsverlaufs die ganze Zeile dargestellt und die "Vorsequenz" kennzeichnet Zeilenanfänge. Es gibt auch genauso "Vorsequenzen" für Seitenwechsel. Es wird allerdings nicht die Farbe, sondern die HELLIGKEIT der Bildpunkte in der Zeile dargestellt.

DarkSoldier
21.11.2012, 20:43
Gut :D
Naja ich werd mir dann mal die kamera besorgen und mal mitm oszi usw mir des anschauen vlt versteh ichs dann ja komplett (:

Danke erstmal,
DarkSoldier

BeWe
04.02.2013, 18:57
Hallo,

nun muss ich dieses in die Jahre gekommene Thema auch mal ausgraben. Ich habe ebenfalls vor, eine SW-Kamera an den RP6 anzuschließen, damit er Linien verfolgen kann. Dazu habe ich mir folgende Platinenkamera besorgt: http://www.conrad.de/ce/de/product/156952/Miniatur-CMOS-Monochrom-Kameramodul-RS-OV5116-1330-12-3-VDC-Aufloesung-101000-Pixel-PAL-352-H-x-288-V

Zunächst ist sie am ADC0 angeschlossen, ihre Stromversorgung ist VDD und wurde mit einem Widerstand versehen, um auf die 3V Betriebsspannung zu kommen. Das schwarze Kabel ist an GND angeschlossen. Ein einfaches Programm ließt die ADC-Werte und schreibt sie in den Terminal (Light-Detection Beispielprogramm auf andere ADC-Kanäle abgeändert). Dabei erhalte ich Werte von 350 +/-20 mV. Theoretisch sollte sich der Großteil der Werte verändern, wenn man die Kamera direkt ins Licht hält oder die Linse verdeckt, da die Sync-Signale ja nur sehr kurz andauern. Allerdings schwanken die Spannungen ohne erkennbares Muster in dem genannten Bereich. Beeinflussen kann man sie nicht.

Daher erst einmal folgende Frage: Kann es sein dass meine Kamera defekt ist/ nicht dafür geeignet ist/ meine Überlegungen bezüglich der Beeinflussung der Werte völlig falsch sind?

Andererseits wurde ja immer zum E_Int 1 als ADC geraten. Hierbei wüsste ich aber nicht, wie ich diesen Einlesen soll. Ist dafür ADCH zuständig?

Einen schönen Abend!

radbruch
04.02.2013, 22:56
Hallo

So auf den ersten Blick scheint die Kamera geeignet, auch wenn sie leider nicht gerade billig ist.

Bei einer Stromaufnahme (laut minimalem Datenblatt der Kamera (http://www.produktinfo.conrad.com/datenblaetter/150000-174999/156952-ce-01-en-S_W_KAMERA_MODUL_RS_OV5116_1330.pdf)) von ca. 40mA sollte der Vorwiderstand wohl ca. (5V-3V)/0,04A = 50 Ohm betragen. Größer wäre vielleicht sicherer, ein echter Spannungregler, eine Spannungsregelung mit Z-Diode oder zumindest ein Spannungsteiler scheint mir aber besser/genauer zu sein. Zusätzlich sollte auch das Signal der Kamera mit einem Widerstand (nominal ca. 75 Ohm, kann aber auch bis zu mehrere 100 Ohm sein) belastet werden.

"Ein einfaches Programm ließt die ADC-Werte und schreibt sie in den Terminal" Das Programm solltest du uns zumindest mal zeigen. Der Mega32 hat nur einen analogen Wandler (=ADC) der ein 10-bitiges Ergebniss liefert. Mit diesem Wandler kann man aber verschiedene Eingänge (=Kanäle) wandeln, ob das der E-INT oder der ADC0 ist ist egal solange der verwendete Kanal im ADMUX-Register auch richtig ausgewählt ist. Das Ergebniss der Wandlung steht im ADC-Register welches sich aus ADCH (Bit 15-8) und ADCL (Bit 7-0) zusammensetzt. Es werden allerdings nur die Bits 9-0 verwendet. Das Videosignal sollte maximal (weiser Bildpunkt) ca. 1V betragen (je nach Belastungswiderstand kann es auch etwas mehr sein!)

Als Referenzspannung verwendet man die interne 2,56V-Reverenz. Um das Kopieren des Ergebnisses aus dem 16bit ADC-Register etwas zu beschleunigen, und um Speicherplatz zu sparen, wird das Ergebniss im ADC-Register linksbündig dargestellt und nur das ADCH-Register gelesen.


Ein kleiner Überblick:

Hier werden 254 Werte eingelesen und zum Terminal gesendet:
https://www.roboternetz.de/community/threads/47457-Hardware-Mitmach-Projekt-Kamera-f%C3%BCr-den-RP6?p=457514&viewfull=1#post457514

Für ADC0 muss hier der Kanal geändert werden:
// ADC interne Referenz 2,56V, Ergebniss linksbündig, Kanal ADC4 (E_INT1)
ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<ADLAR) | 4; // 0 für ADC0

Hier sieht man ein Sync (55-57), die Schwarzschultern (52-54 und 58-64) und ab Zeile 65 beginnt dann die Bildzeile:

51: 54 30 51 54
52: 15 30 31 35
53: 15 30 31 35
54: 15 30 31 35
55: 4 6 6 6
56: 4 6 6 6
57: 4 6 6 6
58: 28 28 28 28
59: 28 28 28 28
60: 28 28 28 28
61: 28 28 28 28
62: 28 28 28 28
63: 28 28 28 28
64: 28 56 28 28
65: 56 56 56 56
66: 56 56 56 56
67: 56 56 56 56
68: 56 57 56 56
69: 56 57 56 56
70: 56 57 56 56
71: 57 60 60 60
( Aus https://www.roboternetz.de/community/threads/47457-Hardware-Mitmach-Projekt-Kamera-f%C3%BCr-den-RP6?p=457514&viewfull=1#post457514)

Die Syncwerte muss man eventuell an die Kamera anpassen:

do{sync=0;while (ADCH > 20);while (ADCH < 30) sync++;}while (sync < 40);(ADCH > 20 bedeutet Bildzeile, ADCH < 30 ist ein Sync und mehr als 40 Syncs am Stück bedeutet der Start eines neuens Bildes)


Das hier ist die letzte Variante des Programms. Es wird gewartet bis die jeweilige Wandlung beendet ist:
https://www.roboternetz.de/community/threads/47457-Hardware-Mitmach-Projekt-Kamera-f%C3%BCr-den-RP6?p=463226&viewfull=1#post463226

Der aktuelle Stand der Entwicklung ist die Verwendung des analogen Komperators zur Erkennung der Syncs:
https://www.roboternetz.de/community/threads/29906-Minimall%C3%B6sung-Kamera-f%C3%BCr-den-RP6?p=519622&viewfull=1#post519622
(Leider beim RP6 nicht so einfach umsetzbar)

Gruß

mic

BeWe
05.02.2013, 12:10
Vielen Dank für die Antwort!

Mich würde nur erst einmal interessieren, ob überhaupt etwas aus der Kamera herauskommt. Die Verpackung sah schon benutzt aus, darum vermute ich, dass sie schon einmal verwendet wurde. Könntest du vielleicht die 2 Zeilen posten, die benötigt werden, um E_INT als Eingang zu nutzen, damit ich die Werte im Terminal anzeigen kann? Das würde mir erstmal bedeutend weiter helfen.

radbruch
05.02.2013, 12:49
Hallo

Der E_INT kann beim RP6 als Interupteingang für I2C genutzt werden und ist in der Library schon integriert. Diese Funktion kann man ausschalten, dabei wird der E_INT zu einem normalen Eingang:

extIntOFF();

So mache ich es auch in meinem Setup:


void ADC_Init(void)
{
extIntOFF(); // schaltet den E_INT1-Port auf Eingang für den ADC

// ADC interne Referenz 2,56V, Ergebniss linksbündig, Kanal ADC4 (E_INT1)
ADMUX = (1<<REFS1) | (1<<REFS0) | (1<<ADLAR) | 4;
// setze free running triggern
SFIOR = (0<<ADTS2) | (0<<ADTS1) | (0<<ADTS0);
// kein Interupt, Wandler einschalten, prescaler /2, ADC läuft nun mit 4MHz!
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 empfohlene Initiallesung
while (!(ADCSRA & (1<<ADIF)));
ADCSRA |= (1<<ADIF);
}
(Aus https://www.roboternetz.de/community/threads/47457-Hardware-Mitmach-Projekt-Kamera-f%C3%BCr-den-RP6?p=457514&viewfull=1#post457514)

Nach dem Initialisieren und dem Init des ADC steht dann immer der aktuelle Wert im ADCH-Register:


initRobotBase();
ADC_Init();

while(1)
{
writeInteger(ADCH, 10);
writeString_P("\n");
}

Gruß

mic

BeWe
05.02.2013, 14:24
Etwas dergleichen habe ich bereits probiert. Nun habe ich noch einmal die beiden Codes von dir als Programm ausgeführt. Wie schon vorher werden ununterbrochen und scheinbar zufällig die Zahlen 1 und 3 ausgegeben. Dabei würde es sich ja um äußert niedrige Spannungen handeln. Interessanterweise ist es dabei egal, ob ich das Signalkabel der Kamera am E_INT angeschlossen habe oder nicht. Hast du eine Idee was ich falsch machen könnte?

radbruch
05.02.2013, 16:58
Hallo

Möglicherweise ist doch die Kamera defekt. Richtigen E_INT-Pin verwendet? E_INT (INT1) ist über einen 10k-Widerstand (R34) mit GND verbunden, deshalb wird der Pin mit 0V gemessen, wenn nichts angschlossen ist. GND der Kamera ist mit GND vom RP6 verbunden? Was wird gemessen, wenn du eine 1,5V-Batterie zwischen GND und E_INT klemmst (bei 2,56V-Referenz bitte nicht mehr als 2,56V anschliessen!) Andere Signalquelle versuchen (TV, VCR, DVD, Playstation...).


Ein einfaches Programm ließt die ADC-Werte und schreibt sie in den Terminal (Light-Detection Beispielprogramm auf andere ADC-Kanäle abgeändert). Dabei erhalte ich Werte von 350...Was wird gemessen wenn du den ADC= mit Vdd oder GND verbindest? Zeig mal bitte dein Programm.

Gruß

mic

BeWe
05.02.2013, 18:59
Guten Abend,

verbinde ich eine Batterie mit + an E_INT und - an GND, so messe ich mit dem Programm Spannungen von 140, was ja völlig richtig ist. Daher dürfte das Programm auch korrekt laufen. Posten kann ich es zwar nicht, da der Laptop auf dem ich programmiere ab und zu nicht anzuschalten ist. Es handelt sich im großen und ganzen um das was du selbst gepostet hast.

Gruß

radbruch
05.02.2013, 20:11
Tja, 140 bedeutet bei der linksbündigen Auswertung von ADCH ca. 1,4V, das liegt in plausiblen Bereich. Entweder ist die Kamera wirklich kaputt oder falsch angeschlossen oder die Spannungsversorgung mit dem Vorwiderstand (wie groß ist der denn?) funktioniert nicht wie erwartet.

Kannst du als Gegenprobe die Kamera an einem Bildschirm anschließen?

BeWe
05.02.2013, 22:02
Als Vorwiderstand verwende ich 100 Ohm. Ich erhalte auch keine Spannungen, wenn ich mit einem Multimeter nachmesse. Ein Kabelbruch ist auszuschließen, da ich die 'Messsonde' auch direkt an die Lötstelle an der Platine halten kann und nichts passiert. Ich werde das gute Stück morgen einfach wieder abgeben, ist leider nicht das erste mal dass ich etwas defektes einkaufe.

Bei Conrad finde ich ansonsten nur eine CMOS-Kamera für 30€. Du meintest, dass selbst die andere sehr teuer sei. Welche würdest du stattdessen empfehlen?

Ansonsten dürfte morgen ein Arduino Leonardo eintreffen. Dieser könnte dann gegebenfalls mit seinen 16MHz die Kameraauswertung übernehmen und den RP6 entlasten.

radbruch
05.02.2013, 22:21
Hallo

Leider kann ich keine andere Kamera empfehlen. Teuer ist relativ. Wenn man die Möglichkeiten einer Kamera in Betracht zieht ist der Preis durchaus vertretbar, auch wenn es bisher noch wenige geschafft haben das Teil sinnvoll einzusetzen. Spannend wäre zu überprüfen ob deine Kamera wirklich defekt ist und ob es dann mit dem Ersatz funktioniert.

Die Belastung für den RP6 hält sich ja in Grenzen, die Engstelle ist der geringe Speicher (Ram) beim Mega32. Mit dem Arduino solltest du aber gleich bei der Variante mit dem analogen Komperator als Syncerkennung beginnen. Dazu gibt es allerdings noch keine Umsetzung in C oder als Sketch. Wenn du also eh beim C vorbeischaust kannst du dir gleich noch ein Poti zur Einstellung des Triggerlevels besorgen (10K?).

Gruß

mic

P.S.:
Das ist ja spannend. Auf dem Arduino Leonardo werkelt ein 16MHz ATMega32u4. Zufällig habe ich den hier leicht verstaubt rumliegen:
https://www.roboternetz.de/community/threads/25435-Erfolg-der-Woche?p=539603&viewfull=1#post539603

BeWe
05.02.2013, 23:46
http://www.conrad.de/ce/de/product/190686/Conrad-Platinenkamera-36-mm-13-AI-CCD-SW-Kamera-Aufloesung-320-589-Pixel-Aufloesung-optisch-537-x-597-Pixels

Würde dieses Gerät die Zwecke erfüllen? In Leipzig ist die Kamera zurzeit auch auf Lager. Mich schreckt nur die hohe Auflösung ab, die sicher sehr ressourcenfordernd ist.

radbruch
06.02.2013, 07:34
Hallo

Die Auflösung der Kamera ist kein Problem, wir lesen ja wegen des geringen Speichers des Mega32 eh nur einzelne Pixel oder Zeilen ein. Aber 12V und 90mA erscheinen mir ungünstig, auch wenn die Kamera selbst vermutlich mit einer geringeren Spannung läuft.

Leider ist deine jetzige Kamera in deiner Finiale nicht verfügbar und erst im Mai wieder lieferbar. (In Stuttgart liegen wohl noch 4 Stück rum, vielleicht können sich die Finialen untereinander austauschen...).

Gut dazu passen würde auch dieser Spannungsregler:
http://www.conrad.de/ce/de/product/147109/

Bei minimaler Eingangsspannung von 4,75V liefert der sogar zwischen 2,45V und 2,75V bei bis zu 70mA. 0,33µF und 0,1µF-Kondensatoren nicht vergessen.

Eine Alternative wäre z.B. http://www.ebay.de/itm/mini-Farb-Spionkamera-Uberwachungskamera-Kamera-Infrarot-mit-Audio-Uberwachung-/230916036819

Oder schau mal bei Pollin vorbei: http://www.pollin.de/shop/p/ODk2OTE5/Haustechnik/Sicherheitstechnik/Kameras.html

Ich hatte meine erste Kamera auch aus einer solchen "Überwachungskamera" ausgeschlachtet.

Gruß

mic

P.S.: Wenn man etwas sucht wird man durchaus findig: http://www.sander-electronic.de/gm00009.html

BeWe
06.02.2013, 12:30
Nunja, ich habe mich aber blöde angestellt...

Kurz bevor ich zu Conrad aufbrechen wollte, habe ich dann doch noch einmal die Kamera ausgepackt. Diesmal habe ich sie einfach an eine 1.5V Batterie angeschlossen, und siehe da, ich messe Spannungen von 0,7-1,3 Volt, je nachdem, ob ich die Linse zuhalte oder nicht.

Wenn ich die Kamera mit Plus- und Minuspol einer Batterie verbinde, den Minuspol der Batterie mit GND am XBUS und das Signalkabel an E_INT, dann erhalte ich auch im Terminal vernünftige Werte. Regelt der RP6 die VDD-Spannung vielleicht, damit 5 Volt eingehalten werden? Schließe ich nämlich ein Voltmeter zwischen VDD und GND sowie 2 100 Ohm-Widerständen an, so steigt die Spannung bis 12 Volt an. Die Batteriespannung +UB klettert sogar bis 20 Volt hoch. Der vermutlich vorhandene Schutz in der Kamera würde dann ja die Stromversorgung unterbrechen um die Kamera vor Überspannung zu schützen. Damit kommt letzlich kein Signal am ADC an.

Da ich jetzt vernünftige Werte erhalte kann ich dann auch mit der Linienerkennung loslegen. Ein Glück dass ich das gute Stück vor der Reklamation noch einmal überprüft habe :D

Gruß

BeWe
06.02.2013, 20:20
Guten Abend,

nun habe ich dein Programm, welches 254 Werte in den Terminal schreib, ausgeführt. Momentan ist noch kein Widerstand, der das Signal belastet, verbaut. (Ist dieser wirklich notwendig?) Dabei komme ich auf folgende Ergebnisse:



---------------------------
0: 48 48 48 48
1: 48 48 48 48
2: 48 48 48 48
3: 48 48 99 54
4: 48 48 99 54
5: 48 48 99 54
6: 48 48 99 54
7: 48 116 99 54
8: 118 116 124 120
9: 118 116 124 120
10: 118 116 124 120
11: 118 116 124 120
12: 126 126 127 128
13: 126 126 127 128
14: 126 126 127 128
15: 126 126 127 128
16: 128 128 128 129
17: 128 128 128 129
18: 128 128 128 129
19: 128 128 128 129
20: 128 129 128 129
21: 129 129 129 131
22: 129 129 129 131
23: 129 129 129 131
24: 129 129 129 131
25: 129 128 131 134
26: 129 128 131 134
27: 129 128 131 134
28: 129 128 131 134
29: 134 131 135 135
30: 134 131 135 135
31: 134 131 135 135
32: 134 131 135 135
33: 134 135 135 135
34: 135 135 135 135
35: 135 135 135 135
36: 135 135 135 135
37: 135 135 135 135
38: 135 135 135 135
39: 135 135 135 135
40: 135 135 135 135
41: 135 135 135 135
42: 135 135 135 135
43: 135 135 135 135
44: 135 135 135 135
45: 135 135 135 135
46: 135 134 135 135
47: 135 134 131 131
48: 135 134 131 131
49: 135 134 131 131
50: 135 134 131 131
51: 131 131 129 131
52: 131 131 129 131
53: 131 131 129 131
54: 131 131 129 131
55: 128 128 120 126
56: 128 128 120 126
57: 128 128 120 126
58: 128 128 120 126
59: 128 112 120 126
60: 113 112 112 112
61: 113 112 112 112
62: 113 112 112 112
63: 113 112 112 112
64: 112 112 110 110
65: 112 112 110 110
66: 112 112 110 110
67: 112 112 110 110
68: 108 110 104 103
69: 108 110 104 103
70: 108 110 104 103
71: 108 110 104 103
72: 108 103 104 103
73: 103 103 81 83
74: 103 103 81 83
75: 103 103 81 83
76: 103 103 81 83
77: 51 51 17 17
78: 51 51 17 17
79: 51 51 17 17
80: 51 51 17 17
81: 14 14 41 24
82: 14 14 41 24
83: 14 14 41 24
84: 14 14 41 24
85: 14 48 41 24
86: 48 48 48 48
87: 48 48 48 48
88: 48 48 48 48
89: 48 48 48 48
90: 48 48 118 118
91: 48 48 118 118
92: 48 48 118 118
93: 48 48 118 118
94: 118 120 126 124
95: 118 120 126 124
96: 118 120 126 124
97: 118 120 126 124
98: 118 126 126 124
99: 127 126 127 128
100: 127 126 127 128
101: 127 126 127 128
102: 127 126 127 128
103: 128 128 129 129
104: 128 128 129 129
105: 128 128 129 129
106: 128 128 129 129
107: 129 129 129 129
108: 129 129 129 129
109: 129 129 129 129
110: 129 129 129 129
111: 129 131 129 129
112: 131 131 132 135
113: 131 131 132 135
114: 131 131 132 135
115: 131 131 132 135
116: 135 135 135 135
117: 135 135 135 135
118: 135 135 135 135
119: 135 135 135 135
120: 135 135 135 135
121: 135 135 135 135
122: 135 135 135 135
123: 135 135 135 135
124: 135 135 135 135
125: 135 135 135 135
126: 135 135 135 135
127: 135 135 135 135
128: 135 135 135 135
129: 135 135 135 135
130: 135 135 135 135
131: 135 135 135 135
132: 135 135 135 135
133: 134 134 131 131
134: 134 134 131 131
135: 134 134 131 131
136: 134 134 131 131
137: 134 134 131 131
138: 131 134 128 129
139: 131 134 128 129
140: 131 134 128 129
141: 131 134 128 129
142: 128 128 113 115
143: 128 128 113 115
144: 128 128 113 115
145: 128 128 113 115
146: 112 112 112 112
147: 112 112 112 112
148: 112 112 112 112
149: 112 112 112 112
150: 112 112 112 112
151: 110 112 108 110
152: 110 112 108 110
153: 110 112 108 110
154: 110 112 108 110
155: 108 108 105 105
156: 108 108 105 105
157: 108 108 105 105
158: 108 108 105 105
159: 92 96 51 51
160: 92 96 51 51
161: 92 96 51 51
162: 92 96 51 51
163: 92 48 51 51
164: 38 48 14 14
165: 38 48 14 14
166: 38 48 14 14
167: 38 48 14 14
168: 9 12 48 48
169: 9 12 48 48
170: 9 12 48 48
171: 9 12 48 48
172: 48 48 48 48
173: 48 48 48 48
174: 48 48 48 48
175: 48 48 48 48
176: 48 48 48 48
177: 48 48 118 118
178: 48 48 118 118
179: 48 48 118 118
180: 48 48 118 118
181: 120 118 126 126
182: 120 118 126 126
183: 120 118 126 126
184: 120 118 126 126
185: 127 127 128 128
186: 127 127 128 128
187: 127 127 128 128
188: 127 127 128 128
189: 127 127 128 128
190: 128 127 129 129
191: 128 127 129 129
192: 128 127 129 129
193: 128 127 129 129
194: 129 129 131 131
195: 129 129 131 131
196: 129 129 131 131
197: 129 129 131 131
198: 131 131 135 135
199: 131 131 135 135
200: 131 131 135 135
201: 131 131 135 135
202: 131 135 135 135
203: 135 135 135 135
204: 135 135 135 135
205: 135 135 135 135
206: 135 135 135 135
207: 135 135 135 135
208: 135 135 135 135
209: 135 135 135 135
210: 135 135 135 135
211: 135 135 135 135
212: 135 135 135 135
213: 135 135 135 135
214: 135 135 135 135
215: 135 135 135 135
216: 134 135 135 135
217: 134 135 135 135
218: 134 135 135 135
219: 134 135 135 135
220: 134 134 131 131
221: 134 134 131 131
222: 134 134 131 131
223: 134 134 131 131
224: 129 129 128 128
225: 129 129 128 128
226: 129 129 128 128
227: 129 129 128 128
228: 129 127 128 128
229: 127 127 113 113
230: 127 127 113 113
231: 127 127 113 113
232: 127 127 113 113
233: 112 112 112 112
234: 112 112 112 112
235: 112 112 112 112
236: 112 112 112 112
237: 108 112 108 110
238: 108 112 108 110
239: 108 112 108 110
240: 108 112 108 110
241: 108 103 108 110
242: 105 103 96 96
243: 105 103 96 96
244: 105 103 96 96
245: 105 103 96 96
246: 88 88 49 49
247: 88 88 49 49
248: 88 88 49 49
249: 88 88 49 49
250: 22 24 14 12
251: 22 24 14 12
252: 22 24 14 12
253: 22 24 14 12
254: 22 7 14 12

Nun würde mich noch interessieren, wie ich zwischen dem Sync-Signal für eine neue Zeile und dem für ein neues Bild unterscheide. Da ich erst einmal nur Linien verfolgen möchte, habe ich die Kamera um 90° gedreht. Es würde somit ja ausreichen, nur den ersten Bildpunkt aus einer Zeile zu speichern. Somit müsste ich lediglich das neue Bild abwarten und immer nach dem Sync-Signal den nächsten ADC-Wert speichern sowie einen Zähler hochzählen, der mir verrät, in welcher Linie ich aktuell bin. Im Moment weiß ich aber noch nicht, ob ich bei den niedrigen ADC-Werten ein neues Bild oder eine neue Zeile erwarten soll.

Gruß

radbruch
06.02.2013, 20:59
Hallo

Das sieht ja schon recht brauchbar aus. Der Widerstand sollte schon drin sein, weil sonst die Werte nicht stabil sind. Bei einem zu großen oder fehlendem Widerstand sind die Werte zu groß, deshalb stimmen deine Sync und Schwarzschulterwerte nicht.

Das Programm in der orginalen Version liest viermal die Daten ab dem Start der 150ten Zeile in einen 1KB großen Speicherbereich:


for(c=0; c<4; c++) // viermal den selben Bereich einlesen
{
bildzeiger=&bildspeicher[256*c]; // Zeiger auf Start des Bildspeicherbereich
cli();
do // Warten auf langen Syncbereich = Bildstart
{
sync=0;
while (ADCH > 20); // warten solange Bilddaten erkannt werden
while (ADCH < 30) sync++; // Länge des Sync-Signal zählen
}while (sync < 40); // größer 40 bedeutet Bildstart

zeile=150; // durchhangeln bis Zeile 150
while(zeile--)
{
while (ADCH > 20); // Bilddaten
while (ADCH < 30); // Sync
}

spalte=255; // oje
do *bildzeiger++=ADCH; while(spalte--); // 254 Werte einlesen
//while(spalte--) *bildzeiger++=ADCH; // dito
//do *bildzeiger=ADCH; while(*bildzeiger++ > 20); // schneller ;)
sei();
}
Werte kleiner 30 werden als Sync gedeutet, Werte größer als 20 als Bilddaten. Wenn 40 mal (oder öfter) hintereinander ein Sync erkannt wurde sollte das der Bildstart sein. Ab hier werden dann 150 Bilddaten/Sync gezählt und schließlich 254 Werte hintereinander eingelesen und gespeichert.

Der Syncbereich beim Bildstart ist deutlich länger als der zwischen den einzelnen Zeilen, deshalb kann man daran den Bildstart erkennen. Die Halbbilder kann man so aber nicht unterscheiden. Brauchbare Zeilendaten kommen erst ab ca. der 10. Zeile. Wenn du mehrfach ohne irgendwelche Startbedingungen die Werte als Block einliest wirst du irgendwann auch den Bildwechseln erwischen...

Gruß

mic

Noch eine kleine Analyse deiner Daten:

0-7 ist noch der Rest des Syncs der 150ten Bildzeile, genauer eine Schwarzschulter die ca. 0,3V sein sollte (fehlender Widerstand).
8-76 Bilddaten der 150. Bildzeile
77-93 Schwarzschultern
81-85 Sync
94-158 Bilddaten der 151ten Bildzeile
159-180 Schwarzschulter
168-171 Sync
181-241 Bilddaten Zeile 152
usw.


Bei den Bilddaten sind einige Werte doppelt, weil diese Programmversion die Daten aus dem ADCH schneller rauskopiert als der ADC neue Daten bereitstellt. (bisher habe ich noch nicht gefunden, wie man mit dem ADC weniger als 10 Bits sampelt).

Eine weiße Fläche sollte Werte bei ca. 120 liefern (automatischer Weisabgleich?) und eine schwarze Fläche (oder Objektiv zugehalten) Werte von ca. 35.

Die Kamera würde ich zum Linienfolgen nicht drehen, denn wenn du nur eine Bildzeile einliest wird eine senkrechte Linie in den Bilddaten der eingelesenen Zeile erscheinen. Einlesezeit wäre dann Warten_auf_Bildstart+Warten_auf_Zeile+Einlesen der Zeile. Warten auf Bildstart dauert maximal ein Halbbild, Warten auf Zielzeile mit Einlesen nur einen Bruchteil des Halbbildes, wenn du eine obere Zeile verwendest. Die Werte einer Zeile machen dann einen deutlichen Sprung, wenn die Linie getroffen wird. Wenn du oben, mitte und unten jeweils eine Zeile einliest kannst du beurteilen, ob die Linie quer durchs Bild läuft.

Bei gedrehter Kamera mußt du immer alle Zeilen abwarten und erkennen, wann die Schwarzschulter endet und die Bilddaten beginnen.

BeWe
06.02.2013, 22:57
Vielen Dank für die umfangreiche Antwort!

Noch einmal eine Verständnissfrage: Eine Zeile verläuft horizontal durch das Bild. Würde ich jetzt eine Linie suchen, so müsste ich auf mindestens einer Zeile mehrere Pixel abfragen, um die Stelle zu finden, an der der Schwellwert über-/unterschritten wird. Drehe ich die Kamera hingegen auf die Seite, so reicht es, nur den Anfang jeder neuen Zeile einzulesen und dann auf das Sync-Signal zur nächsten Zeile zu warten, um dort wieder den ersten Bildpunkt abzufragen. Dadurch hat der ADC wesentlich mehr Zeit, da ich pro Zeile nur eine Spalte abfrage. Du meintest, dass ich nur eine Bildzeile pro Bild einlesen will. Habe ich da etwas falsch verstanden..?

radbruch
07.02.2013, 09:13
Hallo


Vielen Dank für die umfangreiche Antwort!Bitte, immer gerne.

Die Kamera ist ja mein Liebling aus einer Zeit als ich noch Einsteiger in Sachen C, AVR und Kamera war. Vieles aus der Anfangszeit war falsch oder umständlich und ist nach und nach gewachsen.

Der erste Ansatz war soviele Werte so schnell wie möglich einzulesen. Das ist die Variante die du im Moment verwendest. Dabei wurde nicht beachtet, dass der ADC mit der Wandlung eines neuen Wertes noch nicht fertig war und deshalb einige Werte doppelt eingelesen wurden. Der aktuelle Stand ist, dass man beim Einlesen auf die Meldungen des ADC wartet und den Wert erst einliest, wenn die Wandlung beendet wurde. Dadurch wird alles sehr entspannt und es funktioniert sogar ohne dass man beim Einlesen die Interrupts sperren muss.

Der ADC (mit Takt-Prescaler /2) braucht im Dauerlauf (Freerunning) 13 ADC-Takte zur Wandlung eines Wertes, das Programm muss deshalb innerhalb von 26 Kontrollertakten das Flag des ADC prüfen und bei fertiger Wandlung den Wert aus dem ADCH-Register verarbeiten. Das reicht locker um ein Sync zu erkennen oder Bilddaten abzuspeichern. Bei 8MHz-Takt des AVR dauert eine Wandlung 1/4 MHz*13 Takte = 0,00000325 Sekunden bzw. 3,25µs. Wenn ein Zeilensync ca. 4,7µs dauert sollten wir diesen zweimal hintereinander erkennen:

http://upload.wikimedia.org/wikipedia/commons/f/fb/BAS_Zeilensignal_unmoduliert.PNG
(Bild aus http://de.wikipedia.org/wiki/Fernsehsignal#Horizontale_Synchronisation)

Und wie groß ist nun die Auflösung? Deine Kamera hat eine Auflösung von 352H x 288V, kann also 288 Zeilen mit je 352 unterschiedlichen Helligkeitswerten ausgeben. Da wir sicher zwischen Sync und Bilddaten unterscheiden können, und den Zeilenstart deshalb genau erkennen können, ist es möglich gezielt auf jede einzelne Zeile zuzugreifen. Damit ist die mögliche vertikale (von oben nach unten) Auflösung so gut wie die der Kamera selbst. Innerhalb einer Zeile können wir die Werte nur in einem festen Abstand (3,25µs) zueinander einlesen. Deshalb sind mit dieser Technik nur ca. 52µs/3,25µs=16 Pixel Auflösung möglich (In der Praxis etwas weniger). Mit einem kleinen Trick läßt sich das aber steigern: Wenn am Zeilenende der Sync (bzw. die Schwarzschulter) erkannt wird, wird der ADC gestoppt und wieder neu gestartet. Es dauert dann zwar wieder 26 ADC-Takte bis der nächste gültige Wert gewandelt wird, aber das ist immer noch im Sync-Bereich und noch vor den Bilddaten. Die nächsten Werte werden dann wieder im Dauerlauf mit 3,25µs Abstand gewandelt. Und nun der Trick: Vor dem erneuten Starten ADC wird kurz gewartet und so ein kleiner Versatz zwischen den eingelesenen Pixeln erzeugt. So wird die maximale horizontale Auflösung durch die kleinste mögliche Verzögerungszeit bzw. den Takt des AVR begrenzt. Ein Beispiel mit einer Verzögerung von zwei Takten (nop's):

Grundlagen zur Auflösung:
https://www.roboternetz.de/community/threads/47457-Hardware-Mitmach-Projekt-Kamera-f%C3%BCr-den-RP6?p=457599&viewfull=1#post457599
https://www.roboternetz.de/community/threads/47457-Hardware-Mitmach-Projekt-Kamera-f%C3%BCr-den-RP6?p=457945&viewfull=1#post457945
ADC-Flag beachten:
https://www.roboternetz.de/community/threads/47457-Hardware-Mitmach-Projekt-Kamera-f%C3%BCr-den-RP6?p=458253&viewfull=1#post458253
Analoger Komperator:
https://www.roboternetz.de/community/threads/29906-Minimall%C3%B6sung-Kamera-f%C3%BCr-den-RP6?p=520992&viewfull=1#post520992

Aber ich schweife ab...

Wieviel Auflösung braucht man zum Linienfolgen? Zum sicheren Erkennen müssen mindestens zwei gemessene Punkte auf der Linie liegen (Nyquist-Shannon-Abtasttheorem (http://de.wikipedia.org/wiki/Nyquist-Shannon-Abtasttheorem)?). Deshalb ist die benötigte Auflösung von der Linienbreite und dem Bildausschnitt bzw. dem Abstand der Kamera von der Linie abhängig. Hier muss man das Optimum aus Auflösung, Abtastrate, Datenmenge und Reglergeschwindigkeit finden...

Danke fürs Lesen. Das Thema Speicherplatz und Farbtiefe möchte ich euch hier ersparen...

Gruß

mic

BeWe
07.02.2013, 10:47
Hallo,

das mit der höheren Bilddichte durch abwechselnd verschiedene Verzögerungen mit dem Beginn des ADC-Auslesens an einer neuen Zeile habe ich soweit verstanden. Aber dennoch reicht es zum simplen Linienfolgen doch aus, nur einen Punkt auf einer Linie zu erkennen. Ich habe es mal dargestellt, da ich es anscheinend nicht so gut beschreiben konnte:

24450

Dabei würde der Microcontroller kaum belastet werden. Man liest einfach den Wert nach jedem Sync-Impuls aus bzw. erst mit einem gewissen zeitlichen Abstand. Der dunkelste der pro Frame gefundenen Punkte wird sicherlich die schwarze Linie sein.

021aet04
07.02.2013, 11:10
@radbruch
Bedeutet Auflösung von 352H x 288V nicht 352 Pixel pro Zeile (Horizontal) und 288 Zeilen (Vertikal), das wären ca. 100k Pixel?


MfG Hannes

radbruch
07.02.2013, 11:47
Hallo


Der dunkelste der pro Frame gefundenen Punkte wird sicherlich die schwarze Linie sein.Ja, davon kann man ausgehen. Ob man die Kamera dreht ist für die Belastung des AVR allerdings eher unbedeutend. Bei der Auswertung nur einer Zeile ist der Code etwas schlanker. Bei gedrehter Kamera braucht man auch nicht alle Zeilen auszuwerten, es genügt nur jede x-te Zeile zu betrachten. Ich denke, den Ansatz hast du verstanden, wie du das jetzt umsetzt bleibt dir überlassen. Ein paar neue Ideen könnten der Kamera vielleicht doch noch zum Durchbruch verhelfen. Nach wie vor halte ich sie für einen genial vielfältigen und extrem günstigen Sensor.

Und ja, es sind 100K Pixel, aber wir betrachen und bewerten nur ein paar einzelne davon. Mehr brauchen wir nicht und mehr kann der AVR auch nicht speichern.

Gruß

mic

021aet04
07.02.2013, 11:52
@radbruch
Du hast oben geschrieben 352H bedeutet 352 Helligkeitsstufen, es sind aber 352 Pixel/Zeile

MfG Hannes

radbruch
07.02.2013, 12:17
Da habe ich mich etwas ungenau ausgedrückt:


... 288 Zeilen mit je 352 unterschiedlichen Helligkeitswerten ...Sollte bedeuten, in jeder Zeile werden 352 Punkte mit jeweils einem eigenen Helligkeitswert ausgegeben.

Vermutlich ist das die Auflösung des CCD-Chips in der Kamera. Echte Pixel innerhalb einer Zeile gibt es wohl nicht, denn eine Zeile besteht nur aus einer schwankenden Spannung. Nur das Timing bestimmt, wo ein Pixel anfängt oder endet.

Um es mal bildhaft zu beschreiben: Wir machen im 3,25µs-Abstand nebeneinander 16 kleine Löcher in ein Blatt Papier und halten das Papier vor das TV-Bild.

Es gibt noch viel zu erforschen...

Gruß

mic

Bostrot
24.07.2013, 01:28
Das Bild der Kamera kann ich doch nur einsehen, wenn sie mit dem Computer verbunden ist, oder nicht? Ich kenn mich da nicht so gut aus. Aber toll, was du da gemacht hast

Croal
24.07.2013, 16:44
@Bostrot das ist noch besser du brauchst das bild garnicht einzusehen das macht der microcontroller und reagiert darauf.

Bostrot
24.07.2013, 22:55
@Croal Cool, danke.
Wie kann ich denn die Anschlüsse meiner Kamera erkennen? Da sind fünf. Weiß, Rot, Grün, Schwarz, Schwarz? WAR mal eine Logitech Kamera.

Croal
24.07.2013, 23:17
Das kann ich dir leider nicht sagen. Ich habe vor mit eine Game Boy Camera was zu machen, da gibt ein paar infos dazu im net. Ich bin aber auch nicht so der crack auf dem Gebiet und muss mich intensiv einlesen. Auch den Thread hier nochmal von vorne durchlesen, der Radbruch hat gute pionierarbeit geleistet und sind gute ansätze dabei. Ich muss nur das ganze irgendwie auf dem Conrad Basic dialekt verwirklichen. Weiss net ob ich mir zu viel vorgenommen habe. Mit deine Logitech, was hast du damit eigendlich vor? Soll die am Microcontroller dran?