Вот две строки, я гений, прочь сомненья
Даешь восторги, лавры и цветы… 
Данный пост посвящен довольно таки старой задаче о считывании таймера, с которой лично я ознакомился в книге Джека Гансли (The Art of Designing Embedded Systems (Second Edition), (2008) by Jack Ganssle) в которой рассматривается борьба с гонками в асинхронных устройства. Сформулирована проблема и показаны 4 способа ее решения (2 неправильных и 2 правильных), рассмотрены их недостатки, в общем, добротная работа в стиле Джека (я к нему отношусь очень хорошо). К сожалению, на мой взгляд, даже работающие решения не имели должной степени элегантности, но более красивое долго не приходило в голову, а вчера неожиданно осенило. Так что я считаю себя вправе изложить данную проблему в ее историческом контексте, поскольку придумал очень элегантное решение (сам себя не похвалишь, весь день ходишь как оплеванный).
Формулируем задачу: у нас имеется аппаратный счетчик, который засинхронизирован от некоторой сиcтемной частоты и может быть использован для измерения времени. Однако, в силу аппаратных ограничения, его вместимости недостаточно для формирования продолжительных промежутков времени, поэтому создается программное расширение разрядности счетчика. Реализуется оно следующим образом — при переполнении счетчика происходит прерывание и подпрограмма обработки прерывания модифицирует некую глобальную переменную, которая совокупно с самим счетчиком образует расширенный счетчик времени, что-нибудь вроде
unsigned int High; 
interrupt TimerOv(void) {
   High++;
} unsigned long int GetTimer(void) { 
   return (High<<sizeof(int))+ ReadReg(TimerCounter);
}Есть очевидное решение проблемы — на время считывания остановить аппаратную часть таймера, но такое решение неприемлемо из-за влияния на подсчитываемое время.
Попробуем другое очевидное решение — запретить на время считывания прерывания и мы получаем
 unsigned long int GetTimer(void) { 
   DisableInt();
   unsigned long Tmp=(High<<sizeof(int))+ ReadReg(TimerCounter);
   EnableInt();
   return Tmp;
}Возможная модификация данного решения приводит к следующему коду
 unsigned long int GetTimer(void) { 
   DisableInt();
   unsigned long TmpH=High;
   unsigned long TmpL=ReadReg(TimerCounter);
   if (TimerOvBit()) {
      TmpH++;
      TmpL=ReadReg(TimerCounter);
    };
   EnableInt();
   TmpH=(TmpH<<sizeof(int))+ TmpL;
   return Tmp;
}Да подобное решение существует и найдено Джеком и мной (честное слово, самостоятельно). Код этого решения следующий
 unsigned long int GetTimer(void) { 
   unsigned long TmpH,TmpL;
   do {
       TmpH=High;
       TmpL= ReadReg(TimerCounter);
   while (TmpH!=High);
   return (TmpH<<sizeof(int))+TmpL;
}И вот тут придумана небольшая модификация данного алгоритма, а именно
 unsigned long int GetTimer(void) { 
   unsigned long TmpH,TmpL;
   TmpH=High;
   TmpL= ReadReg(TimerCounter);
   if (TmpH!=(TmpH=High)) TmpL= ReadReg(TimerCounter);
   return (TmpH<<sizeof(int))+TmpL;
}Как мне совершенно верно указали в комментах, компилятор может неправильно скомпилить ключевую строку алгоритма, поэтому вот исправленная версия
 unsigned long int GetTimer(void) { 
   unsigned long TmpHFirst,TmpH,TmpL;
   TmpHFirst=High;
   TmpL= ReadReg(TimerCounter);
   if (TmpHFirst!=(TmpH=High)) TmpL= ReadReg(TimerCounter);
   return (TmpH<<sizeof(int))+TmpL;
}Почему данное решение не было найдено мною раньше (в литературе я его тоже не встречал, может быть, просто не попадалось), совершенно непонятно, ведь все кристально просто и ясно, видимо, сказывалась определенная зашоренность. Почему оно работает, предоставляю рассмотреть читателю, но точно работает, я контр-примера найти не смог, если Вам удастся это сделать, прошу в комменты.
И в заключение еще один интересный момент. Нас не интересует время, как таковое, обычно мы используем текущее время для того, чтобы выставить относительно него некоторый момент в будущем, и по достижении него что-либо сделать. Так вот, по Вашему мнению, являются ли следующие две строки эквивалентными?
 unsigned long TmpWait;
   TmpWait=GetTimer()+SomeDelay;
   while (TmpWait > GetTimer()) ; /* первая строка */ 
   while ((TmpWait - GetTimer()) > 0) ; /*вторая строка */
}Комментарии (39)
 - sguwenka21.12.2015 17:21- 1. volatile в определении переменных не указываете — тоже очевидно, опустим? ;) 
 2. А если в нашей «высоконагруженной системе» бомбанёт ряд длительных прерываний вот в этом месте?- if (TmpH!=High) TmpL= ReadReg(TimerCounter); … а таймер будет достаточно скоростным — имеем реальный риск всё же неверно считать данные.
 
 Я бы крайне рекомендовал, если синхронизация настолько важна, всё же выключать прерывания на время считывания таких критичных значений. Конструкция проще, вероятность допустить ошибку меньше.- GarryC21.12.2015 18:14- В том то и дело, что все равно мы считаем правильные данные. Да, они будут отставать от текущего значения, но они НЕ будут невалидными, в этом вся фишка. - MacIn21.12.2015 23:01+3- Нет. Решение Джека Гансли — bulletproof, ваше — выдает правильное значение с некоторой веротяностью, большей, чем для решения «в лоб» в самом начале статьи. 
 - if (TmpH!=High) TmpL= ReadReg(TimerCounter);
 Допустим, вы считываете новое значение High, т.к. оно отличается от предыдущего, мы принимаем решение считать по новой TmpL. Теперь допустим, мы классически вываливаемся после принятия решения, но до считывания TmpL. В это время High опять обновляется. Теперь мы возвращаемся, считываем TmpL и имеем некорректную пару — старый TmpH и новый TmpL. - ProLimit22.12.2015 01:41- Обычно таймер времени переполняется гораздо дольше, чем самое долгое прерывание. Как мне кажется, этот алгоритм для таких случаев, но это надо специально отметить в описании. - MacIn22.12.2015 01:55- Разумеется, описанный алгоритм покрывает все более-менее реальные случаи. Тем не менее, алгоритм с циклом ничем не хуже и покрывает все. - GarryC22.12.2015 09:18- Адгоритм с циклов может выполняться значительное время, что Джек и указал. 
 Мой алгоритм работает для всех случаев, я пропустил одно присваивание в тексте, теперь исправлено, и он дает правильный результат во всех случаев. Я пост дополнил более расширенным вариантом. - izyk22.12.2015 10:11- Да не работает ваш алгоритм в определенных условиях. Например, если значение High может измениться более чем один раз во время вызова функции (см. мои комментарии и мой вариант ниже): 
 - unsigned long int GetTimer(void) { unsigned long TmpHFirst,TmpH,TmpL; TmpHFirst=High; TmpL= ReadReg(TimerCounter); if (TmpHFirst!=(TmpH=High)) { //Вот тут произошло прерывание, изменилось значение High, а вы уже считали старое. TmpL= ReadReg(TimerCounter); // Считываете значение соответствующее новому значению High. } return (TmpH<<sizeof(int))+TmpL; }
 - MacIn22.12.2015 15:56+1- Алгоритм с циклом может выполняться значительное время, что Джек и указал. 
 Не-а. То-есть, конечно, может, но какова вероятность того, что у нас фазы будут так совпадать? Сдается мне, гораздо ниже, чем длительное прерывание и т.д. (см. критику выше). Или ниже, где izyk еще раз описал то же самое.
 
 
 
 - GarryC22.12.2015 09:53- Эта пара ( старый TmpH и новый TmpL) является абсолютно корректной. Она не отражает текущее состояние счетчика, но гарантировано возвращает правильное время, то есть не прошлое и не будущее, а одно из настоящих, а это — главное. - MacIn22.12.2015 15:58- Нет. У вас может high измениться несколько раз между проверкой условия и считыванием low (теоритически, если таймер очень скорострельный). В итоге у вас high от одной итерации, low — от совсем другой. - MacIn22.12.2015 16:06- Представьте: 
 1) вы считываете high, он равен 10
 1.5) считываете low, он равен 50
 2) считываете high повторно, он равен 11
 3) принимаете решение перечитать low
 4) вываливаетесь
 5) high изменяется на 12
 6) low изменяется на 38
 7) считываете low
 получаем: high = 11, low = 38, хотя на самом деле пара — 12/38.
 Практически — я согласен — вы правы, эту «случайную» пару можно считать «настоящим» временем. И в практической реализации это действительно нормально. Просто алгоритм с циклом абсолютно корректен. Ваш — компромиссный.- GarryC23.12.2015 08:50- Рассмотрим любой из первых двух алгоритмов. Вот они отработали, получили текущее время, разрешили прерывание (тот, кто запрещал), произошло прерывания, счетчик убежал вперед и мы получаем то же самое, 11/38, хотя на самом деле пара 12/25 (к примеру). В высоко-нагруженной системе вернуть истинное время просто невозможно, и здесь ничего не поделать. Главное, чтобы полученное значение не было артефактом. 
 В UNIX системах так и пишут, что задание задержки гарантирует только то, что она не истечет ДО требуемого момента, а вот на сколько может его превзойти — не определено.
 НО если мы правильно используем полученные данные в стиле следующего псевдокода, то все хорошо
 1) выставляем (к примеру) 1 на выходе,
 2) считываем текущее время,
 3) прибавляем к нему требуемую задержку,
 … делаем что-то
 4) читаем текущее время и сравниваем с границей,
 5) если граница перейдена, снимаем 1 с выхода,
 то все три верных решения нам гарантируют, что 1 простоит на выходе минимум задержку времени.
 Обратите внимание на последовательность 1 и 2 — именно так, иначе неверно.
  - izyk23.12.2015 11:01- 1) выставляем (к примеру) 1 на выходе, 
 2) считываем текущее время (реально сейчас, на момент возврата из функции, 12.02, а мы считываем 11.02), но единица установлена уже не меньше чем 1.00,
 3) прибавляем к нему требуемую задержку (к примеру 0.30, получаем 11.32),
 … делаем что-то
 4) читаем текущее время и сравниваем с границей (реально сейчас 12.14, и мы считываем 12.14),
 5) если граница перейдена, снимаем 1 с выхода (12.14 > 11.32, снимаем 1 с задержкой как минимум 0.12+1.00=1.12 вместо 0.30).
 
 Но позвольте, какое отношение это имеет к ОСРВ. Если вас устраивает такая точность, тогда весь код должен быть(повторюсь):
 - unsigned long int GetTimer(void) { return (High<<sizeof(int))+0; }- GarryC23.12.2015 11:30- Нет нет и еще раз нет, Вы демонстрируете непонимание проблемы. 
 Главное требование — Вы не должны получить время меньше входа в процедуру и время после выхода из нее.
 Потому что в этом случае вы не гарантируете минимальную задержку. Именно вокруг этого и идет речь.
 Нет ни одной даже ОСРВ, которая выдаст Вам точное время в условиях сильной загрузки, не надо обольщаться. И если посмотрите описание любой ОСРВ, она гарантирует отклонение не более, чем заданное, причем именно вверх. - izyk23.12.2015 11:46- del - GarryC23.12.2015 12:03- ? Я Вас убедил?  - izyk23.12.2015 12:49- Кажется, я начинаю понимать, о чем вы. 
 Но какой смысл в такой процедуре, зачем вам вообще значение «TimerCounter», если ваша система не гарантирует точность меньше max(TimerCounter), достаточно значения High. А если вы гарантируете такую точность, то входными данными для данной процедуры должно быть — во время ее выполнение возможно только одно прерывание таймера.
 И это надо проверять на случай сбоя.
 
 Если сравнивать вашу процедуру с процедурой из книжки, в случае одного прерывания во время выполнения они эквивалентны. А в случае нескольких прерываний, более эффективна, т.к. возвращает первое подходящее значение.
 Но во втором случае это означает, что ваша система не гарантирует точность меньше max(TimerCounter). И ее (процедуру) можно упростить.
 
 - unsigned long int GetTimer(void) { return (High<<sizeof(int))+0; }- GarryC23.12.2015 15:02- Да, Вы правы, можно, такой и был первый вариант, но мы заведомо ухудшаем точность, причем за совсем слабый выигрыш. 
 
 
 
 
 
 
 
 
 
 
 
 
 - ToSHiC21.12.2015 19:15+4- Кажется, в вашей реализации баг. 
 
 Допустим, High = 10, счётчик переполняется при 1000.
 
 - TmpH=High; // = 10 TmpL= ReadReg(TimerCounter); // = 999 // тут случилось прерывание и High заинкрементилось if (TmpH!=High) TmpL= ReadReg(TimerCounter); // TmpL = 001 return (TmpH<<sizeof(int))+TmpL; // Вернули 10.001
 
 В оригинальном алгоритме вся мякотка заключается в while цикле — данные нижней части считаются валидными только в том случае, если верхняя за момент вычитывания не изменилась. У вас верхняя не перечитывается.- GarryC21.12.2015 22:23- Точно, спасибо большое, немного неправильно записал, конечно верхняя часть перечитывается, сейчас подправлю в тексте, но перечитывается 1 раз, без цикла. Еще раз спасибо, подправил.  - ToSHiC22.12.2015 00:13- Тогда не сработает, возникнет прерывание, которое будет длиться дольше, чем 1 цикл переполнения. На подобные штуки нужно смотреть с другой стороны (тут разбираю вариант с while циклом): 
 1. Идеальный случай, никакого переполнения. Избыточность — 1 команда чтения, 1 команда сравнения, 1 команда условного перехода, которая сфейлится. Вероятность такого исхода зависит от конкретной программы, других возможных прерываний, скорости переполнения счётчика.
 2. Неидеальный случай, который будет случаться иногда — дополнительно 1 чтение из памяти и одно чтение из регистра счётчика к варианту 1.
 3. Плохой случай, если много прерываний, или они длинные. В этом случае придётся перечитывать много раз. Может случиться, если этот кусок кода имеет совсем уж низкий приоритет. Но работать будет.
 
 Ваш вариант (в котором был баг) с точки зрения 1 и 2 случаев эквивалентен, но сфейлится в случае 3 (и ещё если просто очень долгое прерывание было). При этом с точки зрения количества исполненных инструкций он идентичен, разница только в команде, на которую будет указывать условный переход. На самом деле, вариант с while мне больше нравится даже с эстетической точки зрения.
 
 
 - imwode21.12.2015 22:08+2- Меня смущает сочетание «аппаратный счетчик» и sizeof(int) 
 Во всяких микроконтроллерах, как правило, соседствуют таймеры разной разрядности, а sizeof(int) зависит от компилятора. Можно неслабо так нарваться…- GarryC21.12.2015 22:20- Ну здесь подразумевалось, что таймер разрядностью в слово. Если меньше, то тоже не страшно, а вот если больше, то можно было бы и нарваться. У Джека было просто 16, это я слегка перестраховался.  - imwode21.12.2015 23:15+1- Не, эта какая-то негодная и опасная перестраховка непонятно от чего непонятно зачем :-) 
  - encyclopedist22.12.2015 02:30+1- sizeof(int) выдаёт размер инта в байтах (точнее, в символах, sizeof(char) == 1 по определению), а не в битах. Поэтому для сдвига его надо умножить на CHAR_BITS. 
 
 
