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. Перед этим необходимо настроить частоту работы МК в конфигурации проекта:
-
-
-Выбор частоты выполнения инструкций для симулятора
+
+
+ Выбор частоты выполнения инструкций для симулятора
+
Установим точки останова на холостые операции **nop** до и после вызова макроса и выполним отладку:
@@ -129,7 +131,7 @@ avrasm2 -fI ./main.asm

-Можно видеть что после выхода из подпрограммы 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
+
-Таблица выбора предделителя
-
-Будем использовать таймер в 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

-Можно видеть что после выхода из подпрограммы 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
+
-Таблица выбора предделителя
-
-Будем использовать таймер в 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;