Я хочу рассказать историю одного говнокода, но сначала вводная.
(кручу-верчу, обмануть хочу)Один из блоков, имеющийся почти во всех микроконтроллерах -- это таймер. Их обычно несколько.
Таймеры предназначены для счёта времени. Есть регистр-счётчик, который с определённой скоростью растёт. Когда он достигает заданного значения, он обнуляется и всё начинается заново. В этот момент генерируется прерывание или что-нибудь в этом духе.
Основные регистры таймера в любом микроконтроллере -- это счётчик (CNT) и граница (PR). CNT содержит в себе текущее значение таймера, а PR определяет, когда надо прекращать это дело и начинать заново.
Считать время нужно с несколькими целями, например:
1. Измерить, сколько времени прошло между событиями.
2. Сделать паузу на заданное время.
3. Делать действие через определённые промежутки времени.
Если у вас микроволновка мигает лампочкой, то это сделано через таймер.
У меня был вариант 3: я проводил действия через определённые промежутки времени. Всё уже было настроено нашими друзьями из соседней организации на определённую частоту, скажем, 1 кГц. А мне надо было, чтобы это можно было изменить на лету.
У меня была готовая магическая константа типа:
#define TIMER_PERIOD 11458
Я превратил эту константу в переменную и стал её менять, увеличивать и уменьшать, однако результаты я получал очень странные. При умножении её на два и на четыре, я получал кратное уменьшенные частоты. Однако при делении на полтора, скажем, получалась частота в некратное количество раз больше. При этом были промежутки значений констант, когда частота менялась плавно (хотя и не в то количество раз, в которое я менял константу), а бывали скачки.
Сначала я думал на то, что возникает перенос, и правильно думал. То есть, я иногда задавал такое значение константы, которое не влезало в регистр таймера (который в моём контроллере 16-битный). Но это покрывало не все случаи.
Когда я внимательно прочитал в документации алгоритм работы таймера, я пошёл читать, каким же образом в имеющейся программе настраивается граница обнуления, т.е. PR. Я обнаружил, что обновление настроек таймера зачем-то делается при каждом срабатывании прерывания, т.е. при достижении таймером порогового значения, того самого PR.
В обработчике прерывания я ожидал такой строчки:
PR=TIMER_PERIOD;
Но вместо неё там было вот это:
CNT=TIMER_PERIOD;
А PR не настраивался вообще нигде. Я посмотрел значение по умолчанию у PR и это было 0xFFFF.
То есть, счёт происходил следующим образом:
1. Счётчик устанавливается в TIMER_PERIOD.
2. Счётчик увеличивается, пока не дойдёт до 0xFFFF.
3. Вызывается обработчик прерывания, который вручную (!) сбрасывает CNT в TIMER_PERIOD.
Это, конечно, работало, но:
а) это неграмотное применение, т.к. вручную делается то, что может делаться автоматически.
б) это дезинформирующее применение, т.к. фактический период срабатывания оказывается равен 0xFFFF-TIMER_PERIOD.
С этим и было связано странное поведение и изменение частоты в не то количество раз, в котрое я менял константу. Когда я изменил формулу пересчёта, всё заработало правильно.
В чём же была причина применения столь нерационального метода? Не знаю. Я было подумал, что за этим кроется какая-то тайна, типа если сделать просто и логично, ничего работать не будет (в микроконтроллерах такое бывает). Поэтому я (сделав предварительно коммит) изменил алгоритм на следующий:
1. При подаче команды изменения настроек выполняется PR=TIMER_PERIOD;
2. При вызове прерывания с регистрами таймера ничего не делается.
Теперь счётчик считает от 0 до TIMER_PERIOD, потом автоматом сбрасывается. И такой вариант заработал!
То есть, никакой хитрости за этим костылём не стояло.
@темы:
Программирование,
Говнокод,
Борьба с техникой
21.09.2018 в 09:51
Не факт )
21.09.2018 в 10:02