ATmega8: счетчик на прерывании Timer/Counter overflow

разделы: AVR , дата: 28 июля 2015г.

Если посмотреть таблицу прерываний для ATmega8 из предыдущего поста, то видно, что почти половина из них, это прерывания для работы с таймерами/cчетчиками. Также для их конфигурации отведена значительная часть регистров:

Также для их конфигурации отведена значительная часть регистров:

Чтобы в этом не увязнуть, я попробую, для начала, сделать что-нибудь простое, например Blink на таймере.

В ATmega8 три таймера, два 8-битных и один 16-битный. Я буду использовать нулевой Timer0, на который отведено одно прерывание по переполнению: TIMER0 OVR.

Счётчик таймера может тактироваться с частотой кварца, или в пропорциях 1:8 , 1:64, 1:256, 1:1024 к частоте кварца. Пропорция называется prescaler и выставляется в регистре TCCR0 (Timer/Counter Control Register) установкой соответствующих битов:


    Теперь считаем:
  • кварц 16МГц за секунду отрабатывает 16*1024*1024 тиков(На самом деле 16*1000*1000. И вместо числа 64 предложенного ниже, лучше использовать 61. ПРИМЕЧАНИЕ от 30.10.17);
  • если выставим пропорцию 1:1024, то счётчик будет индексироваться 16*1024 раз в секунду;
  • т.е. счётчик 8-битный то, его переполнение будет происходить каждые 256 индексаций, т.е. (16*1024)/256 = 64 раз в секунду;
  • т.е. чтобы моргать светодиодом один раз в секунду, в обработчик прерывания нам нужно поставить счётчик, который при достижения числа 64, будет сбрасываться и инвертировать порт со светодиодом.

Текст программы у меня получился таким:

/* timer.c LED Blink via 8-bit Timer0 for AVR ATmega8 */
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

register unsigned char myTimer asm("r28");

ISR(TIMER0_OVF_vect)
{
    myTimer--;
    if (!myTimer)
    {
        PORTB ^= (1<<PB5);
        myTimer=64;
    }
}

int main(void)
{
    DDRB |= (1<<PB5); //  pinMode(13,OUTPUT); в Wiring

    TIMSK =(1<<TOIE0);  // timer0 enable
    TCCR0 = (1<<CS02) | (1<<CS00); // prescaler 1/1024

    myTimer=64;
    sei();
    for (;;) {}

    return 0;
}

Прошивка уместилась в 122 байта. Здесь сначала, счётчик myTimer объявляем как регистровую переменную. При наличии 32 регистров РОН, проблем с их нехваткой не должно быть. Для прерывания в файле interrupt.h определён макрос ISR, в качестве параметра следует указывать используемый вектор. С регистром TCCRO вроде уже разобрались, а регистр TIMSK отвечает за включённые таймеры.

Смотрим ассемблерный вариант программы сгенерированный avr-gcc:

avr-gcc -mmcu=atmega8 -Wall -Os -S  timer.c
.file    "timer.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
    .text
.global  __vector_9
    .type    __vector_9, @function
__vector_9:
    push r1
    push r0
    in r0,__SREG__
    push r0
    clr __zero_reg__
    push r24
    push r25
/* prologue: Signal */
/* frame size = 0 */
/* stack size = 5 */
.L__stack_usage = 5
    ldi r24,lo8(-1)
    add r24,r28
    mov r28,r24
    brne .L1
    in r24,0x18
    ldi r25,lo8(32)
    eor r24,r25
    out 0x18,r24
    ldi r28,lo8(64)
.L1:
/* epilogue start */
    pop r25
    pop r24
    pop r0
    out __SREG__,r0
    pop r0
    pop r1
    reti
    .size    __vector_9, .-__vector_9
    .section .text.startup,"ax",@progbits
.global  main
    .type    main, @function
main:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
    sbi 0x17,5
    ldi r24,lo8(1)
    out 0x39,r24
    ldi r24,lo8(5)
    out 0x33,r24
    ldi r28,lo8(64)
