PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : SainSmart Arduino UNO R3 Timer



SACO
05.05.2013, 02:26
Hallo,

es geht um folgendes: Ich möchte gerne mit dem Arduino das Signal von einem IR-Empfänger dekodieren. Hierzu nutze ich den Timer vom Arduino (das Board hat den Mega328p), da die millis Funktion eine zu geringe Auflösung hat. Das Problem ist jetzt, dass ich nicht verstehe, wie ich ausrechne wie lang ein Signal ist.

Ich habe folgendes Testprogramm geschrieben, um den Timer besser zu verstehen. Eigentlich sollte alle 10 Sekunden eine Meldung per Serial übertragen werden. Leider geht das hinten und vorne nicht auf:


void setup() {
Serial.begin(9600);
setup_timer();
}

unsigned long x;

void loop() {
// count 10 seconds
TCNT1 = 0;

while(TCNT1 < 50000);
x++; // 50000 = 0,025s

if(x >= 400) { // 10 / 0,025 = 10sec
Serial.println(x);
x = 0;
}
}

void setup_timer() {
TCNT1 = 0; // reset the counter
TIMSK1 = 0; // disable overflow interrupt

// set the registers of the 16 bit timer
// mode = normal; divide clock by 8
// this will also start the timer
TCCR1A = 0;
TCCR1B = 0x5; // 00000101b
}


Wie ich das verstehe:

TCCR1B = 0x5 heißt, dass die Frequenz des Counters die Frequenz des Quartzes auf dem Board / 8 erhöht wird.
Auf dem Quarz steht 16000; ich geh mal davon aus, dass die kHz sind und somit das Quartz mit 16000000 Hz läuft.

1.) 16000000 Hz / 8 = 2000000 Hz
2.) 1 / 2000000 Hz = 0,0000005 s = 0,5us
3.) Wenn der Counter = 50000 ist, heißt das es sind 50000 * 0,0000005s = 0,025s vergangen
4.) 10s / 0,025s = 400

Heißt also, alle 10 Sekunden sollte der Wert von x auf Serial ausgegeben werden. Ist aber nicht so; was mache ich falsch?

Che Guevara
05.05.2013, 08:58
Hi,

deine Anfangsberechnungen sind richtig, allerdings machst du einen Fehler, weil ein Timer im AVR nicht von 0 - xxx sondern von xxx - max. Wert ( 8- od. 16Bit) zählt.
Also:
1) 16MHz / 8 = 2MHz
2) 1 / 1MHz = 0.5µs
3) (65536-50000)*0.5µs = 7768µs = 7.768ms = 0.007768s
4) 10s / 7768µs = 1287

Gruß
Chris

EDIT:
Deine Vorangehensweise ist übrigens IMHO auch nicht ganz richtig. Normalerweise benutzt man den Interrupt von einem Timer, um eine bestimmte Aktion auszuführen. Du könntest beim Ovf in eine Sub springen, dort dann x inkrementieren und das dann im Main-Loop abfragen. Oder du multiplizierst die schon vergangen Timer-Ticks mit einer Variable / Konstante. Wenn das Ergebnis dann einen bestimmten Wert erreicht oder überschreitet, kannst du wieder eine Aktion durchführen.

SACO
05.05.2013, 11:11
Hi,

danke für die Antwort. Zu deinem Hinweis mit dem Interrupt: Ich nutze den Timer um einen IR-Sensor zu dekodieren. Meine Vorgehensweise habe ich mir aus verschiedenen Quellen zusammengeholt.

Beispiel Start-Bit nach NEC:

9000µs HIGH, 4500µs LOW

Anfangs hatte ich etwas in der Art wie:


unsigned long startTime, highPulsTime, lowPulsTime;

void waitForStartSequence() {
startTime = millis();

while(IsIrHigh); // call macro which checks if ir is high

highPulsTime = millis();

while(IsIrLow);

lowPulsTime = millis();

lowPulsTime -= highPulsTime;
highPulsTime -= time;

if(highPulsTime >= THE_VALUE_FOR_9600us IsAlmostEqual(lowPulsTime , THE_VALUE_FOR_4500us, TOLERANCE))
Serial.println("X");
}

Das hat zur Erkennung des Start-Bits soweit funktioniert. Um dann aber die weiteren bits auszulesen scheint millis nicht akkurat genug zu sein. Wie könnte da eine saubere Lösung aussehen?

Che Guevara
05.05.2013, 11:59
Hi,

also ich würde das Signal auf einen ext. Interrupt-Pin legen (zur Not ginge auch PinChange). Dann würde ich den Timer so initialisieren, dass er alle 10ms (als Beispie) einen Ovf hat. Wenn jetzt dein ext. Interrupt getriggert wird, liest du den aktuellen Zählerstand aus und setzt den Timer wieder auf 0, sodass der nächste Interrupt wieder erst nach 10ms kommen würde. Der ausgelesene Wert enthält dann die Information, wie lange eine Flanke gedauert hat. Wenn nötig müsstest du noch in einem Flag speichern, ob der Pegel high oder low ist.

