Jak wyglądał by kod na Attiny 13A ,zmiana polaryzacji LOW 64 sekund , HIGH 64 sekund , okres 128 sekund , dutycycle 50% , między jakimi 2 pinami najlepiej ,tak aby zmiana polaryzacji była jak najdokładniejsza , bez zbędnych dodatków pogarszających dokładność.
Tak — na ATtiny13A da się to zrobić prosto i bardzo poprawnie.
Najlepszy wybór pinów: PB0 i PB1
Jeśli zależy Ci na najdokładniejszej zmianie polaryzacji bez zbędnych dodatków, to najlepsza metoda na samym ATtiny13A to:
PINx przełącza odpowiadający bit PORTx. (ww1.microchip.com)Najkrótszy sensowny kod:
#define F_CPU 1200000UL
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint16_t cnt = 0;
ISR(TIM0_COMPA_vect)
{
if (++cnt >= 4800) { // 64 s
cnt = 0;
PINB = (1 << PB0) | (1 << PB1); // jednoczesne przełączenie PB0 i PB1
}
}
int main(void)
{
DDRB = (1 << PB0) | (1 << PB1); // PB0, PB1 jako wyjścia
PORTB = (1 << PB0); // start: PB0=1, PB1=0
GTCCR = (1 << PSR10); // reset preskalera Timer0
TCNT0 = 0;
TCCR0A = (1 << WGM01); // CTC
TCCR0B = (1 << CS01) | (1 << CS00); // preskaler 64
OCR0A = 249; // 1.2 MHz / 64 / 250 = 75 Hz
TIMSK0 = (1 << OCIE0A); // przerwanie Compare Match A
sei();
while (1) {
}
}
To rozwiązanie jest poprawne z dwóch powodów:
Obciążenie podłączasz tak:
PB0 ----[ OBCIĄŻENIE ]---- PB1
Stany pracy:
PB0=1, PB1=0PB0=0, PB1=1Czyli polaryzacja na obciążeniu odwraca się co 64 s. Piny PB0 i PB1 są do tego najwygodniejsze i najbardziej „czyste” funkcjonalnie. (ww1.microchip.com)
ATtiny13A jest fabrycznie ustawiany na wewnętrzny RC 9.6 MHz z włączonym CKDIV8, więc domyślnie CPU pracuje z częstotliwością:
\[
F_{CPU} = 9.6\ \text{MHz} / 8 = 1.2\ \text{MHz}
\]
To jest ustawienie domyślne po wyjęciu układu z pudełka. (ww1.microchip.com)
Dobieramy Timer0 tak, aby miał dokładny podział całkowitoliczbowy:
\[
f{ISR} = \frac{F{CPU}}{N \cdot (1 + OCR0A)}
\]
Dla:
otrzymujesz:
\[
f_{ISR} = \frac{1\,200\,000}{64 \cdot 250} = 75\ \text{Hz}
\]
A więc:
\[
64\ \text{s} \cdot 75 = 4800
\]
Po 4800 przerwaniach mija dokładnie 64 s — oczywiście pod warunkiem, że sam zegar procesora ma dokładną częstotliwość. Tryb CTC zeruje licznik sprzętowo przy zgodności z OCR0A, więc matematyka czasu jest czysta i nie ma błędu narastającego od pętli programowej. (ww1.microchip.com)
PINB, a nie dwa osobne polecenia?W AVR zapis logicznej „1” do bitu PINxn powoduje przełączenie odpowiadającego bitu PORTxn. Dzięki temu można zmienić PB0 i PB1 równocześnie jednym zapisem:
PINB = (1 << PB0) | (1 << PB1);
To jest lepsze niż:
PORTB |= (1 << PB0);
PORTB &= ~(1 << PB1);
bo w drugim przypadku stany zmieniają się sekwencyjnie, a nie jednym aktem aktualizacji portu. Jeśli zależy Ci na możliwie symetrycznej zmianie polaryzacji — zapis do PINB jest bardzo dobrym wyborem. (ww1.microchip.com)
Jedna z przykładowych odpowiedzi online z Phase Correct PWM dla 64 s jest po prostu niepoprawna. Timer0 w ATtiny13A jest 8-bitowy, a sprzętowe PWM/toggle na tym timerze nie pozwala uzyskać bezpośrednio półokresu 64 s bez dodatkowego programowego liczenia. Z noty wynika, że Timer0 ma tylko preskalery do 1024 i licznik 8-bitowy, więc samym PWM nie wygenerujesz bezpośrednio 128 s okresu na wyjściu. (ww1.microchip.com)
Watchdog ma osobny oscylator i maksymalny timeout 8.0 s, więc do 64 s trzeba by liczyć osiem wybudzeń. To działa, ale dla precyzyjnego odmierzania czasu nie traktowałbym tego jako lepszego rozwiązania niż Timer0, bo w datasheet dla tych timeoutów masz podane wartości typowe, a nie precyzyjny wzorzec czasu. To jest więc rozwiązanie bardziej „proste energetycznie” niż „najdokładniejsze”. (ww1.microchip.com)
W praktyce największe ograniczenie nie leży tu w kodzie, tylko w źródle zegara.
ATtiny13A z wewnętrznym oscylatorem RC ma według datasheet:
To oznacza, że:
Jeżeli kiedyś będziesz chciał uzyskać znacznie lepszą dokładność długookresową, ATtiny13A może pracować z zewnętrznym zegarem na pinie PB3/CLKI. To już jednak jest dodatkowy element zewnętrzny. (ww1.microchip.com)
Jeżeli zostawiasz domyślne fuse, ten kod jest wygodny, bo nie wymaga zmian konfiguracyjnych. Układ fabrycznie startuje z 9.6 MHz i preskalerem /8, więc F_CPU = 1200000UL jest poprawne dla nowego układu. (ww1.microchip.com)
Gdybyś wcześniej zmienił fuse i procesor pracował pełnym 9.6 MHz, trzeba przeliczyć timer. Dla 9.6 MHz też da się uzyskać równe 75 Hz, np. przez preskaler 1024 i OCR0A = 124, ale to już inny wariant konfiguracji. Wynika to bezpośrednio z tabeli preskalerów Timer0 i wzoru dla CTC. (ww1.microchip.com)
Od strony bezpieczeństwa najważniejsze jest nieprzeciążanie pinów I/O. W nocie katalogowej 40 mA na pin i 200 mA łącznie przez VCC/GND to wartości z działu Absolute Maximum Ratings, czyli granice uszkodzeniowe, a nie zalecany punkt pracy ciągłej. Nie powinno się projektować układu „na styk” pod te liczby. (ww1.microchip.com)
Jeśli obciążenie to:
to nie należy podłączać go bezpośrednio między PB0 i PB1. Wtedy trzeba użyć zewnętrznego stopnia mocy. (ww1.microchip.com)
delay()PINB (ww1.microchip.com)Najważniejsze zastrzeżenie:
bez zewnętrznego wzorca czasu nie uzyskasz „laboratoryjnie dokładnych” 64.000 s, nawet jeśli kod jest idealny. Ograniczeniem jest oscylator RC, nie Timer0. (ww1.microchip.com)
Drugie zastrzeżenie:
odpowiedź z przykładowym PWM 64 s na Timer0 należy odrzucić jako błędną koncepcyjnie dla ATtiny13A. To nie wynika z opinii, tylko z parametrów 8-bitowego Timer0 i dostępnych podziałów zegara. (ww1.microchip.com)
Jeżeli chcesz ten układ dopracować dalej, warto sprawdzić:
Najlepsza odpowiedź na Twoje pytanie jest taka:
PINBJeśli chcesz, mogę od razu podać jeszcze:
wersję w czystym asemblerze AVR, wersję pod avr-gcc + avrdude, dokładne fuse bity dla tego wariantu.
Dla wariantu ATtiny13A, w którym PB0 i PB1 zmieniają polaryzację co 64 s, poprawna konfiguracja jest następująca:
avr-gcc + avrdude – poniżej,0x6A0xFFLFUSE=0x62 jest dla tego układu po prostu błędna. W ATtiny13A mapa fuse’ów jest inna niż w wielu większych AVR: SPIEN siedzi w low fuse, nie ma tu klasycznego CKOUT, a stan fabryczny to CKSEL=10, SUT=10, CKDIV8=0. (ww1.microchip.com)Dla tego taktowania ustawienie Timer0 w CTC, preskaler 64, OCR0A = 249 daje:
[
f_{ISR}=\frac{1200000}{64\cdot(249+1)}=75\ \text{Hz}
]
a więc 4800 przerwań = 64.000 s nominalnie. Matematycznie podział jest idealny; praktyczny błąd czasu wynika już głównie z tolerancji wewnętrznego oscylatora RC. (ww1.microchip.com)
Zakładam dokładnie ten wariant funkcjonalny:
Bardzo dobrym trikiem sprzętowo-programowym jest tu przełączanie obu linii przez zapis do PINB. W ATtiny13A zapis logicznej jedynki do bitu rejestru PINxn powoduje przełączenie odpowiadającego bitu PORTxn, więc jednym zapisem możesz równocześnie odwrócić PB0 i PB1 bez chwilowego stanu pośredniego. (ww1.microchip.com)
Najwięcej zamieszania było przy fuse bitach. Dla ATtiny13A poprawne jest:
| Byte | Wartość | Bity | Znaczenie |
|---|---|---|---|
| LFUSE | 0x6A |
0110 1010 |
SPIEN=0, EESAVE=1, WDTON=1, CKDIV8=0, SUT=10, CKSEL=10 |
| HFUSE | 0xFF |
1111 1111 |
SELFPRGEN=1, DWEN=1, BODLEVEL=11, RSTDISBL=1 |
| EFUSE | — | — | brak w tym układzie |
Czyli:
0x6A jest poprawne,0x62 jest niepoprawne dla tego wariantu,Poniższy kod jest przygotowany pod składnię AVRASM2 / avra. Jeżeli Twój pakiet include używa nazwy tn13Adef.inc zamiast tn13def.inc, zmień tylko linię .include.
; ============================================================
; ATtiny13A
; Zmiana polaryzacji PB0 <-> PB1 co 64 s
; Timer0 CTC, F_CPU = 1.2 MHz (9.6 MHz / 8)
; LFUSE = 0x6A, HFUSE = 0xFF
; ============================================================
.nolist
.include "tn13def.inc"
.list
.equ OCR0A_VAL = 249
.equ CNT_TARGET = 4800
.dseg
cnt_lo: .byte 1
cnt_hi: .byte 1
.cseg
.org 0x0000
rjmp reset ; 0x0000 RESET
reti ; 0x0001 INT0
reti ; 0x0002 PCINT0
reti ; 0x0003 TIM0_OVF
reti ; 0x0004 EE_RDY
reti ; 0x0005 ANA_COMP
rjmp tim0_compa_isr ; 0x0006 TIM0_COMPA
reti ; 0x0007 TIM0_COMPB
reti ; 0x0008 WDT
reti ; 0x0009 ADC
tim0_compa_isr:
push r16
in r16, SREG
push r16
push r24
push r25
; cnt++
lds r24, cnt_lo
lds r25, cnt_hi
adiw r24, 1
sts cnt_lo, r24
sts cnt_hi, r25
; if (cnt == 4800)
cpi r24, low(CNT_TARGET)
ldi r16, high(CNT_TARGET)
cpc r25, r16
brne isr_done
; cnt = 0
clr r24
clr r25
sts cnt_lo, r24
sts cnt_hi, r25
; jednoczesne przełączenie PB0 i PB1
ldi r16, (1<<PB0) | (1<<PB1)
out PINB, r16
isr_done:
pop r25
pop r24
pop r16
out SREG, r16
pop r16
reti
reset:
; stos
ldi r16, RAMEND
out SPL, r16
; licznik = 0
clr r24
clr r25
sts cnt_lo, r24
sts cnt_hi, r25
; PB0, PB1 jako wyjścia
ldi r16, (1<<PB0) | (1<<PB1)
out DDRB, r16
; stan początkowy: PB0=1, PB1=0
ldi r16, (1<<PB0)
out PORTB, r16
; reset preskalera Timer0
ldi r16, (1<<PSR10)
out GTCCR, r16
clr r16
out TCNT0, r16
; Timer0 CTC
ldi r16, (1<<WGM01)
out TCCR0A, r16
; OCR0A = 249
ldi r16, OCR0A_VAL
out OCR0A, r16
; włącz przerwanie COMPA
ldi r16, (1<<OCIE0A)
out TIMSK0, r16
; preskaler 64
ldi r16, (1<<CS01) | (1<<CS00)
out TCCR0B, r16
; sleep idle
ldi r16, (1<<SE)
out MCUCR, r16
sei
main_loop:
sleep
rjmp main_loop
Ten układ wektorów odpowiada tabeli przerwań ATtiny13A, a użycie PINB do przełączania wyjść jest zgodne z dokumentacją portu I/O. (ww1.microchip.com)
Przykładowa kompilacja przez avra:
avra polaryzacja.asm
Po tym zwykle dostajesz plik .hex, który można programować przez avrdude.
avr-gcc + avrdudePoniżej wersja w C pod klasyczny toolchain AVR-GCC:
#define F_CPU 1200000UL
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
volatile uint16_t tick75 = 0;
ISR(TIM0_COMPA_vect)
{
if (++tick75 >= 4800) {
tick75 = 0;
PINB = _BV(PB0) | _BV(PB1); // jednoczesne przełączenie PB0 i PB1
}
}
int main(void)
{
DDRB = _BV(DDB0) | _BV(DDB1); // PB0, PB1 jako wyjścia
PORTB = _BV(PB0); // start: PB0=1, PB1=0
GTCCR = _BV(PSR10); // reset preskalera Timer0
TCNT0 = 0;
TCCR0A = _BV(WGM01); // CTC
OCR0A = 249;
TIMSK0 = _BV(OCIE0A); // przerwanie COMPA
TCCR0B = _BV(CS01) | _BV(CS00); // preskaler 64
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_enable();
sei();
for (;;) {
sleep_mode();
}
}
Kompilacja:
avr-gcc -std=c11 -Wall -Wextra -Os -mmcu=attiny13a -DF_CPU=1200000UL -o polaryzacja.elf polaryzacja.c
avr-objcopy -O ihex -R .eeprom polaryzacja.elf polaryzacja.hex
Programowanie FLASH:
avrdude -c usbasp -p attiny13a -U flash:w:polaryzacja.hex:i
Jeśli Twoja wersja avrdude nie przyjmuje attiny13a, użyj zwyczajowego aliasu:
avrdude -c usbasp -p t13 -U flash:w:polaryzacja.hex:i
Składnia -U memtype:op:filename:format jest standardowa w dokumentacji avrdude, a nowsze wersje rozpoznają zarówno identyfikatory części, jak i oficjalne nazwy z bazy urządzeń. (avrdudes.github.io)
Odczyt:
avrdude -c usbasp -p attiny13a -U lfuse:r:-:h -U hfuse:r:-:h
Zapis:
avrdude -c usbasp -p attiny13a -U lfuse:w:0x6A:m -U hfuse:w:0xFF:m
W wersji z aliasem:
avrdude -c usbasp -p t13 -U lfuse:w:0x6A:m -U hfuse:w:0xFF:m
Jeszcze raz: dla tego konkretnego wariantu poprawne jest LFUSE=0x6A, nie 0x62. Dokumentacja Microchip wprost podaje, że ATtiny13A ma tylko dwa bajty fuse, a ustawienie fabryczne odpowiada CKSEL=10, SUT=10, CKDIV8 zaprogramowany. (ww1.microchip.com)
Aktualna dokumentacja Microchip dla ATtiny13A nadal potwierdza klasyczny model fuse’ów tego układu: 2 fuse bytes, wewnętrzny oscylator 4.8/9.6 MHz, start fabryczny z 9.6 MHz + CKDIV8, czyli efektywnie 1.2 MHz. To nie jest obszar, w którym zaszły ostatnio zmiany funkcjonalne w samym układzie. (ww1.microchip.com)
Z praktycznego punktu widzenia istotniejsze są zmiany w narzędziach: współczesne wydania avrdude nadal obsługują rodzinę ATtiny13/13A, a instrukcja pokazuje aktualną składnię -U do odczytu i zapisu pamięci oraz fuse’ów. (avrdudes.github.io)
Najważniejszy detal techniczny: 64.000 s jest “idealne” tylko od strony arytmetyki timera. Sam timer dzieli częstotliwość bez błędu całkowitoliczbowego, ale wewnętrzny RC ma ograniczoną dokładność. Microchip podaje dla kalibracji fabrycznej ±10% przy 3 V i 25°C, a po ręcznej kalibracji można zejść do około ±2% przy stałym napięciu i temperaturze. Jeżeli więc potrzebujesz rzeczywistego czasu “zegarkowego”, sam Timer0 nie wystarczy — trzeba jeszcze kalibrować źródło zegara albo użyć zewnętrznego odniesienia. (ww1.microchip.com)
Drugi detal: zapis do PINB jest tu lepszy niż ręczne ustawianie PORTB na dwa osobne stany, bo zapewnia przełączenie obu linii jednym zapisem. To zmniejsza ryzyko chwilowego zwarcia lub chwilowej niesymetrii polaryzacji. (ww1.microchip.com)
Tu nie ma szczególnych aspektów prawnych, ale są aspekty bezpieczeństwa technicznego. Nie należy bezpośrednio obciążać PB0/PB1 dużym prądem ani sterować nimi bezpośrednio elementów indukcyjnych bez zabezpieczeń. W nocie katalogowej jest zaznaczone, że suma prądów wyjściowych portów nie powinna przekraczać 60 mA. (ww1.microchip.com)
Jeżeli kiedyś rozważysz użycie pinu RESET jako zwykłego I/O, pamiętaj, że zaprogramowanie RSTDISBL wymaga później programowania wysokonapięciowego do odzyskania normalnego dostępu programującego. W tym wariancie celowo tego nie robimy. (ww1.microchip.com)
lfuse = 0x6Ahfuse = 0xFFJedyna istotna niepewność wynika z tego, że w Twoim pytaniu widzę tylko fragment kontekstu. Odpowiedź zbudowałem dla wariantu wynikającego z przykładów: ATtiny13A, PB0/PB1, zmiana polaryzacji co 64 s. Jeżeli wcześniej zakładałeś inny pinout, inny zegar lub inny sposób odliczania, to trzeba przeliczyć OCR0A, preskaler i ewentualnie fuse’y.
Druga uwaga: odpowiedzi przykładowe były niespójne. W szczególności wersja z LFUSE=0x62 myli fuse mapę ATtiny13A z innymi AVR. W tym punkcie poprawka jest jednoznaczna: właściwa wartość to 0x6A. (ww1.microchip.com)
Jeżeli chcesz ten projekt dopracować inżyniersko, kolejne sensowne kroki to:
Najkrócej:
LFUSE=0x6A, HFUSE=0xFF.0x62 jest błędne dla ATtiny13A.Jeżeli chcesz, w następnym kroku mogę od razu przygotować jeszcze:
Makefile, avr-as/GNU assembler .S, Proszę przygotować ,gotowy Makefile, wersję pod avr-as/GNU assembler .S, wariant z kalibracją OSCCAL, schemat podłączenia z tranzystorami dla większego obciążenia. dla tego kodu , #define F_CPU 1200000UL #include <stdint.h> #include <avr/io.h> #include <avr/interrupt.h> #include <avr/sleep.h> volatile uint16_t tick75 = 0; ISR(TIM0_COMPA_vect) { if (++tick75 >= 4800) { tick75 = 0; PINB = _BV(PB0) | _BV(PB1); // jednoczesne przełączenie PB0 i PB1 } } int main(void) { DDRB = _BV(DDB0) | _BV(DDB1); // PB0, PB1 jako wyjścia PORTB = _BV(PB0); // start: PB0=1, PB1=0 GTCCR = _BV(PSR10); // reset preskalera Timer0 TCNT0 = 0; TCCR0A = _BV(WGM01); // CTC OCR0A = 249; TIMSK0 = _BV(OCIE0A); // przerwanie COMPA TCCR0B = _BV(CS01) | _BV(CS00); // preskaler 64 set_sleep_mode(SLEEP_MODE_IDLE); sleep_enable(); sei(); for (;;) { sleep_mode(); } }
Poniżej przygotowałem komplet dla ATtiny13A:
Makefile,.S,OSCCAL,Najpierw istotna korekta do przykładowych odpowiedzi: dla tego projektu poprawnym MCU jest ATtiny13A, nie atmega328p; na ATtiny13A nie ma SPH ani rejestru SMPR; przerwanie od compare A Timer0 to TIM0_COMPA / TIM0_COMPA_vect, a w GCC plik .S powinien być kompilowany przez avr-gcc, bo rozszerzenie .S oznacza assembler z preprocesorem. Dodatkowo układ z samymi dwoma tranzystorami low-side nie odwraca polaryzacji obciążenia — do tego potrzebny jest mostek H. (ww1.microchip.com)
Dla ATtiny13A ustawienie F_CPU = 1200000UL jest zgodne z ustawieniami fabrycznymi: układ startuje z wewnętrznego RC 9,6 MHz i zaprogramowanym CKDIV8, więc systemowo daje 1,2 MHz. Po resecie sprzęt automatycznie ładuje fabryczną kalibrację oscylatora do OSCCAL. Tryb Idle pozostawia Timer0 i system przerwań aktywne, a zapis logicznej jedynki do bitu PINxn przełącza odpowiadający bit PORTxn, więc zapis do PINB jest poprawnym sposobem na jednoczesne przełączenie PB0 i PB1. (ww1.microchip.com)
Pański kod realizuje:
Zatem:
To arytmetycznie jest dobrane prawidłowo. Problem praktyczny nie leży w timerze, tylko w dokładności RC oscylatora, stąd sens wariantu z OSCCAL. OSCCAL stroi wewnętrzny oscylator, ale dokumentacja zaznacza, że kalibracja dotyczy bazowo oscylatora 9,6/4,8 MHz, a zmiany wartości należy wykonywać ostrożnie i małymi krokami. Microchip nadal wskazuje notę AVR053 jako właściwe odniesienie do kalibracji RC. (ww1.microchip.com)
MakefilePoniższy Makefile obsługuje zarówno plik C, jak i plik .S. Domyślnie buduje assembler polaryzacja.S, ale można podać inny plik przez SRC=....
# ============================================================
# ATtiny13A / AVR-GCC / GNU assembler (.S)
# Domyślnie buduje polaryzacja.S
# Przykłady:
# make
# make SRC=polaryzacja_osccal.c TARGET=polaryzacja_osccal
# make flash
# make fuse
# ============================================================
MCU ?= attiny13a
F_CPU ?= 1200000UL
TARGET ?= polaryzacja
SRC ?= polaryzacja.S
PROGRAMMER ?= usbasp
PORT ?= usb
LFUSE ?= 0x6A
HFUSE ?= 0xFF
CC := avr-gcc
OBJCOPY := avr-objcopy
OBJDUMP := avr-objdump
SIZE := avr-size
AVRDUDE := avrdude
OBJ := $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SRC)))
CFLAGS := -mmcu=$(MCU) -DF_CPU=$(F_CPU)
CFLAGS += -Os -std=gnu11 -Wall -Wextra
CFLAGS += -ffunction-sections -fdata-sections
ASFLAGS := -mmcu=$(MCU) -x assembler-with-cpp
ASFLAGS += -Wa,-adhlns=$(TARGET).lst
LDFLAGS := -mmcu=$(MCU) -Wl,--gc-sections
.PHONY: all flash fuse verify readfuses size clean
all: $(TARGET).hex $(TARGET).lss size
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.S
$(CC) $(ASFLAGS) -c $< -o $@
$(TARGET).elf: $(OBJ)
$(CC) $(LDFLAGS) $^ -o $@
$(TARGET).hex: $(TARGET).elf
$(OBJCOPY) -O ihex -R .eeprom $< $@
$(TARGET).lss: $(TARGET).elf
$(OBJDUMP) -d -S $< > $@
size: $(TARGET).elf
$(SIZE) -C --mcu=$(MCU) $<
flash: $(TARGET).hex
$(AVRDUDE) -c $(PROGRAMMER) -P $(PORT) -p $(MCU) \
-U flash:w:$<:i
verify: $(TARGET).hex
$(AVRDUDE) -c $(PROGRAMMER) -P $(PORT) -p $(MCU) \
-U flash:v:$<:i
fuse:
$(AVRDUDE) -c $(PROGRAMMER) -P $(PORT) -p $(MCU) \
-U lfuse:w:$(LFUSE):m \
-U hfuse:w:$(HFUSE):m
readfuses:
$(AVRDUDE) -c $(PROGRAMMER) -P $(PORT) -p $(MCU) \
-U lfuse:r:-:h \
-U hfuse:r:-:h
clean:
rm -f *.o *.elf *.hex *.lss *.lst
Ten Makefile jest celowo zrobiony pod avr-gcc także dla .S, ponieważ .S ma być wcześniej przepuszczone przez preprocesor. To jest poprawna metoda dla kodu używającego #include <avr/io.h>. (gcc.gnu.org)
.SPlik: polaryzacja.S
/* ============================================================
* ATtiny13A
* PB0/PB1 przełączane jednocześnie co 64 s
* F_CPU = 1.2 MHz (9.6 MHz / 8)
* Timer0 CTC, preskaler 64, OCR0A = 249
* ISR = 75 Hz, 4800 przerwań = 64 s
* ============================================================ */
#define __SFR_OFFSET 0
#include <avr/io.h>
.equ OCR0A_VALUE, 249
.equ TICK_TARGET, 4800
.section .bss
.global tick75
tick75:
.space 2
.section .text
.global __vector_6
.type __vector_6, @function
/* TIM0_COMPA = vector 0x0006 na ATtiny13A */
__vector_6:
push r16
in r16, SREG
push r16
push r24
push r25
/* tick75++ */
lds r24, tick75
lds r25, tick75+1
adiw r24, 1
/* if (tick75 >= 4800) */
cpi r24, lo8(TICK_TARGET)
ldi r16, hi8(TICK_TARGET)
cpc r25, r16
brlo 1f
/* tick75 = 0 */
clr r24
clr r25
sts tick75, r24
sts tick75+1, r25
/* jednoczesne przełączenie PB0 i PB1 */
ldi r16, (1<<PB0) | (1<<PB1)
out PINB, r16
rjmp 2f
1:
sts tick75, r24
sts tick75+1, r25
2:
pop r25
pop r24
pop r16
out SREG, r16
pop r16
reti
.global main
.type main, @function
main:
/* PB0, PB1 jako wyjścia */
ldi r16, (1<<DDB0) | (1<<DDB1)
out DDRB, r16
/* stan początkowy: PB0=1, PB1=0 */
ldi r16, (1<<PB0)
out PORTB, r16
/* tick75 = 0 */
clr r16
sts tick75, r16
sts tick75+1, r16
/* reset preskalera Timer0 */
ldi r16, (1<<PSR10)
out GTCCR, r16
/* TCNT0 = 0 */
clr r16
out TCNT0, r16
/* CTC */
ldi r16, (1<<WGM01)
out TCCR0A, r16
/* OCR0A = 249 */
ldi r16, OCR0A_VALUE
out OCR0A, r16
/* przerwanie COMPA */
ldi r16, (1<<OCIE0A)
out TIMSK0, r16
/* preskaler 64 */
ldi r16, (1<<CS01) | (1<<CS00)
out TCCR0B, r16
/* sleep: Idle (SM1=0, SM0=0), SE=1 */
ldi r16, (1<<SE)
out MCUCR, r16
sei
loop:
sleep
rjmp loop
Ta wersja jest poprawna dla ATtiny13A, bo:
TIM0_COMPA ma wektor programu pod adresem 0x0006,MCUCR zawiera bity SE, SM1, SM0,SPL, a nie SPH,TIM0_COMPA_vect jest nazwą znaną w avr-libc dla tego układu. (ww1.microchip.com)OSCCALNajpraktyczniejszy wariant to korekcja względem wartości fabrycznej, bo po resecie sprzęt i tak ładuje kalibrację 9,6 MHz do OSCCAL. Zamiast wpisywać „sztywną” liczbę absolutną, lepiej dodać lub odjąć mały offset. (ww1.microchip.com)
Plik: polaryzacja_osccal.c
#define F_CPU 1200000UL
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
volatile uint16_t tick75 = 0;
/*
* Korekta względem fabrycznej wartości OSCCAL załadowanej przy resecie.
* Dodatnia wartość -> szybszy zegar
* Ujemna wartość -> wolniejszy zegar
*
* Zalecenie praktyczne: stroić małymi krokami, np. od -8 do +8.
*/
#define OSCCAL_DELTA 0
static void osccal_apply_delta(void)
{
int16_t cal = (int16_t)OSCCAL + OSCCAL_DELTA;
if (cal < 0) {
cal = 0;
}
if (cal > 0x7F) {
cal = 0x7F;
}
OSCCAL = (uint8_t)cal;
}
ISR(TIM0_COMPA_vect)
{
if (++tick75 >= 4800) {
tick75 = 0;
PINB = _BV(PB0) | _BV(PB1);
}
}
int main(void)
{
osccal_apply_delta();
DDRB = _BV(DDB0) | _BV(DDB1);
PORTB = _BV(PB0);
GTCCR = _BV(PSR10);
TCNT0 = 0;
TCCR0A = _BV(WGM01);
OCR0A = 249;
TIMSK0 = _BV(OCIE0A);
TCCR0B = _BV(CS01) | _BV(CS00);
/* opcjonalnie dla mniejszego poboru w Idle:
ACSR = _BV(ACD);
ADCSRA = 0;
PRR = _BV(PRADC);
*/
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_enable();
sei();
for (;;) {
sleep_mode();
}
}
Praktycznie:
OSCCAL_DELTA,OSCCAL_DELTA.Do strojenia nie polecam mierzyć od razu pełnych 128 s. Lepiej tymczasowo ustawić próg na 75 zamiast 4800, wtedy przełączenie nastąpi co 1 s i strojenie będzie znacznie szybsze. Jest to poprawne inżyniersko, bo OSCCAL skaluje cały zegar systemowy, więc po zestrojeniu krótkiego odcinka przeskaluje się także odcinek 64 s. To jest wniosek wynikający bezpośrednio z tego, że OSCCAL stroi wewnętrzny oscylator używany przez zegar systemowy, a Timer0 korzysta z tego samego zegara synchronicznego. (ww1.microchip.com)
Z punktu widzenia dokumentacji producenta nadal aktualne są dwie rzeczy:
Wniosek praktyczny: jeśli obciążenie jest małe i zasilane z tego samego napięcia co ATtiny13A, można zrobić prosty mostek dyskretny. Jeśli jednak:
to lepiej przejść na dedykowany driver/mostek H niż walczyć z dyskretną topologią. To nie jest kwestia „czy zadziała”, tylko stabilności, odporności i bezpieczeństwa projektu. (developerhelp.microchip.com)
PINB = _BV(PB0) | _BV(PB1) jest dobreW ATtiny13A zapis logicznej jedynki do PINxn przełącza PORTxn. Dzięki temu oba bity można przełączyć jednym zapisem magistralowym, co minimalizuje asymetrię czasową między PB0 i PB1. (ww1.microchip.com)
SLEEP_MODE_IDLEW trybie Idle procesor zatrzymuje clkCPU, ale Timer/Counter i przerwania dalej działają. To dokładnie pasuje do Pańskiego zastosowania: CPU śpi, a Timer0 wybudza go tylko na obsługę ISR. (ww1.microchip.com)
Jeżeli celem jest odwracanie polaryzacji na obciążeniu, potrzebne są cztery przełączniki w topologii H-bridge. Dokumentacja Microchipa dla aplikacji BDC wyraźnie rozróżnia:
W tym projekcie nie ma szczególnych aspektów prywatności, ale są ważne aspekty bezpieczeństwa technicznego:
Jeżeli urządzenie ma trafić do produktu komercyjnego, dochodzą jeszcze kwestie:
Najprostszy poprawny wariant dla odwracania polaryzacji to mostek H. Dla bezpośredniego sterowania z PB0/PB1 proponuję wersję z MOSFET logic-level, ale tylko gdy:
[
V{LOAD} = V{CC_MCU}
]
czyli np. mikrokontroler i obciążenie pracują z tego samego 5 V.
+VLOAD = +VCC MCU (np. 5 V)
|
.------+------.
| |
Q1 P-MOS Q3 P-MOS
| |
węzeł A ----+---- OBCIĄŻENIE ----+---- węzeł B
| |
Q2 N-MOS Q4 N-MOS
| |
GND GND
Sterowanie lewej połówki:
PB0 ---[100Ω]--- bramka Q1
PB0 ---[100Ω]--- bramka Q2
bramka Q1 ---[100k]--- +VLOAD
bramka Q2 ---[100k]--- GND
Sterowanie prawej połówki:
PB1 ---[100Ω]--- bramka Q3
PB1 ---[100Ω]--- bramka Q4
bramka Q3 ---[100k]--- +VLOAD
bramka Q4 ---[100k]--- GND
PB0 = 1, PB1 = 0
PB0 = 0, PB1 = 1
To odpowiada dokładnie idei mostka H: cztery przełączniki, dwa kierunki pracy i stany przekątnych. Microchip wprost opisuje dla H-bridge tryby „Forward” i „Reverse” jako załączenie odpowiednich przekątnych. (microchip.com)
Ważna uwaga: dla klasycznego pojedynczego low-side switcha daje się jedną diodę równolegle do obciążenia, ale dla H-bridge to podejście nie wystarcza; Microchip pokazuje dla H-bridge zabezpieczenia związane z każdą gałęzią mostka. (microchip.com)
Nie polecam bezpośredniego dyskretnego mostka z PB0/PB1, jeśli:
VLOAD jest większe niż VCC mikrokontrolera,Wtedy lepiej użyć dedykowanego mostka/drivera z ochroną przed shoot-through i kontrolą dead-time. Microchip wprost wskazuje takie rozwiązania w rodzinach swoich driverów. (developerhelp.microchip.com)
Polecam kolejność uruchomienia:
OSCCAL_DELTA.Dodatkowa korekta względem przykładowych odpowiedzi: gotowy kod assemblera nie powinien używać rejestrów i konstrukcji nieobecnych w ATtiny13A, takich jak SPH czy SMPR; poprawne odniesienia wynikają wprost z mapy rejestrów tego układu. (ww1.microchip.com)
Jeżeli będzie Pan chciał rozwinąć projekt, sensowne kierunki to:
OSCCAL do EEPROM,Do kalibracji RC najlepszym dalszym materiałem jest AVR053, a do samego ATtiny13A podstawą pozostaje datasheet urządzenia. (onlinedocs.microchip.com)
Najkrócej:
Makefile powinien budować .S przez avr-gcc,__vector_6 dla TIM0_COMPA,OSCCAL najlepiej zrobić jako małą korekcję względem wartości fabrycznej,Jeżeli Pan chce, w następnym kroku mogę przygotować jeszcze:
OSCCAL,