PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : counter Problem (SRF05 Ultraschallsensor C code)



Maxtronik
13.04.2012, 15:03
Hallo,

Ich habe vor den srf05 ultraschall sensor an meinen Asuro zu bauen. Das hat mit Bascom auch schon wunderbar funktioniert aber mit c will es nicht so wie ich es möchte.



#include <stdlib.h>
#include <stdio.h>
#include <avr/io.h>
#include <inttypes.h>
#include <util/delay.h>
#include "asuro.h"

int main(void)
{

Init();

uint16_t ergebnis;
char buffer [20];
ergebnis = 0;


while (1)
{

DDRC |= (1<<PC3); // PC3 Ausgang
PORTC |= (1<<PC3); // PC3 High
_delay_us(20); // warte 20 µSekunden
PORTC &= ~(1<<PC3); // PC3 Low
DDRC &= ~(1<<PC3); // PC3 Eingang
while(!(PINC & (1<<PC3))); // tue solange PC3 nicht 1 ist nichts
TCNT0 = 0; // setze Timer0 auf 0
TCCR0 = (1<<CS01); // starte timer Prescaler 8
while((PINC & (1<<PC3))); // tue solange PC3 auf 1 ist nichts
TCCR0 = (0<<CS01); // Stoppe timer

ergebnis = TCNT0; //setze Timerergebnis in Variable ergebnis

SerPrint("\n\r Ergebnis: \n\r");
itoa(ergebnis, buffer, 10); //übersetze (int)ergebnis in ASCII
SerPrint(buffer); //übertragung des Ergebnisses über RS232
_delay_ms(500); //warte 500ms
}
}


Der Timer müsste nach meinem Verständnis von 0 bis 256 zählen wenn das echo signal startet und wenn das Echo signal aufhört gibt der Timer seinen Wert an die Variable 'ergebnis'. Allerdings weiß ich das der timer wenn er die 256 erreicht hat wieder von 0 beginnt. Das ist wahrscheinlich auch der Grund warum ich wirre Werte von 0 bis 256 im Hyperterminal erhalte. Denn das Echo Signal kann ja länger anhalten als ein ?Overflow? des Timers. Ich hoffe mir kann jemand erklären wie ich feststellen kann wie häufig ein Overflow statt gefunden hat.

P.s.: Ich habe schon die Suche benutzt und auch code gefunden, aber jeder dieser codes funktionierte mit dem Timer im normalen Modus.

Ich hoffe ich habe jetzt nicht zu viel schwachsinn geschrieben:eek:

MfG Maxtronik

MagicWSmoke
14.04.2012, 10:19
Der Timer müsste nach meinem Verständnis von 0 bis 256 zählen
8Bit geht von 0..255
Das hat sich aber in Bascom nicht anders verhalten, Du musst es dort doch auch gelöst haben, wenn's funktionieren sollte.

Denn das Echo Signal kann ja länger anhalten als ein ?Overflow? des Timers.
Möglich, Du machst ja keine Angaben zur Taktfrequenz, aber bei einem Takt von 8MHz käme der Schall 8,5cm weit bis zum Overflow.

Ich hoffe mir kann jemand erklären wie ich feststellen kann wie häufig ein Overflow statt gefunden hat.
Indem Du eine Overflow ISR einbaust und dort eine Variable erhöhst. Pollen des Timeroverflowflags im letzten "while" würde auch gehen.

mit dem Timer im normalen Modus.
Was ist "normal" in diesem Zusammenhang, bzw. warum sollte der Timerbetrieb hier un-normal sein ?

Maxtronik
14.04.2012, 14:08
Danke erstmal,


Pollen des Timeroverflowflags im letzten "while" würde auch gehen.
Ich hoffe ich habe deinen Tipp richtig verstanden und umgesetzt. Der Code funktioniert so weit, allerdings bin ich mit der geringen Auflösung nicht ganz zufrieden.



#include <stdlib.h>
#include <stdio.h>
#include <avr/io.h>
#include <inttypes.h>
#include <util/delay.h>
#include "asuro.h"