/* #APP */
 ;  27 "timer.c" 1
    sei
 ;  0 "" 2
/* #NOAPP */
.L5:
    rjmp .L5
    .size    main, .-main
    .ident   "GCC: (GNU) 4.8.1"

Как видно на этот раз основную массу кода занимает подпрограмма обработки девятого прерывания, т.е. TIMER0_OVF, причём подпрограмму имеет характерную обёртку из PUSH <--> POP команд сохраняющих используемые регистры... Однако, R1 и R0 вроде как не используются однако, тоже сохраняются, это т.н. fixed registers. Читаем официальные комментарии по этому поводу: https://gcc.gnu.org/wiki/avr-gcc

Fixed Registers Fixed Registers are registers that won't be allocated by GCC's register allocator. Registers R0 and R1 are fixed and used implicitly while printing out assembler instructions: R0is used as scratch register that need not to be restored after its usage. It must be saved and restored in interrupt service routine's (ISR) prologue and epilogue. In inline assembler you can use __tmp_reg__ for the scratch register. R1always contains zero. During an insn the content might be destroyed, e.g. by a MUL instruction that uses R0/R1 as implicit output register. If an insn destroys R1, the insn must restore R1 to zero afterwards. This register must be saved in ISR prologues and must then be set to zero because R1 might contain values other than zero. The ISR epilogue restores the value. In inline assembler you can use __zero_reg__ for the zero register. Tthe T flag in the status register (SREG) is used in the same way like the temporary scratch register R0.

и вольный перевод найденый здесь:

Fixed registers (r0, r1), фиксированные регистры. Никогда не выделяются gcc для локальных данных, но часто используются для определённых целей: r0 - временный регистр, может использоваться любым кодом на C (за исключением обработчиков прерываний, которые сохраняют значение этого регистра). Обычно используется для запоминания чего-нибудь в пределах кусков кода ассемблера. r1 - подразумевается, что всегда 0 в любом коде C. Может использоваться для запоминания чего-нибудь в пределах кусков кода ассемблера, однако после использования должен очищаться (clr r1). Это относится к любому использованию инструкций [f]mul[s[u]], которые возвращают результат в паре регистров r1:r0. Обработчики прерываний сохраняют и очищают r1 на входе, и восстанавливают r1 на выходе (в том случае, если r1 не ноль).

Дизасcемблируем elf:

$ avr-objdump -S timer.elf

timer.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__vectors>:
   0:   12 c0           rjmp    .+36        ; 0x26 <__ctors_end>
   2:   19 c0           rjmp    .+50        ; 0x36 <__bad_interrupt>
   4:   18 c0           rjmp    .+48        ; 0x36 <__bad_interrupt>
   6:   17 c0           rjmp    .+46        ; 0x36 <__bad_interrupt>
   8:   16 c0           rjmp    .+44        ; 0x36 <__bad_interrupt>
   a:   15 c0           rjmp    .+42        ; 0x36 <__bad_interrupt>
   c:   14 c0           rjmp    .+40        ; 0x36 <__bad_interrupt>
   e:   13 c0           rjmp    .+38        ; 0x36 <__bad_interrupt>
  10:   12 c0           rjmp    .+36        ; 0x36 <__bad_interrupt>
  12:   12 c0           rjmp    .+36        ; 0x38 <__vector_9>
  14:   10 c0           rjmp    .+32        ; 0x36 <__bad_interrupt>
  16:   0f c0           rjmp    .+30        ; 0x36 <__bad_interrupt>
  18:   0e c0           rjmp    .+28        ; 0x36 <__bad_interrupt>
  1a:   0d c0           rjmp    .+26        ; 0x36 <__bad_interrupt>
  1c:   0c c0           rjmp    .+24        ; 0x36 <__bad_interrupt>
  1e:   0b c0           rjmp    .+22        ; 0x36 <__bad_interrupt>
  20:   0a c0           rjmp    .+20        ; 0x36 <__bad_interrupt>
  22:   09 c0           rjmp    .+18        ; 0x36 <__bad_interrupt>
  24:   08 c0           rjmp    .+16        ; 0x36 <__bad_interrupt>

