mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 02:19:08 +03:00
upd
This commit is contained in:
parent
64a011b4cc
commit
d47e00762f
@ -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. Перед этим необходимо настроить частоту работы МК в конфигурации проекта:
|
||||
|
||||

|
||||
|
||||
Выбор частоты выполнения инструкций для симулятора
|
||||
<figure>
|
||||
<img src="/media/mplabx-ide-sim-option.png" alt="Выбор частоты выполнения инструкций для симулятора">
|
||||
<figcaption>Выбор частоты выполнения инструкций для симулятора</figcaption>
|
||||
</figure>
|
||||
|
||||
Установим точки останова на холостые операции **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 что очень удобно:
|
||||
|
||||

|
||||
<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,17 +215,17 @@ INIT:
|
||||
; ============================================================================
|
||||
ldi t1, (1<<CTC1) | (1<<CS13) | (1<<CS12) | (1<<CS11) | (1<<CS10)
|
||||
out TCCR1, t1 ; setup timer
|
||||
clr t1 ; clear timer counter register
|
||||
out TCNT1, t1
|
||||
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
|
||||
|
||||
LOOP:
|
||||
LOOP:
|
||||
nop
|
||||
rjmp LOOP
|
||||
|
||||
@ -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
|
||||
@ -345,12 +348,12 @@ INIT:
|
||||
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
||||
out TCCR1, t1 ; setup timer
|
||||
clr t1 ; clear timer counter register
|
||||
out TCNT1, t1
|
||||
out TCNT1, t1
|
||||
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
||||
out TIMSK, t1
|
||||
sei
|
||||
|
||||
LOOP:
|
||||
LOOP:
|
||||
nop
|
||||
rjmp LOOP
|
||||
|
||||
@ -438,7 +441,7 @@ int main(void) {
|
||||
|
||||
Бывают случаи когда нужно выполнять какую либо логику раз в несколько часов или дней. Решением является завести глобальную переменную где будем хранить количество прошедших секунд с момента начала отсчета таймера, а в прерывании переполнения таймера будем уже сравнивать значение счетчика с заданным числом. Следующая программа переключает светодиод раз в 6 часов.
|
||||
|
||||
```nasm
|
||||
```asmatmel
|
||||
.DEF t1 = r16
|
||||
.DEF t2 = r17
|
||||
.DEF tc1_ovf_cnt = r18
|
||||
@ -486,12 +489,12 @@ INIT:
|
||||
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
||||
out TCCR1, t1 ; setup timer
|
||||
clr t1 ; clear timer counter register
|
||||
out TCNT1, t1
|
||||
out TCNT1, t1
|
||||
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
||||
out TIMSK, t1
|
||||
sei
|
||||
|
||||
LOOP:
|
||||
LOOP:
|
||||
nop
|
||||
rjmp LOOP
|
||||
|
||||
@ -528,8 +531,8 @@ TIMER1_OVF:
|
||||
clr r25
|
||||
sts ellapsed_seconds, r25 ; clear ellapsed seconds
|
||||
sts ellapsed_seconds+1, r25 ; -----------------------
|
||||
|
||||
_TIMER1_OVF_EXIT:
|
||||
|
||||
_TIMER1_OVF_EXIT:
|
||||
out SREG, t1
|
||||
pop t1
|
||||
reti
|
||||
@ -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/)
|
||||
|
||||
@ -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,23 +30,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
|
||||
@ -58,10 +59,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
|
||||
@ -82,7 +83,7 @@ INIT:
|
||||
LOOP:
|
||||
nop
|
||||
delay24 500000
|
||||
nop
|
||||
nop
|
||||
rcall LED_TOGGLE
|
||||
rjmp LOOP
|
||||
|
||||
@ -98,8 +99,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
|
||||
@ -111,13 +112,14 @@ DELAY24_LOOP:
|
||||
avrasm2 -fI ./main.asm
|
||||
```
|
||||
|
||||
Если использовать 32 бита то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM).
|
||||
Если использовать 32 бита для хранения кол-ва тактов то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM).
|
||||
|
||||
Чтобы проверить точно ли выполняется задержка, воспользуемся симулятором который встроен в IDE MPLAB X. Перед этим необходимо настроить частоту работы МК в конфигурации проекта:
|
||||
|
||||

|
||||
|
||||
Выбор частоты выполнения инструкций для симулятора
|
||||
<figure>
|
||||
<img src="/media/mplabx-ide-sim-option.png" alt="Выбор частоты выполнения инструкций для симулятора">
|
||||
<figcaption>Выбор частоты выполнения инструкций для симулятора</figcaption>
|
||||
</figure>
|
||||
|
||||
Установим точки останова на холостые операции **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 что очень удобно:
|
||||
|
||||

|
||||
<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,17 +213,17 @@ INIT:
|
||||
; ============================================================================
|
||||
ldi t1, (1<<CTC1) | (1<<CS13) | (1<<CS12) | (1<<CS11) | (1<<CS10)
|
||||
out TCCR1, t1 ; setup timer
|
||||
clr t1 ; clear timer counter register
|
||||
out TCNT1, t1
|
||||
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
|
||||
|
||||
LOOP:
|
||||
LOOP:
|
||||
nop
|
||||
rjmp LOOP
|
||||
|
||||
@ -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
|
||||
@ -343,12 +346,12 @@ INIT:
|
||||
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
||||
out TCCR1, t1 ; setup timer
|
||||
clr t1 ; clear timer counter register
|
||||
out TCNT1, t1
|
||||
out TCNT1, t1
|
||||
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
||||
out TIMSK, t1
|
||||
sei
|
||||
|
||||
LOOP:
|
||||
LOOP:
|
||||
nop
|
||||
rjmp LOOP
|
||||
|
||||
@ -436,7 +439,7 @@ int main(void) {
|
||||
|
||||
Бывают случаи когда нужно выполнять какую либо логику раз в несколько часов или дней. Решением является завести глобальную переменную где будем хранить количество прошедших секунд с момента начала отсчета таймера, а в прерывании переполнения таймера будем уже сравнивать значение счетчика с заданным числом. Следующая программа переключает светодиод раз в 6 часов.
|
||||
|
||||
```nasm
|
||||
```asmatmel
|
||||
.DEF t1 = r16
|
||||
.DEF t2 = r17
|
||||
.DEF tc1_ovf_cnt = r18
|
||||
@ -484,12 +487,12 @@ INIT:
|
||||
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
||||
out TCCR1, t1 ; setup timer
|
||||
clr t1 ; clear timer counter register
|
||||
out TCNT1, t1
|
||||
out TCNT1, t1
|
||||
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
||||
out TIMSK, t1
|
||||
sei
|
||||
|
||||
LOOP:
|
||||
LOOP:
|
||||
nop
|
||||
rjmp LOOP
|
||||
|
||||
@ -526,8 +529,8 @@ TIMER1_OVF:
|
||||
clr r25
|
||||
sts ellapsed_seconds, r25 ; clear ellapsed seconds
|
||||
sts ellapsed_seconds+1, r25 ; -----------------------
|
||||
|
||||
_TIMER1_OVF_EXIT:
|
||||
|
||||
_TIMER1_OVF_EXIT:
|
||||
out SREG, t1
|
||||
pop t1
|
||||
reti
|
||||
@ -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/)
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user