int main(void)
{

Init();

uint16_t ergebnis;
char buffer [20];
ergebnis = 0;


while (1)
{

DDRC |= (1<<PC3); // PC3 Ausgang
PORTC |= (1<<PC3); // PC3 High
_delay_us(20); // warte 20 µSekunden
PORTC &= ~(1<<PC3); // PC3 Low
DDRC &= ~(1<<PC3); // PC3 Eingang
while(!(PINC & (1<<PC3))); // tue solange PC3 nicht 1 ist nichts
TCCR1B = (1<<CS11); // starte timer Prescaler 8 (Quarz 8Mhz)
TCNT1 = 0; // setze Timer0 auf 0
while((PINC & (1<<PC3)))
{
if (TIFR & (1<<TOV1))
{
ergebnis = ergebnis + 1;
TIFR = (1<<TOV1);
}
}
TCCR1B &= ~(1<<CS11); // Stoppe timer

itoa(ergebnis, buffer, 10); //übersetze (int)ergebnis in ASCII
ergebnis = 0;
SerPrint("\n\r Wen Ergebnis: \n\r");
SerPrint(buffer); //übertragung des Ergebnisses über RS232
_delay_ms(500); //warte 500ms
}
}


Ich habe mich auch im Wiki über Overflow ISR schlau gemacht. Das bedeutet doch, dass eine Variable in einem Interrupt die Overflows zählt? Ich dachte mir, dass es so wie ich es jetzt gemacht habe einfacher ist.


Was ist "normal" in diesem Zusammenhang, bzw. warum sollte der Timerbetrieb hier un-normal sein ?
Der Timer könnte doch auch im CTC Modus laufen. Wenn der Timer im CTC modus läuft, bedeutet das dann, dass ich eine Zahl vorgeben kann bis der Overflow passiert. Zum Beispiel so, dass der Timer von 0 - 500 zählt bis ein Overflow geschieht? (Da bin ich noch ein wenig verwirrt)

MfG Maxtronik

MagicWSmoke
14.04.2012, 14:27
Hmmm, warum jetzt plötzlich Timer1 ?

Das bedeutet doch, dass eine Variable in einem Interrupt die Overflows zählt? Ich dachte mir, dass es so wie ich es jetzt gemacht habe einfacher ist.
Ist anders auch nicht kompliziert.

Der Code funktioniert so weit, allerdings bin ich mit der geringen Auflösung nicht ganz zufrieden.
Verständlich, da Du vergessen hast den Zähler mit auszuwerten. :)
Ansonsten muss man noch Race-Conditions vermeiden. Stell Dir vor die Auswertung fällt gerade so, dass bei 65535 die while verlassen wird und bis zur Auswertung der Zähler dann 0 wird, dann stimmt das Ergebnis nicht mehr.

Der Timer könnte doch auch im CTC Modus laufen. Wenn der Timer im CTC modus läuft, bedeutet das dann, dass ich eine Zahl vorgeben kann bis der Overflow passiert. Zum Beispiel so, dass der Timer von 0 - 500 zählt bis ein Overflow geschieht?
Ja, aber das macht hier doch keinen Sinn, denn man möchte ja am liebsten die maximale und zusammenhängende Zählerbreite haben, bis man per Software einen Overflow behandeln muss.

Maxtronik
14.04.2012, 15:21
Danke nochmal :)
Race-Conditions.... so heißt das also :p Schon wieder was dazu gelernt.
Aber wo genau siehst du da denn eine Race-Condition? Ich meine der Zähler hört doch nach der while schleife auf zu zählen und die Variable 'ergebnis' enthält einen Maximalen Wert von etwa 60 (liegt am Ultraschallsensor).

Die Auswertung des Timers ist mir auch schon in den Kopf gegangen:


