CopyThread Multitasking

Fra HTX Arduino
Spring til navigation Spring til søgning

Denne gennemgang er baseret på et multitasking bibliotek CopyThread[1].

Bibliotek til multitasking

Biblioteket er baseret på frivillig taskdeling, så man kan ikke lave hvad som helst i de forskellige tasks. Man skal overholde nogle simple regler for at alle tasks kører rimeligt.

Biblioteket kan installeres simpelt ved af bruge Arduino-IDE'ets Library Manager (Dropdown menu Sketch - Include Library - Manage Libraries...) og søge på CopyThread som vist her:
Installing CopyThread library with Library Manager
Installing CopyThread library with Library Manager

Specielt omkring frivillig task-deling

Der er både fordele og ulemper ved frivillig taskdeling. Fordelen er at den proces der afvikles har kontrollen over processoren, mens koden arbejder sig igennem sin del af koden. Ulempen er at man, når man skriver sit program, så SKAL man frigive processen i programforløbet, for at andre tasks kan komme til.

For at uddybe fordelen lidt, så betyder det at hvis man arbejder med fælles variabler ind over flere tasks, så skal man ikke være nervøs for at en variabel kan ændre sig midt i udførslen af en sætning. Det kan ske hvis man fx. arbejder med interrupts, som kan afbryde på et vilkårligt tidspunkt. Hvis man fx har en flere bytes variabel, så vil man kunne risikere at man henter den ene del af variablen, og at et interrupt opdateret begge bytes i variabeln, hvorefter man henter den næste del og regner med at de til sammen danner et tal, men som kan være væsentligt anderledes end det forventede. Dette vil selvfølgelig kunne ske, hvis man blander interrupt sammen med multitasking, men de forskellige tasks vil ikke afbryde andre midt i programafviklingen. Tasket får lov til at gøre sig færdigt indtil det frigiver processen.

Den fordel man har ved at man har kontrol over processen, giver så samtidigt den ulempe man må leve med, nemlig at alle tasks skal frigive processen, så andre tasks kan komme til. Det sker ved at man i alle tasks skal sørge for at man aktiverer scheduleren ved at man venter på noget. Hvis man aktivt "stjæler" alt proces-tiden, så kan de andre tasks ikke komme til.

En anden ulempe er at man må vente på at man får tildelt processortid, så hvis man laver noget der er tidskritisk, så skal man indrette alle tasks på at der bliver den fornødne tid til at afvikle den tidskritiske kode.

Simpelt eksempel med to uafhængige tråde

Ideen med dette program er at man kan have to loops kørende samtidigt, som om det er uafhængige programmer, hvor de konkret får to lysdioder til at blinke med hver sin takt, altså lidt samme princip som det helt klassiske Blink Eksempel, men bare med to lysdioder der kan programmeres til at køre med hver sin takt.

#include <Cth.h>

// Output
int grnPin = 3;  // Grøn LED, på pin 3
int redPin = 4;  // Red LED, på pin 4

// Setup, som køres efter reset eller power-up
void setup() {
  pinMode(redPin, OUTPUT);   // Set pins som output
  pinMode(grnPin, OUTPUT);

  Serial.begin(9600);  // serielt output

  Scheduler.startLoop(loop1);
  Scheduler.startLoop(loop2);
}

// Det ene loop (navngivningen er helt fri)
void loop1(void)
{
  digitalWrite(redPin, HIGH);
  Scheduler.delay(1000);
  digitalWrite(redPin, LOW);
  Scheduler.delay(1000);
  Serial.println("Rød har blinket");
}

// Det andet loop
void loop2(void)
{
  digitalWrite(grnPin, HIGH);
  Scheduler.delay(500);
  digitalWrite(grnPin, LOW);
  Scheduler.delay(250);
  Serial.println("Grøn har blinket");
}

Som det kan ses i koden, så skal vi starte med at henvise til biblioteket Cth.

Derefter defineres konstanter og der er en setup med helt standard ting, lige bortset fra at der i setup() laves to kald til Sheduler.startLoop() med en henvisning til to procedurer, der her hedder loop1 og loop2.

