Skocz do zawartości

[C] Linefollower problem z ADC


greebqmaster

Pomocna odpowiedź

Witam,

Postanowiłem zrobić linefollowera wg przepisu z waszej strony:

Linefollower - kurs

Wszystko IDEALNIE TAK SAMO podłączyłem, wgrałem kod bascomowy i niestety nie robot nie banglał ;_; Zabrałem się zatem do napisania własnego kodu w C, ponieważ w tym lepiej mi idzie, w celu sprawdzenia czy w ogóle hardware działa.

Posprawdzałem miernikiem czy procek, cny70 i l293 działają i wszytko było super. Procek przez l293 dawał napięcie i silniczki jeździły na sztywnym kodzie. Posprawdzałem napięcie na czujnikach i zmieniało się w zależności biały/czarny kolor, czy zakrycie/odkrycie czujnika.

#include <avr/io.h>		// zalaczenie biblioteki obslugi uC AVR
#include <util/delay.h> // zalaczenie biblioteki opoznien
#include "hd44780.h"	// zalaczenie biblioteki obslugi ekranu LCD
#define cz_L PC5 // czujniki
#define cz_S PC4
#define cz_P PC3

int granica=300;

volatile uint8_t czl;//zmienna do pomiaru ADC z czujnika lewego
volatile uint8_t czs;//zmienna do pomiaru ADC z czujnika srodkowego
volatile uint8_t czp;//zmienna do pomiaru ADC z czujnika prawego

int main(void)
{
ADCSRA = (1<<ADEN) //włączenie ADC  
           |(1<<ADFR) //włączenie trybu Free run  
           |(1<<ADSC); //rozpoczęcie konwersji  

ADMUX  =  (1<<ADLAR)     //Wyrównanie wyniku do lewej  
            |(1<<REFS0)            //VCC jako napięcie referencyjne  
            |(0<<MUX0);   //Wybór wejścia początkowego czyli PC0

DDRC=0x00;  // ustawienie wszystkich bitów portu C na wejścia
DDRB=0xFF;  // ustawienie wszystkich bitow portu B na wyjscia
DDRD=0xFF;  // ustawienie wszystkich bitów portu D na wyjścia


for(;;) // petla glowna
{
	for(int i=0;i<3;i++) // petla zczytywania wyników
	{

		if(i==0)
		{
			ADMUX = 0;
			ADMUX  =   (1<<ADLAR) |(1<<REFS0)
						|(1<<MUX2) | (1<<MUX0);
			czl=ADCH;
		}

		if(i==1)
		{
			ADMUX = 0;
			ADMUX  =   (1<<ADLAR) |(1<<REFS0)
						|(1<<MUX2);
			czs=ADCH;
		}

		if(i==2)
		{
			ADMUX = 0;
			ADMUX  =   (1<<ADLAR) |(1<<REFS0)
						|(1<<MUX1) | (1<<MUX0);
			czp=ADCH;
		}
	}

	//ustawianie silników w zależności od wyników
	if(czs < granica) // srodek
	{
		PORTB=0x06;
		PORTD=0x02;
	}

	else if (czl < granica) // prawy
	{
		PORTB=0x06;
		PORTD=0x0A;

	}

	else if (czp < granica) // lewy
	{	
		PORTB=0x06;
		PORTD=0x08;
	}
} 

}

Oczywiście nie działa, bo po co w takim razie zakładałbym ten temat.. Jakiej bym granicy nie ustawił, to działa mi tylko lewy silnik. Doszedłem do wniosku, że to pewnie dlatego, że zacina mi się wszystko na prawym czujniku i coś się nie zeruje, ale przecież MUX'y są zerowane za każdym razem więc ręce mi opadają.. Poszperałem w internetach i w ogóle tam gdzie się dało chcąc uzyskać wiedzę, która pomogłaby mi w problemie, niestety bez skutku...

Czy ktoś mógłby mi pomóc w znalezieniu ewentualnego błędu? Dziękuję za wszelkie odpowiedzi i pozdrawiam.

greebqmaster

PS różnicą w schemacie może być pomieszane podłączenie czujników - lewy PC5, środek PC4, prawy PC3.

