Język C dla mikroprocesorów AVR Wykład 6
Programowanie AVR Narzędzia 2
Języki programowania Mikrokontrolery AVR ze względu na swoją popularność doczekały się implementacji kompilatorów dla znacznej części dostępnych języków programowania Najpopularniejsze w chwili obecnej są assembler i C 3
Kompilator języka C Najpopularniejszy zestaw narzędzi dla języka C dla platformy AVR bazuje na porcie kompilatora gcc: avr-gcc Zestaw ten dla środowiska Windows nasi nazwę WinAVR http://winavr.sourceforge.net/ 4
WinAVR W skład pakietu WinAVR wchodzą następujące elementy Kompilator C/C++ (avr-gcc, avr-g++) Zestaw plików nagłówkowych dla wszystkich mikrokontrolerów AVR Program make Powłoka bash wraz z podstawowymi narzędziami Wygodny edytor Programmer s Notepad 2 (PN2) Program avrdude do programowania pamięci mikrokontrolerów AVR 5
Programmer s Notepad 2 Jest to darmowy prosty edytor dla programistów, z obsługą projektów, kolorowaniem składni wielu języków oraz możliwością korzystania z kompilatora i programatora z poziomu edytora http://www.pnotepad.org/ 6
Programmer s Notepad 2 7
Atmel Studio 6 Jest to darmowe zintegrowane środowisko programistyczne, rozwijane przez firmę Atmel, producenta mikrokontrolerów AVR Umożliwia pisanie programów dla architektura AVR i ARM w assemblerze oraz C/C++ Bazuje na MS Visual Studio, zintegrowanym z avr-gcc i arm-gcc 8
Atmel Studio 6 Środowisko wspiera kilka typów programatorów oraz mechanizmy ułatwiające debugowanie http://www.atmel.com/microsite/atmel_studi o6/ 9
Atmel Studio 6 10
Przygotowanie projektu Przygotowanie projektu WinAVR + PN2 11
Etapy Utworzenie w PN2 nowego projektu Skopiowanie do katalogu projektu generycznego pliku Makefile, który można znaleźć w katalogu domowym WinAVR (podkatalog sample) Dodanie plik Makefile do projektu w PN2 Konfiguracja pliku Makefile 12
Etapy Utworzenie jednego lub kilku plików z kodem źródłowym Dodanie informacji o plikach źródłowych do Makefile Kompilacja z poziomu PN2 Programowanie mikrokontrolera AVR z poziomu PN2 13
Konfiguracja pliku Makefile W domyślnym pliku Makefile zmiany wymagają następujące elementy Model mikrokontrolera i częstotliwość zegara taktującego Lista plików z kodem źródłowym do kompilacji Flagi kompilatorów (opcjonalnie) Konfiguracja programatora (opcjonalnie) 14
Makefile - konfiguracja Zmiany w domyślnym pliku Makefile MCU = atmega32 #model mikrokontrolera F_CPU = 11059200 #częstotliwość zegara w Hz TARGET = main #prefix plików wynikowych SRC = $(TARGET).c #lista plików źródłowych C CPPSRC = #lista plików źródłowych C++ ASRC = #lista plików źródłowych assemblera 15
Makefile konfiguracja Flagi optymalizacji OPT = 0 #optymalizacja wyłączona OPT = 1 #2 lub 3 optymalizacja wydajności OPT = s #optymalizacja rozmiaru pliku wynikowego 16
Makefile flagi CFLAGS i CXXFLAGS kompilatora C/C++ -funsigned-char używaj domyślnie unsigned char zamiast char -funsigned-bitfields - używaj domyślnie pól bitowych bez znaku zamiast ze znakiem -fpack-struct umieszczaj elementy struktur bezpośrednio jeden za drugim, bez wyrównywania i powstałych w ten sposób dziur -fshort-enums alokuj tylko tyle miejsce dla typu enum ile mogą zająć możliwe wartości o zadeklarowanych zakresach 17
Makefile flagi kompilatora C/C++ -Wstrict-prototypes ostrzegaj, jeśli funkcja jest zadeklarowana lub zdefiniowana bez deklaracji typów argumentów -mshort-calls używaj funkcji rjmp/rcall (funkcje skoku o ograniczonym zakresie) dla układów z >8kB pamięci, domyślnie dla takich układów wykorzystywane są funkcje jmp/call -fno-unit-at-a-time opcja pozostawiona w celu zapewnienia kompatybilności 18
Makefile flagi kompilatora C/C++ -Wundef ostrzegaj, jeśli dyrektywa #if korzysta z niezadeklarowanego argumentu -Wunreachable-code ostrzegaj, jeśli program zawiera kod, który nigdy nie zostanie wykonany -Wsign-compare ostrzegaj, gdy jest porównywana zmienna ze znakiem i bez znaku -Wall wyświetlaj wszystkie typy ostrzeżeń podczas kompilacji 19
Makefile - programator W procesie programowania wykorzystywany jest program avrdude, obsługujący wszystkie popularne programatory dla AVR dostępne na rynku, w tym USBASP i STK500v2 Konfiguracja polega na wybraniu modelu programatora i portu, do którego jest podłączony 20
Makefile - programator #wskazanie modelu programatora AVRDUDE_PROGRAMMER = stk500v2 #port, do którego jest podłączony programator AVRDUDE_PORT = com1 21
Użyteczne komendy Kompilacja programu make all Usunięcie plików tymczasowych i wynikowych make clean Programowanie mikrokontrolera make program 22
avrdude obsługa z konsoli Konfiguracja programu avrdude w pliku Makefile nie zawsze wystarcza do wykonania nietypowych operacji podczas programowania Niektóre przypadki, takie jak modyfikacja fusebitów jest niewygodna podczas realizacji przez Makefile i wygodniej jest wykonać niezbędne operacje z konsoli 23
avrdude obsługa z konsoli Komenda bazowa avrdude -p atmega32 -c stk500v2 -P com1 Odczyt pamięci EEPROM -D -U eeprom:r:"eeprom.eep":i Odczyt pamięci FLASH -U flash:r:"flash.hex":i Odczyt fusebitów -u -U lfuse:r:"fuse_lo.hex":i -U hfuse:r:"fuse_hi.hex":i Odczyt lockbitów -U lock:r:"fuse_lock.hex":i 24
avrdude obsługa z konsoli Komenda bazowa avrdude -p atmega32 -c stk500v2 -P com1 -V Zapis pamięci EEPROM -D -U eeprom:w:"eeprom.eep":i Zapis pamięci FLASH -U flash:w:"flash.hex":i Zapis fusebitów -u -U lfuse:w:0xe1:m -U hfuse:w:0x99:m Zapis lockbitów -U lock:w:0x3f:m 25
avrdude obsługa z konsoli Kasowanie pamięci mikrokontrolera avrdude -p atmega32 -c stk500v2 -P com1 -e Komendy z jednego slajdu mogą być ze sobą łączone 26
Język C dla AVR Język C dla AVR 27
Język C dla AVR Konstrukcja programu w języku C dla mikrokontrolerów AVR jest bardzo podobna do programu dla PC W celu optymalnej i wygodnej obsługi wszystkich elementów mikrokontrolera programista ma do dyspozycji szereg makr, funkcji i typów, niespotykanych w bibliotece standardowej dla innych platform 28
Typy w języku C dla AVR Rozmiar generycznych typów zmiennych Typ Rozmiar (bitów) Wartość minimalna char 8-128 (0) 127 (255) signed char 8-128 127 unsigned char 8 0 255 short int 16-32768 32767 unsigned short int 16 0 65535 int 16-32768 32767 unsigned int 16 0 65535 long int 32-2^31 2^31-1 Wartość maksymalna 29
Typy w języku C dla AVR Typ Rozmiar (bitów) Wartość minimalna unsigned long int 32 0 2^32-1 long long int 64-2^63 2^63-1 unsigned long long int 64 0 2^64-1 Wartość maksymalna float 32 ±1.18 10^(-38) ±3.4 10^38 double 32 ±1.18 10^(-38) ±3.4 10^38 long double 32 ±1.18 10^(-38) ±3.4 10^38 30
Typy w języku C dla AVR Skrócone nazwy typów C dla AVR signed char unsigned char signed int unsigned int signed long int unsigned long int signed long long int unsigned long long int int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t 31
Obsługa rejestrów w języku C dla AVR Zastąpienie zawartości NAZWA_REJESTRU = (1 << NAZWA_BITU); Ustawienie bitu NAZWA_REJESTRU = (1 << NAZWA_BITU); Wyczyszczenie bitu NAZWA_REJESTRU &= ~(1 << NAZWA_BITU); Odczyt stanu bitu wartosc_bitu = ((NAZWA_REJESTRU >> NAZWA_BITU) & 0x1); 32
Obsługa rejestrów w języku C dla AVR Aby skonfigurować port PA0 jako wyjście: DDRA = (1 << DDA0); Aby skonfigurować port PB1 jako wejście z domyślnym stanem logicznym 1 : DDRB &= ~(1 << DDB1); PORTB = (1 << PORTB1); Aby ustawić na porcie PC2 logiczną 1 : PORTC = (1 << PORTC2); Aby odczytać stan portu PD3: stan_pd3 = ((PIND >> PIND3) & 0x1); 33
Przerwania w języku C dla AVR Do obsługi przerwań w języku C służy makro ISR ISR (NAZWA_WEKTORA_PRZERWANIA) { } //obsługa przerwania 34
Przerwania w języku C dla AVR Alternatywnie można użyć do tego celu makra SIGNAL (NAZWA_SYGNAŁU) { //obsługa przerwania } 35
Przerwania lista wektorów i sygnałów ATmega32 Opis Wektor Sygnał External Interrupt Request 0 INT0_vect SIG_INTERRUPT0 External Interrupt Request 1 INT1_vect SIG_INTERRUPT1 External Interrupt Request 2 INT2_vect SIG_INTERRUPT2 Timer/Counter2 Compare Match TIMER2_COMP_vect SIG_OUTPUT_COMPARE2 Timer/Counter2 Overflow TIMER2_OVF_vect SIG_OVERFLOW2 Timer/Counter1 Capture Event TIMER1_CAPT_vect SIG_INPUT_CAPTURE1 Timer/Counter1 Compare Match A TIMER1_COMPA_vect SIG_OUTPUT_COMPARE1A Timer/Counter1 Compare Match B TIMER1_COMPB_vect SIG_OUTPUT_COMPARE1B Timer/Counter1 Overflow TIMER1_OVF_vect SIG_OVERFLOW1 Timer/Counter0 Compare Match TIMER0_COMP_vect SIG_OUTPUT_COMPARE0 Timer/Counter0 Overflow TIMER0_OVF_vect SIG_OVERFLOW0 Serial Transfer Complete SPI_STC_vect SIG_SPI 36
Przerwania lista wektorów i sygnałów ATmega32 Opis Wektor Sygnał USART, Rx Complete USART_RXC_vect SIG_USART_RECV SIG_UART_RECV USART Data Register Empty USART_UDRE_vect SIG_USART_DATA SIG_UART_DATA USART, Tx Complete USART_TXC_vect SIG_USART_TRANS SIG_UART_TRANS ADC Conversion Complete ADC_vect SIG_ADC EEPROM Ready EE_RDY_vect SIG_EEPROM_READY Analog Comparator ANA_COMP_vect SIG_COMPARATOR 2-wire Serial Interface TWI_vect SIG_2WIRE_SERIAL Store Program Memory Ready SPM_RDY_vect SIG_SPM_READY 37
Modyfikator volatile Zmienne globalne, które mają być wykorzystywane wewnątrz zwykłych funkcji i funkcji obsługi przerwań powinny być zaopatrzone w modyfikator volatile volatile TYP_ZMIENNEJ NAZWA_ZMIENNEJ; Modyfikator volatile zapobiega optymalizacji polegającej na operowaniu przez program na tymczasowej kopii zmiennej, co może być źródłem utraty danych 38
PROGMEM Zmienne, które nie będą modyfikowane, a potrzebują dużo pamięci mogą być trzymane w pamięci programu, zamiast pamięci RAM #include <avr/pgmspace.h> TYP ZMIENNA PROGMEM = WARTOŚĆ; TYP ZMIENNA_1 = pgm_read_xxx(adres_zmiennej); 39
PROGMEM W zależności od lokalizacji zmiennej w pamięci wyróżnia się wersje near i far funkcji pgm_read_xxx Adres w wersji near jest 16-bitowy, natomiast w wersji far 32-bitowy 40
PROGMEM Funkcje w wersji far pgm_read_byte_far(address_long) pgm_read_word_far(address_long) pgm_read_dword_far(address_long) pgm_read_float_far(address_long) 41
PROGMEM Funkcje w wersji near pgm_read_byte(address_short) pgm_read_word(address_short) pgm_read_dword(address_short) pgm_read_float(address_short) 42
Modyfikator inline Modyfikator ten informuje kompilator, że jeśli nastąpi wywołanie funkcji inline, to zamiast wygenerować w tym miejscu przeniesienie sterowania (skok) do tej funkcji, bezpośrednio wstawi wygenerowany kod tej funkcji Funkcje inline stosuje się w sytuacji, gdy wydajność jest ważniejsza od objętości kodu wynikowego 43
Modyfikator inline Przykład inline uint8_t max(uint8_t a, uint8_t b) { return a > b? a : b; } 44
cli() i sei() Aby zapewnić niepodzielność operacji przez przerwania, które mogłyby być w tym czasie zgłoszone, stosuje się funkcje cli() i sei(), blokującą i odblokowującą globalną obsługę przerwań na czas wykonywania takiej operacji 45
Obsługa portów I/O Makra upraszczające obsługę portów I/O #define PORT(x) XPORT(x) #define XPORT(x) (PORT##x) #define PIN(x) XPIN(x) #define XPIN(x) (PIN##x) #define DDR(x) XDDR(x) #define XDDR(x) (DDR##x) Np. #define DRIVER_PORT A #define DRIVER_PIN_0 0 PORT(DRIVER_PORT) = (1 << DRIVER_PIN); 46
Optymalizacja kodu Optymalizacja kodu 47
Cele optymalizacji Ograniczenie rozmiaru kodu wynikowego Ograniczenie zużycia pamięci operacyjnej Ograniczenie zużycia czasu procesora Zapewnienie niezawodności programu Ograniczenie liczby potencjalnie niebezpiecznych elementów kodu 48
Ograniczenie rozmiaru kodu wynikowego Przykłady realizacji Unikanie funkcji inline Unikanie powtarzania tych samych fragmentów kodu, jeśli to możliwe zastąpić je jedną funkcją Niekorzystanie z rozbudowanych gotowych funkcji, jeśli ich możliwości nie będą w pełni wykorzystane Użycie flagi optymalizacji s 49
Ograniczenie zużycia pamięci operacyjnej Stosowanie zmiennych ze znakiem i/lub zmiennoprzecinkowych tylko w razie konieczności Stosowanie możliwie małych typów danych Dokładne szacowanie objętości potrzebnych danych w pamięci RAM Unikanie rekurencji Stosowanie dynamicznego przydziału pamięci tylko w ostateczności (ryzyko wycieku) 50
Ograniczenie zużycia czasu procesora Wykorzystywanie funkcji inline Wstawki assemblerowe Jeśli to możliwe zastąpienie złożonych funkcji arytmetycznych (np. sin, cos) tablicami Korzystanie z flag optymalizacji 1, 2, 3 Unikanie typów zmiennych liczbowych, na których operacje arytmetyczne wykonywane są w sposób programowy Operacje arytmetyczne wykonywać na liczbach całkowitych 51
Poprawienie czasu odpowiedzi programu Wykorzystywanie asychronicznych trybów pracy peryferiów Przeniesienie możliwie dużej części sterowania programem do funkcji obsługi przerwań (pamiętając o priorytetach przerwań!) Priorytetyzacja zadań w programie Uproszczenie i optymalizacja funkcji krytycznych dla responsywności systemu 52
Zapewnienie niezawodności programu Unikanie dynamicznego alokowania pamięci oraz korzystanie z narzędzi do analizy użycia pamięci Unikanie funkcji operujących na strumieniach, o zmiennej liczbie argumentów itp. Unikanie operowania na rejestrach korzystając z numerów bitów zamiast nazw bitów Korzystanie z układów BOD (Brown-out Detection) i watchdog 53
Przykład Przykład prostego programu 54
Przykład prostego programu #include <inttypes.h> #include <avr/io.h> #include <avr/interrupt.h> volatile uint8_t led = 0; //obsluga przerwan INT0 (przycisk) ISR (INT0_vect) { led = (led + 1) % 2; } 55
Przykład prostego programu int main(void) { //konfiguracja portow I/O DDRA = (1<<DDA0); //wyjscia sterujace diodami PORTA = (1<<PORTA0); PORTD = (1<<PORTD2); //wejscie przycisku cli(); //blokujemy globalnie dzialanie przerwan MCUCR = (1<<ISC01); //przerwania INT0 reagujace na zbocze opadajace (nacisniecie przycisku) GICR = (1<<INT0)); //wlaczamy przerwania INT0 sei(); //odblokowujemy globalnie dzialanie przerwan 56
Przykład prostego programu //nieskonczona petla glowna programu while(1) { if(led == 0) PORTA &= ~(1<<dioda); else PORTA = (1<<dioda); } } return 0; 57