00000026 <__ctors_end>:
  26:   11 24           eor r1, r1
  28:   1f be           out 0x3f, r1    ; 63
  2a:   cf e5           ldi r28, 0x5F   ; 95
  2c:   d4 e0           ldi r29, 0x04   ; 4
  2e:   de bf           out 0x3e, r29   ; 62
  30:   cd bf           out 0x3d, r28   ; 61
  32:   19 d0           rcall   .+50        ; 0x66 <main>
  34:   20 c0           rjmp    .+64        ; 0x76 <_exit>

00000036 <__bad_interrupt>:
  36:   e4 cf           rjmp    .-56        ; 0x0 <__vectors>

00000038 <__vector_9>:
  38:   1f 92           push    r1
  3a:   0f 92           push    r0
  3c:   0f b6           in  r0, 0x3f    ; 63
  3e:   0f 92           push    r0
  40:   11 24           eor r1, r1
  42:   8f 93           push    r24
  44:   9f 93           push    r25
  46:   8f ef           ldi r24, 0xFF   ; 255
  48:   8c 0f           add r24, r28
  4a:   c8 2f           mov r28, r24
  4c:   29 f4           brne    .+10        ; 0x58 <__SREG__+0x19>
  4e:   88 b3           in  r24, 0x18   ; 24
  50:   90 e2           ldi r25, 0x20   ; 32
  52:   89 27           eor r24, r25
  54:   88 bb           out 0x18, r24   ; 24
  56:   c0 e4           ldi r28, 0x40   ; 64
  58:   9f 91           pop r25
  5a:   8f 91           pop r24
  5c:   0f 90           pop r0
  5e:   0f be           out 0x3f, r0    ; 63
  60:   0f 90           pop r0
  62:   1f 90           pop r1
  64:   18 95           reti

00000066 <main>:
  66:   bd 9a           sbi 0x17, 5 ; 23
  68:   81 e0           ldi r24, 0x01   ; 1
  6a:   89 bf           out 0x39, r24   ; 57
  6c:   85 e0           ldi r24, 0x05   ; 5
  6e:   83 bf           out 0x33, r24   ; 51
  70:   c0 e4           ldi r28, 0x40   ; 64
  72:   78 94           sei
  74:   ff cf           rjmp    .-2         ; 0x74 <main+0xe>

00000076 <_exit>:
  76:   f8 94           cli

00000078 <__stop_program>:
  78:   ff cf           rjmp    .-2         ; 0x78 <__stop_program>

Видим, что в таблице прерываний появился rjmp на подпрограмму, обработчик RESET уже разбирали, c push/pop командами разобрались. Main не представляет интереса, там только присваивание констант, зато декримент в обработчике нашего прерывания реализован, на первый взгляд мутно.

46:   8f ef           ldi     r24, 0xFF       ; R24 = -1; R28 = счётчик
48:   8c 0f           add     r24, r28        ; R24 = R24 + R28
4a:   c8 2f           mov     r28, r24        ; R28 = R24
4c:   29 f4           brne    .+10            ; если не ноль, то переход по метке

Три первых оператора можно было заменить одним dec R28; Но если вспомнить Си там различается предекримент и постдекримент. И здесь как видно реализация постдекримента.

Итоговый исходник на ассемблере:

.equ DDRB, 0x17
.equ PB5, 0x05
.equ PORTB, 0x18
.equ SREG, 0x3F
.equ SPL, 0x3D
.equ SPH, 0x3E
.equ TIMSK, 0x39
.equ TCCR0, 0x33

.equ value, 0x40
; R28 is COUNTER

