mirror of
https://github.com/sergeyyarkov/yarkov.tech.git
synced 2026-10-11 10:29:21 +03:00
upd
This commit is contained in:
parent
64a011b4cc
commit
d47e00762f
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
coverImage: null
|
coverImage: null
|
||||||
draft: true
|
draft: true
|
||||||
author: Сергей Ярков
|
author: Sergey Yarkov
|
||||||
title: Реализация задержек в AVR микроконтроллере
|
title: Implementation of delays in the AVR microcontroller
|
||||||
description: Очень часто в программах приходится выполнять задержку. Например
|
description: Очень часто в программах приходится выполнять задержку. Например
|
||||||
при реализации какого либо протокола необходимо в точности выдерживать
|
при реализации какого либо протокола необходимо в точности выдерживать
|
||||||
тайминги, или же необходимо выполнять какую либо логику раз в несколько часов
|
тайминги, или же необходимо выполнять какую либо логику раз в несколько часов
|
||||||
@ -13,17 +13,18 @@ tags:
|
|||||||
pubDate: 2023-10-15T16:28:52.587Z
|
pubDate: 2023-10-15T16:28:52.587Z
|
||||||
updatedDate: 2023-10-15T16:28:52.601Z
|
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
|
.MACRO delay16
|
||||||
ldi delay8_r, LOW(@0 * F_CPU / 4 - 3) ; 1
|
ldi delay8_r, LOW(@0 * F_CPU / 4 - 3) ; 1
|
||||||
ldi delay16_r, HIGH(@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
|
.ENDMACRO
|
||||||
|
|
||||||
DELAY16_LOOP:
|
DELAY16_LOOP:
|
||||||
subi delay8_r, 1 ; 1
|
subi delay8_r, 1 ; 1
|
||||||
sbci delay16_r, 0 ; 1
|
sbci delay16_r, 0 ; 1
|
||||||
brcc DELAY16_LOOP ; 1 = false, 2 = true
|
brcc DELAY16_LOOP ; 1 = false, 2 = true
|
||||||
ret ; 4
|
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
|
.EQU F_CPU = 8 ; MHz
|
||||||
|
|
||||||
.DEF t1 = r16
|
.DEF t1 = r16
|
||||||
.DEF t2 = r17
|
.DEF t2 = r17
|
||||||
.DEF delay24_r = r18
|
.DEF delay24_r = r18
|
||||||
@ -60,10 +61,10 @@ DELAY16_LOOP:
|
|||||||
.EQU LED_PIN = PB0
|
.EQU LED_PIN = PB0
|
||||||
|
|
||||||
.MACRO delay24
|
.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 delay16_r, HIGH(@0 * F_CPU / 5 - 3)
|
||||||
ldi delay24_r, BYTE3(@0 * F_CPU / 5 - 3)
|
ldi delay24_r, BYTE3(@0 * F_CPU / 5 - 3)
|
||||||
rcall DELAY24_LOOP
|
rcall DELAY24_LOOP
|
||||||
.ENDMACRO
|
.ENDMACRO
|
||||||
|
|
||||||
.CSEG
|
.CSEG
|
||||||
@ -84,7 +85,7 @@ INIT:
|
|||||||
LOOP:
|
LOOP:
|
||||||
nop
|
nop
|
||||||
delay24 500000
|
delay24 500000
|
||||||
nop
|
nop
|
||||||
rcall LED_TOGGLE
|
rcall LED_TOGGLE
|
||||||
rjmp LOOP
|
rjmp LOOP
|
||||||
|
|
||||||
@ -100,8 +101,8 @@ LED_TOGGLE:
|
|||||||
ret
|
ret
|
||||||
|
|
||||||
DELAY24_LOOP:
|
DELAY24_LOOP:
|
||||||
subi delay8_r, 1
|
subi delay8_r, 1
|
||||||
sbci delay16_r, 0
|
sbci delay16_r, 0
|
||||||
sbci delay24_r, 0
|
sbci delay24_r, 0
|
||||||
brcc DELAY24_LOOP
|
brcc DELAY24_LOOP
|
||||||
ret
|
ret
|
||||||
@ -113,13 +114,14 @@ DELAY24_LOOP:
|
|||||||
avrasm2 -fI ./main.asm
|
avrasm2 -fI ./main.asm
|
||||||
```
|
```
|
||||||
|
|
||||||
Если использовать 32 бита то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM).
|
Если использовать 32 бита для хранения кол-ва тактов то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM).
|
||||||
|
|
||||||
Чтобы проверить точно ли выполняется задержка, воспользуемся симулятором который встроен в IDE MPLAB X. Перед этим необходимо настроить частоту работы МК в конфигурации проекта:
|
Чтобы проверить точно ли выполняется задержка, воспользуемся симулятором который встроен в IDE MPLAB X. Перед этим необходимо настроить частоту работы МК в конфигурации проекта:
|
||||||
|
|
||||||