- Amomum21.12.2015 22:28- TmpWait=GetTimer()+SomeDelay; 
 while (TmpWait > GetTimer()); /* первая строка */
 while ((TmpWait — GetTimer()) > 0); /*вторая строка */
 Насколько я понимаю, первая строка неправильная, т.к. не учитывает переполнение, которое могло случится при вычислении TmpWait. - Ocelot21.12.2015 22:39+1- Они обе не учитывают переполнение, а во второй еще и вычитаются две беззнаковые переменные, так что результат тоже будет беззнаковый. И выражение (0-1) там даст 0xFFFFFFFF, а не (-1), как ожидалось. - Amomum21.12.2015 23:14+1- Вероятно, вы правы. Я обычно пишу 
 - prevTime = GetTime(); while( GetTime() - prevTime < delay );
 Сходу не могу сообразить, но вроде бы вторая строка этому не эквивалентна; не помню досконально правила неявного приведения типа. - Ocelot21.12.2015 23:40- Ноуп! Тут тоже ошибка. Допустим, prevTime=900, delay=200, а GetTime() переполняется на 1000. Тогда выход из цикла произойдет сразу после переполнения, через 100 тиков вместо ожидаемых 200.
 
 UPD: нет, туплю, все вроде ок.
 
 
 
 - izyk22.12.2015 02:05- Если в вашей системе возможно что-то отличное от следующего: 
 - unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; TmpH=High; TmpL= ReadReg(TimerCounter); if ( TmpH != High || TmpH++ != High ) exit(error); return (TmpH<<sizeof(int))+TmpL; }
 то значение «TimerCounter» для вас не должно иметь смысла(ИМХО). И вы его можете просто не учитывать (выполнять округление). - izyk22.12.2015 02:52- Накосячил. Более правильно, наверное, так. 
 - unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; TmpH=High; TmpL=ReadReg(TimerCounter); if ( TmpH+1 == High ) TmpL=ReadReg(TimerCounter); if ( TmpH != High || TmpH++ != High ) exit(error); return (TmpH<<sizeof(int))+TmpL; }
 Многовато if, но вроде должно работать. Заодно своеобразный watchdog получился.
 А если значение High во время вызова функции может увеличится более чем один раз, и это нормально, тогда функция должна быть такой:
 - unsigned long int GetTimer(void) { return (High<<sizeof(int))+0; // Или просто return High; }
 Но это мое мнение. И вполне могу предположить что оно не верное. - izyk22.12.2015 14:35+1- Не, мой вариант не правильный. 
 Кстати, первый «правильный», тоже не правильный.
 - unsigned long int GetTimer(void) { DisableInt(); unsigned long TmpH=High; if (TimerOvBit()) TmpH++; //Это условие не сработало здесь. //А Здесь происходит переполнение и следующей строкой "ReadReg", вместо значения близкого к максимуму, считает близкое к нулю. TmpH=(TmpH<<sizeof(int))+ ReadReg(TimerCounter); EnableInt(); return Tmp; }
 Единственный правильный вариант:
 - unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; do { TmpH=High; TmpL= ReadReg(TimerCounter); while (TmpH!=High); return (TmpH<<sizeof(int))+TmpL; }
 А если нужно предотвратить зависание, нужно ввести счетчик циклов.- MacIn22.12.2015 16:08- А если нужно предотвратить зависание, нужно ввести счетчик циклов. 
 Плюс за здравую мысль. Если цикл занимает уже слишком долго, мы выдаем компромиссный вариант.
 
 
 
 - encyclopedist22.12.2015 02:33
 — это неопределенное поведение. В условии одна и та же переменная читается и модифицируется.- if (TmpH!=(TmpH=High)) TmpL= ReadReg(TimerCounter);- GarryC22.12.2015 09:20- Да, Вы правы, у меня работает, но это как повезет. Дополнил пост непротиворечивой функцией. 
 
 - mark_ablov21.12.2015 17:14- > while ((TmpWait — GetTimer()) > 0) 
 Даже неинтересно. false только когда GetTimer() == TmpWait, если дискретности не хватает на измерение, то это будет нечасто.
 
 бтв, ваше решение ломается на высокочастотном таймере с узким регистром счетчика. Понятно, что это очень теоретический МК, и на практике это просто никогда не встретится, но в рамках теоретизирования — почему нет? - GarryC21.12.2015 22:26- Не совсем верно, посмотрите случай когда начальное значение близко к переполнению, там интересно. 
 
 Да, полученные значения будут отставать от реальных, но главное, что они будут валидными всегда.
 
 - sguwenka21.12.2015 17:21- 1. volatile в определении переменных не указываете — тоже очевидно, опустим? ;) 
 2. А если в нашей «высоконагруженной системе» бомбанёт ряд длительных прерываний вот в этом месте?- if (TmpH!=High) TmpL= ReadReg(TimerCounter); … а таймер будет достаточно скоростным — имеем реальный риск всё же неверно считать данные.
 
 Я бы крайне рекомендовал, если синхронизация настолько важна, всё же выключать прерывания на время считывания таких критичных значений. Конструкция проще, вероятность допустить ошибку меньше. - GarryC21.12.2015 18:14- В том то и дело, что все равно мы считаем правильные данные. Да, они будут отставать от текущего значения, но они НЕ будут невалидными, в этом вся фишка.  - MacIn21.12.2015 23:01+3- Нет. Решение Джека Гансли — bulletproof, ваше — выдает правильное значение с некоторой веротяностью, большей, чем для решения «в лоб» в самом начале статьи. 
 - if (TmpH!=High) TmpL= ReadReg(TimerCounter);
 Допустим, вы считываете новое значение High, т.к. оно отличается от предыдущего, мы принимаем решение считать по новой TmpL. Теперь допустим, мы классически вываливаемся после принятия решения, но до считывания TmpL. В это время High опять обновляется. Теперь мы возвращаемся, считываем TmpL и имеем некорректную пару — старый TmpH и новый TmpL. - ProLimit22.12.2015 01:41- Обычно таймер времени переполняется гораздо дольше, чем самое долгое прерывание. Как мне кажется, этот алгоритм для таких случаев, но это надо специально отметить в описании.  - MacIn22.12.2015 01:55- Разумеется, описанный алгоритм покрывает все более-менее реальные случаи. Тем не менее, алгоритм с циклом ничем не хуже и покрывает все.  - GarryC22.12.2015 09:18- Адгоритм с циклов может выполняться значительное время, что Джек и указал. 
 Мой алгоритм работает для всех случаев, я пропустил одно присваивание в тексте, теперь исправлено, и он дает правильный результат во всех случаев. Я пост дополнил более расширенным вариантом. - izyk22.12.2015 10:11- Да не работает ваш алгоритм в определенных условиях. Например, если значение High может измениться более чем один раз во время вызова функции (см. мои комментарии и мой вариант ниже): 
 - unsigned long int GetTimer(void) { unsigned long TmpHFirst,TmpH,TmpL; TmpHFirst=High; TmpL= ReadReg(TimerCounter); if (TmpHFirst!=(TmpH=High)) { //Вот тут произошло прерывание, изменилось значение High, а вы уже считали старое. TmpL= ReadReg(TimerCounter); // Считываете значение соответствующее новому значению High. } return (TmpH<<sizeof(int))+TmpL; }
  - MacIn22.12.2015 15:56+1- Алгоритм с циклом может выполняться значительное время, что Джек и указал. 
 Не-а. То-есть, конечно, может, но какова вероятность того, что у нас фазы будут так совпадать? Сдается мне, гораздо ниже, чем длительное прерывание и т.д. (см. критику выше). Или ниже, где izyk еще раз описал то же самое.
 
 
 
  - GarryC22.12.2015 09:53- Эта пара ( старый TmpH и новый TmpL) является абсолютно корректной. Она не отражает текущее состояние счетчика, но гарантировано возвращает правильное время, то есть не прошлое и не будущее, а одно из настоящих, а это — главное.  - MacIn22.12.2015 15:58- Нет. У вас может high измениться несколько раз между проверкой условия и считыванием low (теоритически, если таймер очень скорострельный). В итоге у вас high от одной итерации, low — от совсем другой.  - MacIn22.12.2015 16:06- Представьте: 
 1) вы считываете high, он равен 10
 1.5) считываете low, он равен 50
 2) считываете high повторно, он равен 11
 3) принимаете решение перечитать low
 4) вываливаетесь
 5) high изменяется на 12
 6) low изменяется на 38
 7) считываете low
 получаем: high = 11, low = 38, хотя на самом деле пара — 12/38.
 Практически — я согласен — вы правы, эту «случайную» пару можно считать «настоящим» временем. И в практической реализации это действительно нормально. Просто алгоритм с циклом абсолютно корректен. Ваш — компромиссный. - GarryC23.12.2015 08:50- Рассмотрим любой из первых двух алгоритмов. Вот они отработали, получили текущее время, разрешили прерывание (тот, кто запрещал), произошло прерывания, счетчик убежал вперед и мы получаем то же самое, 11/38, хотя на самом деле пара 12/25 (к примеру). В высоко-нагруженной системе вернуть истинное время просто невозможно, и здесь ничего не поделать. Главное, чтобы полученное значение не было артефактом. 
 В UNIX системах так и пишут, что задание задержки гарантирует только то, что она не истечет ДО требуемого момента, а вот на сколько может его превзойти — не определено.
 НО если мы правильно используем полученные данные в стиле следующего псевдокода, то все хорошо
 1) выставляем (к примеру) 1 на выходе,
 2) считываем текущее время,
 3) прибавляем к нему требуемую задержку,
 … делаем что-то
 4) читаем текущее время и сравниваем с границей,
 5) если граница перейдена, снимаем 1 с выхода,
 то все три верных решения нам гарантируют, что 1 простоит на выходе минимум задержку времени.
 Обратите внимание на последовательность 1 и 2 — именно так, иначе неверно.
  - izyk23.12.2015 11:01- 1) выставляем (к примеру) 1 на выходе, 
 2) считываем текущее время (реально сейчас, на момент возврата из функции, 12.02, а мы считываем 11.02), но единица установлена уже не меньше чем 1.00,
 3) прибавляем к нему требуемую задержку (к примеру 0.30, получаем 11.32),
 … делаем что-то
 4) читаем текущее время и сравниваем с границей (реально сейчас 12.14, и мы считываем 12.14),
 5) если граница перейдена, снимаем 1 с выхода (12.14 > 11.32, снимаем 1 с задержкой как минимум 0.12+1.00=1.12 вместо 0.30).
 
 Но позвольте, какое отношение это имеет к ОСРВ. Если вас устраивает такая точность, тогда весь код должен быть(повторюсь):
 - unsigned long int GetTimer(void) { return (High<<sizeof(int))+0; } - GarryC23.12.2015 11:30- Нет нет и еще раз нет, Вы демонстрируете непонимание проблемы. 
 Главное требование — Вы не должны получить время меньше входа в процедуру и время после выхода из нее.
 Потому что в этом случае вы не гарантируете минимальную задержку. Именно вокруг этого и идет речь.
 Нет ни одной даже ОСРВ, которая выдаст Вам точное время в условиях сильной загрузки, не надо обольщаться. И если посмотрите описание любой ОСРВ, она гарантирует отклонение не более, чем заданное, причем именно вверх. - izyk23.12.2015 11:46- del  - GarryC23.12.2015 12:03- ? Я Вас убедил?  - izyk23.12.2015 12:49- Кажется, я начинаю понимать, о чем вы. 
 Но какой смысл в такой процедуре, зачем вам вообще значение «TimerCounter», если ваша система не гарантирует точность меньше max(TimerCounter), достаточно значения High. А если вы гарантируете такую точность, то входными данными для данной процедуры должно быть — во время ее выполнение возможно только одно прерывание таймера.
 И это надо проверять на случай сбоя.
 
 Если сравнивать вашу процедуру с процедурой из книжки, в случае одного прерывания во время выполнения они эквивалентны. А в случае нескольких прерываний, более эффективна, т.к. возвращает первое подходящее значение.
 Но во втором случае это означает, что ваша система не гарантирует точность меньше max(TimerCounter). И ее (процедуру) можно упростить.
 
 - unsigned long int GetTimer(void) { return (High<<sizeof(int))+0; } - GarryC23.12.2015 15:02- Да, Вы правы, можно, такой и был первый вариант, но мы заведомо ухудшаем точность, причем за совсем слабый выигрыш.  - izyk23.12.2015 17:12- Точность не мы ухудшаем, а нагрузка на систему, как я понял. Мы просто честно признаем, что не можем ручаться за младшие разряды. А вот если выдавать значения TimerCounter когда время выполнения функции может быть больше TimerCounter, можно создать иллюзию точности не более того. 
 
 Если нагрузка на систему имеет не постоянный, короткий период, вам видимо захотелось в остальных случаях выдавать более точные значения, но использовать полученные значения младших разрядов надо крайне аккуратно. Для статистики, я думаю можно. А для процессов РВ, нет.
 
 
 
 
 
 
 
 
 
 
 
 
 
 - ToSHiC21.12.2015 19:15+4- Кажется, в вашей реализации баг. 
 
 Допустим, High = 10, счётчик переполняется при 1000.
 
 - TmpH=High; // = 10 TmpL= ReadReg(TimerCounter); // = 999 // тут случилось прерывание и High заинкрементилось if (TmpH!=High) TmpL= ReadReg(TimerCounter); // TmpL = 001 return (TmpH<<sizeof(int))+TmpL; // Вернули 10.001
 
 В оригинальном алгоритме вся мякотка заключается в while цикле — данные нижней части считаются валидными только в том случае, если верхняя за момент вычитывания не изменилась. У вас верхняя не перечитывается. - GarryC21.12.2015 22:23- Точно, спасибо большое, немного неправильно записал, конечно верхняя часть перечитывается, сейчас подправлю в тексте, но перечитывается 1 раз, без цикла. Еще раз спасибо, подправил.  - ToSHiC22.12.2015 00:13- Тогда не сработает, возникнет прерывание, которое будет длиться дольше, чем 1 цикл переполнения. На подобные штуки нужно смотреть с другой стороны (тут разбираю вариант с while циклом): 
 1. Идеальный случай, никакого переполнения. Избыточность — 1 команда чтения, 1 команда сравнения, 1 команда условного перехода, которая сфейлится. Вероятность такого исхода зависит от конкретной программы, других возможных прерываний, скорости переполнения счётчика.
 2. Неидеальный случай, который будет случаться иногда — дополнительно 1 чтение из памяти и одно чтение из регистра счётчика к варианту 1.
 3. Плохой случай, если много прерываний, или они длинные. В этом случае придётся перечитывать много раз. Может случиться, если этот кусок кода имеет совсем уж низкий приоритет. Но работать будет.
 
 Ваш вариант (в котором был баг) с точки зрения 1 и 2 случаев эквивалентен, но сфейлится в случае 3 (и ещё если просто очень долгое прерывание было). При этом с точки зрения количества исполненных инструкций он идентичен, разница только в команде, на которую будет указывать условный переход. На самом деле, вариант с while мне больше нравится даже с эстетической точки зрения.
 
 
 - imwode21.12.2015 22:08+2- Меня смущает сочетание «аппаратный счетчик» и sizeof(int) 
 Во всяких микроконтроллерах, как правило, соседствуют таймеры разной разрядности, а sizeof(int) зависит от компилятора. Можно неслабо так нарваться… - GarryC21.12.2015 22:20- Ну здесь подразумевалось, что таймер разрядностью в слово. Если меньше, то тоже не страшно, а вот если больше, то можно было бы и нарваться. У Джека было просто 16, это я слегка перестраховался.  - imwode21.12.2015 23:15+1- Не, эта какая-то негодная и опасная перестраховка непонятно от чего непонятно зачем :-) 
  - encyclopedist22.12.2015 02:30+1- sizeof(int) выдаёт размер инта в байтах (точнее, в символах, sizeof(char) == 1 по определению), а не в битах. Поэтому для сдвига его надо умножить на CHAR_BITS. 
 
 
 - Amomum21.12.2015 22:28- TmpWait=GetTimer()+SomeDelay; 
 while (TmpWait > GetTimer()); /* первая строка */
 while ((TmpWait — GetTimer()) > 0); /*вторая строка */
 Насколько я понимаю, первая строка неправильная, т.к. не учитывает переполнение, которое могло случится при вычислении TmpWait. - Ocelot21.12.2015 22:39+1- Они обе не учитывают переполнение, а во второй еще и вычитаются две беззнаковые переменные, так что результат тоже будет беззнаковый. И выражение (0-1) там даст 0xFFFFFFFF, а не (-1), как ожидалось.  - Amomum21.12.2015 23:14+1- Вероятно, вы правы. Я обычно пишу 
 - prevTime = GetTime(); while( GetTime() - prevTime < delay );
 Сходу не могу сообразить, но вроде бы вторая строка этому не эквивалентна; не помню досконально правила неявного приведения типа. - Ocelot21.12.2015 23:40- Ноуп! Тут тоже ошибка. Допустим, prevTime=900, delay=200, а GetTime() переполняется на 1000. Тогда выход из цикла произойдет сразу после переполнения, через 100 тиков вместо ожидаемых 200.
 
 UPD: нет, туплю, все вроде ок.
 
 
 
 - izyk22.12.2015 02:05- Если в вашей системе возможно что-то отличное от следующего: 
 - unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; TmpH=High; TmpL= ReadReg(TimerCounter); if ( TmpH != High || TmpH++ != High ) exit(error); return (TmpH<<sizeof(int))+TmpL; }
 то значение «TimerCounter» для вас не должно иметь смысла(ИМХО). И вы его можете просто не учитывать (выполнять округление). - izyk22.12.2015 02:52- Накосячил. Более правильно, наверное, так. 
 - unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; TmpH=High; TmpL=ReadReg(TimerCounter); if ( TmpH+1 == High ) TmpL=ReadReg(TimerCounter); if ( TmpH != High || TmpH++ != High ) exit(error); return (TmpH<<sizeof(int))+TmpL; }
 Многовато if, но вроде должно работать. Заодно своеобразный watchdog получился.
 А если значение High во время вызова функции может увеличится более чем один раз, и это нормально, тогда функция должна быть такой:
 - unsigned long int GetTimer(void) { return (High<<sizeof(int))+0; // Или просто return High; }
 Но это мое мнение. И вполне могу предположить что оно не верное. - izyk22.12.2015 14:35+1- Не, мой вариант не правильный. 
 Кстати, первый «правильный», тоже не правильный.
 - unsigned long int GetTimer(void) { DisableInt(); unsigned long TmpH=High; if (TimerOvBit()) TmpH++; //Это условие не сработало здесь. //А Здесь происходит переполнение и следующей строкой "ReadReg", вместо значения близкого к максимуму, считает близкое к нулю. TmpH=(TmpH<<sizeof(int))+ ReadReg(TimerCounter); EnableInt(); return Tmp; }
 Единственный правильный вариант:
 - unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; do { TmpH=High; TmpL= ReadReg(TimerCounter); while (TmpH!=High); return (TmpH<<sizeof(int))+TmpL; }
 А если нужно предотвратить зависание, нужно ввести счетчик циклов. - MacIn22.12.2015 16:08- А если нужно предотвратить зависание, нужно ввести счетчик циклов. 
 Плюс за здравую мысль. Если цикл занимает уже слишком долго, мы выдаем компромиссный вариант.
 
 
 
 - encyclopedist22.12.2015 02:33
 — это неопределенное поведение. В условии одна и та же переменная читается и модифицируется.- if (TmpH!=(TmpH=High)) TmpL= ReadReg(TimerCounter); - GarryC22.12.2015 09:20- Да, Вы правы, у меня работает, но это как повезет. Дополнил пост непротиворечивой функцией. 
 
 
           
 
mark_ablov
> while ((TmpWait — GetTimer()) > 0)
Даже неинтересно. false только когда GetTimer() == TmpWait, если дискретности не хватает на измерение, то это будет нечасто.
бтв, ваше решение ломается на высокочастотном таймере с узким регистром счетчика. Понятно, что это очень теоретический МК, и на практике это просто никогда не встретится, но в рамках теоретизирования — почему нет?
GarryC
Не совсем верно, посмотрите случай когда начальное значение близко к переполнению, там интересно.
Да, полученные значения будут отставать от реальных, но главное, что они будут валидными всегда.