Це не дивно виглядає. Ось як насправді виглядає звичайний код 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
- це регістр, який контролює значення вихідних штифтів в межах PORTC
ATmega328P. PINC
- це регістр, де доступні вхідні значення PORTC
. Принципово такі речі відбуваються всередині, коли ви використовуєте функції digitalWrite
або digitalRead
. Однак існує операція пошуку, яка перетворює ардуїнські "штифтові номери" у фактичні апаратні номери штифтів, що займає десь у царині 50 тактових циклів. Як ви, напевно, можете здогадатися, якщо ви намагаєтеся йти швидко, витрачати 50 циклів годин на операцію, яка повинна зажадати лише 1, трохи смішно.
Наведена вище функція, ймовірно, займає десь у царині 100-200 тактових циклів для передачі 8 біт. Це тягне за собою 24 пін-записи і 8 читання. Це багато, у багато разів швидше, ніж використання digital{stuff}
функцій.