Mikrokontrolery - Jak zacząć?

... czyli zbiór praktycznej wiedzy dot. mikrokontrolerów.

niedziela, 20 marca 2011

DIY: Dalmierz ultradźwiękowy


Autor: legier
Redakcja: Dondu

Dalmierz ultradźwiękowy jest urządzeniem o stosunkowo prostej konstrukcji i zasadzie działania. W wielkim skrócie jego działanie opiera się na wysłaniu paczki impulsów ultradźwiękowych, a następnie nasłuchiwaniu echa (powrót impulsów po odbiciu od przeszkody).

Czas między nadaniem, a odbiorem pomnożony przez prędkość dźwięku da nam całkowitą odległość przebytą przez impulsy. Trzeba tylko zwrócić uwagę, że jest ona 2 razy większa niż odległość od przeszkody, gdyż fala musiała najpierw do niej dotrzeć, a potem pokonać drogę powrotną.

Z uzyskanych pomiarów wynika, że błąd pokazywanego wyniku wynosi około +/-1cm, a zasięg wynosi od 0,5 metra do kilku metrów.





Dalmierz ultradźwiękowy



Układ nadajnika

Układ nadajnika składa się jedynie z nadajnika oraz kanałów PWM timera1. Jeden pin nadajnika podłączony jest do OC1A, a drugi do OC1B. W przerwaniu od zrównania tego licznika generuję najpierw stan wysoki na OC1A i stan niski na OC1B, a następnie stan niski na OC1A i wysoki na OC1B. Dzięki temu napięcie między szczytowe na nadajniku wynosi 10V. Wszystko jest przeliczone tak, aby uzyskać częstotliwość 40kHz (częstotliwość podana przez producenta nadajnika).


Układ odbiornika

Z odbiornikiem nie jest już tak prosto jak z nadajnikiem. Dlaczego? Oto kilka powodów:
  • Niska sprawność nadajnika
  • Bardzo szybki wzrost pola powierzchni fali, przez co energia przypadająca na m^2 bardzo szybko maleje
  • Niska sprawność odbiornika
  • Zakłócenia
Napięcie jakie odczytałem bezpośrednio na odbiorniku wyniosło kilkadziesiąt mV, co mogłoby być kłopotliwe do wykrycia przez wewnętrzny komparator analogowy. Dlatego obowiązkowe było wzmocnienie tego sygnału. Do tego zadania użyłem wzmacniacza operacyjnego LM358 w układzie odwracającym fazę.

Na 1. WO (Wzmacniaczu Operacyjnym) ustawiłem teoretyczne wzmocnienie około 25, a następnie jego wyjście podłączyłem do wejścia kolejnego wzmacniacza, na którym jest wzmocnienie około 47, więc całkowite wzmocnienie teoretycznie wyniosło 1170.

Czemu teoretyczne? W nocie katalogowej wzmacniacza możemy wyczytać, że w zależności od częstotliwości jego wzmocnienie może się zmieniać. Dla częstotliwości 40kHz i napięcia zasilania 5V ten wzmacniacz charakteryzuje się wzmocnieniem rzędu 20dB, co daje nam praktyczne wzmocnienie około: 400 razy.

Dlaczego minimalny zasięg wynosi zaledwie 0,5m? Wiąże się to z tym, że po wysłaniu paczki impulsów z nadajnika odbiornik od razu je wykrywa co spowodowałoby pokazanie odległości rzędu 2cm. Dlatego trzeba wprowadzić opóźnienie włączenia nasłuchiwania echa, ze względu na drgania, w które na początku został wprowadzony odbiornik.


Wykres zależności wzmocnienia wzmacniacza
od częstotliwości

Wyjście tego układu wzmacniaczy doprowadzam do pinu AIN0 (wejście nieodwracające komparatora), a na wejście AIN1 (wejście odwracające komparatora) podałem napięcie odniesienia wzmacniaczy, tj. Vcc/2 czyli około 2,5V.


Schemat układu odbiornika


Czujnik temperatury