while((PINC & (1<<PC3)))
{
if (TIFR & (1<<TOV1))
{
ergebnis = ergebnis + 1; //ergebnis1
TIFR = (1<<TOV1);
}
}
TCCR1B &= ~(1<<CS11); // Stoppe timer
ergebnis2 = TCNT1; //ergebnis2


Aber so wirklich schön finde ich das nicht. Hat auch nicht wirklich funktioniert.
Hast du da vielleicht noch einen Tipp/Codeschnipsel :confused:

Maxtronik
14.04.2012, 16:11
So, habs jetzt doch geschaft :rolleyes:



while((PINC & (1<<PC3)))
{
if (TIFR & (1<<TOV1))
{
ergebnis = ergebnis + 255;
TIFR = (1<<TOV1);
}
}
ergebnis = ergebnis + TCNT1;
TCCR1B &= ~(1<<CS11); // Stoppe timer

MagicWSmoke
14.04.2012, 16:15
Danke nochmal :)
Bitte.

Aber wo genau siehst du da denn eine Race-Condition? Ich meine der Zähler hört doch nach der while schleife auf zu zählen und die Variable 'ergebnis' enthält einen Maximalen Wert von etwa 60 (liegt am Ultraschallsensor).
Im letzten Code war nur noch Init() zu sehen, da war nicht erkennbar welcher Precaler verwendet wird.
Jeder Befehl benötigt mindestens einen oder mehrere Takte, der Compiler setzt das ja intern in Maschinenbefehle um.
Dafür z.B.
TCCR1B &= ~(1<<CS11); muss er das Register laden, ver-unden und zurück speichern, wird um die 3 Takte dauern, der vorherige Test auf den gesetzten Pin dauert auch noch ein paar Takte, lass es mal 2 sein, sind also 5.
Wenn der Prescaler 8 ist, dann hat er noch nicht weiter gezählt, bis der Zähler tatsächlich steht. Ist der Prescaler aber 1, dann ist er schon weiter.
War er gerade an der Grenze, dann ist er nachher vielleicht bei Stand 1, nur wurde der Overflow nicht mitgezählt, damit stimmt dann das Ergebnis nicht mehr.

Die Auswertung des Timers ist mir auch schon in den Kopf gegangen:
Die Overflows sind ein Vielfaches der Timergröße, man muss das natürlich zum Schluss zusammenrechnen, also das Eine multiplizieren und das Andere addieren.

Hat auch nicht wirklich funktioniert.
Wie erkennst Du das ?

Hast du da vielleicht noch einen Tipp/Codeschnipsel
Das hier ist ein typischer Anwendungsfall für die Input Capture Fähigkeit, die der Timer1 Deines µC möglicherweise hat.
Kannst ja mal danach suchen, ist aber ein wenig komplexer als das hier.

Allerdings wäre, im Falle dass Timer1 ein 16Bit-Timer ist, ein besseres Ergebnis bereits erreichbar, wenn man den Prescaler auf 1 setzt, denn 16Bit bei 8MHz entsprechen einer Strecke von ca. 2,70m, also 1,35m einfach, bis ein Overflow auftritt.
Keine Ahnung wie weit diese Sensoren messen können, aber 1,35m wäre schon mal ok.
Wenn man einfach den 16Bit Zählbereich ausnutzt und bei einem Overflow das Ergebnis verwirft weil als zu weit betrachtet, kann man sich einiges sparen.

Maxtronik
14.04.2012, 17:09
Allerdings wäre, im Falle dass Timer1 ein 16Bit-Timer ist, ein besseres Ergebnis bereits erreichbar, wenn man den Prescaler auf 1 setzt, denn 16Bit bei 8MHz entsprechen einer Strecke von ca. 2,70m, also 1,35m einfach, bis ein Overflow auftritt.
Aha!
So funktionieren dann wahrscheinlich auch die ganzen Beispielcodes.
Ja, also ich habe es mal so gemacht wie du es mir gesagt hast. Aber ich glaube, das der Controller den Prescaler nicht auf 1 setzt, denn er liefert nur Werte von ca.10 bis 250. Habe es so gemacht:

TCCR1B = (1<<CS10); // starte timer Prescaler 1

Werden die Prescaler Bits eigentlich beim ausschalten des Controllers gelöscht, könnte ja sein das CS11 noch gesetzt ist?

Besserwessi
14.04.2012, 17:09
Der 16 Bit Timer reicht für Ultraschall (in Luft) auch ohne Überlauf. Das Limit bei 1,35 m gilt für Prescaler 1 und eine entsprechend sehr hohe Auflösung. Auch mit Prescaler 8 oder ggf. auch 64 sollte die Auflösung reichen und die maximale Strecke ist dann schon 8 bzw. 64 mal so groß. Schon die Abfrage per Polling in der While schleife gibt eine Unsicherheit von vermutlich mehr als 8 Zyklen - eine Prescaler unter 8 ist also nicht wirklich sinnvoll. Wenn man es mit so hoher Auflösung braucht, dann ist ICP die Lösung, braucht aber einen passenden PIN.

Im RN-Wissen ist unter timer (AVR) ein Beispiel zum ICP und auch wie man die Race-conditon abfängt mit drin.

MagicWSmoke
14.04.2012, 18:00
Schon die Abfrage per Polling in der While schleife gibt eine Unsicherheit von vermutlich mehr als 8 Zyklen
Nicht wirklich. Da ohne Test auf Overflow die Bedingung einfacher ist, beträgt die Laufzeit minimal 1 und maximal 3 Zyklen, also ein Jitter von 2 Zyklen.

SBIC 0x13,3; ATM32, PC3, Opt. -Os
RJMP PC-0x0001
Nachfolgender Code ist in der Laufzeit konstant und kann eingerechnet werden, es stellt sich aber wirklich die Frage, warum man auf 1,35m / 2^16 = 0,00002 = 2 Hundertstel Millimeter auflösen sollte.

Werden die Prescaler Bits eigentlich beim ausschalten des Controllers gelöscht, könnte ja sein das CS11 noch gesetzt ist?
Ja, bei einem Hardreset werden Defaults eingetragen, außerdem setzt Du das Register ja richtig und machst nicht den Fehler es bei Initialisierung zu ver-odern.

TCCR1B = (1<<CS10); // starte timer Prescaler 1
Selbst bei 1MHz Clock müsste ein Wert von 3000 bei einem Hindernis in 50cm Entfernung und Prescaler 1 rauskommen.
Stell den vollständigen Code ein und schreib auch dazu was es für ein µC-Typ ist und mit welcher Taktfrequenz er betrieben wird.

Maxtronik
14.04.2012, 18:32
Hardware:
-Asuro
->Atmega8
->externer Quarz 8Mhz
-Ultraschallsensor SRF05

Hier ist mein aktuellster Code:


#include <stdlib.h>
#include <stdio.h>
#include <avr/io.h>
#include <inttypes.h>
#include <util/delay.h>
#include "asuro.h"

int main(void)
{

Init();

uint16_t ergebnis;
char buffer [20]; //Doofe Frage: Was macht die Zahl in den eckigen Klammern???
ergebnis = 0;


while (1)
{

DDRC |= (1<<PC3); // PC3 Ausgang
PORTC |= (1<<PC3); // PC3 High
_delay_us(20); // warte 20 µSekunden
PORTC &= ~(1<<PC3); // PC3 Low
DDRC &= ~(1<<PC3); // PC3 Eingang
while(!(PINC & (1<<PC3))); //warte auf Echo Signal
TCCR1B = (1<<CS11); // starte timer Prescaler 8
TCNT1 = 0; // setze Timer1 auf 0
while((PINC & (1<<PC3))); //warte auf Echoende
ergebnis = TCNT1;
TCCR1B &= ~(1<<CS11); // Stoppe timer

itoa(ergebnis, buffer, 10); //übersetze (int)ergebnis in ASCII
ergebnis = 0;
SerPrint("\n\r Ergebnis: \n\r");
SerPrint(buffer); //übertragung des Ergebnisses über RS232
_delay_ms(500); //warte 500ms
}
}