.org 0x00 ; начало
        rjmp RESET ; Reset Handler
        reti ;rjmp EXT_INT0 ; IRQ0 Handler
        reti ;rjmp EXT_INT1 ; IRQ1 Handler
        reti ;rjmp TIM2_COMP ; Timer2 Compare Handler
        reti ;rjmp TIM2_OVF ; Timer2 Overflow Handler
        reti ;rjmp TIM1_CAPT ; Timer1 Capture Handler
        reti ;rjmp TIM1_COMPA ; Timer1 CompareA Handler
        reti ;rjmp TIM1_COMPB ; Timer1 CompareB Handler
        reti ;rjmp TIM1_OVF ; Timer1 Overflow Handler
        rjmp  vector_9 ;rjmp TIM0_OVF ; Timer0 Overflow Handler
        reti ;rjmp SPI_STC ; SPI Transfer Complete Handler
        reti ;rjmp USART_RXC ; USART RX Complete Handler
        reti ;rjmp USART_UDRE ; UDR Empty Handler
        reti ;rjmp USART_TXC ; USART TX Complete Handler
        reti ;rjmp ADC ; ADC Conversion Complete Handler
        reti ;rjmp EE_RDY ; EEPROM Ready Handler
        reti ;jmp ANA_COMP ; Analog Comparator Handler
        reti ;rjmp TWSI ; Two-wire Serial Interface Handler
        reti ;rjmp SPM_RDY ; Store Program Memory Ready Handler

vector_9:
    dec r28
    brne    exit_9
    ldi r28,lo8(value)
    ldi     r25, 0x20
        in      r24, PORTB;  r24=PORTB
        eor     r24, r25;    r24 = r24 xor r25
        out     PORTB, r24;  PORTB=r25
exit_9:
    reti

RESET:
        eor     r1, r1
        out     SREG, r1
        ldi     r16, hi8(0x045F);  top memory for ATmega8
        out     SPH,r16
        ldi     r16, lo8(0x045F)
        out     SPL,r16
main:
        sbi     DDRB, PB5;   порт PB5 на передачу
    ldi     r24,lo8(1)
        out     TIMSK,r24; set TIMSK; enable TIMER0
        ldi     r24,lo8(PB5)
        out     TCCR0,r24; set TCCR0; prescaler = 1:1024 
        ldi     r28,lo8(value)
    sei;
loop:
        rjmp    loop;      main loop

компилируем, прошиваем:

$ avr-as -mmcu=atmega8 -o timer.o timer.asm
$ palladium:~/mydev: avr-ld -o timer.elf timer.o
$ palladium:~/mydev:  avr-objdump -S timer.elf

timer.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__ctors_end>:

Disassembly of section .text:

00000000 <__ctors_end>:
   0:   1a c0           rjmp    .+52            ; 0x36 <RESET>
   2:   18 95           reti
   4:   18 95           reti
   6:   18 95           reti
   4:   18 95           reti
   6:   18 95           reti
   0:   1a c0           rjmp    .+52            ; 0x36 <RESET>
   2:   18 95           reti
   4:   18 95           reti
   6:   18 95           reti
   8:   18 95           reti
   a:   18 95           reti
   c:   18 95           reti
   e:   18 95           reti
  10:   18 95           reti
  12:   09 c0           rjmp    .+18            ; 0x26 <vector_9>
  14:   18 95           reti
  16:   18 95           reti
  18:   18 95           reti
  1a:   18 95           reti
  1c:   18 95           reti
  1e:   18 95           reti
  20:   18 95           reti
  22:   18 95           reti
  24:   18 95           reti

00000026 <vector_9>:
  26:   ca 95           dec     r28
  28:   29 f4           brne    .+10            ; 0x34 <exit_9>
  2a:   c0 e4           ldi     r28, 0x40       ; 64
  2c:   90 e2           ldi     r25, 0x20       ; 32
  2e:   88 b3           in      r24, 0x18       ; 24
  30:   89 27           eor     r24, r25
  32:   88 bb           out     0x18, r24       ; 24

00000034 <exit_9>:
  34:   18 95           reti

