Чи може хтось пояснити цей дивний код, який використовується для налаштування таймерів?


10

Переглядаючи ескізи, написані іншими людьми, я час від часу стикаюся з кодом, який виглядає приблизно так:

TCCR1A = 0;
TCCR1B = 0;

TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);

Все, що я знаю, це те, що має щось із тимчасовими / таймерами (я думаю). Як я можу розшифрувати та створити такий код? Що TCCR1A, TCCR1B, TCNT1, CS12, TIMSK1, і TOIE1?


Мало , щоб відповісти , але знаю electronics.stackexchange.com/questions/92350 / ... , forum.arduino.cc/index.php?topic=134602.0 і stackoverflow.com/questions/9475482 / ... . Не знаю, чи бачили ви їх вже.
Анонімний пінгвін

1
Завантажте таблицю даних "Повне" для свого пристрою з веб-сайту Atmel та прочитайте глави про таймери. Лист даних на диво добре читати на мою думку.
джиппі

Відповіді:


15

Це не дивно виглядає. Ось як насправді виглядає звичайний код MCU.

У вас є приклад концепції пам’яті, відображеної на пам’яті . В основному, апаратне забезпечення MCU має спеціальні місця в адресному просторі SRAM призначеного йому MCU. Якщо ви пишете на ці адреси, біти байта, написані для адреси n, керують поведінкою периферійних m .

В основному, в деяких банках пам'яті буквально мало проводів, що працюють від комірки SRAM до апаратних засобів. Якщо ви записуєте "1" цьому біту в цей байт, він встановлює цю комірку SRAM на логічну висоту, яка потім вмикає частину обладнання.

Якщо ви заглянете до заголовків MCU, є великі великі таблиці відображення ключових слів <-> адрес. Ось як TCCR1Bвирішуються такі речі, як тощо ... під час компіляції.

Цей механізм відображення пам’яті надзвичайно широко використовується в MCU. ATmega MCU в ардуїно використовують його, як і PIC, ARM, MSP430, STM32 та STM8 MCU, а також багато MCU, з якими я не одразу знайомий.


Код Arduino - це дивна річ, з функціями, які отримують доступ до регістрів керування MCU опосередковано. Хоча це виглядає дещо "приємніше", воно також набагато повільніше і використовує набагато більше програмного простору.

Загадкові константи всі детально описані у таблиці даних ATmega328P , яку ви дійсно повинні прочитати, якщо вам цікаво робити щось більше, ніж професійно перемикати шпильки на ардуїно.

Виберіть уривки з наведеного вище таблиці даних:

введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення

Так, наприклад, TIMSK1 |= (1 << TOIE1);встановлює біт TOIE1в TIMSK1. Це досягається зміщенням бінарного 1 ( 0b00000001) вліво на TOIE1біти, при TOIE1цьому в файлі заголовка визначається як 0. Це потім порозрядно ORI в поточне значення TIMSK1, яке фактично встановлює цей один біт високим.

Переглядаючи документацію для біта 0 TIMSK1, ми можемо бачити, що вона описана як

Коли цей біт записується в один і встановлюється прапор I в Реєстрі статусу (переривається глобально), перемикання таймера / Counter1 переповнення вмикається. Відповідний вектор переривання (див. "Переривання" на стор. 57) виконується, коли встановлено прапор TOV1, розташований у TIFR1.

Усі інші рядки слід інтерпретувати однаково.


Деякі примітки:

Ви також можете бачити такі речі TIMSK1 |= _BV(TOIE1);. _BV()є загальновживаним макросом, що спочатку починається з реалізації AVR libc . _BV(TOIE1)функціонально ідентичний (1 << TOIE1), з перевагою кращої читабельності.

Також ви можете побачити рядки, такі як: TIMSK1 &= ~(1 << TOIE1);або TIMSK1 &= ~_BV(TOIE1);. Це має функцію протилежності TIMSK1 |= _BV(TOIE1);, в тому , що він скидає біт TOIE1в TIMSK1. Це досягається за допомогою взяття бітової маски, виконаної шляхом _BV(TOIE1)виконання побітової операції NOT ( ~), а потім ANDing TIMSK1за цим значенням NOTED (яке дорівнює 0b11111110).

Зауважте, що у всіх цих випадках значення таких речей, як (1 << TOIE1)або _BV(TOIE1)повністю вирішуються під час компіляції , тому вони функціонально зводяться до простої константи, а тому не вимагають часу на виконання для обчислення під час виконання.


Правильно написаний код, як правило, містить коментарі, вбудовані в код, який детально описує, що реєстри призначені робити. Ось досить простий порядок програмного забезпечення SPI, про який я писав нещодавно:

uint8_t transactByteADC(uint8_t outByte)
{
    // Transfers one byte to the ADC, and receives one byte at the same time
    // does nothing with the chip-select
    // MSB first, data clocked on the rising edge

    uint8_t loopCnt;
    uint8_t retDat = 0;

    for (loopCnt = 0; loopCnt < 8; loopCnt++)
    {
        if (outByte & 0x80)         // if current bit is high
            PORTC |= _BV(ADC_MOSI);     // set data line
        else
            PORTC &= ~(_BV(ADC_MOSI));  // else unset it

        outByte <<= 1;              // and shift the output data over for the next iteration
        retDat <<= 1;               // shift over the data read back

        PORTC |= _BV(ADC_SCK);          // Set the clock high

        if (PINC & _BV(ADC_MISO))       // sample the input line
            retDat |= 0x01;         // and set the bit in the retval if the input is high

        PORTC &= ~(_BV(ADC_SCK));       // set clock low
    }
    return retDat;
}

