LINUX.ORG.RU

AVR PORT Blink

 


0

1

Не могу понять, как работает это:

int main() {
  pinMode(13, OUTPUT);
  while(1) {
    PORTB |= 0x20;
    PORTB &= ~0x20;    
  }
}

сначала думал, точно так же как это:

int main() {
  pinMode(13, OUTPUT);
  while(1) {
    digitalWrite(13, HIGH);
    digitalWrite(13, LOW);   
  }
}

но загрузил в симулятор Atmel Studio и увидел, что регистры порта переключаются по-разному в этих двух случаях (при пошаговой отладке). В первом примере регистры PORTB и PINB переключаются поочередно, а во втором - одновременно (после каждой строчки). Понимаю, что чего-то не понимаю, а чего не понимаю - не понимаю.

P.S.
А линукс тут при том, что утилита «оффтопик 8.1» работает гостем под KVM

★★★★★

А на самом деле, между записью в регистр PORTx и установкой значения на PINx проходит один такт. В первом случае компилятор, вероятно оптимизирует цикл, загружая значения для PORTB в два регистра и потом делает

label:
  out PORTB, r16   # 1 такт
  out PORTB, r17   # 1 такт
  rjmp label

что в отладчике-симуляторе выглядит как поочерёдное переключение, что на самом деле является просто отставанием PIN от PORT.

Во втором случае у тебя вызовы функций, которые занимают гораздо больше, чем 1 такт. Если мне не изменяет память, то call и ret каждый занимает по 4 такта, плюс ещё тело функции. Поэтому во втором варианте в отладчике происходит кажущееся одновременное переключение PORT и PIN, если не заходить в функции.

Посмотри на ассм-код в обоих случаях. Через obj-dump можно как-то было декомпилировать. Не уверен, что опция -S gcc выдаст нужный асм.

Спасибо за внимание.

anonymous ()
Ответ на: комментарий от af5

Одновременно они всё равно не переключаются, это только в отладчике так выглядит. В любом случае будет задержка в 1 такт и от неё никак не избавиться, потому, что так устроен контроллер.

Есть ещё клёвый приём, о котором мало кто знает. На самом деле в порт PIN можно записывать значения, а не только читать оттуда. Если пин порта настроен на вывод (DDR = 1), то при записи 1 в соответствующий PIN произойдёт переключение состояния вывода. То есть, если PIN был в 0, то при записи туда 1 он переключится в 1, а если был в 1, то станет 0. Если требуется переключить состояние пина, то это гораздо проще, чем через PORTx.

На асме вся программа будет выглядеть так:

ldi r16, 0b00000001
out DDRB, r16         ;  PB0 настроен на вывод

label:
  out PINB, r16       ;  переключить PB0
  rjmp label

А занимать она будет во флеш-памяти контроллера ровно 8 байт :) Так как out выполняется за 1 такт, а rjmp за 2, то на выходе PB0 будет меандр с частотой F_CPU/6. Два nop'а, кстати, тоже выполняются за 2 такта ;)

anonymous ()
Ответ на: комментарий от anonymous

F_CPU/6

У меня в первом примере тоже F_CPU/6, только за руками регистрами следить сложнее

af5 ★★★★★ ()
Ответ на: комментарий от af5

Конечно можно. Просто пиши PINB = 1 или типа того.

anonymous ()
Ответ на: комментарий от af5

В твоём случает это будет:

int main() {
  pinMode(13, OUTPUT);   // DDRB = 0x20; — тоже самое
  while(1)
    PINB = 0x20;
}
anonymous ()

Так как память у меня не очень, то я всё же решил свериться с даташитом.

Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of DDRxn. Note that the SBI instruction can be used to toggle one single bit in a port.

rcall и icall выполняются за 3 такта, ret и reti за 4. Так что любой вызов функции, если она не заинлайнилась компилятором, или обработка прерывания расходуют минимум 7 драгоценных тактов :)

anonymous ()

Я обычно делаю так:

while (1) {
    PORTB ^= 0x20;
}

Это куда приятнее смотрится, чем поочерёдная запись двух значений.

Про вариант с PINB не знал, по количеству инструкций это самое эффективное решение, да.

KivApple ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.