Do odczytu temperatury użyłem popularnego czujnika ds18b20. Więcej o konwersji temperatury możecie poczytać tutaj: Temperatura - pomiar.

Po co ten czujnik? Otóż prędkość dźwięku w powietrzu najbardziej zależy od temperatury. Jeśli różnica temperatury w 2 miejscach gdzie wykonywaliśmy pomiary wyniesie np. około 10 stopni celsjusza, to dalmierz, bez wprowadzonej korekty mógłby pokazać wyniki różniące się między sobą nawet o kilka-kilkanaście centymetrów. Włożenie czujnika nie jest obowiązkowe - jeśli mikrokontroler go nie wykryje, przyjmie za prędkość dźwięku standardową wartość 343,5m/s.

Przybliżony wzór, według którego jest obliczana prędkość dźwięku:







Buzzer

Dodałem buzzer do projektu, aby móc użyć dalmierza jako tzw. "czujnik parkowania". W programie w zależności od uzyskanej odległości generuję z niego dźwięki o rożnym wypełnieniu - im mniejsza odległość tym szybciej pika.


Schemat układu buzzera


Wyświetlacz LCD

Do wyświetlenia odległości użyłem standardowego wyświetlacza LCD 8x2. Podłączenie tego wyświetlacza jest chyba oczywiste i wynika ze schematu. Jedyne co, to skupię się na opisie jego zasilania. Wyświetlacz włączy się po ustawieniu pinu PD4 w rejestrze PORTD na 1.

Regulacja kontrastu odbywa się za pomocą jednego z potencjometrów. Drugi wraz z fotorezystorem służy za analogową zmianę jasności wyświetlacza. Potencjometrem możemy oczywiście regulować tę jasność.


Schemat układu LCD



Schemat i płytka


Schemat dalmierza ultradźwiękowego

Płytka drukowana dalmierza ultradźwiękowego

Do pobrania pliki Eagle: Dalmierz.rar (kopia)



Część programowa


Wyświetlacz LCD

Do obsługi wyświetlacza użyłem darmowej biblioteki udostępnionej przez autora na stronie: radzio.dxp.pl/hd44780

Działa on w trybie 4 bitowym z obsługą flagi zajętości.



Czujnik temperatury DS18B20


Swego czasu pisałem sobie kiedyś bibliotekę obsługi tego czujnika (interfejs 1-wire). Do tego wystarczy tylko kilka funkcji, które opisałem w komentarzach kodu.


//*********************************************************
//////////////////FUNKCJE OBSLUGI DS18B20//////////////////
//*********************************************************
void send_bit(char bit)     //wysłanie jednego bitu na magistralę
{
 clear_1wire;
 _delay_us(10);
 
 if(bit == 1) set_1wire;
 
 _delay_us(60);
 
 set_1wire;
 _delay_us(3);
}
void send_byte(unsigned char value)  //wysłanie jednego bajtu na magsitralę
{
 char i;
 unsigned char sub_value;
 
 for(i = 0; i < 8; i++)
 {
  sub_value = value >> i;
  sub_value = 0x01 & sub_value;
  
  send_bit(sub_value);
 }
}
unsigned char read_bit(void)   //odebranie jednego bitu z magistrali
{
 clear_1wire;
 _delay_us(3);
 set_1wire;
 _delay_us(11);
 
 if(state)
 {
  _delay_us(60);
  return 1;
 }
 else
 {
  _delay_us(60);
  return 0;
 }
}
unsigned char read_byte(void)   //odebranie jednego bajtu z magistrali
{
 unsigned char value = 0;
 char i;
 
 for(i = 0; i < 8; i++)
 {
  if(read_bit()) value |= 0x01 << i;
 }
 return value;
}
char reset(void)      //zresetowanie magistrali
{
 char presence = 0;
 
 clear_1wire;
 _delay_us(500);
 set_1wire;
 _delay_us(60);
 
 if(state == 0) presence = 1;
 
 _delay_us(500);
 
 return presence;
}
inline void skip_rom(void)
{
 send_byte(0xCC);   //skip rom
}
void write_scratchpad(void)
{
 send_byte(0x4E);   //write scratchpad
 send_byte(Th);
 send_byte(Tl);
 send_byte(31 + ((precision - 9) << 5));
}
void read_scratchpad(char *bytes) //
{
 char i;
 send_byte(0xBE);   //read scratchpad
 for(i = 0; i < 2; i++)
 {
  bytes[i] = read_byte();
 }
}
void convert_t(void)
{
 char n;
 send_byte(0x44);   //convert T
 n = 0x01 << (precision - 9);
 while(n > 0)
 {
  _delay_ms(96);
  n--;
 }
}