|
<figure>
|
||||||
|
<img src="/media/mplabx-ide-sim-option.png" alt="Выбор частоты выполнения инструкций для симулятора">
|
||||||
Выбор частоты выполнения инструкций для симулятора
|
<figcaption>Выбор частоты выполнения инструкций для симулятора</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
Установим точки останова на холостые операции **nop** до и после вызова макроса и выполним отладку:
|
Установим точки останова на холостые операции **nop** до и после вызова макроса и выполним отладку:
|
||||||
|
|
||||||
@ -129,7 +131,7 @@ avrasm2 -fI ./main.asm
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Можно видеть что после выхода из подпрограммы DELAY24_LOOP прошло ровно 500 миллисекунд.
|
Можно видеть что после выхода из подпрограммы `DELAY24_LOOP` прошло ровно 500 миллисекунд.
|
||||||
|
|
||||||
Для языка Си все будет довольно просто. Библиотека [AVR Libc](https://www.nongnu.org/avr-libc/) поставляется с набором утилит которые упрощают программирование. Поэтому нам достаточно указать частоту МК и вызвать определенную функцию. Эта функция как раз из рассчитает нужное кол-во тактов процессора и выполнит цикл.
|
Для языка Си все будет довольно просто. Библиотека [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
|
### 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 t1 = r16
|
||||||
.DEF t2 = r17
|
.DEF t2 = r17
|
||||||
|
|
||||||
@ -212,17 +215,17 @@ INIT:
|
|||||||
; ============================================================================
|
; ============================================================================
|
||||||
ldi t1, (1<<CTC1) | (1<<CS13) | (1<<CS12) | (1<<CS11) | (1<<CS10)
|
ldi t1, (1<<CTC1) | (1<<CS13) | (1<<CS12) | (1<<CS11) | (1<<CS10)
|
||||||
out TCCR1, t1 ; setup timer
|
out TCCR1, t1 ; setup timer
|
||||||
clr t1 ; clear timer counter register
|
clr t1 ; clear timer counter register
|
||||||
out TCNT1, t1
|
out TCNT1, t1
|
||||||
ldi t1, 244 ; 0.5 sec
|
ldi t1, 244 ; 0.5 sec
|
||||||
out OCR1A, t1
|
out OCR1A, t1
|
||||||
out OCR1C, t1 ; for clearing timer on compare match
|
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
|
out TIMSK, t1
|
||||||
|
|
||||||
sei
|
sei
|
||||||
|
|
||||||
LOOP:
|
LOOP:
|
||||||
nop
|
nop
|
||||||
rjmp LOOP
|
rjmp LOOP
|
||||||
|
|
||||||
@ -301,11 +304,11 @@ int main(void) {
|
|||||||
|
|
||||||
### 8-ми битный таймер с предделителем 1024
|
### 8-ми битный таймер с предделителем 1024
|
||||||
|
|
||||||
Что если доступен предделитель частоты только на 1024? Тогда регистр для сравнения OCRn будет равняться 3906, что не подходит для 8-ми битного регистра. Как вариант можно считать количество переполнений таймера. 3906.25/255 = 15 переполнений.
|
Что если доступен предделитель частоты только на 1024? Тогда регистр для сравнения `OCRn` будет равняться 3906, что не подходит для 8-ми битного регистра. Как вариант можно считать количество переполнений таймера. `3906.25/255 = 15` переполнений.
|
||||||
|
|
||||||
Программа на ассемблере:
|
Программа на ассемблере:
|
||||||
|
|
||||||
```nasm
|
```asmatmel
|
||||||
.DEF t1 = r16
|
.DEF t1 = r16
|
||||||
.DEF t2 = r17
|
.DEF t2 = r17
|
||||||
.DEF tc1_ovf_cnt = r18
|
.DEF tc1_ovf_cnt = r18
|
||||||
@ -345,12 +348,12 @@ INIT:
|
|||||||
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
||||||
out TCCR1, t1 ; setup timer
|
out TCCR1, t1 ; setup timer
|
||||||
clr t1 ; clear timer counter register
|
clr t1 ; clear timer counter register
|
||||||
out TCNT1, t1
|
out TCNT1, t1
|
||||||
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
||||||
out TIMSK, t1
|
out TIMSK, t1
|
||||||
sei
|
sei
|
||||||
|
|
||||||
LOOP:
|
LOOP:
|
||||||
nop
|
nop
|
||||||
rjmp LOOP
|
rjmp LOOP
|
||||||
|
|
||||||
@ -438,7 +441,7 @@ int main(void) {
|
|||||||
|
|
||||||
Бывают случаи когда нужно выполнять какую либо логику раз в несколько часов или дней. Решением является завести глобальную переменную где будем хранить количество прошедших секунд с момента начала отсчета таймера, а в прерывании переполнения таймера будем уже сравнивать значение счетчика с заданным числом. Следующая программа переключает светодиод раз в 6 часов.
|
Бывают случаи когда нужно выполнять какую либо логику раз в несколько часов или дней. Решением является завести глобальную переменную где будем хранить количество прошедших секунд с момента начала отсчета таймера, а в прерывании переполнения таймера будем уже сравнивать значение счетчика с заданным числом. Следующая программа переключает светодиод раз в 6 часов.
|
||||||
|
|
||||||
```nasm
|
```asmatmel
|
||||||
.DEF t1 = r16
|
.DEF t1 = r16
|
||||||
.DEF t2 = r17
|
.DEF t2 = r17
|
||||||
.DEF tc1_ovf_cnt = r18
|
.DEF tc1_ovf_cnt = r18
|
||||||
@ -486,12 +489,12 @@ INIT:
|
|||||||
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
||||||
out TCCR1, t1 ; setup timer
|
out TCCR1, t1 ; setup timer
|
||||||
clr t1 ; clear timer counter register
|
clr t1 ; clear timer counter register
|
||||||
out TCNT1, t1
|
out TCNT1, t1
|
||||||
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
||||||
out TIMSK, t1
|
out TIMSK, t1
|
||||||
sei
|
sei
|
||||||
|
|
||||||
LOOP:
|
LOOP:
|
||||||
nop
|
nop
|
||||||
rjmp LOOP
|
rjmp LOOP
|
||||||
|
|
||||||
@ -528,8 +531,8 @@ TIMER1_OVF:
|
|||||||
clr r25
|
clr r25
|
||||||
sts ellapsed_seconds, r25 ; clear ellapsed seconds
|
sts ellapsed_seconds, r25 ; clear ellapsed seconds
|
||||||
sts ellapsed_seconds+1, r25 ; -----------------------
|
sts ellapsed_seconds+1, r25 ; -----------------------
|
||||||
|
|
||||||
_TIMER1_OVF_EXIT:
|
_TIMER1_OVF_EXIT:
|
||||||
out SREG, t1
|
out SREG, t1
|
||||||
pop t1
|
pop t1
|
||||||
reti
|
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)
|
- [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)
|
- [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 Delay Loop Calculator](http://darcy.rsgc.on.ca/ACES/TEI4M/AVRdelay.html)
|
||||||
* [AVR Timer Calculator](https://eleccelerator.com/avr-timer-calculator/)
|
- [AVR Timer Calculator](https://eleccelerator.com/avr-timer-calculator/)
|
||||||
|
|||||||
@ -11,17 +11,18 @@ tags:
|
|||||||
pubDate: 2023-10-15T16:28:52.587Z
|
pubDate: 2023-10-15T16:28:52.587Z
|
||||||
updatedDate: 2023-10-15T16:28:52.601Z
|
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
|
.MACRO delay16
|
||||||
ldi delay8_r, LOW(@0 * F_CPU / 4 - 3) ; 1
|
ldi delay8_r, LOW(@0 * F_CPU / 4 - 3) ; 1
|
||||||
ldi delay16_r, HIGH(@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
|
.ENDMACRO
|
||||||
|
|
||||||
DELAY16_LOOP:
|
DELAY16_LOOP:
|
||||||
subi delay8_r, 1 ; 1
|
subi delay8_r, 1 ; 1
|
||||||
sbci delay16_r, 0 ; 1
|
sbci delay16_r, 0 ; 1
|
||||||
brcc DELAY16_LOOP ; 1 = false, 2 = true
|
brcc DELAY16_LOOP ; 1 = false, 2 = true
|
||||||
ret ; 4
|
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
|
.EQU F_CPU = 8 ; MHz
|
||||||
|
|
||||||
.DEF t1 = r16
|
.DEF t1 = r16
|
||||||
.DEF t2 = r17
|
.DEF t2 = r17
|
||||||
.DEF delay24_r = r18
|
.DEF delay24_r = r18
|
||||||
@ -58,10 +59,10 @@ DELAY16_LOOP:
|
|||||||
.EQU LED_PIN = PB0
|
.EQU LED_PIN = PB0
|
||||||
|
|
||||||
.MACRO delay24
|
.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 delay16_r, HIGH(@0 * F_CPU / 5 - 3)
|
||||||
ldi delay24_r, BYTE3(@0 * F_CPU / 5 - 3)
|
ldi delay24_r, BYTE3(@0 * F_CPU / 5 - 3)
|
||||||
rcall DELAY24_LOOP
|
rcall DELAY24_LOOP
|
||||||
.ENDMACRO
|
.ENDMACRO
|
||||||
|
|
||||||
.CSEG
|
.CSEG
|
||||||
@ -82,7 +83,7 @@ INIT:
|
|||||||
LOOP:
|
LOOP:
|
||||||
nop
|
nop
|
||||||
delay24 500000
|
delay24 500000
|
||||||
nop
|
nop
|
||||||
rcall LED_TOGGLE
|
rcall LED_TOGGLE
|
||||||
rjmp LOOP
|
rjmp LOOP
|
||||||
|
|
||||||
@ -98,8 +99,8 @@ LED_TOGGLE:
|
|||||||
ret
|
ret
|
||||||
|
|
||||||
DELAY24_LOOP:
|
DELAY24_LOOP:
|
||||||
subi delay8_r, 1
|
subi delay8_r, 1
|
||||||
sbci delay16_r, 0
|
sbci delay16_r, 0
|
||||||
sbci delay24_r, 0
|
sbci delay24_r, 0
|
||||||
brcc DELAY24_LOOP
|
brcc DELAY24_LOOP
|
||||||
ret
|
ret
|
||||||
@ -111,13 +112,14 @@ DELAY24_LOOP:
|
|||||||
avrasm2 -fI ./main.asm
|
avrasm2 -fI ./main.asm
|
||||||
```
|
```
|
||||||
|
|
||||||
Если использовать 32 бита то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM).
|
Если использовать 32 бита для хранения кол-ва тактов то максимальная задержка уже составит 3221 секунда! Также вместо регистров общего назначения можно использовать ячейки, определенные в оперативной памяти (SRAM).
|
||||||
|
|
||||||
Чтобы проверить точно ли выполняется задержка, воспользуемся симулятором который встроен в IDE MPLAB X. Перед этим необходимо настроить частоту работы МК в конфигурации проекта:
|
Чтобы проверить точно ли выполняется задержка, воспользуемся симулятором который встроен в IDE MPLAB X. Перед этим необходимо настроить частоту работы МК в конфигурации проекта:
|
||||||
|
|
||||||

|
<figure>
|
||||||
|
<img src="/media/mplabx-ide-sim-option.png" alt="Выбор частоты выполнения инструкций для симулятора">
|
||||||
Выбор частоты выполнения инструкций для симулятора
|
<figcaption>Выбор частоты выполнения инструкций для симулятора</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
Установим точки останова на холостые операции **nop** до и после вызова макроса и выполним отладку:
|
Установим точки останова на холостые операции **nop** до и после вызова макроса и выполним отладку:
|
||||||
|
|
||||||
@ -127,7 +129,7 @@ avrasm2 -fI ./main.asm
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Можно видеть что после выхода из подпрограммы DELAY24_LOOP прошло ровно 500 миллисекунд.
|
Можно видеть что после выхода из подпрограммы `DELAY24_LOOP` прошло ровно 500 миллисекунд.
|
||||||
|
|
||||||
Для языка Си все будет довольно просто. Библиотека [AVR Libc](https://www.nongnu.org/avr-libc/) поставляется с набором утилит которые упрощают программирование. Поэтому нам достаточно указать частоту МК и вызвать определенную функцию. Эта функция как раз из рассчитает нужное кол-во тактов процессора и выполнит цикл.
|
Для языка Си все будет довольно просто. Библиотека [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
|
### 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 t1 = r16
|
||||||
.DEF t2 = r17
|
.DEF t2 = r17
|
||||||
|
|
||||||
@ -210,17 +213,17 @@ INIT:
|
|||||||
; ============================================================================
|
; ============================================================================
|
||||||
ldi t1, (1<<CTC1) | (1<<CS13) | (1<<CS12) | (1<<CS11) | (1<<CS10)
|
ldi t1, (1<<CTC1) | (1<<CS13) | (1<<CS12) | (1<<CS11) | (1<<CS10)
|
||||||
out TCCR1, t1 ; setup timer
|
out TCCR1, t1 ; setup timer
|
||||||
clr t1 ; clear timer counter register
|
clr t1 ; clear timer counter register
|
||||||
out TCNT1, t1
|
out TCNT1, t1
|
||||||
ldi t1, 244 ; 0.5 sec
|
ldi t1, 244 ; 0.5 sec
|
||||||
out OCR1A, t1
|
out OCR1A, t1
|
||||||
out OCR1C, t1 ; for clearing timer on compare match
|
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
|
out TIMSK, t1
|
||||||
|
|
||||||
sei
|
sei
|
||||||
|
|
||||||
LOOP:
|
LOOP:
|
||||||
nop
|
nop
|
||||||
rjmp LOOP
|
rjmp LOOP
|
||||||
|
|
||||||
@ -299,11 +302,11 @@ int main(void) {
|
|||||||
|
|
||||||
### 8-ми битный таймер с предделителем 1024
|
### 8-ми битный таймер с предделителем 1024
|
||||||
|
|
||||||
Что если доступен предделитель частоты только на 1024? Тогда регистр для сравнения OCRn будет равняться 3906, что не подходит для 8-ми битного регистра. Как вариант можно считать количество переполнений таймера. 3906.25/255 = 15 переполнений.
|
Что если доступен предделитель частоты только на 1024? Тогда регистр для сравнения `OCRn` будет равняться 3906, что не подходит для 8-ми битного регистра. Как вариант можно считать количество переполнений таймера. `3906.25/255 = 15` переполнений.
|
||||||
|
|
||||||
Программа на ассемблере:
|
Программа на ассемблере:
|
||||||
|
|
||||||
```nasm
|
```asmatmel
|
||||||
.DEF t1 = r16
|
.DEF t1 = r16
|
||||||
.DEF t2 = r17
|
.DEF t2 = r17
|
||||||
.DEF tc1_ovf_cnt = r18
|
.DEF tc1_ovf_cnt = r18
|
||||||
@ -343,12 +346,12 @@ INIT:
|
|||||||
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
||||||
out TCCR1, t1 ; setup timer
|
out TCCR1, t1 ; setup timer
|
||||||
clr t1 ; clear timer counter register
|
clr t1 ; clear timer counter register
|
||||||
out TCNT1, t1
|
out TCNT1, t1
|
||||||
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
||||||
out TIMSK, t1
|
out TIMSK, t1
|
||||||
sei
|
sei
|
||||||
|
|
||||||
LOOP:
|
LOOP:
|
||||||
nop
|
nop
|
||||||
rjmp LOOP
|
rjmp LOOP
|
||||||
|
|
||||||
@ -436,7 +439,7 @@ int main(void) {
|
|||||||
|
|
||||||
Бывают случаи когда нужно выполнять какую либо логику раз в несколько часов или дней. Решением является завести глобальную переменную где будем хранить количество прошедших секунд с момента начала отсчета таймера, а в прерывании переполнения таймера будем уже сравнивать значение счетчика с заданным числом. Следующая программа переключает светодиод раз в 6 часов.
|
Бывают случаи когда нужно выполнять какую либо логику раз в несколько часов или дней. Решением является завести глобальную переменную где будем хранить количество прошедших секунд с момента начала отсчета таймера, а в прерывании переполнения таймера будем уже сравнивать значение счетчика с заданным числом. Следующая программа переключает светодиод раз в 6 часов.
|
||||||
|
|
||||||
```nasm
|
```asmatmel
|
||||||
.DEF t1 = r16
|
.DEF t1 = r16
|
||||||
.DEF t2 = r17
|
.DEF t2 = r17
|
||||||
.DEF tc1_ovf_cnt = r18
|
.DEF tc1_ovf_cnt = r18
|
||||||
@ -484,12 +487,12 @@ INIT:
|
|||||||
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
ldi t1, (1<<CS13) | (1<<CS11) | (1<<CS10)
|
||||||
out TCCR1, t1 ; setup timer
|
out TCCR1, t1 ; setup timer
|
||||||
clr t1 ; clear timer counter register
|
clr t1 ; clear timer counter register
|
||||||
out TCNT1, t1
|
out TCNT1, t1
|
||||||
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
ldi t1, (1<<TOIE1) ; enable overflow interrupt
|
||||||
out TIMSK, t1
|
out TIMSK, t1
|
||||||
sei
|
sei
|
||||||
|
|
||||||
LOOP:
|
LOOP:
|
||||||
nop
|
nop
|
||||||
rjmp LOOP
|
rjmp LOOP
|
||||||
|
|
||||||
@ -526,8 +529,8 @@ TIMER1_OVF:
|
|||||||
clr r25
|
clr r25
|
||||||
sts ellapsed_seconds, r25 ; clear ellapsed seconds
|
sts ellapsed_seconds, r25 ; clear ellapsed seconds
|
||||||
sts ellapsed_seconds+1, r25 ; -----------------------
|
sts ellapsed_seconds+1, r25 ; -----------------------
|
||||||
|
|
||||||
_TIMER1_OVF_EXIT:
|
_TIMER1_OVF_EXIT:
|
||||||
out SREG, t1
|
out SREG, t1
|
||||||
pop t1
|
pop t1
|
||||||
reti
|
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)
|
- [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)
|
- [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 Delay Loop Calculator](http://darcy.rsgc.on.ca/ACES/TEI4M/AVRdelay.html)
|
||||||
* [AVR Timer Calculator](https://eleccelerator.com/avr-timer-calculator/)
|
- [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) {
|
export function buildToc(headings: MarkdownHeading[], maxDepth: number = 2) {
|
||||||
const toc: MarkdownHeadingToc[] = [];
|
const toc: MarkdownHeadingToc[] = [];
|
||||||
const parentHeadings = new Map();
|
const parentHeadings = new Map();
|
||||||
|
|
||||||
headings.forEach((h) => {
|
headings.forEach((h) => {
|
||||||
const heading = { ...h, subheadings: [] };
|
const heading = { ...h, subheadings: [] };
|
||||||
parentHeadings.set(heading.depth, heading);
|
parentHeadings.set(heading.depth, heading);
|
||||||
if (heading.depth === 1) {
|
if (heading.depth === 1) {
|
||||||
toc.push(heading);
|
toc.push(heading);
|
||||||
} else if (heading.depth <= maxDepth) {
|
} else if (heading.depth <= maxDepth) {
|
||||||
parentHeadings.get(heading.depth - 1).subheadings.push(heading);
|
parentHeadings.get(heading.depth - 1)?.subheadings.push(heading);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return toc;
|
return toc;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user