Resten af koden er de to loop-funktioner, hvor indholdet minder meget om Blink Eksempel, dog med den væsentlige forskel, at der anvendes Sheduler.delay() - det er en forudsætning for at scheduleren kan frigive processen i loopene, så begge loops kan afvikles.

Koden kan hentes som blink2.ino i denne ZIP-fil sammen med de andre programmer på denne side.

Et program med flere tasks

For at illustrere at programmet kan håndtere flere tasks med forskellige funktioner, så er der dette eksempel.

Der er et task der aflæser et potentiometer, og giver en delaytid ud fra det.

Der er to almindelige blink-funktioner på tid lige som før.

Endelig er der et task som skifter en lysdiode når der trykkes på en kontakt.

Dette er lavet i følgende kode:

#include <Cth.h>

// Output
int grnPin = 3;  // Green LED, connected to digital pin 3
int yelPin = 4;  // Yellow LED,  connected to digital pin 4
int redPin = 5;  // Red LED,   connected to digital pin 5
int kontLED = 6; // LED for indicationg LED state
int kontakt = 8; // Contact input from switch


// the setup function runs once when you press reset or power the board
void setup() {
  pinMode(redPin, OUTPUT);   // sets the pins as output
  pinMode(grnPin, OUTPUT);
  pinMode(yelPin, OUTPUT);
  pinMode(kontLED, OUTPUT);
  pinMode(kontakt, INPUT);

  Serial.begin(9600);  // ...set up the serial ouput

  Scheduler.startLoop(blinkRed);
  Scheduler.startLoop(blinkGrn);
  Scheduler.startLoop(blinkYel);
  Scheduler.startLoop(toggleLED);
}

void blinkRed(void)
{
  static boolean redState = false;

  int val = analogRead(A0);
  Serial.print(val);
  Serial.print(" ");

  digitalWrite(redPin, redState);
  redState = ! redState;
  val = map(val, 0, 1023, 100, 1000);
  Serial.print(val);
  Serial.println(" Red");
  Scheduler.delay(val);
}

void blinkGrn(void)
{
  static boolean grnState = false;
  digitalWrite(grnPin, grnState);
  grnState = ! grnState;
  Serial.print(millis());
  Serial.println("  Grn");
  Scheduler.delay(2000);
}

void blinkYel(void)
{
  static boolean yelState = false;
  digitalWrite(yelPin, yelState);
  yelState = ! yelState;
  Serial.print(millis());
  Serial.println("  Yel");
  Scheduler.delay(500);
}

void toggleLED(void)
{
  static boolean LEDState = false;
  static boolean lastKontakt = digitalRead(kontakt);

  if (digitalRead(kontakt)) {
    if (! lastKontakt) {
      digitalWrite(kontLED, LEDState);
      LEDState = ! LEDState;
      Serial.print(millis());
      Serial.println("  Kontakt");
    }
    lastKontakt = true;
  } else {
    lastKontakt = false;
  }
  Scheduler.delay(0);
}

Den funktion blinkRed, som er det taske der blinker ud fra et potentiometers værdi ind på A0.

I starten af funktionen er der en static variabel, som initialiseres første gang funktionen køres, og som angiver hvilket niveau der skal ud på lysdioden.

Den næste variabel er blot en lokal variabel - det er vigtigt at denne ikke erklæres static, for så ville den kun aflæse den analoge indgang første gang funktionen kaldes, og ikke siden. Der er heller ikke grund til at værdien bavares til næste kald.

De næste linjer sørger for at værdien skrives til lysdioden og at værdien vendes til næste gang, samt lidt udskrifter.

Det sidste der sker i funktionen er at scheduleren sørger for det delay der skal til. Igen her er det vigtigt at man anvender sheduleren til at give delay, for at de andre tasks også skal får program-tid.

De to funktioner blinkGrn og blinkYel er opbygget efter samme princip som blinkRed - bare med konstante tider.

Hurtig reaktion i et task

Den sidste funktion toggleLED skal kunne reagere hurtigt på et kontakt-tryk. Det ville også fungere rimeligt med et længere delay, bare det er holdt i en størrelse som brugeren ikke bemærker (fx. 10 ms), men koden her illustrerer princippet i kode som skal reagere rigtigt hurtigt.