00000036 <RESET>:
  36:   11 24           eor     r1, r1
  38:   1f be           out     0x3f, r1        ; 63
  3a:   04 e0           ldi     r16, 0x04       ; 4
  3c:   0e bf           out     0x3e, r16       ; 62
  3e:   0f e5           ldi     r16, 0x5F       ; 95
  40:   0d bf           out     0x3d, r16       ; 61

00000042 <main>:
  42:   bd 9a           sbi     0x17, 5 ; 23
  44:   81 e0           ldi     r24, 0x01       ; 1
  46:   89 bf           out     0x39, r24       ; 57
  48:   85 e0           ldi     r24, 0x05       ; 5
  4a:   83 bf           out     0x33, r24       ; 51
  4c:   c0 e4           ldi     r28, 0x40       ; 64
  4e:   78 94           sei

00000050 <loop>:
  50:   ff cf           rjmp    .-2             ; 0x50 <loop>

palladium:~/mydev$ avr-objcopy --output-target=ihex timer.elf timer.hex

palladium:~/mydev$ avrdude  -v -patmega8 -carduino -P/dev/ttyUSB0 -b19200 -D -Uflash:w:./timer.hex:i

avrdude: Version 6.0.1, compiled on Aug 11 2014 at 12:06:25
         Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
         Copyright (c) 2007-2009 Joerg Wunsch

         System wide configuration file is "/etc/avrdude.conf"
         User configuration file is "/home/flanker/.avrduderc"
         User configuration file does not exist or is not a regular file, skipping

         Using Port                    : /dev/ttyUSB0
         Using Programmer              : arduino
         Overriding Baud Rate          : 19200
         AVR Part                      : ATmega8
         Chip Erase delay              : 10000 us
         PAGEL                         : PD7
         BS2                           : PC2
         RESET disposition             : dedicated
         RETRY pulse                   : SCK
         serial program mode           : yes
         parallel program mode         : yes
         Timeout                       : 200
         StabDelay                     : 100
         CmdexeDelay                   : 25
         SyncLoops                     : 32
         ByteDelay                     : 0
         PollIndex                     : 3
         PollValue                     : 0x53
         Memory Detail                 :

                                  Block Poll               Page                       Polled
           Memory Type Mode Delay Size  Indx Paged  Size   Size #Pages MinW  MaxW   ReadBack
           ----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
           eeprom         4    20   128    0 no        512    4      0  9000  9000 0xff 0xff
           flash         33    10    64    0 yes      8192   64    128  4500  4500 0xff 0x00
           lfuse          0     0     0    0 no          1    0      0  2000  2000 0x00 0x00
           hfuse          0     0     0    0 no          1    0      0  2000  2000 0x00 0x00
           lock           0     0     0    0 no          1    0      0  2000  2000 0x00 0x00
           calibration    0     0     0    0 no          4    0      0     0     0 0x00 0x00
           signature      0     0     0    0 no          3    0      0     0     0 0x00 0x00

         Programmer Type : Arduino
         Description     : Arduino
         Hardware Version: 2
         Firmware Version: 1.18
         Topcard         : Unknown
         Vtarget         : 0.0 V
         Varef           : 0.0 V
         Oscillator      : Off
         SCK period      : 0.1 us

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e9307
avrdude: safemode: lfuse reads as 0
avrdude: safemode: hfuse reads as 0
avrdude: reading input file "./timer.hex"
avrdude: writing flash (82 bytes):

Writing | ################################################## | 100% 0.10s

avrdude: 82 bytes of flash written
avrdude: verifying flash memory against ./timer.hex:
avrdude: load data flash data from input file ./timer.hex:
avrdude: input file ./timer.hex contains 82 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.08s

avrdude: verifying ...
avrdude: 82 bytes of flash verified

avrdude: safemode: lfuse reads as 0
avrdude: safemode: hfuse reads as 0
avrdude: safemode: Fuses OK (H:FF, E:00, L:00)

avrdude done.  Thank you.

82 байта против 122 на Си, выглядит неплохо.