Skocz do zawartości
Komentator

Zdobądź Proxxona FBS 240/E - #4 - Uniwersalna funkcja

Pomocna odpowiedź

html_mig_img
Zgodnie z rozpoczętym cyklem pora na kolejne zadanie. Stawka jest wysoka, bo do rozdania mam jeszcze 9  Proxxonów FBS 240/E.Dziś rusza następne zadanie, czyli okazja do zdobycia kolejnej szlifierko-wiertarki!

UWAGA, to tylko wstęp! Dalsza część artykułu dostępna jest na blogu.

Przeczytaj całość »

Poniżej znajdują się komentarze powiązane z tym wpisem.

Udostępnij ten post


Link to post
Share on other sites

Klasy w C++ (Arduino) też wchodzą w grę? 😉

Udostępnij ten post


Link to post
Share on other sites

Bobby, pytanie, czy będzie to zrozumiałe i przydatne dla początkujących?

Udostępnij ten post


Link to post
Share on other sites

Jednocześnie przypominam wszystkim o trwającej ankiecie, która wyłoni zwycięzcę poprzedniego zadania!

Udostępnij ten post


Link to post
Share on other sites

Treker, myślę, że tak. Generalnie wszystkie biblioteki arduino praktycznie są napisane typowo jako kasy - servo, stepper, serial. Napisałem ostatnio np. klasę do obsługi średniej kroczącej (zero sprzętu), wtedy po zaincludowaniu pliku .h kod wygląda tak:

deklaracja:

SMA average(L); //konstruujemy obiekt klasy SMA, parametrem konstruktora jest ilość próbek średniej

używanie:

average.add(probka); //dodajemy próbkę do bufora, reszta zostaje automatycznie przesunięta
zmienna = average.get(); //odczytujemy średnią z ostatnich L próbek

Sama średnia obsługuje na razie tylko twory mojej innej klasy, Cartesian, która to przechowuje współrzędne w układzie kartezjańskim, ale nic nie stoi na przeszkodzie, by oprzeć klasę o szablon - wtedy będzie mogła liczyć średnią z dowolnych zmiennych. Właśnie obiektowość w Arduino jest świetna - tak mi teraz przemknęło przez myśl, by napisać może jakiś odcinek kursu arduino o klasach właśnie. Pozwala to na nieporównywalnie prostszą względem czystego C skalowalność kody, staje się on też prostszy w wykorzystaniu na inne platformy (jeśli tylko znajdzie się dla nich kompilator C++, na przykład STMy).

Udostępnij ten post


Link to post
Share on other sites

Chciałbym Wam przedstawić funkcję do obsługi przycisku (ang. switch) w przerwaniu INT0 na pinie PD2 portu D mikrokontrolera AVR firmy ATMEL ATmega8. Kod źródłowy napisany jest w języku C dla 8-bitowych mikrokontrolerów AVR. Podstawowe informacje o tym mikrokontrolerze można znaleźć w jego nocie katalogowej (ang. datasheet) wpisując w przeglądarkę internetową frazę "ATmega8 datasheet".

 

Takie rozwiązanie pozwala na użycie tegoż przycisku jako przycisk ON/OFF naszego robota lub innego urządzenia 🙂

 

Przedstawię teraz konfigurację przycisku i zmiennej globalnej do obsługi przerwania:

// Definiujemy przycisk, który znajduje się na pinie 2 portu D, czyli PD2.
#define Przycisk_PIN (1<<PD2)
#define Przycisk (PIND & Przycisk_PIN)

volatile int jazda = 0;

 

Teraz przechodzimy do "Głównej funkcji programu" i ustawiamy pin PD2 jako wejście, ponieważ będzie on odbierał sygnały, które "wygeneruje" przycisk (stan bliski VCC (+5V), czyli logiczna jedynka (1) lub stany bliskie GND, czyli logiczne zero (0)). Musimy też pamiętać, że aby korzystać z przerwań musimy włączyć globalne przerwania --> sei();

//--- Główna funkcja programu ---
int main(void) {
DDRD &= ~Przycisk_PIN;		// Wejcie - Przycisk
PORTD |= Przycisk_PIN;		// Pull-Up wewnętrzny (rezystor podciągający do VCC, dlatego nie trzeba stosować fizycznego rezystora podciągającego)

sei();						// Włączenie globalnych przerwań
}

 

Przejdźmy teraz do obsługi przerwania INT0 na pinie PD2:

//--- Obsługa przerwania z INT0 ---
ISR(INT0_vect) {

GICR &= ~(1<<INT0);	// Wyłączamy obsługę przerwania na PD2 - drgania styków spowodowałyby wielokrotne jego wywołanie

jazda ^= 1;			// Zmieniamy stan flagi

GICR |= (1<<INT0);	// Włączamy z powrotem obsługę przerwania
}

 