Pomiar odległości

W przerwaniu od zbocza narastającego na wyjściu komparatora zatrzymuję zegar liczący czas oraz wyłączam to przerwanie aby mikrokontroler nie reagował na zakłócenia mogące pojawić się na odbiorniku.

W przerwaniach od przepełnienia timera0 (preskaler 1) liczę czas między wysłaniem impulsów, a odbiorem echa. Timer ten jest włączany w momencie wysyłania impulsów.


ISR(ANA_COMP_vect)
{
 TCCR0 ^= (1<<CS00); //zatrzymanie zegara odmierzającego czas między nadaniem impulsu a jego odbiorem
 ACSR &= (~(1<<ACIE)); //zatrzymanie przerwań od komparatora
 TCNT2 = 0;    //wyzerowanie licznika timera0
}
ISR(TIMER0_OVF_vect)
{
 time++;     //każde +1 to 16us
}
ISR(TIMER2_OVF_vect)
{
 delay++;        //zmienna odmierzająca upływ czasu
 licznik++;        //j.w używana tylko dla buzzera
 if((PIND & (1<<PD3)) == 0)    //sprawdzenie czy przycisk jest wcisnięty
  button++;
 else button = 0;
 
 if(button == 25)      //jeśli będzie wciśnięty przez ponad 0.1s to ustawiam flagę
  button_flag = 1;
  
 if(button_flag == 1 && button == 0) //jesli flaga będzie ustawiona, a przycisk zwolniony 
 {          //to jest to krótkie przyciśnięcie
  start ^= 0x01;      //krótkim przyciśnięciem włączam/wyłączam pomiar odległości
  button_flag = 0;
 }
  
 if(button == 255)      //jesli przycisk będzie wciśnięty ponad 1s to
 {          //jest to włączenie/wyłączenie buzzera
  buzz ^= 0x01;
  button_flag = 0;
  PORTB |= (1<<PB3);
 }
 
 if(delay == 255)
  PORTB &= ~(1<<PB3);
  
 if(buzz)        //zmiana wypełnienia na wyjściu buzzera
 {          // w zależności od odelgłości
  if(licznik <= 50)
   PORTB |= (1<<PB3);
  else PORTB &= ~(1<<PB3);
  if(licznik == 250 - 10000/dist)
   licznik = 0;
  
 }
}
ISR(TIMER1_COMPA_vect)
{
 count++;     //licznik wysłanych impulsów
 if(count == no_impulse)  //po wysłaniu liczby impulsów równej no_impulse -> wyłaczenie zegara
 {
  TCCR1B ^= (1<<CS10); //zatrzymanie timera1 -> zatrzymanie nadajnika
  TCNT1 = 0;    //wyzerowanie stanu licznika timera1
  count = 0;    //wyzerowanie liczby wysłanych impulsów
 }
}


Pętla główna i funkcje dokonujące pomiarów


#define no_impulse 5

volatile long int time = 0;
volatile char volatile count = 0;
volatile unsigned char delay= 0, licznik = 0;

volatile char flag, button_flag = 0, start = 0, buzz = 0;
volatile unsigned int button = 0;
volatile int dist = 0;

void sprint(int dist, char *distance, char size);
long int get_dist(void);
int speed(void);
void init_ds18b20(void);


//******************************************************