Der er som i de andre funktioner også en variabel der indeholder niveauet af LED'en, og så en variabel med sidste niveau på kontaktindgangen.

Herefter er det lavet en traditionel detektering af en forkant, som skifter niveauet på LED'en.

Den sidste del Scheduler.delay(0) kan virke overflødig, da den bare laver et delay på 0, som man kunne tænke er unødvendigt. Det viser sig dog at den er meget vigtig, da programmet blokerer alle de andre tasks, hvis man udelader den, så kaldet til Shedulers delay er den som overgiver processoren til de andre tasks.

Koden kan hentes som thread.ino i denne ZIP-fil sammen med de andre programmer på denne side.

Kode der venter på en begivenhed

En anden måde at få et task til at reagere hurtigt på et signal som fx en kontakt, er ved at man kan lade koden vente på en sådan hændelse ud fra resultatet af en funktion.

Dette er illustreret i følgende kode:

#include <Cth.h>

// Output
int grnPin = 3;  // Grøn LED, på pin 3
int redPin = 4;  // Red LED, på pin 4
int kontakt = 8; // Kontakt input på pin 8

// Setup, som køres efter reset eller power-up
void setup() {
  pinMode(redPin, OUTPUT);   // Set pins som output
  pinMode(grnPin, OUTPUT);
  pinMode(kontakt, INPUT);

  Serial.begin(9600);  // serielt output

  Scheduler.startLoop(loop1);
  Scheduler.startLoop(kontaktLoop);
}

// Det ene loop (navngivningen er helt fri)
void loop1(void)
{
  digitalWrite(redPin, HIGH);
  Scheduler.delay(1000);
  digitalWrite(redPin, LOW);
  Scheduler.delay(1000);
  Serial.println("Rød har blinket");
}

int buttonPressed(void) {
  return digitalRead(kontakt);
}

int buttonNotPressed(void) {
  return ! digitalRead(kontakt);
}

void kontaktLoop(void) {
  static boolean grnState = false;
  Serial.println("Venter på kontakt");
  Scheduler.wait(buttonPressed);
  Serial.println("Tryk på kontakt");
  grnState = ! grnState;
  digitalWrite(grnPin, grnState);
  Scheduler.wait(buttonNotPressed);
}

loop1() laver blot en blink hvert andet sekund.

kontaktLoop() gør det at den venter på at der trykkes på en kontakt - dette er lavet ved hjælp af en funktion buttonPressed(), som blot returnerer niveauet af kontakten. Når der trykkes på kontakten vippes niveauet af en udgang, og den stiller sig til at vente på at der ikke er trykket ved en ny funktion buttonNotPressed().

Koden kan hentes som kontakt.ino i denne ZIP-fil sammen med de andre programmer på denne side.

Kode der venter på serielt input

I sheduleren kan man også vente at der er modtaget karakterer på det serielle input. Dette illustreres i følgende kode:

#include <Cth.h>

// Output
int grnPin = 3;  // Grøn LED, på pin 3
int redPin = 4;  // Red LED, på pin 4

// Setup, som køres efter reset eller power-up
void setup() {
  pinMode(redPin, OUTPUT);   // Set pins som output
  pinMode(grnPin, OUTPUT);

  Serial.begin(9600);  // serielt output

  Scheduler.startLoop(loop1);
  Scheduler.startLoop(serialLoop);
  Serial.println("Send 0/1 for at tænde eller slukke");
}

// Det ene loop (navngivningen er helt fri)
void loop1(void)
{
  digitalWrite(redPin, HIGH);
  Scheduler.delay(1000);
  digitalWrite(redPin, LOW);
  Scheduler.delay(1000);
  Serial.println("Rød har blinket");
}

void serialLoop(void) {
  Scheduler.wait_available(Serial);
  char txt = Serial.read();
  if (txt == '0') {
    digitalWrite(grnPin, LOW);
    Serial.println("LED slukket");
  }
  if (txt == '1') {
    digitalWrite(grnPin, HIGH);
    Serial.println("LED Tændt");
  }
}

loop1() er stadig bare et task der blinker med en lysdiode.