Jeszcze napiszemy uproszczony kod źródłowy do obsługi tego przerwania:

// --- Pętla nieskończona programu ---
while(1) {

		if(jazda) {     	// Sprawdzamy czy "jazda" jest 1-ką lub 0-em
		_delay_ms(50);		// Eliminacja drgania styków
		if(jazda) {
			LED1_ON;     	// Dioda LED świeci
		}else{
			LED1_OFF;     	// Dioda LED nie świeci
		}
		}
}

 

Drgań styków możemy się jeszcze pozbyć sprzętowo, przez realizację podłączenia przycisku do mikrokontrolera. Mianowicie przez użycie odpowiednich rezystorów jak i kondensatorów - na Forum było to omawiane wiele razy (a nawet w #3 odsłonie tego konkursu).

 

Mam nadzieję, że ta prosta funkcja do obsługi przycisku w przerwaniu jako przycisk ON/OFF pozwoli wielu początkującym okiełznanie swojego robota 🙂

 

Pozdrawiam, Adam (aixI).

Udostępnij ten post


Link to post
Share on other sites

aixI, delay w przerwaniu? "Trochę" to się mija z ideą przerwania...

Udostępnij ten post


Link to post
Share on other sites

Tak, wiem. Ale wżywamy tego przycisku jak włącznik ON/OFF i tyle - nic więcej ma nie robić, tylko spełniać swoją funkcję, a taki drobny "delay" przy tym nie przeszkadza.

Udostępnij ten post


Link to post
Share on other sites

Nawet jeśli, to nie tłumaczy to użycia delay'a w przerwaniu. To jest po prostu zły nawyk i nie należy go powielać.

Mam nadzieję, że to zmienisz. Ja daję autodelet'y na swoje posty, bo szkoda tutaj śmiecić.

Udostępnij ten post


Link to post
Share on other sites

W sumie jakby się tak dobrze zastanowić, to jeżeli mają to być jakieś dobre, pomocne funkcje, które mają nauczać początkujących i wpajać im od samego początku dobre nawyki, to jest to prawdą, że jednak nie powinno się stosować "delay'a" w przerwaniu - sam wiem, że się go tam nie powinno stosować, ale niestety nie usprawiedliwia mnie to. Poprawiłem i ustawiłem autodel.

Pozdrawiam,

Udostępnij ten post


Link to post
Share on other sites

Nie chcę być namolny, ale usuwając tego delay'a i nie zastępując go inną metodą eliminacji drgań styków (a są takie 😉 ), problemu wcale się do końca nie pozbywasz. Owszem, kod staje się nieco poprawniejszy, ale inny problem powraca.

Udostępnij ten post


Link to post
Share on other sites

Bobby, ok w takim razie dopuszczam również takie rozwiązania.

aixI, zwracam też uwagę, że to rozwiązanie nie do końca jest funkcją. Jest to kilka "rozrzuconych" kawałków kodu. Oczywiście rozumiem, że tutaj inaczej ciężko byłoby to rozwiązać, ale proszę, aby wszyscy pamiętali, że należy ograniczać takie rozdrabnianie.

aixI, piotreks-89, nie wygaszajcie postów, niech zostaną widoczne dla wszystkich.

Udostępnij ten post


Link to post
Share on other sites
int8_t uchyb(uint8_t pomiary[], uint8_t czujniki, uint8_t prog)
{
static int8_t wagi[8];						//tablica zawierajaca wagi poszczególnych czujników
if (wagi[0] == 0)							//wyliczenie wag czujników przy pierwszym wywołaniu funkcji
{
	for (uint8_t i=0; i<(czujniki/2); i++)
	{
		wagi[i] = 1 << ((czujniki / 2) - i);
		wagi[czujniki-i-1] = -wagi[i];
	}
}

int8_t uchyb = 0;
uint8_t ile = 0;
for (uint8_t i=0; i<czujniki; i++)
{
	if (pomiary[i] < prog)					//kierunek znaku nierówności zależy od reakcji czujnika na kolor podłoża
	{										// < dla sytuacji gdy linia daje niskie wartości z ADC
		uchyb += wagi[i];					// > dla sytuacji gdy linia daje wysokie wartości z ADC
		ile++;
	}
}
if (ile>1)
	uchyb /= ile;
return uchyb;
}

Funkcja zwraca wartość uchybu dla regulatora P(ID) na podstawie tablicy z wartościami z przetwornika ADC.

Argumentu funkcji:

1. nazwa tablicy z pomiarami z ADC

2. liczba czujników

4. próg rozpoznawania kolorów

przyjąłem 2 założenia: 8-bitowy odczyt ADC i max. 8 czujników

Podaję też cały program wykorzystujący tę funkcję, a także z kodem do generowania tablicy z pomiarami ADC w przerwaniach.

Poniższy kod jest przeznaczony dla mikrokontrolerów ATmega innych niż: ATmega8, ATmega8A, ATmega128, ATmega406. Z oczywistych względów nie będzie działał tez na mikrokontrolerach, które nie są wyposażone w ADC.

#include <avr/io.h>
#include <avr/interrupt.h>

#define SENSORS	8		//liczba czujników [max. 8]

volatile uint8_t reading[SENSORS];   //tablica przechowująca odczyty ADC

void Init_ADC();
uint8_t potega(uint8_t podstawa, uint8_t wykladnik);
int8_t uchyb(uint8_t pomiary[], uint8_t czujniki, uint8_t prog);

int main(void)
{
Init_ADC();
int8_t error;
while (1)
{
	error = uchyb(reading, SENSORS, 128);
}
}

void Init_ADC()
{
ADMUX |= (1<<REFS0);				//VCC jako napięcie odniesienia z pinem AREF podłączonym do GND przez kondensator
ADMUX |= (1<<ADLAR);				//przesunięcie wyniku do lewej -> 8-bitowy odczyt wyniku
ADCSRA |= (1<<ADPS2) | (1<<ADPS1);	//prescaler = 64 -> przy 16MHz daje to 250kHz dla ADC
ADCSRA |= (1<<ADIE);				//włączenie przerwań od ADC
ADCSRA |= (1<<ADEN);				//włączenie przetwornika ADC
ADCSRA |= (1<<ADSC);				//start konwersji
sei();								//globalne zezwolenie na przerwania
}

ISR(ADC_vect)
{
static uint8_t i;					//
reading[i] = ADCH;
i++;
i%=SENSORS;
ADMUX = (ADMUX & 0b11111000) | i;
ADCSRA |= (1<<ADSC);
}

int8_t uchyb(uint8_t pomiary[], uint8_t czujniki, uint8_t prog)
{
static int8_t wagi[8];						//tablica zawierajaca wagi poszczególnych czujników
if (wagi[0] == 0)							//wyliczenie wag czujników przy pierwszym wywołaniu funkcji
{
	for (uint8_t i=0; i<(czujniki/2); i++)
	{
		wagi[i] = 1 << ((czujniki / 2) - i);
		wagi[czujniki-i-1] = -wagi[i];
	}
}

int8_t uchyb = 0;
uint8_t ile = 0;
for (uint8_t i=0; i<czujniki; i++)
{
	if (pomiary[i] < prog)					//kierunek znaku nierówności zależy od reakcji czujnika na kolor podłoża
	{										// < dla sytuacji gdy linia daje niskie wartości z ADC
		uchyb += wagi[i];					// > dla sytuacji gdy linia daje wysokie wartości z ADC
		ile++;
	}
}
if (ile>1)
	uchyb /= ile;
return uchyb;
}

@Edit 14-07-2015: poprawiłem potęgowanie ze znaku ^, który w języku C jest operacją XOR, na przesunięcie bitowe. Podziękowania dla Elvisa za wyłapanie błędu i propozycję rozwiązania.

Ja zaproponowałem funkcję liczącą potęgę jednak rozwiązanie Elvisa jest dużo prostsze.

uint8_t potega(uint8_t podstawa, uint8_t wykladnik)
{
if (wykladnik == 0)
	return 1;
else 
	return podstawa * potega(podstawa, (wykladnik - 1));
}

Warto zwrócić uwagę, że ta funkcja przyjmuje i zwraca zmienne 8-bitowe bez znaku, czyli wartości 0-255. Tak jak wykorzystana jest ona w funkcji głównej będzie działać poprawnie dla maks. 15 czujników.

Udostępnij ten post


Link to post
Share on other sites

Hudyvolt, zamiast ciągle uruchamiać od nowa konwersję:

ADCSRA |= (1<<ADSC);

Można ustawić przetwornik w tryb 'Free run' (podczas jego inicjalizacji) poprzez ustawienie bitu 'ADFR', w taki sposób:

ADCSRA |= (1<<ADFR);

Udostępnij ten post


Link to post
Share on other sites

Wojciech, a w którym momencie zmieniać kanał w muxie i skąd mieć pewność wartość z którego kanału odczytuję z rejestru ADCH?

Udostępnij ten post


Link to post
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ę »

×