Das Problem ist eher, dass du die Flankenwechsel-Erkennung über
while (digitalRead(2) == HIGH) ; // warte hier beliebig lange machst.
Wahrscheinlich sind es die "leeren" while Schleifen die keine so gute Idee sind...
Gut erkannt

Wenn du statt dessen deine Funktion so schreibst, dass sie den vorigen Zustand mit dem aktuellen vergleicht und bei Wechsel einen Zusandszähler weiter schaltet und das bisschen DTA/B/C lesen macht, ist sie immer sofort fertig und du kannst "parallel" von loop aus beliebige andere ( genauso schnelle) Sachen machen, oder deine Zeitüberwachung, oder ...
loop ruft dann deine Funktion immer auf:
boolean oldStatus;
byte step; // Schritt-Nr für den nächsten Flankenwechsel : 0 1 2
void dtmfin()
{
boolean newStatus = digitalRead (2);
if (newStatus != oldStatus)
{
if (newStatus == HIGH)
{
switch (step)
{
case 0:
DTA = collectBits();
step = 1;
break;
case 1:
DTB = collectBits();
step = 2;
break;
case 2:
DTC = collectBits();
step = 0;
break;
}
}
oldStatus = newStatus;
}
}