serialLoop() venter på at der kommer en karakter på den serielle port, og når der er noget, så læses en karakter. Der testes om denne karakter er '0' eller '1', hvor den så hhv. slukker eller tænder en lysdiode ud fra det.

Rent faktisk vil loopet køre 3 gange, hvis der i sendingen også er CR og LF (ny linje), men de to ekstra karakterer vil ikke have nogen funktion.

Koden kan hentes som serial.ino i denne ZIP-fil sammen med de andre programmer på denne side.

Kode der ikke looper

Til nogle funktioner kan det være fint at kunne starte et task, som kører et stykke tid og så lukker ned igen.

Dette er illustreret i følgende kode:

#include <Cth.h>

// Output
int grnPin = 3;  // Grøn LED, på pin 3
int redPin = 4;  // Red LED, på pin 4
int kontakt = 8; // Kontakt input på pin 8

// Setup, som køres efter reset eller power-up
void setup() {
  pinMode(redPin, OUTPUT);   // Set pins som output
  pinMode(grnPin, OUTPUT);
  pinMode(kontakt, INPUT);

  Serial.begin(9600);  // serielt output

  Scheduler.startLoop(loop1);
  Scheduler.startLoop(kontaktLoop);
}

// Det ene loop (navngivningen er helt fri)
void loop1(void)
{
  digitalWrite(redPin, HIGH);
  Scheduler.delay(1000);
  digitalWrite(redPin, LOW);
  Scheduler.delay(1000);
  Serial.println("Rød har blinket");
}

int buttonPressed(void) {
  return digitalRead(kontakt);
}

int buttonNotPressed(void) {
  return ! digitalRead(kontakt);
}

void blink(int antal) {
  while (antal > 0) {
    digitalWrite(grnPin, HIGH);
    Serial.print("Blink ");
    Serial.println(antal);
    Scheduler.delay(500);
    digitalWrite(grnPin, LOW);
    Scheduler.delay(500);
    antal--;
  }
}

void kontaktLoop(void) {
  Serial.println("Venter på kontakt");
  Scheduler.wait(buttonPressed);
  Serial.println("Tryk på kontakt");
  Scheduler.start(blink, 5);
  Scheduler.wait(buttonNotPressed);
}

Her kører loop1() selvstændigt og blinker med en lysdiode. Dette task looper på normal vis.

I kontaktLoop() ventes der på et kontakt-tryk i et normalt loop. Når der kommer et kontakttryk, så startes et nyt task blink(), som blinker det antal gange der sendes med til schedulerens start. Når tasket har afsluttet det ønskede antal blink, så afsluttes det og nedlægges af scheduleren.

Der kan faktisk køre flere versioner af blink() samtidigt, hvilket er illustreret ved udskriften i Serial Monitor.

Koden kan hentes som startThread.ino i denne ZIP-fil sammen med de andre programmer på denne side.

Samarbejde mellem tasks

En måde at få flere tasks til at arbejde sammen er ved at lade deres opførsel blive bestemt af indholdet i variabler.

Dette illustreres i følgende kodeeksempel:

#include <Cth.h>

// Output
int grnPin = 3;  // Grøn LED, på pin 3
int redPin = 4;  // Red LED, på pin 4
int yelPin = 5;  // Gul LED, på pin 5
int kontaktPin = 8; // Kontakt input på pin 8

int antalBlink = 0;
boolean blinking = false;

Her defineres blot CopyThread multitasking og de pins der anvendes.

antalBlink er en variabel der siger hvor mange blink der er tilbage inden der stoppes med at blinke på den grønne LED.

blinking er en variabel der fortæller om den gule LED skal blinke.

// Setup, som køres efter reset eller power-up
void setup() {
  pinMode(redPin, OUTPUT);   // Set pins som output
  pinMode(grnPin, OUTPUT);
  pinMode(yelPin, OUTPUT);

  Serial.begin(9600);  // serielt output

  Scheduler.startLoop(loop1);
  Scheduler.startLoop(serialLoop);
  Scheduler.startLoop(kontaktLoop);
  Scheduler.startLoop(blinkLoop2);
  Scheduler.startLoop(blinkLoop3);
  Serial.println("Send antallet af blink");
}