PORTC- це регістр, який контролює значення вихідних штифтів в межах PORTCATmega328P. PINC- це регістр, де доступні вхідні значення PORTC. Принципово такі речі відбуваються всередині, коли ви використовуєте функції digitalWriteабо digitalRead. Однак існує операція пошуку, яка перетворює ардуїнські "штифтові номери" у фактичні апаратні номери штифтів, що займає десь у царині 50 тактових циклів. Як ви, напевно, можете здогадатися, якщо ви намагаєтеся йти швидко, витрачати 50 циклів годин на операцію, яка повинна зажадати лише 1, трохи смішно.

Наведена вище функція, ймовірно, займає десь у царині 100-200 тактових циклів для передачі 8 біт. Це тягне за собою 24 пін-записи і 8 читання. Це багато, у багато разів швидше, ніж використання digital{stuff}функцій.


Зауважте, що цей код також повинен працювати з Atmega32u4 (використовується в Леонардо), оскільки він містить більше таймерів, ніж ATmega328P.
jfpoilpret

1
@Ricardo - Можливо, 90% + вбудованого коду з невеликим MCU використовує пряму маніпуляцію з реєстрацією. Робота з функціями непрямих корисних функцій - це не звичайний спосіб маніпулювання IO / периферійними пристроями. Існує кілька наборів інструментів для абстрагування апаратного управління (наприклад, Атмель ASF), але це, як правило, написано для максимальної компіляції, щоб зменшити накладні витрати, і майже незмінно вимагає фактичного розуміння периферійних пристроїв, читаючи таблиці.
Вонор Коннор

1
В основному, ардуїно речі, кажучи "ось функції, які виконують X", не намагаючись посилатися на фактичну документацію або на те, як апаратне забезпечення робить те, що робить, дуже не є нормальним. Я розумію, що це цінність як вступного інструменту, але, за винятком швидкого прототипування, це фактично ніколи не робиться в реальних професійних умовах.
Коннор Вольф

1
Щоб було зрозуміло, те, що робить ардуїно-код незвичайним для вбудованої мікропрограми MCU, не є унікальним для коду arduino, це функція загального підходу. По суті, після того, як ви гідно розумієте фактичний MCU, правильне виконання справ (наприклад, використання апаратних реєстрів безпосередньо) займає мало часу і не вимагає додаткового часу. Таким чином, якщо ви хочете навчитися справжньому розробнику MCU, набагато краще просто сісти і зрозуміти, що насправді робить ваш MCU , а не покладатися на чужу абстракцію, яка, як правило, є герметичною.
Вонор Коннор

1
Зауважте, що я, можливо, тут трохи цинічний, але багато поведінки, яку я бачу у спільноті ардуїно, програмують анти-шаблони. Я бачу багато програмування «копіювати-вставити», трактування бібліотек як чорних скриньок і просто загальна погана практика дизайну в громаді взагалі. Звичайно, я досить активний у EE.stackexchange, тому у мене може бути трохи похилий погляд, оскільки у мене є деякі інструменти для модератора, і як таке я бачу багато закритих питань. Очевидно, що в ардуїнських питаннях, які я бачив там, є упередженість щодо "скажи мені, що C&P виправити", а не "чому це не працює".
Коннор Вольф

3

TCCR1A є реєстратором таймера / лічильника 1 управління A

TCCR1B є реєстратором таймера / лічильника 1 управління B

TCNT1 є лічильником таймера / лічильника 1

CS12 - це біт 3-го вибору годинника для таймера / лічильника 1

TIMSK1 реєструє маску переривання таймера / лічильника 1

TOIE1 є увімкненням переривання переповнення таймера / лічильника 1

Отже, код включає таймер / лічильник 1 на 62,5 кГц і встановлює значення 34286. Потім він дозволяє переривати переповнення, тому коли він досягне 65535, він запустить функцію переривання, швидше за все, позначену як ISR(timer0_overflow_vect)


1

CS12 має значення 2, оскільки він представляє біт 2 реєстру TCCR1B.

(1 << CS12) приймає значення 1 (0b00000001) і зміщує його вліво на 2 рази, щоб отримати (0b00000100). Порядок операцій диктує, що речі в () відбуваються спочатку, тому це робиться до того, як буде оцінено "| =".

(1 << CS10) приймає значення 1 (0b00000001) і зміщує його вліво на 0 разів, щоб отримати (0b00000001). Порядок операцій диктує, що речі в () відбуваються спочатку, тому це робиться до того, як буде оцінено "| =".

Отже, тепер ми отримуємо TCCR1B | = 0b00000101, що те саме, що TCCR1B = TCCR1B | 0b00000101.

Оскільки "|" є "АБО", всі біти, крім CS12 в TCCR1B, не впливають.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.