Чтение 16-битного таймера на 8-битном MCU

Поскольку 8-битный MCU не может прочитать весь 16-битный таймер за один цикл, это создает состояние гонки, при котором младшее слово может переключаться между чтениями. Есть ли у сообщества предпочтительный способ избежать этих условий гонки? В настоящее время я рассматриваю возможность остановки таймера во время чтения, но я хотел бы знать, есть ли более элегантное решение.

К вашему сведению, это для PIC16F690 .

Насколько я знаю, это проблема, о которой заботятся все микроконтроллеры. Когда читается один байт, другой фиксируется, и чтение другого байта будет считываться из защелки. Задача решена.

Ответы (4)

Уинделл прав, если вы говорите о PIC, техническое описание (и аппаратное обеспечение) уже справится с этим за вас.

Если вы не используете PIC, я использую общий метод:

byte hi, lo;
word timer_value;

do {
    hi = TIMER_HI;
    lo = TIMER_LO;
} while(hi != TIMER_HI);

timer_value = (hi << 8) | lo;

Что это делает, так это считывает старший байт, за которым следует младший байт, и продолжает делать это до тех пор, пока старший байт не изменится. Это легко обрабатывает случай, когда младший байт 16-битного значения переполняется между чтениями. Предполагается, конечно, что многократное чтение регистра TIMER_HI не вызывает побочных эффектов. Если ваш конкретный микропроцессор этого не позволяет, пришло время выбросить его и использовать тот, который не настолько безмозглый. :-)

Этот метод ТАКЖЕ предполагает, что ваш таймер не меняется так быстро, что вы рискуете переполнить младшие 8 битов в течение одного или двух циклов выборки процессора. Если вы запускаете таймер так быстро (или микропроцессор так медленно), то пришло время переосмыслить вашу реализацию.

