Если посмотреть таблицу прерываний для ATmega8 из предыдущего поста, то видно, что почти половина из них, это прерывания для работы с таймерами/cчетчиками. Также для их конфигурации отведена значительная часть регистров:
Также для их конфигурации отведена значительная часть регистров:
Чтобы в этом не увязнуть, я попробую, для начала, сделать что-нибудь простое, например Blink на таймере.
В ATmega8 три таймера, два 8-битных и один 16-битный. Я буду использовать нулевой Timer0, на который отведено одно прерывание по переполнению: TIMER0 OVR.
Счётчик таймера может тактироваться с частотой кварца, или в пропорциях 1:8 , 1:64, 1:256, 1:1024 к частоте кварца. Пропорция называется prescaler и выставляется в регистре TCCR0 (Timer/Counter Control Register) установкой соответствующих битов:
Текст программы у меня получился таким:
/* 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 на Си, выглядит неплохо.