Setup() definerer pins som output og sætter den serielle port i gang, så der kan modtages og skrives ud.

Der startes 5 forskellige loops.

// Det ene loop (navngivningen er helt fri)
void loop1(void)
{
  digitalWrite(redPin, HIGH);
  Scheduler.delay(1000);
  digitalWrite(redPin, LOW);
  Scheduler.delay(1000);
  Serial.println("Rød har blinket");
}

loop1() kører helt selvstændigt og blinker langsomt med den røde LED.

void serialLoop(void) {
  Scheduler.wait_available(Serial);
  int temp = Serial.parseInt();
  if (temp > 0) {
    antalBlink = temp;
    Serial.println(antalBlink);
  }
}

void blinkLoop2(void) {
  if (antalBlink <= 0) {
    Scheduler.delay(10);
  } else {
    digitalWrite(grnPin, HIGH);
    Scheduler.delay(300);
    digitalWrite(grnPin, LOW);
    Scheduler.delay(300);
    antalBlink--;
    Serial.print("Blink: ");
    Serial.println(antalBlink);
  }
}

serialLoop() venter på input fra den serielle port, og den tager talindholdet i inputtet og gemmer i antalBlink.

blinkLoop2() tjekker variablen antalBlink om der skal blinkes, ellers ventes der lidt inde der tjekkes igen. Hvis der er et antal blink, så blinkes en gang med den grønne LED, og antalBlink tælles en ned. På denne måde blinker der det antal gange der er indlæst i serial Monitor.

int isBlinking(void) {
  return blinking;
}

void blinkLoop3(void) {
  Scheduler.wait(isBlinking);
  digitalWrite(yelPin, HIGH);
  Scheduler.delay(250);
  digitalWrite(yelPin, LOW);
  Scheduler.delay(250);
}

int kontaktPressed(void) {
  return digitalRead(kontaktPin);
}

void kontaktLoop(void) {
  Scheduler.wait(kontaktPressed);
  blinking = ! blinking;
  if (blinking) {
    Serial.println("Start på Blink");
  } else {
    Serial.println("Stop på Blink");
  }  
  // de-bounce 500ms
  Scheduler.delay(500);
}

kontaktLoop() venter på input fra kontaktPressed(), som viser om der er trykket på kontakt. Når der kommer et kontakttryk, så vendes niveauet af blinking.

blinkLoop3() kigger på blinking variablen (via isBlinking) om der skal blinkes eller ej. Dette gøres for hvert "hele" blink med den gule LED, så den vil altid ende slukket.

Koden kan hentes som samarbejde.ino i denne ZIP-fil sammen med de andre programmer på denne side.

Tidskritiske ting i tasks

Hvis man vil lave noget i programmering, som er rigtigt tidskritisk, så vil det være en god ide at anvende interrupt, selvom det er noget mere kompliceret. Man kan dog stadig lave tinge der er mere tidskritiske, hvis man laver tingene rigtigt, men der vil kommer begrænsninger.

Dette eksempel viser hvordan man kan få to afstandsmålere til ar arbejde i samme program, så man kan måle to forskellige afstande.

Det fysiske måleprincip sætter en begrænsning her, så man må ikke lade dem måle samtidigt, da det er ultralyd den måler med, og hvis man ikke lader lyden "klinke af", så vil den ene kunne fejlmåle et ekko fra den anden. Dette er her løst ved at have en variabel, som angiver hvilken afstandsmåler der må måle.

Den første del af koden er blot henvendelse til multitaksing biblioteket samt definition af de ben der anvendes i programmet.

#include <Cth.h>

// Output
byte trigPin1 = 3;  // Trigger pin til afstandsmåler 1
byte echoPin1 = 4;  // Echo pin til afstandsmåler 1
byte trigPin2 = 5;  // Trigger pin til afstandsmåler 2
byte echoPin2 = 6;  // Echo pin til afstandsmåler 2
byte redPin = 9;    // rød LED pin

