attiny45 micros()

Guten Abend,
im Grunde genommen geht es um meinen “alten” IR-Code für meine Fernbedinung.
Ich möchte eine Variable jede Mikrosekunde um eins erhöhen um unteranderem auch ohne delay Timen zu können.
Hierzu habe ich meinen Code um die Einstellungen und den ISR für Timer0 erweitert.

Dummerweise stimmen die Zeiten nicht (delay_micros() blockiert zu lange).
Kann mir vlt. jemand sagen, was da schief gelaufen ist?

/*
 * RemoteCont.c
 *
 * Created: 13.08.2014 21:56:20
 * Author: Adrian
 */ 

#define F_CPU 8000000L

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>

//Variablen
#define CODE_PROG_UP   0b1011110100011000111001111
#define CODE_PROG_DOWN 0b1011110100111000110001111
#define CODE_POWER_ON  0b1011110110000000011111111
#define CODE_TELETEXT  0b1011110100010000111011111

//Makros zum An/Ausschalten der IR-Led
#define START_IR TCCR1 |= (1 << COM1A0)
#define END_IR TCCR1 &= ~(1 << COM1A0)

//Variable für Micros()
volatile unsigned long micros;
volatile unsigned long startTime;

void delay_micros(unsigned long time)
{
	startTime = micros;
	while(micros - startTime < time);
}

//Sendet den übergebenen Code über das NEC-Protokoll
void sendNECCode(unsigned long code)
{
	unsigned long mask = 0;
	
	START_IR;
	delay_micros(9000);
	
	END_IR;
	delay_micros(4500);
	    
	for(int i = 32; i >= 0; i--)
	{
		START_IR;
		delay_micros(560);
		
		mask = (1UL << i);
		    
		END_IR;
		   
		//Zu übertragendes Bit überprüfen
		if(code & mask)
			delay_micros(1690); //Ist das Bit gesetzt 1.69mS warten
		else
		    delay_micros(560); //Sonst 0.56mS warten
	}
}

//ISR für Timer0 Compare Match A
ISR(TIM0_COMPA_vect)
{
	micros ++;
}

int main(void)
{
	//Outputpins einstellen
	DDRB  = (1 << PB1);
	PORTB = 0xFF & ~(1 << PB1);
	
	//Timer0 für die Mikrosekunden vorbereiten
	TCCR0A = (1 << WGM01);
	TCCR0B = (1 << CS00);
	OCR0A  = 8;
	TIMSK  = (1 << OCIE0A);
	
	//Timer1 für eine Frequenz von 38kHz vorbereiten
	TCCR1 = (1 << CTC1)|(1 << CS12)|(1 << CS10); //Prescaler auf 16 stellen -> 8000000/16 = 500000
	OCR1A = OCR1C = 6; //Compare Match Register auf 6 stellen -> 500000/6 Frequenz ~38kHz
	
	//AnalogDigitalKonverter ausschalten
	ADCSRA = 0;
	PRR    = 0x03;
	
	sei();
		
    while(1)
    {
		if(!(PINB & (1 << PIN2)))
		{
			sendNECCode(CODE_PROG_UP);
		}
		else if(!(PINB & (1 << PIN0)))
		{
			sendNECCode(CODE_PROG_DOWN);
		}
		else if(!(PINB & (1 << PIN3)))
		{
			sendNECCode(CODE_POWER_ON);
		}
		else if(!(PINB & (1 << PIN4)))
		{
			sendNECCode(CODE_TELETEXT);
		}	
    }
}

Der Tiny läuft mit 8MHz

Edit:
Eine Mikrosekunde dauert hier ca 8 :confused:

Sind die Fuses im Attiny 45 richtig gesetzt.Im Auslierferungszustand ist nämlich CLKDIV /8 gesetzt als er läft auf 1Mhz dann dauert alles 8mal länger.
Vor dem Flashen einfach mal auf Bootloader installieren drücken in der IDE das setzt bei den kleinen Attiny nur die Fuses bringt aber keinen Bootloader auf den Chip. Das ist in diesem Zusammenhang ein bisschen Irreführend.
Gruß
DerDani

Ja, die Fuses sind richtig gesetzt, der Tiny läuft mit dem internen 8MHz Oszi ;).

Ich habe auch verschiedene Timereinstellungen versucht (Prescaler 8 und OCR0A auf 1 usw.),
da kommt immer ein falsches Ergebnis bei raus.

Sowohl im Simulator von Atmel Studio als auch beim Test in der Realität
Habe die Pausen mal mit nem Logic Analysator überprüft
→ anstatt 9ms wartet er satte 68ms und das ist definitiv viel zu viel.

Der Hintergrund, warum ich nicht die vorgefertigte _delay_us() neheme ist wie gesagt, weil ich später - wenn das mal funktioniert :relaxed: - messen will, wie lange ein taster gedrückt ist, um einem mehrere Funktionen zuordnen zu können und dazu müsste ich die vergangene Zeit zählen…

Addi2438:
Kann mir vlt. jemand sagen, was da schief gelaufen ist?

//ISR für Timer0 Compare Match A
ISR(TIM0_COMPA_vect)
{
micros ++;
}

Dir geht offenbar jedes Zeitgefühl im Bereich von Millionstel Sekunden völlig ab.

