Вообщето, при работе с устройствами, используются прерывания. А таймер вешается дополнительно. Это делается чтобы оперативно реагировать на данные/состояние устройства + НЕ жрать ресурсы процессора (!!!)
В Delphi особых изощрений не выйдет. Если ты используешь промещуточное API, то обычно в него закладывается работа с TimeOut. Если же такого нет, или орудуешь напрямую, то лучше сделать следующее.
1) Разбить время ожидания на мелкие порции по 10-100 милисекунд. (Многие устройства поддерживают буферизацию, даже на COM порт есть аппаратная очередь на 14-15 байт. Поэтому нет смысла опрашивать слишком часто -- берёшь в руки калькулятор и считаешь сколько ждать нужно)
2) Опрашивать состояние портов (ещё чего нибудь) и "спать" на эти милисекунды -- Нужно отдавать время другим процессам, чтобы Windows не замерзал. Тут ещё нужно не забыть, чтобы прорисовывался интерфейс самой программы
Хотя можно оставить и цикл, только внутри него запускать процедуру ... чтоб её, запамятовал

, вроде типа Application.ProcessMessages (Delphi под рукой нет, посмотреть не могу)