kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




Realisierung eines Schedulers in Arduino-Projekten

(EN google-translate)

(PL google-translate)

Nachfolgend soll in mehreren Schritten aufgezeigt werden, wie ein Scheduler innerhalb eines Arduino-Projektes aufgebaut werden kann. Verwiesen wird hierbei auf die Materialien in Echtzeitsysteme 2, wo dies direkt auf einem ATmega32 erfolgt:

20130527_RTOS_AV.zip - Aufeinander aufbauende Programme zur Realisierung eines Schedulers mit dem ATmega32.

Als Beispielprojekt soll ein Metronom realisiert werden, das fest auf 120 BPM (Beats per minute) eingestellt ist und diesen möglichst genau einhalten soll.

Es soll auf den ersten von vier Schlägen einen hohen Pieps und auf den anderen einen tieferen Pieps von sich geben. Eine Kette von vier LEDs zeigt den aktuellen Schlag an.

Alle Projekte finden sich in folgendem .zip-File:

Zuordnung zwischen Timern und PWM-Ausgängen beim Arduino-Micro

  • Um einen sinnvollen Timer auszuwählen, sollte geklärt werden, welcher Timer für welches PWM-Signal verantwortlich ist.
  • Anscheinend wird Timer 0 von Arduino bereits benutzt.
  • Außerdem ist die Bitbreite des Timers wichtig zu wissen.
Digitaler Ausgang Nr. Pin-Bezeichnung Mikrocontroller Zugeordneter Timer Bit
3 OC0B Timer 0 8
5 OC3A ~OC4A Timer 3 und 4 3:16Bit 4:10Bit-Highspeed
6 OC4D Timer 4 10Bit
9 OC1A ~OC4B Timer 1 und 4 1:16Bit 4:10Bit-Highspeed
10 OC1B ~OC4B Timer 1 und 4 1:16Bit 4:10Bit-Highspeed
11 OC0A ~OC1C Timer 0 und 1 0:8Bit 1:16Bit
13 OC4A Timer 4 4:10Bit-Highspeed

Tabelle 0-1: Zuordung zwischen digitalem Ausgang und Timer

  • Digital Pin 2 besitzt kein PWM und kann somit nicht korrumpiert werden durch Timer-Konfigurationen, darum wird dieser für eine Testblinkschaltung als Ausgangspunkt genommen.
  • Timer 1 wird testweise in CTC-Mode versetzt.
scheduler.zip - Alle nachfolgenden Projekte gezipt.

sched001_blink - Ausgangspunkt, Blinken auf Ausgang 2

int zustand = 0;

void setup() 
{
  pinMode(2,OUTPUT);  
}

void loop() 
{
  digitalWrite(2,1);    
  delay(500);
  digitalWrite(2,0);    
  delay(500);
}

Code 0-1: sched001_blink.

CTC-Mode für Timer1

Register Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
TCCR1A: COM1A1 COM1A0 COM1B1 COM1B0 COM1C1 COM1C0 WGM11 WGM10
TCCR1B: ICNC1 ICES1 - WGM13 WGM12 CS12 CS11 CS10
TCCR3A: COM3A1 COM3A0 COM3B1 COM3B0 COM3C1 COM3C0 WGM31 WGM30
TCCR3B: ICNC3 ICES3 - WGM33 WGM32 CS32 CS31 CS30

Tabelle 0-2: Konfigurationsregister zu Timer 1 und 3

WGM13=1
WGM12=1
WGM11=0
WGM10=0
darum (Mode 12 laut Tabelle, S.123 Datenblatt):
TCCR1B |= (1<<WGM13);
TCCR1B |= (1<<WGM12);
TCCR1A &= ~(1<<WGM11);
TCCR1A &= ~(1<<WGM10);

16MHz-Quarz.

Vorteilung: 64, dann 250000Hz Zählung:
TCCR1B &= ~(1<<CS12);
TCCR1B |= (1<<CS11);
TCCR1B |= (1<<CS10);

Setzen der oberen Zählgrenze, die den Interrupt dann auslöst:
ICR1=250; //damit 1kHz Interrupt.

Setzen der Vorteilung:
16MHz Quarz.
Vorteilung 16 

Tabelle Seite 390 Datenblatt:
TIMSK1 |= (1<<ICIE1);

Code 0-2: Einstellung für CTC-Mode bei Timer1 und Interrupt "on compare match"

Aufblitzen mit 2Hz == 120BPM

void setup() 
{

  TCCR1B |= (1<<WGM13); //Mode 12
  TCCR1B |= (1<<WGM12);
  TCCR1A &= ~(1<<WGM11);
  TCCR1A &= ~(1<<WGM10);
    
  //256: 62500Hz
  TCCR1B |= (1<<CS12);
  TCCR1B &= ~(1<<CS11);
  TCCR1B &= ~(1<<CS10);
    
  ICR1=31250;  //2Hz
  
  TIMSK1 |= (1<<ICIE1); //Mode 12

  pinMode(2,OUTPUT);  
  pinMode(4,OUTPUT);  
}

void loop() 
{     
  if(TCNT1<2000)
     digitalWrite(2,1);    
  else
     digitalWrite(2,0);    
}

Code 0-3: sched003_timer1: Aufblitzen mit 2Hz == 120BPM

sched004_metronom

Umsetzung des Metronoms ohne Interrups

bool AN=false;