Jeder Aufruf einer ISR-Routine bedeutet alleine für Aufruf, Sichern der Register, Wiederherstellen der Register und Ende der ISR-Funktion einen Overhead von ca. 60 Takten. Plus mindestens 4 Takte für das Erhöhen einer 4-Byte großen Variablen macht das mindestens 64 Takte für die Ausführung der ISR.

Auf einem mit 8 MHz getakteten Controller können 8 Takte in einer Mikrosekunde ausgeführt werden, d.h. Deine ISR dauert Minimum 8 Mikrosekunden Ausführungsdauer, um die Variable “micros” zu erhöhen.

Also Du versuchst einmal pro Mikrosekunde eine ISR auszuführen, deren Abarbeitung acht Mikrosekunden dauert. Das funktioniert so natürlich nicht, sondern Du bekommst am Ende eine Ausführung der ISR alle 8 Mikrosekunden, und 7 Interrupts gehen verloren und werden nicht ausgeführt.

Das passt vorne (mit der ISR) und hinten (mit dem Code in der loop) überhaupt nicht zusammen.

Wenn Du ein Mikrosekundendelay benötigst und aus irgendwelchen Gründen dafür die Arduino-Komfortfunktion delayMicroseconds() nicht verwenden möchtest, kommt als Alternative auf der AVR-Plattform die entsprechende Funktion der AVR libc in Frage, namens _delay_us().

D.h. Du bindest in Deinen Code die entsprechende Library ein und rufst die Funktion auf:

#include <util/delay.h>   // Einbinden der Library
....
_delay_us(560); // Mikrosekunden-Delay für die angegebene Zeit

Der Hintergrund, warum ich nicht die vorgefertigte _delay_us() neheme ist wie gesagt, weil ich später - wenn das mal funktioniert :relaxed: - messen will, wie lange ein taster gedrückt ist, um einem mehrere Funktionen zuordnen zu können und dazu müsste ich die vergangene Zeit zählen…

Ich will praktisch sowas machen:

startTime = micros
while(digitalRead() == LOW);
if(micros - startTime < 1000000)
...
else
...

Jetzt könnte man sich natürlich auch fragen, warum dann Mikrosekunden und nicht Millisekunden
Ganz einfach:
Weil nur 2 Timer da drin sind.
Timer eins wird für PWM benutz der andere ist dann praktisch für das Timing da.
Und Mikrosekunden eben genau desswegen, weil das NEC Protokoll auch Pausen > 1ms vorschreibt.

Zu Letzt:
Wie kriege ich das dann hin, dass die µs einigermaßen richtig gezählt werde :/.
Notfalls auch mit Assembler. Wäre cool falls da jemand nen Link zu hat, ich habe mich schon blöd gesucht

Addi2438:
Zu Letzt:
Wie kriege ich das dann hin, dass die µs einigermaßen richtig gezählt werde :/.
Notfalls auch mit Assembler. Wäre cool falls da jemand nen Link zu hat, ich habe mich schon blöd gesucht

Schau Dir an, wie Timer0 in der Arduino Core-Library initialisiert und verwendet wird, um die Funktionen millis(), micros(), delay() und delayMicroseconds() bereitzustellen!

Genau so kannst Du es auch machen, wenn Du die Arduino Core-Library nicht verwendest.

Die Auflösung der micros() Funktion wäre 4µs auf 16 MHz AVRs und 8µs auf 8 MHz AVRs.

Also "micros()" als Funktion realisieren, bei der ca. einmal pro Millisekunde eine Variable aktualisiert wird, wenn der Timerüberlauf stattfindet, und die micros()-Funktion liefert dann beim Aufruf jeweils den Variablenstand plus den aktuellen Stand des Timer-Registers multipliziert mit der Timerauflösung zurück.

Warum verwendest Du denn den Timer0 nicht mit der Arduino Core-Library? Die Core-Library stellt Dir doch die Timerinitialisierung mit dem Komfortfunktionen millis(), micros(), delay() und delayMicroseconds() automatisch zur Verfügung, ohne dass Du den Timer0 oder die Funktionen selbst programmieren müßtest?

Du kannst auch auf einem Timer der PWM macht die Überläufe zählen. So macht das der Arduino mit Timer0. Dazu musst du aber einen richtigen PWM Modus nehmen und nicht CTC!
Da wird aber nicht alle 1µs was gemacht. Der Interrupt wird alle 1ms ausgelöst und zwischendrinn wird die Anzahl der Überläufe mit dem Zählerstand verrechnet um auf micros() zu kommen

Aber so oder so musst du die korrekte Ausführungszeit der ISR kennen. Am besten du schaust dir mal das Programm im Disassembler an. Dann siehst du genau was die ISR macht und kannst die Anzahl der Takte zählen.

Wie lange die Befehle dauern steht hier ab Seite 5:

Das AVR-Assembler Handbuch habe ich bereits, danke für den Hinweis ;).

Also kann ich das dann so machen, dass ich den 8ter als Prescaler nehme
dann die Overflows zähle und z.B. so zurück rechne:

unsigned long micros()
{
    return overflows*2ul^8 + TCNT0;
}

Ich werde mir auf jeden Fall mal angucken, wie das im Core gemacht wurde.
Scheint ein interssantes Thema zu sein :smiley: (jedenfalls für mich xD).