Ich erhalte in meiner Uart Konsole bei ca. 12cm Abstand einen Wert von ca. 250,
wenn ich nun den Abstand vergrößere dann wird dieser Wert kleiner bis er 1 beträgt.
Vergrößere ich wieder den Abstand zähl er den Wert wieder bis auf ca. 250 u.s.w..
Ist das nicht ein komisches Verhalten???

MagicWSmoke
14.04.2012, 19:17
Hab' mir mal das Datenblatt für den Sensor gesucht, der setzt das intern um, das ist also nicht die Laufzeit des Schalls, sondern die Formel lautet:
Zurückgegebener Impuls in µs / 58 = Wert in cm, bei min. 100µs und max. 25000µs

Bei einem Prescaler mit 8 und einem 8MHz Takt hast Du in 25000µs einen Zählerstand von 25000. Kann es sein, dass mit der Ausgaberoutine etwas nicht klappt ?
Weise doch mal Ergebnis den Wert 25000 fest zu und gib das aus.

Das Teil mit den eckigen Klammern ist ein Char-Array, das Äquivalent zu Strings in Bascom, die Zahl bezeichnet die Länge des Arrays.

Maxtronik
14.04.2012, 19:35
int main(void)
{

Init();

char buffer [10];
uint16_t distance=0;

while (1)
{
distance = 25000;
itoa(distance, buffer, 10); //übersetze (int)ergebnis in ASCII
SerPrint("\n\r Ergebnis: \n\r");
SerPrint(buffer); //übertragung des Ergebnisses über RS232
_delay_ms(500); //warte 500ms
}
}


Hab ich gemacht. In der Konsole erscheint auch 25000, also hier dürfte der Fehler nicht liegen.
Ich stehe gerade so ziemlich aufm Schlauch :eek:
Ich bin mir ziemlich sicher das der Fehler irgendwie mit dem Timer zusammenhängt.
Ich befürchte ich komme mit der Bit schreibweiße noch ein wenig durcheinander.
Es macht bei mir keinen Unterschied ob ich Das CS11 Bit so schreibe:
TCCR1B = (1<<CS11); // starte timer Prescaler 8
oder so wie man es bei den Ausgängen macht:
TCCR1B |= (1<<CS11); // starte timer Prescaler 8

Mache ich vielleicht dort einen Fehler???

Vielleicht lösche ich das Bit ja auch falsch???
TCCR1B &= ~(1<<CS11); // Stoppe timer

Ich hoffe du kannst mir noch einmal helfen ;)

MagicWSmoke
14.04.2012, 19:53
Es macht bei mir keinen Unterschied ob ich Das CS11 Bit so schreibe:
TCCR1B = (1<<CS11); // starte timer Prescaler 8
oder so wie man es bei den Ausgängen macht:
TCCR1B |= (1<<CS11); // starte timer Prescaler 8
Solange TCCR1B vorher 0 war, macht das keinen Unterschied. Beim Einschalten des µC's ist das Register 0.
Nur soll man bei der ersten Initialisierung nicht verodern (|=), sondern direkt zuweisen. Grund dafür: Wenn z.B. ein Bootlader aktiv war, kann der das Register schon vorbesetzt haben, dann kommt mit Verodern Murks raus.
Was macht eigentlich das Init() ? Auch würde ich noch TCCR1A auf 0 setzen, einfach der Gründlichkeit halber und damit ich sicher bin, das alle beteiligten Register den von mir gewünschten Wert haben.

Mache ich vielleicht dort einen Fehler???
Nein, hab's simuliert und sieht alles gut aus, in Ergebnis steht der Wert in µs.

Vielleicht lösche ich das Bit ja auch falsch???
TCCR1B &= ~(1<<CS11); // Stoppe timer
Ist auch richtig. Du bist sicher das richtige Target gewählt und F_CPU definiert zu haben ?
Welcher Optimierungslevel ist eingestellt ?
Bekommst Du Warnings ? Wenn ja, welche ?