De næste er variabler til at registrere målingerne med, hvor det laves to arrays til at midle værdierne med, som hver er 10 lange, og der er variablen, som angiver hvilken afstandsmåler der må være aktiv. Der er også pointere til at angive hvor den næste måling skal lagres og en variabel til middelværdierne af begge målinger:

const byte antalAvg = 10;
byte aktivMaaling = 0;

unsigned long maal1 [antalAvg];
unsigned long maal2 [antalAvg];
byte index1 = 0;
byte index2 = 0;
long avg1;
long avg2;

setup() sætter benene op til input og output og starter de forskellige tasks der anvendes:

// Setup, som køres efter reset eller power-up
void setup() {
  pinMode(trigPin1, OUTPUT);   // Set pins som output og input
  pinMode(echoPin1, INPUT);
  pinMode(trigPin2, OUTPUT);   
  pinMode(echoPin2, INPUT);
  pinMode(redPin, OUTPUT);   

  Serial.begin(9600);  // serielt output

  Scheduler.startLoop(avgLoop);
  Scheduler.startLoop(loop1);
  Scheduler.startLoop(maalLoop1);
  Scheduler.startLoop(maalLoop2);
  Serial.println("Illustration af tidskritisk måling");
  aktivMaaling = 1;
}

loop1() er et helt selvstændigt task, som blot blinker med en LED - det har ikke noget med resten at gøre.

// Det ene loop (navngivningen er helt fri)
void loop1(void)
{
  digitalWrite(redPin, HIGH);
  Scheduler.delay(1000);
  digitalWrite(redPin, LOW);
  Scheduler.delay(1000);
  // Serial.println("Rød har blinket");
}

avgLoop() tæller de to arrays sammen og beregner middelværdien af de 10 målinger der ligger i hvert array. Ideen er at man kan se hvor meget hver enkelt måling afviger fra gennemsnittet. En anden funktion som dette task har er at det tager noget tid at gennemføre beregningen - en måling har registreret omkring 120 us, så gennemløb af dette task vil kunne forstyrre en måling. Der er slet ingen grund til det, men der laves kun et delay på 3 ms mellem hver beregning - det er for at øge sandsynligheden for at tasket forstyrrer en måling.

trig() er blot en hjælpefunktion til afstandsmåleren, som kan sende en triggerpuls til en pin. Dette bruges til at starte lydmålingen med.

void avgLoop(void) {
  long sum1 = 0;
  long sum2 = 0;
  long tid = micros();
  for (byte n = 0; n < antalAvg; n++) {
    sum1 += maal1[n];
    sum2 += maal2[n];
  }
  avg1 = sum1 / antalAvg;
  avg2 = sum2 / antalAvg; 
  Scheduler.delay(3);
}

void trig(byte pin) {
  digitalWrite(pin, HIGH);
  delayMicroseconds(20);
  digitalWrite(pin, LOW);
}

maalLoop1 er det måleloop, som opfører sig "pænt" over for de andre tasks i sit måleprincip , ved at tasket frigiver processen mens der måles. Der ventes først på at tasket har lov til at køre sin måling. Dette testes med active1(). Når det er tilladt at måle, så trigges der, og for en sikkerheds skyld, så ventes der på at echoPin1 går høj. Når den er det, så registreres starttiden, og derefter ventes på echoFinished1(), som indikerer at echoPin1 er blevet lav igen. Umiddelbart efter registrere hvor lang tid der er gået i pulsen.

Problemet i denne målemetode er, at et andet task kan bryde ind i måleperioden, og dermed forlænge den målte tid. Det kan fx være avgLoop() der laver denne afbrydelse.

Tiden lægges ind i arrayet og der udskrives middelværdien beregnet i avgLoop() og forskellen mellem den målte tid og gennemsnittet af de sidste 10 måinger. Når det er gjort holdes en passende pause, som gør at lydpulsen kan "klinge ud", inden der gives lov til at den anden afstandsmåler man lave sin måling.

int active1(void) {
  return aktivMaaling == 1;
}

int echoFinished1(void) {
  return ! digitalRead(echoPin1);
}