Schemat:

__________

Komentarz dodany przez: Treker

Poprawiłem znaczniki i grafiki.

Proszę zapoznać się z regulaminem punkt 2)i.

Link do komentarza
Share on other sites

Pominąłeś fakt, że konwersja ADC trwa ileś czasu. Stąd Twoje czytanie wyników z ADCH jest błędne, ponieważ w najlepszym wypadku odczytasz trzy razy tą samą wartość (albo raz prawidłową z ostatniego pomiaru, a dwa razy coś losowego - zależy jak interpretuje to mikrokontroler).

Po włączeniu ADC a przed odczytaniem wyniku musisz poczekać na koniec konwersji. Można to robić timerami itd, albo po prostu czytać odpowiedni bit i czekać tak długo aż konwersja się nie skończy.

Kod z wymienionego bloga:

while(ADCSRA & (1<<ADSC)); //czeka na zakończenie konwersji  

PS. Reszty Twojego kodu nie przeglądałem..., ale gdyby coś nadal nie działało polecam jeszcze raz dokładnie przejrzeć wszystkie tutoriale o ADC dla AVR na wymienionej stronie...

Link do komentarza
Share on other sites

Przetwornik ADC nie jest prostym okienkiem, przez które w postaci liczby widzisz napięcie na wybranym wejściu. To znaczy trochę tak, ale nie do końca. Nie wnikałem w część kodu inicjującą pracę przetwornika - ta pewnie jest dobra jeśli w ogóle dostajesz jakieś wyniki. Moim zdaniem powinieneś zastanowić się chwilę jak taki ADC działa. Otóż po pierwsze jego wejście musi być podłączone do odpowiedniego pinu procesora - to jasne. Po drugie przetwornik musi sprawdzić i zapamiętać w specjalnym kondensatorze (mówimy "spróbkować") napięcie na tym wejściu, po trzecie przetworzyć to napięcie na postać cyfrową i wreszcie po czwarte wysłać wynik do rejestrów ADCH i ADCL. Czy dajesz mu taką sznasę i czy Twój program podąża za cyklem pracy przetwornika? Chyba nie. Wysyłasz jakiś numer wejścia do MUX a chwilę później odczytujesz coś z rejestru ADCH. Czy spodziewasz się, że jest tam coś sensownego? Owszem, tam będzie wynik którejś poprzedniej konwersji ale której? Przecież ADC w swoim rytmie (pracuje w trybie Free Run) będzie pobierał adresy z MUX, próbkował, przetwarzał i wysłał wyniki. Nie obchodzi go czy wynik odczytałeś a zawartość MUX jest ważna wyłącznie tuż przed startem przetwarzania, czyli w chwili próbkowania napięcia. Musisz się z tym zsynchronizować. Przetwornik ma taki specjalny bit, którym sygnalizuje zakończenie konwersji i wpisanie wyniku do rejestru wyjściowego. Jeśli poczekasz na jedynkę, odczytasz wynik i wpiszesz nowy adres to.. nie będzie tak prosto. ADC w trybie samobieżnym rozpocznie nową konwersję (z zastanymi ustawieniami MUX i REFS) jednocześnie z ustawieniem bitu gotowości wyniku. Zanim więc zdążysz wpisań nowy numer wejścia, przetwornik spróbkuje napięcie wg dotychczasowych danych z MUX. Prawidłowa praca w tym trybie jest oczywiście możliwa ale musisz to sobie rozpisać na kartce, bo wyniki będą opóźnione o dwa przestawienia MUXa. Ja często tak robię i to działa.

Jeśli nie chcesz się tak motać, możesz pracować w trybie single conversion. To najprostszy rodzaj zabawy, w której to najpierw ustawiasz MUX, startujesz przetwornik na jedną konwersję, czekasz na gotowość ADC i odczytujesz wynik. Wybieraj. Moim zdaniem pełna szybkość przetwarzania nie jest Ci wcale potrzebna a tryb pojedynczy jest dużo prostszy do opanowania.

EDIT: Mirek, byłeś szybszy 🙂

  • Lubię! 1
