Спасибо ответившим.
У меня наоборот: без эха работало корректно, с эхом - хрень. При внимательном прочтении описания протокола обмена обнаружилась интересная деталь:
Короче, я подозреваю, дело у меня было вот в чём. Загрузчик воспринимает три типа строк: с '\r' на конце (ввод с клавиатуры), с '\n', и с '\r\n'. При этом, после получения '\r' он, видимо, ещё некоторое время ждёт '\n', и, если тот не приходит, считает это концом строки и начинает выдавать свой ответ. У меня программатор работает посимвольно, т.е. посылает символ - ждёт эха - сравнивает ответ - посылает следующий символ и т.д. Я думаю, что пока я дожидаюсь эха на '\r', сравниваю ответ и посылаю следующий '\n', наступает таймаут ожидания и загрузчик решает, что строка закончилась.
Всё нормально заработало, когда я стал завершать все вводимые строки не '\r\n', а '\n'. Что интересно, все известные мне программаторы заканчивают строки на '\r\n', но все они посылают команды строками, а не посимвольно. При этом ответы всегда приходят с '\r\n' на конце, независимо от вида строки запроса.
И да, без эха всё работает ощутимо быстрее. Основное время приходится на передачу данных по UART.