int main(void)
{
 DDRB = (1 <<PB1) | (1<<PB2) | (1<< PB3); //ustawienie pinów OC1A i OC1B jako wyjścia, PB3 => tranzystor buzzera
 //timera 1 w tryb szybkiego PWM ze zliczaniem do wartości w rejestrze ICR1, preskaler 1,
 // stan wysoki na OCR1A oraz stan niski na OCR1B przy zrównaniu 
 TCCR1A = (1<<WGM11) | (1<<COM1A1) | (1<<COM1B1) | (1<<COM1B0);//
 TCCR1B = (1<<WGM13) | (1<<WGM12);     // preskaler = 1
 
 TIMSK = (1<<TOIE0) | (1<<TOIE2) | (1<<OCIE1A); //przerwania od przepełnienia licznika timera0, timera 2 oraz zrównania T1A
 TCCR2 = (1<<CS21) | (1<<CS22);      //włączenie timera2 z preskalerem 256;
 
 ACSR |= (1<<ACIS1) | (1<<ACIS0);     //przerwanie od komparatora - zbocze narastające
 ICR1 = 399;           //ustawienie wartości końcowej zliczania (400 tactów procesora)
 OCR1A = 199;          //200 to czas trwania połowy okresu 200 * 1/16 000 000 s = 12.5us -> 
 OCR1B = 199;          //-> okres 25us -> f = 40kHz

 sei();            //włączenie gloablnej obsługi przerwań
 
 DDRD |= (1<<PD4);         //ustawienie odpowiednich wejść/wyjść dla tranzystorów oraz przycisku
 PORTD |= (1<<PD4) | (1<<PD3);      
 
 char distance[5];
 
 init_ds18b20();
 
 LCD_Initalize();
 LCD_Clear();
 while(1)
 {
  if(start && delay == 255)
  {
   dist = get_dist();
   LCD_GoTo(0,0);
   sprint(dist, distance, 5);
   LCD_WriteText(distance);
   LCD_WriteText(" cm");
  }
  
 }
 cli();
 return 0;
}


//******************************************************


void sprint(int dist, char *distance, char size)  //funkcja zamieniająca liczbę na łańcuch znakowy
{
 char i = 0;
 while(i < size - 1)         //wypełnienie tablicy spacjami
 {
  distance[i] = 32;
  i++;
 }
 
 distance[size - 1] = 0;        //wyzerowanie elementu na którym ma się zakońzyć wyświetlanie

 for(i = 0; dist > 0 && i < size - 1; i++)
 {
  distance[size - 2 - i] = (dist % 10) + 48;  //przepisanie do tablicy ostatniej cyfry liczby w kodzie ASCII
  dist /= 10;          //podzielenie liczby przez 10 => odcięcie ostatniej cyfry
 }
}

long int get_dist(void)
{
 time = 0;
 TCCR0 |= (1<<CS00);   //włączenie timera0 odmierzającego czas między nadaniem a odbiorem impulsów
 TCCR1B |= (1<<CS10);   //włączenie timera1 nadającego impulsy ultradźwiękowe
 
 while(time < 160){}    //oczekiwanie w pętli na włączenie nasłuchiwania echa
 ACSR |= (1<<ACIE);    //włączenie przerwań od komparatora
 
 
 while(TCCR0 != 0)     //oczekiwanie na nadejście sygnału zwrotnego - jesli nie nadejdzie po sekundzie zwraca -1
 {
  if(time > 32000)
  {
   TCCR0 = 0;
   return 0;
  }
 }
 return time * speed() / (6250.0 * 2); //obliczenie dystansu
 //return time * 2 * 34 / 125;  //wynik w cm
}