Link do komentarza
Share on other sites

5 min przed Twoim postem doszedłem do wniosku, że skorzystanie z pojedyńczych konwersji chyba będzie szybsze, do czego przekonał mnie post Mirka.

Daję poniżej nowy kod. Po każdym ustawieniu MUX'a robię czytaj-czekaj z konwersją i dopiero potem przypisuję wynik do zmiennej. Niestety, dalej to samo - cały czas jest uruchomiony tylko lewy silnik 🙁

#include <avr/io.h>		// zalaczenie biblioteki obslugi uC AVR
#include <util/delay.h> // zalaczenie biblioteki opoznien

int granica=300;

volatile uint8_t czl;//zmienna do pomiaru ADC z czujnika lewego
volatile uint8_t czs;//zmienna do pomiaru ADC z czujnika srodkowego
volatile uint8_t czp;//zmienna do pomiaru ADC z czujnika prawego

int main(void)
{
ADCSRA = (1<<ADEN) //włączenie ADC  
		|(1<<ADPS2);  //prescaler 16

ADMUX  =  (1<<ADLAR)     //Wyrównanie wyniku do lewej  
            |(1<<REFS0)            //VCC jako napięcie referencyjne  
            |(0<<MUX0);   //Wybór wejścia początkowego czyli PC0


DDRC=0x00;  // ustawienie wszystkich bitów portu C na wejścia
DDRB=0xFF;  // ustawienie wszystkich bitow portu B na wyjscia
DDRD=0xFF;  // ustawienie wszystkich bitów portu D na wyjścia


for(;;) // petla glowna
{
	for(int i=0;i<3;i++) // petla zczytywania wyników
	{

		if(i==0)
		{
			ADMUX = 0;
			ADMUX  =   (1<<ADLAR) |(1<<REFS0)
						|(1<<MUX2) | (1<<MUX0);
			ADCSRA |= (1<<ADSC); //ADSC: uruchomienie pojedynczej konwersji  
			while(ADCSRA & (1<<ADSC)); //czeka na zakończenie konwersji
			czl=ADC;
		}

		if(i==1)
		{
			ADMUX = 0;
			ADMUX  =   (1<<ADLAR) |(1<<REFS0)
						|(1<<MUX2);
			ADCSRA |= (1<<ADSC); //ADSC: uruchomienie pojedynczej konwersji  
			while(ADCSRA & (1<<ADSC)); //czeka na zakończenie konwersji
			czs=ADC;
		}

		if(i==2)
		{
			ADMUX = 0;
			ADMUX  =   (1<<ADLAR) |(1<<REFS0)
						|(1<<MUX1) | (1<<MUX0);
			ADCSRA |= (1<<ADSC); //ADSC: uruchomienie pojedynczej konwersji  
			while(ADCSRA & (1<<ADSC)); //czeka na zakończenie konwersji
			czp=ADC;
		}
	}

	//ustawianie silników w zależności od wyników
	if(czs < granica) // srodek
	{
		PORTB=0x06;
		PORTD=0x02;
	}

	else if (czl < granica) // prawy
	{
		PORTB=0x06;
		PORTD=0x0A;

	}

	else if (czp < granica) // lewy
	{	
		PORTB=0x06;
		PORTD=0x08;
	}
} 

}
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Może zamiast wysyłać nam co chwila nowy, na szybko zmieniony kod usiądź spokojnie i przemyśl sprawę, OK?

Po pierwsze całe for i zagnieżdżone w nim if-y stały się niepotrzebne.

Po drugie robisz dosuwanie wyniku do lewej (ADLAR) a czytasz całe 16 bitów więc wynik będzie raczej duży.

Po co zerujesz MUX? Każdorazowa zamiana napięcia odniesienia wymaga odczekania pewnego czasu na stabilizację i -o ile pamiętam, jednej konwersji na pusto.

Czy zrobiłeś test z protezą przetwornika tzn. czy ręczne wpisywanie spodziewanych wyników daje w tym samym kodzie prawidłowe reakcje silników? Czy możesz wypisać na czymś trzy wyniki z trzech wejść? Zanim wyślesz kod, sam zastanów się co w nim jest nie tak.