void setup() 
{

  TCCR1B |= (1<<WGM13); //Mode 12
  TCCR1B |= (1<<WGM12);
  TCCR1A &= ~(1<<WGM11);
  TCCR1A &= ~(1<<WGM10);
    
  //1024: 15625Hz
  TCCR1B |= (1<<CS12);
  TCCR1B &= ~(1<<CS11);
  TCCR1B |= (1<<CS10);
    
  ICR1=31250;  //0,5Hz == 4 Schläge!
//  ICR1=15000;  //0,5Hz == 4 Schläge!
  
  TIMSK1 |= (1<<ICIE1); //Mode 12

  //4 LEDs:
  pinMode(2,OUTPUT);  
  pinMode(4,OUTPUT);  
  pinMode(6,OUTPUT);  
  pinMode(8,OUTPUT);  
  
  //1 Tonausgang, der nicht auf Timer1 basieren darf!
  pinMode(13,OUTPUT);  
}

void loop() 
{     
  if(TCNT1<500)  //4 Schritte: 31250/4 == 7812.5
  {
     digitalWrite(2,1);  
     if(!AN)  
     {
        tone(13,1720);
        AN=true;
     }        
  }     
  else if(TCNT1>7812 && TCNT1<8112) //2000+7812
  {
     digitalWrite(4,1);    
     if(!AN)  
     {
        tone(13,880);
        AN=true;
     }        
  }     
  else if(TCNT1>15625 && TCNT1<15925) //2000+7812
  {
     digitalWrite(6,1);    
     if(!AN)  
     {
        tone(13,880);
        AN=true;
     }        
  }     
  else if(TCNT1>23437 && TCNT1<23737) //2000+7812
  {
     digitalWrite(8,1);    
     if(!AN)  
     {
        tone(13,880);
        AN=true;
     }        
  }     
  else
  {
     digitalWrite(2,0);    
     digitalWrite(4,0);    
     digitalWrite(6,0);    
     digitalWrite(8,0);    
     
     if(AN)
     {
         tone(13,0);
         AN=false;
     }         
  }
}

Code 0-4: sched004_metronom

Zusatz-LED bei Digital-Pin 12 durch Interrupt-Routine gesteuert

#include<avr/interrupt.h>


volatile bool AN=false;
volatile bool ZUSTAND=false;


//SIGNAL(SIG_OUTPUT_COMPARE1)
//ISR(SIG_OUTPUT_COMPARE1)
//ISR(TIMER1_COMP_vect)
ISR(TIMER1_COMPA_vect)
{
    if(ZUSTAND)
        digitalWrite(12,ZUSTAND);
    else        
        digitalWrite(12,ZUSTAND);
        
    ZUSTAND=!ZUSTAND;        
}

void setup() 
{

//  TCCR1B |= (1<<WGM13); //Mode 12
  TCCR1B &= ~(1<<WGM13); //Mode 4
  TCCR1B |= (1<<WGM12);
  TCCR1A &= ~(1<<WGM11);
  TCCR1A &= ~(1<<WGM10);
    
  //1024: 15625Hz
  TCCR1B |= (1<<CS12);
  TCCR1B &= ~(1<<CS11);
  TCCR1B |= (1<<CS10);
    
  OCR1A=31250;  //0,5Hz == 4 Schläge!
//  ICR1=31250;  //0,5Hz == 4 Schläge!
//  ICR1=15000;  //0,5Hz == 4 Schläge!
  
//  TIMSK1 |= (1<<ICIE1); //Mode 12
  TIMSK1 |= (1<<OCIE1A); //Mode 4
  sei();

  //4 LEDs:
  pinMode(2,OUTPUT);  
  pinMode(4,OUTPUT);  
  pinMode(6,OUTPUT);  
  pinMode(8,OUTPUT);  
  
  //1 Tonausgang, der nicht auf Timer1 basieren darf!
  pinMode(13,OUTPUT);  
  
  //durch Interrupt gesteuert:
  pinMode(12,OUTPUT);  
}

void loop() 
{       
  if(TCNT1<500)  //4 Schritte: 31250/4 == 7812.5
  {
     digitalWrite(2,1);  
     if(!AN)  
     {
        tone(13,1720);
        AN=true;
     }        
  }     
  else if(TCNT1>7812 && TCNT1<8112) //2000+7812
  {
     digitalWrite(4,1);    
     if(!AN)  
     {
        tone(13,880);
        AN=true;
     }        
  }     
  else if(TCNT1>15625 && TCNT1<15925) //2000+7812
  {
     digitalWrite(6,1);    
     if(!AN)  
     {
        tone(13,880);
        AN=true;
     }        
  }     
  else if(TCNT1>23437 && TCNT1<23737) //2000+7812
  {
     digitalWrite(8,1);    
     if(!AN)  
     {
        tone(13,880);
        AN=true;
     }        
  }     
  else
  {
     digitalWrite(2,0);    
     digitalWrite(4,0);    
     digitalWrite(6,0);    
     digitalWrite(8,0);    
     
     if(AN)
     {
         noTone(13);
         AN=false;
     }         
  }
}

Code 0-5: sched005_interrupt

Aufgabe

Realisieren Sie auf der Grundlage des Schedulerprogramms eine zählende Semaphore.

Diese erhält zu Beginn eine Belegung m, die den verfügbaren Plätzen im Ringpuffer entsprechen.

Sie soll dazu benutzt werden, drei verschiedene Tonhöhen, die über Taster in unregelmäßigen Abständen eingegeben werden in einem Ringpuffer zu speichern (semaphore waiting / Verbrauch von Plätzen / m--).

Solange der Puffer nicht leer ist, sollen die gespeicherten Töne in Zeitintervallen von genau 0,5 Sekunden abgespielt werden (semaphore signaling / Freigabe von Pufferplätzen / m++).