Besserwessi
14.04.2012, 20:04
Die Schreibweise mit TCCR1B = (1<<CS11); ist besser, weil man da das ganze Register definiert, also alles richtig einstellt, egal was vorher drin war. Die andere Version wird meistens auch gehen, weil der normale Mode default ist. die Version mit dem |= wird ggf. kürzer weil dafür spezielle ASM Befehle existieren.

So ähnlich ist es mit dem Stoppen des Timers - das kommt hier ohnehin nach dem Auslesen, ist also nicht von Bedeutung. Man könnte es ggf. Probieren den Timer erst zu stoppen, und dann auszulesen. Im Prinzip sollte es aber auch bei laufendem Timer gehen.
An sich macht GCC das auslesen von 16 Bit Registern richtig, man könnte es ggf. auch mal von Hand probieren (erst low Byte lesen), oder den erzeugten Code ansehen.

An sich kann ich da keinen Fehler sehen. Ein versuch wäre ggf. noch den Timer erst auf 0 zu setzen und dann zu starten. Im Prinzip sollte aber beides gehen.

Eine kleine Ungenauigkeit ist da noch drin bei der Ausgabe. Statt itoa sollte man utoa nehmen, denn die variable ist uint16_t. Bis gut 32000 sollte das aber auch noch keinen Unterschied machen.

Die 58 µs/cm kommen übrigens ganz gut hin mit der Laufzeit. Das ist also schon die Zeit bis zu Echo.

Maxtronik
14.04.2012, 20:12
Was macht eigentlich das Init() ?
Gute Frage! Das ist ein Dienst aus der asuro.c
Dieser Dienst setzt folgende Timer bits:
TCCR1A = (1 << WGM10) | (1 << COM1A1) | (1 << COM1B1);
TCCR1B = (1 << CS11); // tmr1-Timer mit MCU-Takt/8 betreiben.
Ich würde sagen wir haben den Fehler entdeckt :cool:.
Das ist dann wohl einer dieser arten von Fehlern, die einem peinlich sein könnten.
Und ich dachte die ganze Zeit dieses init() wäre ein fester Bestandteil von C :-(.
Ich bin halt blutiger Anfänger in C.
Vielen Dank MagicWSmoke :D
EDIT: und natürlich auch dir Besserwessi(Hab deinen Post erst danach gelesen #-o)

MagicWSmoke
14.04.2012, 20:24
die Version mit dem |= wird ggf. kürzer weil dafür spezielle ASM Befehle existieren.
Die Timerregister sind meist oberhalb des IO-Adressbereiches, den SBI & CBI erreichen können, diese Opcodes gehen nur bis 0x1F.
Beim ATM8 sitzt TCCR1B auf 0x2E, damit ist die Sequenz: IN, ORI, OUT, bei der direkten Zuweisung dagegen nur: LDI, OUT, ein Takt weniger.
Ports liegen oft im Bereich unter 0x1F, da ist Bit-weises Setzen und Löschen dann tatsächlich schneller.

Die 58 µs/cm kommen übrigens ganz gut hin mit der Laufzeit. Das ist also schon die Zeit bis zu Echo.
Ja, richtig. Ist ja der Weg hin und zurück.:)

MagicWSmoke
14.04.2012, 20:31
TCCR1A = (1 << WGM10) | (1 << COM1A1) | (1 << COM1B1);
Ja, das war das Kritische, das hat 'ne 8-Bit PWM eingestellt und den Zähler "etwas" zusammengekürzt.

TCCR1B = (1 << CS11); // tmr1-Timer mit MCU-Takt/8 betreiben.
War dagegen kein Problem, da von Dir gesetzt.

Das ist dann wohl einer dieser arten von Fehlern, die einem peinlich sein könnten.
Passieren nicht, wenn man die Kontrolle über die Register selbst übernimmt.

Vielen Dank MagicWSmoke :D
Bitte, gern geschehen.