void maalLoop1(void) {
  Scheduler.wait(active1);
  trig(trigPin1);
  while (! digitalRead(echoPin1)) ;
  long tid = micros();
  Scheduler.wait(echoFinished1);
  tid = micros() - tid;
  maal1[index1] = tid;
  index1++;
  index1 %= antalAvg;
  Serial.print(avg1);
  Serial.print(" : ");
  Serial.print(tid - avg1);
  Serial.print("\t");
  Scheduler.delay(150);
  aktivMaaling = 2;
}

Den anden afstandsmåler bruger et andet princip i maalLoop2() til at måle tiden med. Der ventes på samme måde på lov, og en pin trigger afstandsmåleren. Herefter anvendes pulseIn(), som er en indbygget Arduino funktion til at måle længden af pulsen. pulseIn() frigiver ikke tasket, så andre tasks må vente på at få lov til at køre. Dette burde give en mere præcis måling. Resten af koden i loopet fungerer på samme måde som det første måleloop, der får overgivet kontrollen efter at lyden er "klinget ud".

int active2(void) {
  return aktivMaaling == 2;
}

void maalLoop2(void) {
  Scheduler.wait(active2);
  trig(trigPin2);
  long tid = pulseIn(echoPin2, HIGH, 33000);
  maal2[index2] = tid;
  index2++;
  index2 %= antalAvg;
  Serial.print(avg2);
  Serial.print(" : ");
  Serial.println(tid - avg2);
  Scheduler.delay(150);
  aktivMaaling = 1;
}

En udskrift i Serial monitor viser at det fungerer noget i den retning - det er dog ikke helt konsekvent, da der også er variationer i i selve tidsmålingen. Noget skyldes også at micros(), som ligger bag begge måleprincipper har en opløsning på 4 us, så hvis både start og slut ligger uheldigt, så kan de alene give variation op til 8 us. Udsvingene kan dog ses at være størst på den første måling.

1301 : -1	1248 : 1
1301 : -1	1248 : 1
1301 : 3	1248 : 0
1302 : -2	1248 : 0
1302 : 6	1248 : 1
1302 : 2	1248 : 3
1302 : -2	1248 : 3
1302 : -2	1249 : 3
1302 : 2	1249 : 2
1302 : -14	1249 : 2
1300 : 8	1249 : -1
1301 : -1	1249 : 0
1301 : -1	1249 : 26
1301 : -1	1252 : -3
1301 : 7	1252 : -4
1301 : 3	1252 : -4
1301 : -1	1252 : -4
1301 : -41	1251 : -3
1297 : 3	1251 : -2
1296 : -8	1251 : -3
1296 : 12	1251 : -2
1296 : 4	1251 : -3
1296 : 4	1251 : -2
1296 : 8	1248 : -1
1297 : -9	1248 : 1
1295 : 13	1248 : 1
1295 : 5	1248 : 0
1295 : 5	1248 : 0
1299 : 5	1248 : 1
1300 : 4	1248 : 1
1301 : 3	1248 : 1
1301 : 3	1248 : 0
1301 : 3	1248 : 1
1302 : 2	1248 : 0
1302 : 30	1248 : 1
1306 : -6	1248 : 1
1305 : -1	1248 : 1
1306 : -6	1248 : 0
1306 : -6	1248 : 0
1305 : -5	1248 : 0
1305 : -1	1248 : 1
1305 : 39	1248 : 0
1309 : -5	1248 : 0
1309 : -5	1248 : 0
1309 : -9	1248 : 1
1306 : 2	1248 : 0
1306 : -6	1248 : 1
1306 : -6	1248 : 27
1306 : -6	1251 : -3
1306 : -6	1251 : -3
1306 : -6	1251 : -3
1306 : -6	1250 : -2
1301 : 3	1250 : -2
1301 : 3	1250 : -2
1301 : -1	1250 : 0
1301 : 7	1251 : 0
1301 : 3	1251 : 0
1302 : 2	1251 : 0
1302 : -2	1249 : 2
1302 : 2	1249 : 2
1302 : 2	1249 : 2
1303 : -3	1250 : 1
1303 : -3	1250 : 1
1302 : -2	1250 : 1
1302 : -2	1250 : -1

Koden kan hentes som tidsKritisk.ino i denne ZIP-fil sammen med de andre programmer på denne side.

Referencer