Gruß
Chris

SACO
05.05.2013, 17:02
Ich komme da echt nicht weiter... Ich habe es jetzt wie folgt versucht:


void setup()
{
Serial.begin(9600);

noInterrupts();

OCR1A = 0xFFFF - 3000; // Der Wert scheint völlig egal zu sein!
TCCR1A = 0;
TCCR1B = 4 | 5; // Mode 4, CTC on OCR1A; divide by 1024
TIMSK1 = 2; //Set interrupt on compare match for OCIE1A
TCNT1 = 0;

interrupts();
}

ISR(TIMER1_COMPA_vect) {
Serial.println("X");
}

void loop()
{
}

In der Zeile OCR1A = 0xFFFF - 3000; habe ich verschiedenste Werte eingetragen, trotzdem wird nur etwa alle 4 Sekunden etwas auf Serial ausgegeben... Wieso ist das so?

EDIT: Ich habe übrigends das Dingen: http://www.sainsmart.com/arduino-compatibles/sainsmart-uno-r3-atmega328-au-development-board-compatible-with-arduino-uno-r3.html

Leider scheint es dazu kein Datenblatt zu geben...

Che Guevara
05.05.2013, 21:06
Hi,

also zunächst mal, es gibt mit SICHERHEIT ein DB vom ATMega328(p).
Dann wäre es hilfreich, wenn du das ganze Programm einstellst und nicht nur Ausschnitte. Du rufst nirgends die Setup-Funktion auf.
Du musst außerdem den Timer nach jedem OVF new vorladen, da sonst nur der erste OVF richtig getimed ist. Ich weiß nicht, was noInterrupts() bzw. Interrupts() bedeutet. Sind das Makros die cli() bzw. sei() beinhalten? Ansonsten fehlt das auch.

Gruß
Chris

SACO
05.05.2013, 21:21
1. Es ging um das Datenblatt des Boards
2. Ich habe bereits den gesamten Programmcode inkl. setup-Funktion gepostet
3. Wieso muss ich den Timer nach einem OVF neu laden, wenn ich mit CTC arbeite?

Ja das sind macros für cli und die andere Funktion. Sind schon in der IDE vorhanden.

Che Guevara
05.05.2013, 22:14
Von dem Board wirst du wohl kein DB finden, sowas gibts glaube ich nicht. Da gibts dann wohl nur ein paar Schaltpläne und Layout unso.. Wenn du Glück hast.
Es ist sicherlich nie verkehrt, die Quarzfrequenz ins Programm einzutragen, das erleichtert auch das Lesen / Verständnis durch andere ;)
Dass du CTC-Mode benutzt hab ich wohl überlesen, dann stimmt das schon so!
Die Setup-Funktion wird nie aufgerufen!?

Gruß
Chris

SACO
07.05.2013, 15:58
Hm, ok. Auf dem Quarz steht JT 16.000 wenn ich mich nicht irre.

"Die Setup-Funktion wird nie aufgerufen!? "

Keine Ahnung. Ich gehe schwer davon aus, dass Sie aufgerufen wird. Standard Aufbau von Arduino Programmen sind halt die beiden Funktionen setup und loop.
setup wird am Anfang einmal aufgerufen. Und loop unendlich oft. Selber muss man die nicht aufrufen.

P.S.: Die Setup Funktion ist oben im Code vorhanden. Die Funktion ISR(TIMER1_COMPA_vect) {
Serial.println("X");
}

wird halt immer nur alle 4 Sekunden aufgerufen, egal, was ich in OCR1A schreibe.

Hubert.G
07.05.2013, 18:12
Hier findest du das Schaltbild des UNO-SMD http://arduino.cc/en/Main/ArduinoBoardUnoSMD
Mehr gibt es von den Dingern nicht.

Wenn du in die Zeile OCR1A = 0xFFFF - 3000; das so eingibst, kann ich mir vorstellen das es nicht funktioniert. Du willst von einer hex-Zahl eine dezimal-Zahl abziehen.
Schreib mal 15625 hinein ohne dem 0xFFFF, dann solltest du jede Sekunde eine ISR bekommen. In der ISR zählst du dann bis 10 und machst dann deine Ausgabe.

SACO
07.05.2013, 21:36
Hallo,

danke für den Link.

Zu dem anderen Thema und um hier keine Falschinfos aufkommen zu lassen:

Deine Aussage bezüglich der Rechenoperation mit "hex-Zahlen" und "dezimal-Zahlen" sind definitiv falsch. Die Schreibweise einer Zahl ist, solange keine String/char* verwendet wird, völlig irrelevant. Es handlet sich bei 0xFFFF lediglich um eine andere Schreibweise für 65535 (sofern es sich um eine unsigned, also vorzeichenlosen Datentypen handelt). Aus der Schreibweise in Hexadezimal lässt sich allerdings besser ablesen, dass alle bits gesetzt sind bzw. dies der maximale Wert für 16 bit sind.

Ein einfaches Beispiel, mit dem du das verifizieren kannst:

void setup()
{
Serial.begin(9600);
}

unsigned int n;

void loop()
{
n = 0xFFFF - 200;

Serial.println(n);

n = 65535 - 200;

Serial.println(n);

while(1);
}

Die Aussgabe auf Serial:
65335
65335

Leider ist dadurch aber immer noch nicht mein Problem gelöst... Hat denn keiner Erfahrung mit dem SainSmart Board?
Muss ich den Timer vielleicht anders ansteuern oder enthält das Board einen Fehler?

Gruß,

SACO

------------------------------------------------

EDIT: Endlich! Ich habs! Scheinbar sind in dem Code gleich zwei Fehler:

1.) 4 | 5 = 5; ich will aber WGM12 setzen, damit ich CTC nutzen kann. Dieses bit wird durch 8 repräsentiert. Richtig wäre also 13 = 8 | 5 = 1101b oder eben 1 << WGM12 | 1 << CS12 | 1 << CS10
2.) Der zweite Fehler ist, dass das Setzen von OCR1A nur dann funktioniert, wenn TCCR1B = 0, also der Timer aus ist. Wieso das genau so ist, weiß ich noch nicht, aber vielleicht kann jemand hier
im Forum das erklären. Sonst werde ich mir das noch raussuchen.

Der funktionierende Code:


void setup()
{
Serial.begin(9600);

noInterrupts();

// stop the timer
TCCR1A = 0;
TCCR1B = 0;

OCR1A = 15624; // with 16 Mhz: about one second
TCNT1 = 0; // reset counter

// start the timer
TCCR1B = 13; // Mode 4, CTC on OCR1A; divide by 1024
TIMSK1 = 2; //Set interrupt on compare match for OCIE1A

interrupts();
}

ISR(TIMER1_COMPA_vect) {
Serial.println("X");
}

void loop()
{
}

Hubert.G
08.05.2013, 08:25
Mit deiner Berichtigung hast du Recht, der Compiler kommt damit klar. Nur für den Betrachter ist es verwirrend und unübersichtlich.
Ebenso deine Schreibweise TCCR1B= 13; oder TCCR1B= 4|5; Für dich ist das ganz klar, der Betrachter muss sich das Datenblatt suchen und die Bits auseinander klauben.
Daher ist ein TCCR1B= (1<<CS0)|(1<<CS2)|(1<<WGM12); um ein Beispiel zu nennen, für den Betrachter wesentlich übersichtlicher und aussagekräftiger.
Das man OCR1A nur setzen kann wenn der Timer nicht läuft, bezweifle ich sehr stark.
Überlicherweise setzt man zuerst alle Register und startet dann den Timer mit setzen des Teilerfaktors im TCCR1B Register.
Warum suchst du ein Datenblatt des SaintSmart, das Datenblatt des Mega328 beinhaltet doch alle Informationen die du benötigst.

SACO
08.05.2013, 08:49
Ich hatte gedacht, dass aus irgendeinem Grund hier der Timer vielleicht anders angesteuert wird. Mir ging es vor allem um den Taktgeber. Aber ehrlich gesagt, habe ich von dem Kram auch nicht viel Ahnung. Die Schreibweise mit (1<<CS0)|(1<<CS2)|(1<<WGM12) habe ich erst bei der Fehlersuche kennen gelernt. Ist natürlich für den User besser zu verstehen. TCCR1B = 4 | 5 ist übrigends eben nicht 13 sondern 5.

oberallgeier
08.05.2013, 08:50
... Das man OCR1A nur setzen kann wenn der Timer nicht läuft, bezweifle ich sehr stark ...GUTE Zweifel! Dieses Verfahren wäre ja auch äusserst unpraktisch. Aber SACO sollte ja wirklich den Timerabschnitt im Datenblatt lesen. (Das Arbeiten mit Mikrocontrollern ohne zutreffendem Datenblatt gehört zu den letzten großen Abenteuern unserer Tage *gg*)

Besonderes aufmerksam diese Stelle und deren Kontext davor und danach:
... ... However, changing the TOP to a value close to BOTTOM when the counter is running with none or a low prescaler value must be done with care since the CTC mode does not have the double buffering feature. If the new value written to OCR1A or ICR1 is lower than the current value of TCNT1, the counter will miss the compare match ... ...

SACO
11.05.2013, 18:16
Ah, ok. Danke für die Info. Sollte wohl wirklich mal den ganzen Abschnitt lesen...