Глядя на эскизы, написанные другими людьми, я иногда сталкиваюсь с кодом, который выглядит примерно так:
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);
Все, что я знаю, - это то, что связано с таймерами / таймерами (я думаю). Как я могу расшифровать и создать такой код? Что TCCR1A
, TCCR1B
, TCNT1
, CS12
, TIMSK1
, и TOIE1
?
timers
programming
Парень в шляпе
источник
источник
Ответы:
Это не странно выглядит. Вот как на самом деле выглядит нормальный код MCU.
То, что вы имеете здесь, является примером концепции периферийных устройств с отображением в памяти . По сути, аппаратное обеспечение MCU имеет специальные местоположения в адресном пространстве SRAM назначенного ему MCU. Если вы пишете по этим адресам, биты байта, записанного для адреса n, управляют поведением периферийного устройства m .
По сути, некоторые банки памяти буквально имеют небольшие провода, идущие от ячейки SRAM к оборудованию. Если вы записываете «1» в этот бит в этом байте, он устанавливает для этой ячейки SRAM логический высокий уровень, который затем включает некоторую часть аппаратного обеспечения.
Если вы загляните в заголовки для MCU, то вы увидите, что есть большие таблицы сопоставлений адресов с ключевым словом <->. Вот как такие вещи, как и
TCCR1B
т.д ... решаются во время компиляции.Этот механизм отображения памяти чрезвычайно широко используется в MCU. Он используется в микроконтроллере ATmega в Arduino, как и в микроконтроллерах серий PIC, ARM, MSP430, STM32 и STM8, а также во многих микроконтроллерах, с которыми я не сразу знаком.
Код Arduino - странная штука с функциями, которые косвенно обращаются к регистрам управления MCU. Хотя это выглядит несколько «лучше», оно также намного медленнее и использует гораздо больше места для программ.
Все загадочные константы очень подробно описаны в техническом описании ATmega328P , которое вам действительно следует прочитать, если вы хотите сделать что-то большее, чем случайное переключение контактов на Arduino.
Выберите выдержки из таблицы данных, связанной выше:
Так, например,
TIMSK1 |= (1 << TOIE1);
устанавливает битTOIE1
вTIMSK1
. Это достигается путем сдвига двоичного 1 (0b00000001
) влево наTOIE1
биты,TOIE1
причем в заголовочном файле он определен как 0. Затем он побитовым ИЛИ устанавливается в текущее значениеTIMSK1
, что эффективно устанавливает этот бит на единицу.Глядя на документацию для бита 0
TIMSK1
, мы видим, что она описана какВсе остальные строки должны интерпретироваться одинаково.
Некоторые заметки:
Вы также можете увидеть такие вещи, как
TIMSK1 |= _BV(TOIE1);
._BV()
является широко используемым макросом, изначально взятым из реализации AVR libc ._BV(TOIE1)
функционально идентична(1 << TOIE1)
, с преимуществом лучшей читаемости.Кроме того, вы также можете увидеть такие строки, как:
TIMSK1 &= ~(1 << TOIE1);
илиTIMSK1 &= ~_BV(TOIE1);
. Это имеет противоположную функцию в томTIMSK1 |= _BV(TOIE1);
, что оно сбрасывает битTOIE1
вTIMSK1
. Это достигается путем взятия битовой маски, созданной путем_BV(TOIE1)
выполнения побитовой операции NOT над ней (~
), а затем сTIMSK1
помощью AND с этим значением NOTed (которое равно 0b11111110).Обратите внимание, что во всех этих случаях значение таких вещей, как
(1 << TOIE1)
или_BV(TOIE1)
полностью разрешается во время компиляции , поэтому они функционально сводятся к простой константе и, следовательно, не требуют времени выполнения для вычисления во время выполнения.Правильно написанный код, как правило, содержит комментарии, встроенные в код, подробно описывающий, что делать назначаемым регистрам. Вот довольно простая подпрограмма софт-SPI, которую я недавно написал:
PORTC
это регистр, который контролирует значение выходных контактов вPORTC
ATmega328P.PINC
регистр, в котором доступны входные значенияPORTC
. По сути, такие вещи происходят внутри вас, когда вы используете функцииdigitalWrite
илиdigitalRead
. Тем не менее, существует операция поиска, которая преобразует «номера выводов» arduino в реальные номера выводов аппаратного обеспечения, что занимает где-то в области 50 тактов. Как вы, вероятно, можете догадаться, если вы пытаетесь идти быстро, тратить 50 тактов на операцию, которая должна требовать только 1, немного нелепо.Вышеупомянутая функция, вероятно, занимает где-то 100-200 тактовых циклов для передачи 8 битов. Это влечет за собой 24 пин-записи и 8 операций чтения. Это во много раз быстрее, чем при использовании
digital{stuff}
функций.источник
TCCR1A
регистр управления таймером / счетчиком 1TCCR1B
регистр управления таймером / счетчиком 1TCNT1
значение счетчика таймера / счетчика 1CS12
3-й бит выбора часов для таймера / счетчика 1TIMSK1
регистр маски прерываний таймера / счетчика 1TOIE1
активируется ли прерывание по переполнению таймера / счетчика 1Таким образом, код активирует таймер / счетчик 1 на 62,5 кГц и устанавливает значение 34286. Затем он разрешает прерывание по переполнению, поэтому, когда оно достигает 65535, оно запускает функцию прерывания, скорее всего, помеченную как
ISR(timer0_overflow_vect)
источник
CS12 имеет значение 2, так как он представляет бит 2 регистра TCCR1B.
(1 << CS12) принимает значение 1 (0b00000001) и сдвигает его влево 2 раза, чтобы получить (0b00000100). Порядок операций диктует, что сначала в () происходят вещи, так что это делается до того, как "| =" будет вычислено.
(1 << CS10) принимает значение 1 (0b00000001) и сдвигает его влево 0 раз, чтобы получить (0b00000001). Порядок операций диктует, что сначала в () происходят вещи, так что это делается до того, как "| =" будет вычислено.
Итак, теперь мы получаем TCCR1B | = 0b00000101, который совпадает с TCCR1B = TCCR1B | 0b00000101.
С "|" равно "ИЛИ", все биты, кроме CS12 в TCCR1B, не затрагиваются.
источник