This commit is contained in:
Sergey Yarkov 2023-10-15 20:41:50 +03:00
parent 64a011b4cc
commit d47e00762f
No known key found for this signature in database
GPG Key ID: E91CE1FABC619B6F
3 changed files with 104 additions and 97 deletions

View File

@ -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,21 +32,21 @@ 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
@ -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 "Выбор частоты выполнения инструкций для симулятора")
Выбор частоты выполнения инструкций для симулятора
<figure>
<img src="/media/mplabx-ide-sim-option.png" alt="Выбор частоты выполнения инструкций для симулятора">
<figcaption>Выбор частоты выполнения инструкций для симулятора</figcaption>
</figure>
Установим точки останова на холостые операции **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")
<figure>
<img src="/media/screen-3.png" alt="Таблица выбора предделителя для таймера-счетчика 1">
<figcaption>Таблица выбора предделителя для таймера-счетчика 1</figcaption>
</figure>
Таблица выбора предделителя
Будем использовать таймер в 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,12 +215,12 @@ INIT:
; ============================================================================
ldi t1, (1<<CTC1) | (1<<CS13) | (1<<CS12) | (1<<CS11) | (1<<CS10)
out TCCR1, t1 ; setup timer
clr t1 ; clear timer counter register
clr t1 ; clear timer counter register
out TCNT1, t1
ldi t1, 244 ; 0.5 sec
out OCR1A, t1
out OCR1C, t1 ; for clearing timer on compare match
ldi t1, (1<<OCIE1A) ; enable output compare A interrupt
ldi t1, (1<<OCIE1A) ; enable output compare A interrupt
out TIMSK, t1
sei
@ -301,11 +304,11 @@ int main(void) {
### 8-ми битный таймер с предделителем 1024
Что если доступен предделитель частоты только на 1024? Тогда регистр для сравнения OCRn будет равняться 3906, что не подходит для 8-ми битного регистра. Как вариант можно считать количество переполнений таймера. 3906.25/255 = 15 переполнений.
Что если доступен предделитель частоты только на 1024? Тогда регистр для сравнения `OCRn` будет равняться 3906, что не подходит для 8-ми битного регистра. Как вариант можно считать количество переполнений таймера. `3906.25/255 = 15` переполнений.
Программа на ассемблере:
```nasm
```asmatmel
.DEF t1 = r16
.DEF t2 = r17
.DEF tc1_ovf_cnt = r18
@ -438,7 +441,7 @@ int main(void) {
Бывают случаи когда нужно выполнять какую либо логику раз в несколько часов или дней. Решением является завести глобальную переменную где будем хранить количество прошедших секунд с момента начала отсчета таймера, а в прерывании переполнения таймера будем уже сравнивать значение счетчика с заданным числом. Следующая программа переключает светодиод раз в 6 часов.
```nasm
```asmatmel
.DEF t1 = r16
.DEF t2 = r17
.DEF tc1_ovf_cnt = r18
@ -603,7 +606,7 @@ int main(void) {
## Полезные материалы
* [AVR Instruction Set](https://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf)
* [AVR130: Setup and Use of AVR Timers](https://ww1.microchip.com/downloads/en/Appnotes/Atmel-2505-Setup-and-Use-of-AVR-Timers_ApplicationNote_AVR130.pdf)
* [AVR Delay Loop Calculator](http://darcy.rsgc.on.ca/ACES/TEI4M/AVRdelay.html)
* [AVR Timer Calculator](https://eleccelerator.com/avr-timer-calculator/)
- [AVR Instruction Set](https://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf)
- [AVR130: Setup and Use of AVR Timers](https://ww1.microchip.com/downloads/en/Appnotes/Atmel-2505-Setup-and-Use-of-AVR-Timers_ApplicationNote_AVR130.pdf)
- [AVR Delay Loop Calculator](http://darcy.rsgc.on.ca/ACES/TEI4M/AVRdelay.html)
- [AVR Timer Calculator](https://eleccelerator.com/avr-timer-calculator/)

View File

@ -11,17 +11,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
@ -29,21 +30,21 @@ 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
@ -111,13 +112,14 @@ DELAY24_LOOP:
avrasm2 -fI ./main.asm
```
Если использовать 32 бита то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM).
Если использовать 32 бита для хранения кол-ва тактов то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM).
Чтобы проверить точно ли выполняется задержка, воспользуемся симулятором который встроен в IDE MPLAB X. Перед этим необходимо настроить частоту работы МК в конфигурации проекта:
![Выбор частоты выполнения инструкций для симулятора](/media/mplabx-ide-sim-option.png "Выбор частоты выполнения инструкций для симулятора")
Выбор частоты выполнения инструкций для симулятора
<figure>
<img src="/media/mplabx-ide-sim-option.png" alt="Выбор частоты выполнения инструкций для симулятора">
<figcaption>Выбор частоты выполнения инструкций для симулятора</figcaption>
</figure>
Установим точки останова на холостые операции **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")
<figure>
<img src="/media/screen-3.png" alt="Таблица выбора предделителя для таймера-счетчика 1">
<figcaption>Таблица выбора предделителя для таймера-счетчика 1</figcaption>
</figure>
Таблица выбора предделителя
Будем использовать таймер в 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,12 +213,12 @@ INIT:
; ============================================================================
ldi t1, (1<<CTC1) | (1<<CS13) | (1<<CS12) | (1<<CS11) | (1<<CS10)
out TCCR1, t1 ; setup timer
clr t1 ; clear timer counter register
clr t1 ; clear timer counter register
out TCNT1, t1
ldi t1, 244 ; 0.5 sec
out OCR1A, t1
out OCR1C, t1 ; for clearing timer on compare match
ldi t1, (1<<OCIE1A) ; enable output compare A interrupt
ldi t1, (1<<OCIE1A) ; enable output compare A interrupt
out TIMSK, t1
sei
@ -299,11 +302,11 @@ int main(void) {
### 8-ми битный таймер с предделителем 1024
Что если доступен предделитель частоты только на 1024? Тогда регистр для сравнения OCRn будет равняться 3906, что не подходит для 8-ми битного регистра. Как вариант можно считать количество переполнений таймера. 3906.25/255 = 15 переполнений.
Что если доступен предделитель частоты только на 1024? Тогда регистр для сравнения `OCRn` будет равняться 3906, что не подходит для 8-ми битного регистра. Как вариант можно считать количество переполнений таймера. `3906.25/255 = 15` переполнений.
Программа на ассемблере:
```nasm
```asmatmel
.DEF t1 = r16
.DEF t2 = r17
.DEF tc1_ovf_cnt = r18
@ -436,7 +439,7 @@ int main(void) {
Бывают случаи когда нужно выполнять какую либо логику раз в несколько часов или дней. Решением является завести глобальную переменную где будем хранить количество прошедших секунд с момента начала отсчета таймера, а в прерывании переполнения таймера будем уже сравнивать значение счетчика с заданным числом. Следующая программа переключает светодиод раз в 6 часов.
```nasm
```asmatmel
.DEF t1 = r16
.DEF t2 = r17
.DEF tc1_ovf_cnt = r18
@ -601,7 +604,7 @@ int main(void) {
## Полезные материалы
* [AVR Instruction Set](https://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf)
* [AVR130: Setup and Use of AVR Timers](https://ww1.microchip.com/downloads/en/Appnotes/Atmel-2505-Setup-and-Use-of-AVR-Timers_ApplicationNote_AVR130.pdf)
* [AVR Delay Loop Calculator](http://darcy.rsgc.on.ca/ACES/TEI4M/AVRdelay.html)
* [AVR Timer Calculator](https://eleccelerator.com/avr-timer-calculator/)
- [AVR Instruction Set](https://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf)
- [AVR130: Setup and Use of AVR Timers](https://ww1.microchip.com/downloads/en/Appnotes/Atmel-2505-Setup-and-Use-of-AVR-Timers_ApplicationNote_AVR130.pdf)
- [AVR Delay Loop Calculator](http://darcy.rsgc.on.ca/ACES/TEI4M/AVRdelay.html)
- [AVR Timer Calculator](https://eleccelerator.com/avr-timer-calculator/)

View File

@ -69,13 +69,14 @@ export type MarkdownHeadingToc = MarkdownHeading & { subheadings: MarkdownHeadin
export function buildToc(headings: MarkdownHeading[], maxDepth: number = 2) {
const toc: MarkdownHeadingToc[] = [];
const parentHeadings = new Map();
headings.forEach((h) => {
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;