Link do komentarza
Share on other sites

Witam po raz ostatni w tym temacie. Musiałem wtedy zostawić na dłuższą chwilę tego robota z powodu nawału innej pracy, dlatego brak odpowiedzi z mojej strony.

Robot działa! Zastosowałem Wasze porady i udało mi się napisać poprawny kod. Dodatkowo posiedziałem ostatnio sporo nad tym linefollowerem i dowiedziałem się, że m.in. spaliłem jeden z czujników CNY70 + trochę uprościłem kod.

Podaję go poniżej:

#include <avr/io.h>		// zalaczenie biblioteki obslugi uC AVR
#include <util/delay.h> // zalaczenie biblioteki opoznien

int granica=180;

volatile uint8_t czl;//zmienna do pomiaru ADC z czujnika lewego
volatile uint8_t czs;//zmienna do pomiaru ADC z czujnika srodkowego
volatile uint8_t czp;//zmienna do pomiaru ADC z czujnika prawego



int main(void)
{		

ADCSRA = (1<<ADEN) //włączenie ADC  
		|(1<<ADPS2);  //prescaler 16
DDRC=0x00;  // ustawienie wszystkich bitów portu C na wejścia
DDRB=0xFF;  // ustawienie wszystkich bitow portu B na wyjscia
DDRD=0xFF;  // ustawienie wszystkich bitów portu D na wyjścia
PORTB=0x06; // ustawienie odpowiednich wejść portu B na "1" (porty EN w L293D)
PORTD=0x00; // wyzerowanie portu D

for(;;) // petla glowna
{
	ADMUX = 0;		
	ADMUX  = (1<<REFS0)            //VCC jako napięcie referencyjne  
            |(1<<MUX2);   //Wybór wejścia początkowego czyli PC0
	ADCSRA |= (1<<ADSC); //ADSC: uruchomienie pojedynczej konwersji  
	while(ADCSRA & (1<<ADSC)); //czeka na zakończenie konwersji
	czs=ADC; 				// Przypisanie wartosci z przetwornika do zmiennej czs

	ADMUX = 0;		
	ADMUX  = (1<<REFS0)            //VCC jako napięcie referencyjne  
			|(1<<MUX1) | (1<<MUX0);  //Wybór wejścia początkowego czyli PC1
	ADCSRA |= (1<<ADSC); //ADSC: uruchomienie pojedynczej konwersji  
	while(ADCSRA & (1<<ADSC)); //czeka na zakończenie konwersji
	czp=ADC;				// Przypisanie wartosci z przetwornika do zmiennej czp

	ADMUX = 0;		
	ADMUX  = (1<<REFS0)            //VCC jako napięcie referencyjne  
			|(1<<MUX2) | (1<<MUX0);   //Wybór wejścia początkowego czyli PC2
	ADCSRA |= (1<<ADSC); //ADSC: uruchomienie pojedynczej konwersji  
	while(ADCSRA & (1<<ADSC)); //czeka na zakończenie konwersji
	czl=ADC;				// Przypisanie wartosci z przetwornika do zmiennej czl

if (czs>granica)
{
	PORTD=(PORTD | 0x06); // Jezeli wartosc z ADC dla czujnika srodkowego przekroczy granice, dzialaja obydwa silniki
}
else
{
	if(czl>granica) PORTD=(PORTD | 0x04); // Wlaczenie prawego silnika dla odczytu z lewego czujnika
	else PORTD=(PORTD & (~(0x04))); // Wylaczenie prawego silnika

	if(czp>granica) PORTD=(PORTD | 0x02); // Wlaczenie lewego silnika dla odczytu z prawego czujnika
	else PORTD=(PORTD & (~(0x02))); // Wylaczenie lewego silnika
}
} //for
} //main 

Dla zainteresowanych - efekt mojej pracy:

- baterie padły i musiałem ciągnąć z usb

- na górnym fragmencie zamiennik dla kółka (bateria w spinaczu do prania 😃 ) klinował się między dwoma kartkami

Dziękuję za udzieloną pomoc i pozdrawiam,
greebqmaster

Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.