void init_ds18b20(void)
{
 DDRC |= (1<<PC1);        //ustawienie pinu PC1 jako wyjście
             // do komunikacji 1-wire z czujnikiem temperatury
 if(reset())          //zresetowanie magistrali i sprawdzenie czy jest na niej czujnik
 {
  skip_rom();         //jesli czujnik jest,  pomijamy sprawdzenie adresu ROM 
  write_scratchpad();  //czujnik jest tylko jeden, więc sprawdzenie byłoby niepotrzebne
  flag = 1;         //zapisanie w scratchapdzie czujnika temepratur alarmowych
 }            //i precyzji wykonywania konwersji temperatury
 else flag = 0;
}
int speed(void)
{ 
 if(flag)          //jeśli czujnik był obecny flaga jest ustawiona na 1
 {
  char temp[2];        //deklaracja tablicy przechowującej temepraturę
  int temperature;       //zmienna pod którą będzie temperatura
  reset();         //****************************************
  skip_rom();         //procedura konwersji temperatury
  convert_t();        //****************************************
  
  reset();         //****************************************
  skip_rom();         //procedura odebrania bajtów temperatury
  read_scratchpad(temp);      //****************************************
  LCD_Clear();
  temperature = (temp[0] >> 4) | ((temp[1] & 0xF) << 4); //przeliczenie temperatury z pominięciem części ułamkowej
  LCD_GoTo(3,1);
  char t[3];
  sprint(temperature, t, 3);
  //dtostrf(temperature, 2, 1, t);
  LCD_WriteText(t);
  LCD_WriteText("C");

  return (3315 + temperature * 6);   //obliczenie dziesięciokrotności prędkości dźwięku w danej temperaturze  
 }
 else return 3435;        //jeżeli czujnik nie został wykryty zwracana jest prędkość dźwięku
}             //w temperaturze 20st C

Do taktowania ATmega8 użyłem zewnętrznego kwarcu 16MHz. Dlaczego? W artykule Czas - odmierzanie można przeczytać, że wewnętrzne oscylatory mikrokontrolera są dość słabej jakości (+- 2%) co skutkowałoby w najgorszym wypadku (przy odległości 3m) błędnym wskazaniem aż o 10cm! Przy zewnętrznym kwarcu uzyskujemy zdecydowanie lepszy pomiar czasu co skutkuje mniejszym błędem zmierzonej odległości.


Pliki źródłowe

Do pobrania komplet plików: Dalmierz-program-pliki-zrodlowe.zip (kopia)


Oceń artykuł.
Wasze opinie są dla nas ważne, gdyż pozwalają dopracować poszczególne artykuły.
Pozdrawiamy, Autorzy
Ten artykuł oceniam na:

2 komentarze:

  1. Witam, chciał bym się zapytać czy układ na wykonanym pcb działa stabilnie ? Zbudowałem taki dalmierz ale na zestawie uruchomieniowym firmy ATNEL (lekkie zmiany wyprowadzeń pod atmega32, możliwych różnic timerów oraz komparatorów jeszcze nie zbadałem), układ pomiarowy jest na płytce uniwersalnej z kondensatorem kompensującym przy zasilaniu wzmacniacza, wszystko na krótkich kabelkach. Ciągle mierzy 44cm niezależnie od przeszkody, dotknięcie płytki od spodu powoduje zmianę wartości na losową (więc pewnie tu mój problem). Można zapytać skąd czujniki ultradźwiękowe (moje to wymontowane z chińskiego czujnika do arduino) ?

    OdpowiedzUsuń
    Odpowiedzi
    1. Prawidłowo zmontowany układ dalmierza ultradźwiękowego działa bardzo stabilnie. Albo masz gdzieś błąd, albo ci się sprzęga odbiornik z nadajnikiem. Czasami dochodzi do sprzężenia mechanicznego.

      Usuń

Działy
Działy dodatkowe
Inne
O blogu




Dzisiaj
--> za darmo!!! <--
1. USBasp
2. microBOARD M8


Napisz artykuł
--> i wygraj nagrodę. <--


Co nowego na blogu?
Śledź naszego Facebook-a



Co nowego na blogu?
Śledź nas na Google+

/* 20140911 Wyłączona prawa kolumna */
  • 00

    dni

  • 00

    godzin

  • :
  • 00

    minut

  • :
  • 00

    sekund

Nie czekaj do ostatniego dnia!
Jakość opisu projektu także jest istotna (pkt 9.2 regulaminu).

Sponsorzy:

Zapamiętaj ten artykuł w moim prywatnym spisie treści.