Cała rzecz polega na sumowaniu wyników określoną liczbę razy i na końcu przesunięciu wyniku w prawo o zadaną liczbę bitów.
Jeśli chcemy uzyskać 1 bit więcej niż dostępny przetwornik (w AVR 12 bitów), należy zebrać 4 próbki, zsumować je i wynik przed zwróceniem przesunąć w prawo o 1 bit (czyli podzielić przez dwa). Wzór jest dość sprytny. Potraktuj znak ^ jako potęgowanie.
k – ilość dodatkowych bitów które chcę otrzymać
suma_próbek = 4^k
wynik_z_oversample = suma_próbek >> k;
Czyli dla k równego to co niżej, ilość próbek będzie wynosiła:
k | ilość próbek
--+-----------------
1 | 4
2 | 16
3 | 64
4 | 256
5 | 1024
6 | 4096
...
U... jak widać ilość próbek sumowanych bardzo szybko wzrasta z ilością bitów które chcę odzyskać. Jak mówiłem, nie ma nic za darmo :-/
No fajnie, ale jak uzyskać to potęgowanie bez stosowania zmiennego przecinka w C lub C++? Zwróć uwagę, czy ( 1 << ( k << 1 )) nie jest równe „4 do potęgi k” ?
Pamiętać tylko należy przez zwróceniem wartości o przesunięciu w prawo o k bitów.
Kod ustawienia licznika oversample i wykonania pomiaru będzie więc wyglądał tak:
Kod: Zaznacz cały
...
// Pobranie wartości pomiaru z oversample
uint16_t getValue(uint8_t oversample) {
// Ustawienie licznika
counter = ( 1 << ( oversample << 1));
// Zerowanie wartości przed sumowaniem
value = 0;
// Pętla obliczająca sumę oversample
for(uint16_t i = counter; i > 0; --i) {
// Uruchomienie pomiaru
runAdc();
// Oczekiwanie na zakończenie pomiaru
while(!complete());
// Sumowanie próbek
value += ADC;
}
return ( value >> oversample );
}
...
// Ustawienie oversamplingu
void setOversample(uint8_t oversample) {
counter = ( 1 << ( oversample << 1));
return;
}
...Wywołanie w main.cpp to:
Kod: Zaznacz cały
...
// Pobranie wartości z przetwornika
value = myAdc.getValue(2);
...Kompilujemy przykład i sprawdzamy czy rzeczywiście wykonuje pomiary przyłączając do pinu mikrokontrolera potencjometr. W filmach Mirka jest to wszystko opisane dokładnie więc tu już nie będę tego powtarzał a już szczególnie wymagań co do podłączenia sygnału i dobrego filtrowania napięcia. Kod działa i mierzy. Będziesz zdziwiony jak dokładnie wygląda pomiar o ile dobrze zasiliłeś mikrokontroler i wyfiltrowałeś napięcia. A jeśli pomiar jest niedokładny, możesz przesunąć o jeszcze jeden bit w prawo
Krytycznym okiem patrzę teraz na kod i widzę niepotrzebne powtórzenia. Zarzuty do kodu to:
1. Napięcia referencyjne są ustawiane 2 razy a po co?
2. Preskaler jest ustawiany w 2 konstruktorach.
3. Tak samo dzieje się z ustawieniem kanału w konstruktorach.
4. Jak szydło z worka wyszedł nieprzemyślany do końca interfejs ob metoda setOversample() jest niepotrzebna (wyjaśnię niżej)
5. Są jakieś paskudne wartości hex które przesyłam a za 3 msc. zapomnę co znaczą w ATmega16.
Myślę że ustawienie napięcia referencyjnego kwalifikuje się no nowej metody w tej klasie. Podobnie będzie z preskalerem. Dodam więc je.
Zadać sobie także należy pytanie po co mi teraz metoda setOversample() jeśli i tak w wywołaniu pomiaru z oversample podaję wartość odtwarzanych bitów. Metodę setOversample() więc usunę.
Bardzo nie podoba mi się także przesyłanie „magicznych” wartości do preskalera i do napięcia referencyjnego. One w kodzie powinny być opisowe. Kod mam czytać jak książkę
Po uzupełnieniu kod wygląda tak:
Kod: Zaznacz cały
#include <stdint.h>
#include <avr/io.h>
enum ADCReference {
ADC_EXTERNAL = 0,
ADC_AVCC = 1,
ADC_RESERVED = 2,
ADC_INTERNAL = 3
};
enum ADCPrescaler {
ADC_PRESCALER_0 = 0,
ADC_PRESCALER_2 = 1,
ADC_PRESCALER_4 = 2,
ADC_PRESCALER_8 = 3,
ADC_PRESCALER_16 = 4,
ADC_PRESCALER_32 = 5,
ADC_PRESCALER_64 = 6,
ADC_PRESCALER_128 = 7
};
class Adc {
public:
// Konstruktor
Adc() : value(0), counter(0) {
// Uruchomienie przetwornika ADC
ADCSRA = (1 << ADEN);
// Ustawienie napięcia referencyjnego na AVCC oraz kanału na 0
setReference(ADC_AVCC);
setChannel(0x00);
// Ustawienie preskalera na 128
setPrescaler(ADC_PRESCALER_128);
}
// Konstruktor ustawiający preskaler
Adc(ADCPrescaler prescaler) : value(0), counter(0) {
// Uruchomienie przetwornika ADC
ADCSRA = (1 << ADEN);
// Ustawienie napięcia referencyjnego na AVCC oraz kanału na 0
setReference(ADC_AVCC);
setChannel(0x00);
// Ustawienie preskalera
setPrescaler(prescaler);
}
// Ustawienie preskalera
void setPrescaler(ADCPrescaler prescaler) const {
// Ustawienie preskalera na 128
ADCSRA = (ADCSRA & ~(( ADPS2 << 1) - 1)) | prescaler;
}
// Ustawienie napięcia referencyjnego
void setReference(ADCReference reference) {
// Ustawienie napięcia referencyjnego na bez naruszania kanału.
ADMUX = ( ADMUX & ( (1 << REFS0) - 1) ) | ( reference << REFS0);
}
// Ustawienie kanału Adc
void setChannel(uint8_t channel) const {
// Ustawienie kanału
ADMUX = ( ADMUX & ~( (MUX4 << 1) - 1 ) ) | channel;
return;
}
// Pobranie wartości pomiaru
uint16_t getValue() const {
// Uruchomienie pomiaru
runAdc();
// Oczekiwanie na zakończenie konwersji Adc
while(!complete());
// Zwrot wartości po konwersji
return ADC;
}
// Pobranie wartości pomiaru z oversample
uint16_t getValue(uint8_t oversample) {
// Ustawienie licznika
counter = ( 1 << ( oversample << 1));
// Zerowanie wartości przed sumowaniem
value = 0;
// Pętla obliczająca sumę oversample
for(uint16_t i = counter; i > 0; --i) {
// Uruchomienie pomiaru
runAdc();
// Oczekiwanie na zakończenie pomiaru
while(!complete());
// Sumowanie próbek
value += ADC;
}
return ( value >> oversample );
}
// Start konwersji
void runAdc() const {
// Start konwersji
ADCSRA |= (1 << ADSC);
return;
}
// Sprawdzenie czy zakończono pomiar
bool complete() const {
// Jeśli ADSC zgaszony, konwersja zakończona
return !(ADCSRA & (1 << ADSC));
}
private:
// Wartość obliczana
uint16_t value;
// Licznik oversample
uint16_t counter;
};Zwróć uwagę że zmienił się typ argumentu w dodanych metodach obsługi napięcia referencyjnego oraz preskalera. To jest typ enum. Teraz te metody przyjmą nazwy symboliczne oprócz ,,magicznych liczb”
Obliczenia sumy oversample będą dla podanych bitów od 4 do 6 przekraczały uint16_t (zerknij wyżej na obliczenia dla k = 6, to aż 4096 próbek) (atrybut prywatny value w klasie Adc). Niestety będę musiał go zmienić na uint32_t.
Zadam sobie także pytanie czy naprawdę koniecznie mam trzymać atrybut counter kiedy łatwo można go wyliczyć ( przypominam że chodzi o: counter = ( 1 << ( oversample << 1)); ). I tak pętla obliczeń będzie trwała długo a pamięci RAM nigdy za wiele
Po co mi konstruktor z przesłaniem preskalera jeśli od tego mam dedykowaną metodę? Zmieni się trochę kod klasy (zmniejszy się!) i w main.cpp będzie porządniej. Baaa... po co w konstruktorze dotykanie napięcia referencyjnego i preskalera jak mogę ustawić to z użyciem metod. Wprawdzie w main.cpp będzie trochę więcej wywołań ale za to będą bardzo opisowe.
Oto kod po poprawkach. W takim stanie nie wzbudza wstydu i nie świadczy że pisana była „chałtura”
Jednocześnie jest to kod, od którego może zacząć osoba która zna trochę C++ bo „miała na studiach”. Od tego momentu będzie wiele zagadnień dotyczących stricte MCU.
main.cpp:
Kod: Zaznacz cały
#include <util/delay.h>
#include <avr/interrupt.h>
#include "Adc.hpp"
#include "mkuart.h"
// Kreowanie obiektu Adc
Adc myAdc = Adc();
int main(void) {
// Uruchomienie przerwań potrzebnych dla usart
sei();
// Deklaracja zmiennej przechowującej wynik
uint16_t value;
// Ustawienie napięcia referencyjnego
myAdc.setReference(ADC_AVCC);
// Ustawienie kanału 0 jako wejściowego
myAdc.setChannel(ADC_CHANNEL_0);
// Ustawienie preskalera
myAdc.setPrescaler(ADC_PRESCALER_64);
// Inicjalizacja USART
USART_Init(50); // to jest baudrate dla 8MHz i 9600bps
while(true) {
// Uruchomienie pomiaru
myAdc.runAdc();
// Wysłanie napisu na usart
uart_puts("Adc 0: 0x");
// Pobranie wartości z przetwornika
value = myAdc.getValue(ADC_OVERSAMPLE_2);
// Wypisanie watości
uart_putint(value, 16);
// Nowa linia na usart
uart_puts("\r\n");
// Opóźnienie przed następnym pomiarem
_delay_ms(1000);
}
}Adc.hpp:
Kod: Zaznacz cały
#include <stdint.h>
#include <avr/io.h>
enum ADCReference {
ADC_EXTERNAL = 0,
ADC_AVCC = 1,
ADC_RESERVED = 2,
ADC_INTERNAL = 3
};
enum ADCPrescaler {
ADC_PRESCALER_0 = 0,
ADC_PRESCALER_2 = 1,
ADC_PRESCALER_4 = 2,
ADC_PRESCALER_8 = 3,
ADC_PRESCALER_16 = 4,
ADC_PRESCALER_32 = 5,
ADC_PRESCALER_64 = 6,
ADC_PRESCALER_128 = 7
};
enum ADCChannel {
ADC_CHANNEL_0 = 0,
ADC_CHANNEL_1 = 1,
ADC_CHANNEL_2 = 2,
ADC_CHANNEL_3 = 3,
ADC_CHANNEL_4 = 4,
ADC_CHANNEL_5 = 5,
ADC_CHANNEL_6 = 6,
ADC_CHANNEL_7 = 7
// Atmega16 ma więcej jeszcze kanałów, ale to tutorial
// i nie ma sensu rozbudowywać kodu.
};
enum ADCOversample {
ADC_OVERSAMPLE_0 = 0,
ADC_OVERSAMPLE_1 = 1,
ADC_OVERSAMPLE_2 = 2,
ADC_OVERSAMPLE_3 = 3,
ADC_OVERSAMPLE_4 = 4,
ADC_OVERSAMPLE_5 = 5,
ADC_OVERSAMPLE_6 = 6
};
class Adc {
public:
// Konstruktor
Adc() : value(0) {
// Uruchomienie przetwornika ADC
ADCSRA |= (1 << ADEN);
}
// Ustawienie preskalera
void setPrescaler(ADCPrescaler prescaler) const {
// Ustawienie preskalera na 128
ADCSRA = (ADCSRA & ~(( ADPS2 << 1) - 1)) | prescaler;
}
// Ustawienie napięcia referencyjnego
void setReference(ADCReference reference) {
// Ustawienie napięcia referencyjnego na bez naruszania kanału.
ADMUX = ( ADMUX & ( (1 << REFS0) - 1) ) | ( reference << REFS0);
}
// Ustawienie kanału Adc
void setChannel(ADCChannel channel) const {
// Ustawienie kanału
ADMUX = ( ADMUX & ~( (MUX4 << 1) - 1 ) ) | channel;
return;
}
// Pobranie wartości pomiaru
uint16_t getValue() const {
// Uruchomienie pomiaru
runAdc();
// Oczekiwanie na zakończenie konwersji Adc
while(!complete());
// Zwrot wartości po konwersji
return ADC;
}
// Pobranie wartości pomiaru z oversample
uint16_t getValue(ADCOversample oversample) {
// Zerowanie wartości przed sumowaniem
value = 0;
// Pętla obliczająca sumę oversample
for(uint16_t i = (1 << (oversample << 1)); i > 0; --i) {
// Uruchomienie pomiaru
runAdc();
// Oczekiwanie na zakończenie pomiaru
while(!complete());
// Sumowanie próbek
value += ADC;
}
return ( value >> oversample );
}
// Start konwersji
void runAdc() const {
// Start konwersji
ADCSRA |= (1 << ADSC);
return;
}
// Sprawdzenie czy zakończono pomiar
bool complete() const {
// Jeśli ADSC zgaszony, konwersja zakończona
return !(ADCSRA & (1 << ADSC));
}
private:
// Wartość obliczana
uint32_t value;
};Proponuję abyś teraz kod skompilował i uruchomił. Po kompilacji proszę zapisz wielkość wsadu. Będziemy do tego wracali. U mnie jest to:
Kod: Zaznacz cały
Device: atmega16
Program: 796 bytes (4.9% Full)
(.text + .data + .bootloader)
Data: 70 bytes (6.8% Full)
(.data + .bss + .noinit)Ci którzy mieli na studiach C++ lub wiedzą jak się tworzy kod w tym języku zdziwieni są faktem że całą klasę definiowałem w pliku *.hpp w otoczeniu class Adc { ... }; . Śpieszę z wyjaśnieniem. Chciałem pokazać że taki kod powoduje wbudowanie wszystkich metod w ciało klasy. A z racji tego że kompilator taki kod otrzymuje, przyjmuje że będziesz chciał gdzieś zrobić sizeof(...) na klasie, kopiować ją itp. Czyli pracować na niej bajt w bajt
Porządny czytelnik bluebooka został jednak nauczony (i dobrze) że co innego plik nagłówkowy a co innego kod. Tak, w C++ także się tak robi. Zrealizujmy więc odseparowanie definicji kodu od deklaracji klasy. Rzecz jasna stopniowo.
Najpierw wydzielimy metodę: void runAdc() const. W kodzie klasy pozostawimy jedynie jej deklarację, a poniżej kodu klasy wpiszemy definicję.
Kod: Zaznacz cały
...
class Adc {
public:
...
// Start konwersji
void runAdc() const;
...
};
// Start konwersji
void Adc::runAdc() const {
// Start konwersji
ADCSRA |= (1 << ADSC);
return;
}Zwróć uwagę jak wygląda definicja metody „wyciągniętej” z klasy. Jest to typ zwracany, nazwa klasy, dwa dwukropki, nazwa metody, ew. const i ciało. Konstruktor jeśli będzie wyciągnięty poza deklarację klasy, także będzie tak wyglądał. Jedynie nie będzie typu zwracanego.
Proszę, wydziel teraz samodzielnie wszystkie metody z klasy, włącznie z konstruktorem i umieść je pod deklaracją klasy.
Dla leniwców (choć pamiętaj że lenistwo się zemści...), umieszczam gotowy plik Adc.hpp. Pozbawię go jednak części z definicją enum'ów, bo one nic nie wnoszą.
Kod: Zaznacz cały
...
class Adc {
public:
// Konstruktor
Adc();
// Ustawienie preskalera
void setPrescaler(ADCPrescaler prescaler) const;
// Ustawienie napięcia referencyjnego
void setReference(ADCReference reference);
// Ustawienie kanału Adc
void setChannel(ADCChannel channel) const;
// Pobranie wartości pomiaru
uint16_t getValue() const;
// Pobranie wartości pomiaru z oversample
uint16_t getValue(ADCOversample oversample);
// Start konwersji
void runAdc() const;
// Sprawdzenie czy zakończono pomiar
bool complete() const;
private:
// Wartość obliczana
uint32_t value;
};
// Konstruktor
Adc::Adc() : value(0) {
// Uruchomienie przetwornika ADC
ADCSRA |= (1 << ADEN);
}
// Ustawienie preskalera
void Adc::setPrescaler(ADCPrescaler prescaler) const {
// Ustawienie preskalera na 128
ADCSRA = (ADCSRA & ~(( ADPS2 << 1) - 1)) | prescaler;
}
// Ustawienie napięcia referencyjnego
void Adc::setReference(ADCReference reference) {
// Ustawienie napięcia referencyjnego na bez naruszania kanału.
ADMUX = ( ADMUX & ( (1 << REFS0) - 1) ) | ( reference << REFS0);
}
// Ustawienie kanału Adc
void Adc::setChannel(ADCChannel channel) const {
// Ustawienie kanału
ADMUX = ( ADMUX & ~( (MUX4 << 1) - 1 ) ) | channel;
return;
}
// Pobranie wartości pomiaru
uint16_t Adc::getValue() const {
// Uruchomienie pomiaru
runAdc();
// Oczekiwanie na zakończenie konwersji Adc
while(!complete());
// Zwrot wartości po konwersji
return ADC;
}
// Pobranie wartości pomiaru z oversample
uint16_t Adc::getValue(ADCOversample oversample) {
// Zerowanie wartości przed sumowaniem
value = 0;
// Pętla obliczająca sumę oversample
for(uint16_t i = (1 << (oversample << 1)); i > 0; --i) {
// Uruchomienie pomiaru
runAdc();
// Oczekiwanie na zakończenie pomiaru
while(!complete());
// Sumowanie próbek
value += ADC;
}
return ( value >> oversample );
}
// Start konwersji
void Adc::runAdc() const {
// Start konwersji
ADCSRA |= (1 << ADSC);
return;
}
// Sprawdzenie czy zakończono pomiar
bool Adc::complete() const {
// Jeśli ADSC zgaszony, konwersja zakończona
return !(ADCSRA & (1 << ADSC));
}Kompilujemy program i wynik:
Kod: Zaznacz cały
Device: atmega16
Program: 862 bytes (5.3% Full)
(.text + .data + .bootloader)
Data: 70 bytes (6.8% Full)
(.data + .bss + .noinit)Objętość wsadu przyrosła! Kontynujemy jednak....
Przeniesiemy definicje kodu metod w klasie, do odzielnego pliku Adc.cpp.
Zakładam nowy plik w projekcie, zaznaczam Adc.hpp wszystkie definicje metod i przenoszę do Adc.cpp. Oczywiście w Adc.cpp dodam wczytywanie nagłówka Adc.hpp
Wspaniale! Tak oto mamy wydzieloną deklarację klasy od jej definicji metod. Jeszcze stan plików aby się nie pogubić. Tu już podam pełną wersję:
Adc.hpp:
Kod: Zaznacz cały
#include <stdint.h>
#include <avr/io.h>
enum ADCReference {
ADC_EXTERNAL = 0,
ADC_AVCC = 1,
ADC_RESERVED = 2,
ADC_INTERNAL = 3
};
enum ADCPrescaler {
ADC_PRESCALER_0 = 0,
ADC_PRESCALER_2 = 1,
ADC_PRESCALER_4 = 2,
ADC_PRESCALER_8 = 3,
ADC_PRESCALER_16 = 4,
ADC_PRESCALER_32 = 5,
ADC_PRESCALER_64 = 6,
ADC_PRESCALER_128 = 7
};
enum ADCChannel {
ADC_CHANNEL_0 = 0,
ADC_CHANNEL_1 = 1,
ADC_CHANNEL_2 = 2,
ADC_CHANNEL_3 = 3,
ADC_CHANNEL_4 = 4,
ADC_CHANNEL_5 = 5,
ADC_CHANNEL_6 = 6,
ADC_CHANNEL_7 = 7
// Atmega16 ma więcej jeszcze kanałów, ale to tutorial
// i nie ma sensu rozbudowywać kodu.
};
enum ADCOversample {
ADC_OVERSAMPLE_0 = 0,
ADC_OVERSAMPLE_1 = 1,
ADC_OVERSAMPLE_2 = 2,
ADC_OVERSAMPLE_3 = 3,
ADC_OVERSAMPLE_4 = 4,
ADC_OVERSAMPLE_5 = 5,
ADC_OVERSAMPLE_6 = 6
};
class Adc {
public:
// Konstruktor
Adc();
// Ustawienie preskalera
void setPrescaler(ADCPrescaler prescaler) const;
// Ustawienie napięcia referencyjnego
void setReference(ADCReference reference);
// Ustawienie kanału Adc
void setChannel(ADCChannel channel) const;
// Pobranie wartości pomiaru
uint16_t getValue() const;
// Pobranie wartości pomiaru z oversample
uint16_t getValue(ADCOversample oversample);
// Start konwersji
void runAdc() const;
// Sprawdzenie czy zakończono pomiar
bool complete() const;
private:
// Wartość obliczana
uint32_t value;
};Adc.cpp:
Kod: Zaznacz cały
#include "Adc.hpp"
// Konstruktor
Adc::Adc() : value(0) {
// Uruchomienie przetwornika ADC
ADCSRA |= (1 << ADEN);
}
// Ustawienie preskalera
void Adc::setPrescaler(ADCPrescaler prescaler) const {
// Ustawienie preskalera na 128
ADCSRA = (ADCSRA & ~(( ADPS2 << 1) - 1)) | prescaler;
}
// Ustawienie napięcia referencyjnego
void Adc::setReference(ADCReference reference) {
// Ustawienie napięcia referencyjnego na bez naruszania kanału.
ADMUX = ( ADMUX & ( (1 << REFS0) - 1) ) | ( reference << REFS0);
}
// Ustawienie kanału Adc
void Adc::setChannel(ADCChannel channel) const {
// Ustawienie kanału
ADMUX = ( ADMUX & ~( (MUX4 << 1) - 1 ) ) | channel;
return;
}
// Pobranie wartości pomiaru
uint16_t Adc::getValue() const {
// Uruchomienie pomiaru
runAdc();
// Oczekiwanie na zakończenie konwersji Adc
while(!complete());
// Zwrot wartości po konwersji
return ADC;
}
// Pobranie wartości pomiaru z oversample
uint16_t Adc::getValue(ADCOversample oversample) {
// Zerowanie wartości przed sumowaniem
value = 0;
// Pętla obliczająca sumę oversample
for(uint16_t i = (1 << (oversample << 1)); i > 0; --i) {
// Uruchomienie pomiaru
runAdc();
// Oczekiwanie na zakończenie pomiaru
while(!complete());
// Sumowanie próbek
value += ADC;
}
return ( value >> oversample );
}
// Start konwersji
void Adc::runAdc() const {
// Start konwersji
ADCSRA |= (1 << ADSC);
return;
}
// Sprawdzenie czy zakończono pomiar
bool Adc::complete() const {
// Jeśli ADSC zgaszony, konwersja zakończona
return !(ADCSRA & (1 << ADSC));
}main.cpp:
Kod: Zaznacz cały
#include <util/delay.h>
#include <avr/interrupt.h>
#include "Adc.hpp"
#include "mkuart.h"
// Kreowanie obiektu Adc
Adc myAdc = Adc();
int main(void) {
// Uruchomienie przerwań potrzebnych dla usart
sei();
// Deklaracja zmiennej przechowującej wynik
uint16_t value;
// Ustawienie napięcia referencyjnego
myAdc.setReference(ADC_AVCC);
// Ustawienie kanału 0 jako wejściowego
myAdc.setChannel(ADC_CHANNEL_0);
// Ustawienie preskalera
myAdc.setPrescaler(ADC_PRESCALER_64);
// Inicjalizacja USART
USART_Init(50); // to jest baudrate dla 8MHz i 9600bps
while(true) {
// Uruchomienie pomiaru
myAdc.runAdc();
// Wysłanie napisu na usart
uart_puts("Adc 0: 0x");
// Pobranie wartości z przetwornika
value = myAdc.getValue(ADC_OVERSAMPLE_2);
// Wypisanie watości
uart_putint(value, 16);
// Nowa linia na usart
uart_puts("\r\n");
// Opóźnienie przed następnym pomiarem
_delay_ms(1000);
}
}Ostatnim zagadnieniem które w tej części omówię, będzie już poruszony temat extern ”C” czyli łączenia C z C++ i odwrotnie.
Zwróć uwagę że po skompilowaniu projektu tego tutoriala, powstały pliki obiektów:
1. mkuart.o – Mikowa obsługa uart'a: to jest napisane w C
2. main.o – główna część programu: to jest napisane w C++
3. Adc.o – implementacja metod Adc: to jest napisane w C++
Proszę uruchom teraz konsolę (dla Windows to cmd, dla GNU/Linux jakiś terminal) i przejdź do katalogu workspace/nazwa_naszego_projektu/Release.
Uruchom w linii komend polecenie avr-objdump jak jak zobaczysz poniżej. I zwróć uwagę na wyniki.
Polecenie: avr-objdump -t mkuart.o
Kod: Zaznacz cały
mkuart.o: file format elf32-avr
(...)
00000000 g F .text.USART_Init 00000018 USART_Init
00000000 g F .text.__vector_13 00000016 __vector_13
00000000 g F .text.uart_putc 00000022 uart_putc
00000001 O *COM* 00000001 UART_TxHead
00000001 O *COM* 00000001 UART_TxTail
00000010 O *COM* 00000001 UART_TxBuf
00000000 g F .text.uart_puts 00000018 uart_puts
00000000 g F .text.uart_putint 0000003a uart_putint
00000000 *UND* 00000000 __itoa
00000000 g F .text.__vector_12 0000004e __vector_12
00000000 g F .text.uart_getc 0000002a uart_getc
00000001 O *COM* 00000001 UART_RxHead
00000001 O *COM* 00000001 UART_RxTail
00000020 O *COM* 00000001 UART_RxBuf
00000000 g F .text.__vector_11 00000042 __vector_11
00000000 *UND* 00000000 __do_clear_bssPopatrz jak nazywają się metody które zdefiniował Mirek. Ich nazwy włączone w plik binarnym (uart_puts, uart_putint), są podobne do tych jakie ma w pliku źródeł. Tak jest w języku C. On odróżnia metody po nazwach.
Na marginesie, widzisz że vector'y i inne elementy zaczynają się od 2 znaków podkreślnika? Wyjaśnia się teraz dlaczego zalecałem odczepienie się od nazw i makr z dwoma podkreślnikami
Wracamy jednak do C++. Teraz sprawdzimy jak nazywają się metody budowane w C++:
Polecenie: avr-objdump -t Adc.o
Kod: Zaznacz cały
Adc.o: file format elf32-avr
(...)
00000000 g F .text._ZN3AdcC2Ev 0000000e _ZN3AdcC2Ev
00000000 g F .text._ZN3AdcC2Ev 0000000e _ZN3AdcC1Ev
00000000 g F .text._ZNK3Adc12setPrescalerE12ADCPrescaler 0000000a _ZNK3Adc12setPrescalerE12ADCPrescaler
00000000 g F .text._ZN3Adc12setReferenceE12ADCReference 00000012 _ZN3Adc12setReferenceE12ADCReference
00000000 g F .text._ZNK3Adc10setChannelE10ADCChannel 0000000a _ZNK3Adc10setChannelE10ADCChannel
00000000 g F .text._ZNK3Adc8getValueEv 0000000a _ZNK3Adc8getValueEv
00000000 g F .text._ZN3Adc8getValueE13ADCOversample 00000066 _ZN3Adc8getValueE13ADCOversample
00000000 g F .text._ZNK3Adc6runAdcEv 00000004 _ZNK3Adc6runAdcEv
00000000 g F .text._ZNK3Adc8completeEv 0000000e _ZNK3Adc8completeEvOjej! Jakie robaczki. W nich zakodowane są także typy argumentów wywołań funkcji.
Spróbuję je odkodować:
Polecenie: avr-objdump -Ct Adc.o
Kod: Zaznacz cały
Adc.o: file format elf32-avr
(...)
00000000 g F .text._ZN3AdcC2Ev 0000000e Adc::Adc()
00000000 g F .text._ZN3AdcC2Ev 0000000e Adc::Adc()
00000000 g F .text._ZNK3Adc12setPrescalerE12ADCPrescaler 0000000a Adc::setPrescaler(ADCPrescaler) const
00000000 g F .text._ZN3Adc12setReferenceE12ADCReference 00000012 Adc::setReference(ADCReference)
00000000 g F .text._ZNK3Adc10setChannelE10ADCChannel 0000000a Adc::setChannel(ADCChannel) const
00000000 g F .text._ZNK3Adc8getValueEv 0000000a Adc::getValue() const
00000000 g F .text._ZN3Adc8getValueE13ADCOversample 00000066 Adc::getValue(ADCOversample)
00000000 g F .text._ZNK3Adc6runAdcEv 00000004 Adc::runAdc() const
00000000 g F .text._ZNK3Adc8completeEv 0000000e Adc::complete() constPrzełącznik -t to w avr-objdump prezentacja tablicy symboli w pliku a -C to „demanglowanie w drugą stronę” nazw (do pojęcia odsyłam do poprzedniej części tutoriala).
Czy teraz wyjaśnia się dlaczego należy stosować extern ”C” dla kodu w C pisząc w C++? Inaczej linker nie potrafi poskładać obiektów.
Tak więc jeśli mamy funkcję w C++ która ma być wywołana w C, to należy otoczyć ją extern ”C” aby program w C się do niej dostał bo została zakodowana w „stylu C”. Jeśli mamy funkcję w C do której ma się odwołać program w C++, to funkcję tę należy otoczyć extern ”C” żeby C++ wiedziało że „manglowanie nazw” jest w stylu C
Mam nadzieję że proste
Tę część skończyliśmy z kodem który działa, jest ładny, zmodularyzowany ale.. należy popracować nad jego objętością
Ale to w być może następnym odcinku... telenoweli obiektowej
Na koniec jeszcze komplet plików, tym razem załączę archiwum, oczywiście bez Mirkowego mkuart.c oraz mkuart.h. Poprawki w tych plikach były w poprzednim tutorialu.
Następny odcinek będzie po krótkiej przerwie ~ 1 tygodnia. Niestety praca woła