Вы также можете использовать эту технику для чтения таймера, состоящего из аппаратного таймера и программного счетчика, увеличенного в прерывании переполнения.
Спасибо, на самом деле мне эта техника нравится больше, чем заикание таймера во время чтения.
Наконец-то нашел примечание к приложению Microchip: «Семейство микроконтроллеров PIC Micro Mid-range». Он выглядит старым, но, кажется, описывает серию 16F более подробно, чем техническое описание. Метод, предложенный для чтения 16-битного автономного таймера, вполне соответствует вашему предложению, поэтому я отмечаю его как ответ.
В общем, мой ответ был бы "делай то, что делает компилятор" - пиши что-нибудь на C, и смотри-смотри, как выглядит сборка.
O_Engenheiro Цикл выполняется дважды в случае переполнения и один раз во всех остальных случаях. Если вы зацикливаетесь больше, чем это, то либо ваши часы таймера НАМНОГО быстры, либо ваш процессор НАМНОГО медленнее.
@Эндрю Колсмит, я удалил свой комментарий. Извините, я сделал неправильный анализ кода. Спасибо.
@Andrew Kohlsmith: Если таймер находится в синхронном режиме, ваш подход в порядке. Если таймер работает в асинхронном режиме и не слишком быстр (например, он работает от часового кристалла 32 кГц), лучше повторить цикл, если младший бит таймера изменился. В противном случае существует риск того, что таймер может измениться точно во время считывания, что приведет к недопустимым показаниям.
@supercat Я не уверен, что понимаю; если таймер асинхронен с часами процессора, то вы будете отключены не более чем на 1 таймер; это не означает, что чтение является недействительным. Если, однако, вы имеете в виду, что битовая комбинация в регистре будет недействительной, то это очень плохой дизайн ЦП/периферии; периферийные регистры должны быть правильно синхронизированы производителем.
@Эндрю Кольсмит: счетчик, который может надежно считываться процессором, часы которого асинхронны с ним, требует немало кремния. По сути, нужно либо считать в коде серого, либо преобразовать счет в код серого, затем синхронизировать его, а затем преобразовать значение кода серого обратно в «нормальный» двоичный код. В тех случаях, когда часы ЦП во время работы будут по крайней мере в 3 раза быстрее, чем сигнал, который нужно подсчитать, может быть проще организовать синхронную синхронизацию счетчика, когда ЦП бодрствует, и асинхронно, когда он спит. .
@ Эндрю Кольсмит: ... но переключение между синхронным и асинхронным тактированием без увеличения или потери счета - даже в крайних случаях - сложно. Если программное обеспечение может многократно считывать показания таймера, пока не получит один и тот же ответ несколько раз, это позволит использовать более простое оборудование, чем было бы необходимо в противном случае. Обратите внимание, что единственный случай, когда этот подход действительно не будет работать, - это когда скорость счета довольно высока по сравнению с тактовой частотой процессора, что предполагает, что подход с кодом серого является правильным.
@Andrew Kohlsmith: Обратите внимание, что если не предусмотрено средство для синхронного 16-битного чтения или скорость счета настолько высока, что ЦП не сможет считывать низкий-высокий-низкий уровень перед низкими изменениями, нет никаких реальных недостатков в том, чтобы иметь счетчик возвращает поврежденные значения, если он считывается во время его изменения (это, безусловно, меньший недостаток, чем случайное увеличение или потеря счета, вызванное переключением между синхронным и асинхронным режимами).
@supercat интересно. Я имел дело со многими микроконтроллерами на протяжении многих лет и изучаю VHDL, и я никогда не сталкивался с этим раньше. Я также посовещался со своим «учителем» VHDL, и он также никогда не видел такой проблемы в микроконтроллере. Где вы видели это раньше? Бьюсь об заклад, это был настоящий медведь для отладки!
@Andrew Kohlsmith: Отладка асинхронных вещей не так уж сложна, если предположить, что сигналы будут изо всех сил изменяться в самый неподходящий момент. На самом деле, во многих случаях я предпочитаю полностью несинхронизированные таймеры тем, которые пытаются избежать проблем, связанных с асинхронностью, поскольку мне неоднократно приходилось иметь дело с аппаратными ошибками, связанными с такими вопросами. Например, одна недокументированная неприятность многих таймеров PIC заключается в том, что если таймер содержит значение, отличное от FFFF, и записано с помощью FFFF, прерывание по переполнению...
... произойдет не при следующем отсчете, а после 65 537 отсчетов. Я подозреваю, что дизайн проистекает из желания избежать ложного срабатывания прерывания, если, например, счетчик удерживает 128, и он записывался со 127 сразу после того, как поступил счетчик, но я бы предпочел просто указать в документации, что любая запись к таймеру в асинхронном режиме может ошибочно установить флаг прерывания, поэтому безопасная работа потребует отключения прерывания, записи таймера, очистки флага и повторного включения прерывания.
@Andrew Kohlsmith: Я бы больше всего хотел иметь систему с асинхронным 48-битным счетчиком, работающим от собственного источника, и регистром сравнения в нижних 32 битах, который мог бы работать, даже когда основные часы процессора были остановлены. . Не было бы необходимости писать в 48-битный счетчик, и я был бы не против отключить прерывания сравнения при записи в регистр сравнения. Такая система таймеров позволила бы RTOS планировать задачи с точностью 17 или 33 мкс и отключать основной генератор в любое время, когда он не нужен.
@Andrew Kohlsmith: EFM Gekko почти идеален; моя единственная жалоба заключается в том, что таймер всего 24 бита и не может отсчитывать время, пока процессор выключен. Серия ST Micro 32F кажется несколько лучше, с часами реального времени в виде 32-битного линейного счетчика и программируемого предварительного скаляра. Моя единственная жалоба на эти 32 бита — это слишком мало (используя скорость счета 256 Гц, устройство фактически забудет дату, если будет выключено на год; при более высокой скорости счета оно переполнится раньше).
@Andrew Kohlsmith: К сожалению, ST несколько испортил серию 32L. Чип RTC имеет настраиваемый предварительный скаляр, но, к сожалению, его необходимо считывать в формате YMDHMS, который предполагает скорость приращения в 1 секунду, а его регистры сигнализации принимают формат HMS; если скорость счета составляет 256 Гц, регистры тревоги будут представлять единицы 1/256 секунды, 15/64 секунды и 14 1/16 секунды (последний цикл выполняется каждые 337,5 секунды). Гораздо менее удобно для использования с планированием RTOS.

