diff --git a/src/content/blog/en/реализация-задержек-в-avr-микроконтроллере.md b/src/content/blog/en/реализация-задержек-в-avr-микроконтроллере.md index 8a1eea1..1e87081 100644 --- a/src/content/blog/en/реализация-задержек-в-avr-микроконтроллере.md +++ b/src/content/blog/en/реализация-задержек-в-avr-микроконтроллере.md @@ -1,8 +1,8 @@ --- coverImage: null draft: true -author: Сергей Ярков -title: Реализация задержек в AVR микроконтроллере +author: Sergey Yarkov +title: Implementation of delays in the AVR microcontroller description: Очень часто в программах приходится выполнять задержку. Например при реализации какого либо протокола необходимо в точности выдерживать тайминги, или же необходимо выполнять какую либо логику раз в несколько часов @@ -13,17 +13,18 @@ tags: pubDate: 2023-10-15T16:28:52.587Z updatedDate: 2023-10-15T16:28:52.601Z --- -## Способы реализации -Существует два способа как можно реализовать задержку - это использовать аппаратные **таймеры** микроконтроллера или использовать простой **цикл** который выполняется определенное количество тактов. Преимущество использования аппаратного таймера микроконтроллера перед обычным программным циклом заключается в неблокирующем выполнении программы за счет того что работа таймера-счетчика не зависит от выполнения основной программы. Но иногда нужно использовать задержку всего в несколько микросекунд, что особо не повлияет на работу программы в некоторых случаях, тогда можно посмотреть в сторону простых циклов. Будет рассмотрен вариант и с длительной задержкой в несколько часов или дней. Программы будут представлены на языке Си и Ассемблера для микроконтроллера AVR ATtiny45v. В качестве основной программы будет моргающий светодиод который переключается раз в 0.5 секунд. +# Способы реализации + +Существует два способа как можно реализовать задержку - это использовать аппаратные таймеры микроконтроллера или использовать простой цикл который выполняется определенное количество тактов. Преимущество использования аппаратного таймера микроконтроллера перед обычным программным циклом заключается в неблокирующем выполнении программы за счет того что работа таймера-счетчика не зависит от выполнения основной программы. Но иногда нужно использовать задержку всего в несколько микросекунд, что особо не повлияет на работу программы в некоторых случаях, тогда можно посмотреть в сторону простых циклов. Будет рассмотрен вариант и с длительной задержкой в несколько часов или дней. Программы будут представлены на языке Си и Ассемблера для микроконтроллера AVR ATtiny45V. В качестве основной программы будет моргающий светодиод который переключается раз в 0.5 секунд. ## Используем цикл -Для реализации этого способа задержки для начала нужно посчитать кол-во тактов процессора для выполнения задержки в N секунд и затем сделать цикл. Например для МК с частотой в 8MHz один такт будет равен 1/8 = 0.125uS. Значит чтобы рассчитать кол-во тактов например в 0.5 секунд то это будет (0.5 *1,000,000)* 8 = 4,000,000 тактов, т.е половина от частоты МК. +Для реализации этого способа задержки для начала нужно посчитать кол-во тактов процессора для выполнения задержки в N секунд и затем сделать цикл. Например для МК с частотой в 8 MHz один такт будет равен `1/8 = 0.125uS`. Значит чтобы рассчитать кол-во тактов например в 0.5 секунд то это будет `(0.5 × 1,000,000) × 8 = 4,000,000` тактов, т.е половина от частоты МК. Реализация цикла задержки на ассемблере: -```asm +```asmatmel .MACRO delay16 ldi delay8_r, LOW(@0 * F_CPU / 4 - 3) ; 1 ldi delay16_r, HIGH(@0 * F_CPU / 4 - 3) ; 1 @@ -31,23 +32,23 @@ updatedDate: 2023-10-15T16:28:52.601Z .ENDMACRO DELAY16_LOOP: - subi delay8_r, 1 ; 1 - sbci delay16_r, 0 ; 1 - brcc DELAY16_LOOP ; 1 = false, 2 = true - ret ; 4 + subi delay8_r, 1 ; 1 + sbci delay16_r, 0 ; 1 + brcc DELAY16_LOOP ; 1 = false, 2 = true + ret ; 4 ``` -Каждая инструкция в микроконтроллере выполняется за определенное кол-во тактов (комментарии в коде выше). Например инструкция **ldi** выполняется за один такт, а инструкция **brcc** выполняется за два такта - когда условие верно и за один такт когда условие неверно. На кол-во тактов так же влияет и разрядность программного счетчика (см. [AVR Instruction Set](http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf)). +Каждая инструкция в микроконтроллере выполняется за определенное кол-во тактов (комментарии в коде выше). Например инструкция `ldi` выполняется за один такт, а инструкция `brcc` выполняется за два такта - когда условие верно и за один такт когда условие неверно. На кол-во тактов так же влияет и разрядность программного счетчика (см. [AVR Instruction Set](http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf)). ### Пояснения к программе -В данной реализации используется макрос **delay16** который загружает кол-во тактов в два 8-ми битных регистра которые помечены как младший и старший. Затем вызывается подпрограмма выполнения цикла. Зачем нужно в расчете тактов делить полученное число на 4? Посмотрите на подпрограмму **DELAY16_LOOP** которая выполняет декрементирование 16-ти битного числа, вы увидите что одна итерация цикла занимает 4 такта. От полученного числа отнимаем 3 для компенсации времени. +В данной реализации используется макрос `delay16` который загружает кол-во тактов в два 8-ми битных регистра которые помечены как младший и старший. Затем вызывается подпрограмма выполнения цикла. Зачем нужно в расчете тактов делить полученное число на 4? Посмотрите на подпрограмму `DELAY16_LOOP` которая выполняет декрементирование 16-ти битного числа, вы увидите что одна итерация цикла занимает 4 такта. От полученного числа отнимаем 3 для компенсации времени. -В итоге максимальная задержка которая может получиться при работе МК в 8MHz вызвав этот макрос равняется 65535/8 *4 = 32767.5uS что не совсем подходит для мигания светодиодом. Что бы сделать задержку дольшо можно использовать 24 бита для хранения кол-ва тактов. Тогда максимальная задержка составит 16,777,216/8* 5 = 10.48сек. В итоге получается такая программа с переключающимся светодиодом раз в 0.5 секунд: +В итоге максимальная задержка которая может получиться при работе МК в 8 MHz вызвав этот макрос равняется `65535/8 * 4 = 32767.5uS` что не совсем подходит для мигания светодиодом. Что бы сделать задержку дольшо можно использовать 24 бита для хранения кол-ва тактов. Тогда максимальная задержка составит `16,777,216/8 * 5 = 10.48сек`. В итоге получается такая программа с переключающимся светодиодом раз в 0.5 секунд: -```asm +```asmatmel .EQU F_CPU = 8 ; MHz - + .DEF t1 = r16 .DEF t2 = r17 .DEF delay24_r = r18 @@ -60,10 +61,10 @@ DELAY16_LOOP: .EQU LED_PIN = PB0 .MACRO delay24 - ldi delay8_r, LOW(@0 * F_CPU / 5 - 3) + ldi delay8_r, LOW(@0 * F_CPU / 5 - 3) ldi delay16_r, HIGH(@0 * F_CPU / 5 - 3) - ldi delay24_r, BYTE3(@0 * F_CPU / 5 - 3) - rcall DELAY24_LOOP + ldi delay24_r, BYTE3(@0 * F_CPU / 5 - 3) + rcall DELAY24_LOOP .ENDMACRO .CSEG @@ -84,7 +85,7 @@ INIT: LOOP: nop delay24 500000 - nop + nop rcall LED_TOGGLE rjmp LOOP @@ -100,8 +101,8 @@ LED_TOGGLE: ret DELAY24_LOOP: - subi delay8_r, 1 - sbci delay16_r, 0 + subi delay8_r, 1 + sbci delay16_r, 0 sbci delay24_r, 0 brcc DELAY24_LOOP ret @@ -113,13 +114,14 @@ DELAY24_LOOP: avrasm2 -fI ./main.asm ``` -Если использовать 32 бита то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM). +Если использовать 32 бита для хранения кол-ва тактов то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM). Чтобы проверить точно ли выполняется задержка, воспользуемся симулятором который встроен в IDE MPLAB X. Перед этим необходимо настроить частоту работы МК в конфигурации проекта: -![Выбор частоты выполнения инструкций для симулятора](/media/mplabx-ide-sim-option.png "Выбор частоты выполнения инструкций для симулятора") - -Выбор частоты выполнения инструкций для симулятора +
+ Выбор частоты выполнения инструкций для симулятора +
Выбор частоты выполнения инструкций для симулятора
+
Установим точки останова на холостые операции **nop** до и после вызова макроса и выполним отладку: @@ -129,7 +131,7 @@ avrasm2 -fI ./main.asm ![Отладка программы](/media/screen-2.png) -Можно видеть что после выхода из подпрограммы DELAY24_LOOP прошло ровно 500 миллисекунд. +Можно видеть что после выхода из подпрограммы `DELAY24_LOOP` прошло ровно 500 миллисекунд. Для языка Си все будет довольно просто. Библиотека [AVR Libc](https://www.nongnu.org/avr-libc/) поставляется с набором утилит которые упрощают программирование. Поэтому нам достаточно указать частоту МК и вызвать определенную функцию. Эта функция как раз из рассчитает нужное кол-во тактов процессора и выполнит цикл. @@ -167,17 +169,18 @@ avr-objcopy -j .text -j .data -O ihex a.out main.hex ### 8-ми битный таймер с предделителем 16384 -Для микроконтроллера ATtiny45v имеются только 8-ми битные таймеры, причем у одного из них (TIMER1) имеется делитель частоты на 16384 что очень удобно: +Для микроконтроллера ATtiny45V имеются только 8-ми битные таймеры, причем у одного из них (TIMER1) имеется делитель частоты на 16384 что очень удобно: -![Таблица выбора предделителя для таймера 1](/media/screen-3.png "Таблица выбора предделителя для таймера 1") +
+ Таблица выбора предделителя для таймера-счетчика 1 +
Таблица выбора предделителя для таймера-счетчика 1
+
-Таблица выбора предделителя - -Будем использовать таймер в CTC (Clear on Timer Compare match) режиме. Итоговая частота работы таймера-счетчика будет равна: 8MHz/16384 = 488.28Hz. Один тик таймера будет равняться 1/488.28 = 2048uS. Значит для 0.5 секунд значение счетчика будет равняться 0.5*488.38 = 244.14 тиков что достаточно для 8-бит. Будем записывать число в регистр сравнения OCRn и генерировать прерывание при совпадении. +Будем использовать таймер в CTC (Clear on Timer Compare match) режиме. Итоговая частота работы таймера-счетчика будет равна: `8MHz/16384 = 488.28Hz`. Один тик таймера будет равняться `1/488.28 = 2048uS`. Значит для 0.5 секунд значение счетчика будет равняться `0.5×488.38 = 244.14` тиков что достаточно для 8-бит. Будем записывать число в регистр сравнения `OCRn` и генерировать прерывание при совпадении. Программа на ассемблере: -```nasm +```asmatmel .DEF t1 = r16 .DEF t2 = r17 @@ -212,17 +215,17 @@ INIT: ; ============================================================================ ldi t1, (1< + Выбор частоты выполнения инструкций для симулятора +
Выбор частоты выполнения инструкций для симулятора
+ Установим точки останова на холостые операции **nop** до и после вызова макроса и выполним отладку: @@ -127,7 +129,7 @@ avrasm2 -fI ./main.asm ![Отладка программы](/media/screen-2.png) -Можно видеть что после выхода из подпрограммы DELAY24_LOOP прошло ровно 500 миллисекунд. +Можно видеть что после выхода из подпрограммы `DELAY24_LOOP` прошло ровно 500 миллисекунд. Для языка Си все будет довольно просто. Библиотека [AVR Libc](https://www.nongnu.org/avr-libc/) поставляется с набором утилит которые упрощают программирование. Поэтому нам достаточно указать частоту МК и вызвать определенную функцию. Эта функция как раз из рассчитает нужное кол-во тактов процессора и выполнит цикл. @@ -165,17 +167,18 @@ avr-objcopy -j .text -j .data -O ihex a.out main.hex ### 8-ми битный таймер с предделителем 16384 -Для микроконтроллера ATtiny45v имеются только 8-ми битные таймеры, причем у одного из них (TIMER1) имеется делитель частоты на 16384 что очень удобно: +Для микроконтроллера ATtiny45V имеются только 8-ми битные таймеры, причем у одного из них (TIMER1) имеется делитель частоты на 16384 что очень удобно: -![Таблица выбора предделителя для таймера 1](/media/screen-3.png "Таблица выбора предделителя для таймера 1") +
+ Таблица выбора предделителя для таймера-счетчика 1 +
Таблица выбора предделителя для таймера-счетчика 1
+
-Таблица выбора предделителя - -Будем использовать таймер в CTC (Clear on Timer Compare match) режиме. Итоговая частота работы таймера-счетчика будет равна: 8MHz/16384 = 488.28Hz. Один тик таймера будет равняться 1/488.28 = 2048uS. Значит для 0.5 секунд значение счетчика будет равняться 0.5*488.38 = 244.14 тиков что достаточно для 8-бит. Будем записывать число в регистр сравнения OCRn и генерировать прерывание при совпадении. +Будем использовать таймер в CTC (Clear on Timer Compare match) режиме. Итоговая частота работы таймера-счетчика будет равна: `8MHz/16384 = 488.28Hz`. Один тик таймера будет равняться `1/488.28 = 2048uS`. Значит для 0.5 секунд значение счетчика будет равняться `0.5×488.38 = 244.14` тиков что достаточно для 8-бит. Будем записывать число в регистр сравнения `OCRn` и генерировать прерывание при совпадении. Программа на ассемблере: -```nasm +```asmatmel .DEF t1 = r16 .DEF t2 = r17 @@ -210,17 +213,17 @@ INIT: ; ============================================================================ ldi t1, (1< { const heading = { ...h, subheadings: [] }; parentHeadings.set(heading.depth, heading); if (heading.depth === 1) { toc.push(heading); } else if (heading.depth <= maxDepth) { - parentHeadings.get(heading.depth - 1).subheadings.push(heading); + parentHeadings.get(heading.depth - 1)?.subheadings.push(heading); } }); return toc;