Timings mit einem uC erzeugen

 

Timings können nach verschiedenen Methoden erzeugt werden, jed nachem, wie genau die Zeiten sein müssen, ob das Hauptprogramm blockiert werden darf und welche Ressourcen zur Verfügung stehen:

  • Schleife
    das Timing wird erzeugt, indem eine Warteschleife programmiert wird; diese Zeiten können blockierend  oder nicht blockierend realisiert werden. ACHTUNG! Die Timings ändern sich stark wenn die Optimierung des Compilers aktiviert ist.

  • Timeroverflow Interrupt
    Ein Timer wird so eingestellt, dass seine Überläufe den Takt vorgeben; man kann den Timer auf einen bestimmten Wert setzen (preload), dann läuft er schneller über. Nach einem Overflow wird in der Interrupt Service Routine (ISR) mitgezählt wie oft der Überlauf passiert ist und immer wieder der richtige Preload-Wert gesetzt.

  • CTC
    Der Timer läuft im CTC Mode bis zu einem Vergleichswert und wird dann auf 0 rückgesetzt. Man kann durch geeignete Wahl der Zählgeschwindigkeit und des Vergleichswertes das Timing einstellen. In der ISR kann mitgezählt werden, wie oft der Überlauf passiert ist.

 

Aufgabe

Image1Folgendes Timing soll mit allen Methoden implementiert werden. Die Zeiteinteilung ist z.B: in ms .

Für die Beispiele wird ein AVR 328p mit fOSC=16MHz (Periode 62,5ns) verwendet.

 

Man kann dieses Timing als Zählfolge sehen: der Zählerstand ändert sich alle 1ms.

Die Zählfolge ist 0 0 1 1 1 0 6 4 4 4 4 4 0 0 und wiederholt sich dann, die Periode dauert also 14ms. Man muss hier auf die kleinste zu realisierende Zeiteinheit schauen um herauszufinden, welche Zeitauflösung benötigt wird.

 

Lösung 1

Image2problematisch ist bei der Verwendung von delay_ms, dass die Anwendung wartet, bis die angegebene Zeit vergangen ist.

//create create timing by blocking loop 
#include <avr/io.h>
#define F_CPU 16000000UL  //must be defined prior to delay.h
#include <util/delay.h>
#define A  PORTB7
#define B  PORTB6
#define C  PORTB5

void setup(); 
int main(){
	setup(); 
	uint8_t signals[] ={0,0,1,1,1,6,4,4,4,4,4,4,0,0};
	static uint8_t n = 0; 
	while(1){
		 n = n % 14; //0,1,2,3,...11,12,13,0,1,2,... periodic 
		 PORTB = signals[n++]<<5;  //shift bit pattern to correct position
		_delay_us(999); 
	}
}
void setup(){
  DDRB |= (1<<A)|(1<<B)|(1<<C); 	
}

 

Lösung 2

Image3

Hier blockiert die Schleife nicht, dafür ist die Struktur komplexer. Eine Variable time zählt die Schleifendurchläufe und liefert so die Zeitbasis.

Problematisch: jede Änderung des Codes und die Compiler-Einstellungen ändern das Timing.

// create timings by non blocking loop
#include <avr/io.h>
#define F_CPU 16000000UL  //must be defined prior to delay.h
#include <util/delay.h>
#define A  PORTB7
#define B  PORTB6
#define C  PORTB5
#define MAX 14
void setup(); 
int main(){
	setup(); 
	uint8_t signals[] ={1,2,3};
	static uint8_t n = 0; 
	uint16_t time = 0; 
	const uint16_t countMS = 160;   //magic number found by simulation
									//this number depends on optimization mode!!!
									
	while(1){
		// n = n % sizeof(signals)/sizeof(signals[0]); //periodic by 14
		n = n % 3; // much faster than previous line	
		if (time == countMS){  //
			PORTB = signals[n++]<<5;  //shift bit pattern to correct position			
			time=0;
		}
		time++; 
		
	}
}
void setup(){
  DDRB |= (1<<A)|(1<<B)|(1<<C); 	
}

Lösung 3

Interruptsteuerung entlastet das Hauptprogramm . Mittels Simulator wird das Timing abgeglichen.

Hier werden Timer Overflow und Compare Match Interrupt verwendet.

Image4

//create timing by timer0  overflow interrupt
/* timer 1ms = 16000*62.5ns (16MHz)
*  8 bit Timer --> Overflow after 256 clock cycles
* Prescaler  1,8,64
* Overflow after 16us,128us, 1024us 
*
* therefore Prescaler 64 used
*/ 
#include <avr/io.h>
#define F_CPU 16000000UL  //must be defined prior to delay.h
#include <avr/interrupt.h>
#define A  PORTB7
#define B  PORTB6
#define C  PORTB5
#define MAX 14
const uint8_t preloadValue = 6;	// magic number for correct timing
									// use simulator to find out
uint8_t signals[] ={0,0,1,1,1,6,4,4,4,4,4,4,0,0};
volatile uint8_t n; //Signalnr
void setup(); 
int main(){
	setup(); 
	sei(); 
	while(1){
	}
}
ISR (TIMER0_OVF_vect){
   PORTB = signals[n++]<<5; 
   TCNT0 = preloadValue; 
   n = n % 14; //periodic
}
void setup(){
  DDRB |= (1<<A)|(1<<B)|(1<<C); 	
  // Timer0 Overflow interrupt and Prescaler 64
  TIMSK0 |= (1<<TOIE0); 
  TCCR0B |= (1<<CS01)|(1<<CS00); 
}
//create timing by timer0  CTC mode
/* timer 1ms = 16000*62.5ns (16MHz)
*  8 bit Timer --> Overflow after 256 clock cycles
* Prescaler  1,8,64
* Overflow after 16us,128us, 1024us
*
* therefore Prescaler 64 used
*/
#include <avr/io.h>
#define F_CPU 16000000UL  //must be defined prior to delay.h
#include <avr/interrupt.h>
#define A  PORTB7
#define B  PORTB6
#define C  PORTB5
#define MAX 14
//const uint8_t preloadValue = 6;	// magic number for correct timing
// in CTC mode the timer counts from 0 to preload-value
const uint8_t preloadValue = 256-6;
// use simulator to find out
uint8_t signals[] ={0,0,1,1,1,6,4,4,4,4,4,4,0,0};
volatile uint8_t n; //Signalnr
void setup();
int main(){
	setup();
	sei();
	while(1){
	}
}
ISR (TIMER0_COMPA_vect){
	PORTB = signals[n++]<<5;
	n = n % 14; //periodic
}
void setup(){
	DDRB |= (1<<A)|(1<<B)|(1<<C);
	// Timer0 output compare match interrupt and Prescaler 64
	TIMSK0 |= (1<<OCIE0A);			// interrupt enable
	TCCR0A |=(1<<WGM01);			// CTC
	TCCR0B |= (1<<CS01)|(1<<CS00);  // :64
	// set the compare register
	OCR0A = preloadValue; 
}