Проверьте свой техпаспорт! Об этом будет рассказано в кровавых подробностях для вашего конкретного чипа.

Я не уверен, что это верно для всех 16-битных регистров счетчиков/таймеров на всех PIC (может быть, кто-то еще может ответить на это!), но, по крайней мере, для PIC 18F, который я использую, в техническом описании конкретно говорится о том, как это обрабатывается. .

Когда вы читаете младший байт, он буферизует старший байт во временный регистр для старшего байта, так что, когда вы читаете следующий старший байт, он дает вам полный мгновенный снимок значения таймера.

Тот же базовый процесс используется в AVR (и, конечно, в Arduino), а также в большинстве других случаев, когда вам нужно одновременно читать или записывать двухбайтовые регистры на 8-битных микроконтроллерах.

я тоже помню, что читал это в документах AVR
Конечно, я проверил техпаспорт, но на самом деле он не такой кровавый, как вы могли подумать. Ничто в тексте для timer1 (глава 6) не указывает на то, что старший байт буферизуется. Под асинхронным счетчиком (6.5.1) есть подраздел, в котором говорится: «Для записи рекомендуется, чтобы пользователь просто останавливал таймер и записывал нужные значения». Ничего не упоминается для чтения. Вот почему я пришел в сообщество
Теперь , когда вы опубликовали фактический чип, который используете, это значительно упрощается . ;) И... я бы назвал это довольно кровавым: там явно сказано, что между чтениями может произойти переполнение. Ваше решение, останавливающее таймер, действительно неэлегантно, но, возможно, безопасно , в зависимости от вашего приложения.

Текст, на который вы ссылаетесь в своем комментарии (раздел 6.5.1), относится к работе асинхронного таймера, когда таймер синхронизируется с источником, внешним по отношению к микроконтроллеру.

Заявление об отказе от ответственности предлагает запускать и останавливать таймер, потому что общий метод Эндрю (на самом деле любой метод) не будет работать, потому что может быть много тиков (даже более 256) внешних часов и, следовательно, таймер в одном цикле процессора . Я предполагаю, что вы не работаете с этим крайним случаем.

Если вместо этого вы используете внутренний осциллятор в качестве эталона, все будет в порядке. Даже с предварительным делителем, равным 1, у вас есть до 255 циклов после считывания старшего байта для чтения младшего байта. Это должно быть 2 цикла операции. Вам по-прежнему нужен цикл do/while на случай, если ваш старший байт изменится (в чем разница между «привет» и «TIMER_HI», если значение «lo» равно 0?) между этими временами. С предварительным делителем 2 или выше вам больше не нужен этот тест. Только с предделителями намного меньше 1 это становится проблемой, а без внешних часов это невозможно.

Да, я знаю, что это был асинхронный таймер, но это самое близкое к решению проблемы техническое описание. Хотя теперь имеет больше смысла, почему они предлагают остановить часы, а не упоминать решение Эндрю (хотя они даже не упоминают об этом в таблице данных, а в какой-то отдельной примечании к приложению)

В Справочном руководстве по семейству PIC Mid-Range MCU есть пример чтения и записи таймера 1 в режиме асинхронного счетчика.

Пример. Чтение 16-битного автономного таймера

; All interrupts are disabled
MOVF TMR1H, W ; Read high byte
MOVWF TMPH ;
MOVF TMR1L, W ; Read low byte
MOVWF TMPL ;
MOVF TMR1H, W ; Read high byte
SUBWF TMPH, W ; Sub 1st read with 2nd read
BTFSC STATUS,Z ; Is result = 0
GOTO CONTINUE ; Good 16-bit read
;
; TMR1L may have rolled over between the read of the high and low bytes.
; Reading the high and low bytes now will read a good value.
;
MOVF TMR1H, W ; Read high byte
MOVWF TMPH ;
MOVF TMR1L, W ; Read low byte
MOVWF TMPL ;
; Re-enable the Interrupt (if required)
CONTINUE ; Continue with your code
Да, это руководство подтвердило, что шаблон Эндрю был лучшим компромиссом. Хотя я изменил его пример, чтобы он больше походил на сборку; т.е. вместо do-while я просто использовал if.