Dynia + NeoPixel + AVR + C

dyi elektronika avr 2016-10-30, 00:10,

Halloween samo w sobie nie wydaje mi się żadną okazją do świętowania i pewnie przeszedłbym obok niego zupełnie obojętnie gdyby nie pewna dynia, którą zobaczyłem wracając wieczorem z pracy. Wyglądała całkiem okazale i pomyślałem sobie, a może by ją trochę podświetlić?

Wstąpiłem do budki, w której się znajdowała i z pięciokilogramowym okazem, który jak pani w sklepie zapewniała, niezbyt nadaje się do konsumpcji a głównie do dekoracji, udałem się do domu.

A skoro już dynia była to należało przygotować do niej wsad. I tu z pomocą przyszedł mi zakurzony projekt, który zrobiłem kilka tygodni temu. Wtedy jeden z grupowiczów fb/elektronika zastanawiał się w jaki sposób obsłużyć WS2811 bez użycia Arduino, ja konfigurowałem AVRdude pod Atmel Studio i uznałem, że to dobry sposób sprawdzenia czy wszystko działa (no dobra, wszedł mi na ambicję, przyznaję ;) ).

W ten sposób powstało 100 linijek kodu, z których 20 stanowi właściwe sterowanie modułem WS2811 i to je właśnie widzicie poniżej.


#define F_CPU 16000000L

#include 
#include 

#define WS_GRB

#define WS_HI PORTD |= (1 << PD2);
#define WS_LO PORTD &= ~(1 << PD2);
#define WS_1 WS_HI WS_HI WS_HI WS_HI WS_HI WS_HI WS_LO WS_LO
#define WS_0 WS_HI WS_HI WS_LO WS_LO WS_LO WS_LO WS_LO WS_LO
#define WS_RESET PORTD &= ~(1 << PD2); _delay_us(60);
#define WS_LED_COUNT 2 void sendByte(uint8_t b) {
	for (uint8_t bit = 0; bit < 8; bit++) { if (b & 0b10000000) { WS_1 } else { WS_0 } b = b << 1;
	}
}

void sendRGB(uint8_t r, uint8_t g, uint8_t b) {
	#ifdef WS_GRB 
		sendByte(g); sendByte(r); sendByte(b); 
	#endif	
	#ifdef WS_BGR 
		sendByte(b); sendByte(g); sendByte(r); 
	#endif	
}

Sterowanie WS2811 polega na tym, by paczki, złożone z trzech bajtów wysyłać w odpowiedniej kolejności na magistralę. Częstotliwość transmisji, w zależności od wersji, wynosić musi 400 lub 800 kHz, a same NeoPixele (i im podobne) są bardzo wrażliwe na częstotliwość i stabilność transmisji. Ja wybrałem rozwiązanie najprostsze, ale najłatwiejsze do wykonania w czystym C, chociaż bardzo zależne od częstotliwości pracy mikrokontrolera AVR.

Do wysyłania poszczególnych bitów służą mi makra WS_0 oraz WS_1, których czas wykonania wynika z czasu wykonywania instrukcji przez jądro AVR. Następnie przy użyciu sendByte() bit po bicie wysyłam sygnał do pikseli. Aby ułatwić sobie to zadanie przygotowałem jeszcze jedną funkcję, sendRGB(), która służy mi do wysłania jednego piksela w całości.

Po wysłaniu komunikatu dla wszystkich pikseli (WS_LED_COUNT) na magistralę trzeba wysłać sygnał WS_RESET na przynajmniej 60 uS. A następnie można cykl zacząć od nowa.

Wykonanie dyni

Uzbrojony w podstawową bibliotekę do zrealizowania mojego celu poszperałem w szufladach oraz w kuchni i uzbierałem następujący zestaw:

  • 1x dynia 5 kg
  • 2x Adafruit Neopixel
  • 1x Arduino Pro Mini 5V/16MHz (a z niego w zasadzie Atmega328P)
  • 1x FT232 USB/RS z wyjściem zasilania 3.3/5V
  • 2x miniaturowy słoiczek (może być po miodzie lub koncentracie pomidorowym)
  • 1x plastikowa podstawka (ochrona Neopixeli przed wilgocią dyni)
  • 1x taśma izolacyjna
  • 3x wykałaczka
  • wiązka przewodów

W pierwszej kolejności przygotowałem dynię poprzez wycięcie na górze gwiazdki (dzięki temu nie wpada do środka po usunięciu zawartości). Następnie przy użyciu łyżki stołowej i z zachowaniem dużej cierpliwości, wygrzebałem ze środka całą zawartość do miski. Gdy dynia była już czysta wyciąłem odpowiednią grafikę z przodu oraz, co najważniejsze, przygotowałem mały otwór w tylnej, dolnej części, by mieć którędy wyprowadzić przewody.

Następnie podłączyłem Neopixele ze sobą, umieściłem na podstawce i przymocowałem taśmą, by się nie ruszały. Przykryłem je słoiczkami i umieściłem w środku dyni, przewód wyprowadzając nacięciem w tylnej części.

FT232 połączyłem z płytką Arduino złączami VCC i GND, tym samym zapewniając sobie zasilanie z pominięciem wbudowanego regulatora liniowego. Przewody DIN, VCC, GND Neopixeli podłączyłem do portu PD2 i wolnych linii zasilających.

Zostało już tylko przygotować resztę programu. Ja wybrałem prostą animację polegającą na naprzemiennym przenikaniu koloru czerwonego i żółtego na dwóch diodach na zmianę, następnie w przejście 2x żółty i z niego w 2x czerwony. Później cykl się zamyka. Dla zachowania spokoju (to nie disco) przejście przez 255 poziomów jasności odbywa się z krokiem 10 ms, a pomiędzy kolejnymi przejściami umieściłem 5-cio sekundowe przerwy.


#define PAUSE 5000

uint8_t led0r = 0;
uint8_t led0g = 0;
uint8_t led0b = 0;
uint8_t led1r = 0;
uint8_t led1g = 0;
uint8_t led1b = 0;

void sendAndWait() {
	sendRGB(led0r, led0g, led0b);
	sendRGB(led1r, led1g, led1b);
	WS_RESET
	_delay_ms(10);
}

int main(void)
{
	DDRB |= (1 << PB5);
	DDRD |= (1 << PD2);
	
	for (uint8_t i = 0; i < 3; i++) {
		PORTB |= (1 << PB5);
		_delay_ms(100);
		PORTB &= ~(1 << PB5);
		_delay_ms(100);
	}
	sendAndWait();
	_delay_ms(1000);
	for (uint8_t i = 0; i < 255; i++) {
		led0g = 0;
		led1g = i;
		led0r = i;
		led1r = i;
		sendAndWait();
	}
	led0r = 255;
	led1r = 255;
    while (1) 
    {
		PORTB |= (1 << PB5);
		for (uint8_t i = 0; i < 255; i++) {
			led0g = i;
			led1g = 255-i;
			sendAndWait();
		}
		_delay_ms(PAUSE);
		for (uint8_t i = 255; i > 0; i--) {
			led0g = i;
			led1g = 255-i;
			sendAndWait();
		}
		_delay_ms(PAUSE);
		for (uint8_t i = 0; i < 255; i++) {
			led0g = i;
			led1g = 255;
			sendAndWait();
		}
		_delay_ms(PAUSE);
		for (uint8_t i = 0; i < 255; i++) {
			led0g = 255-i;
			led1g = 255-i;
			sendAndWait();
		}
		_delay_ms(PAUSE);
		for (uint8_t i = 0; i < 255; i++) {
			led0g = i;
			led1g = i;
			sendAndWait();
		}
		_delay_ms(PAUSE);
		for (uint8_t i = 255; i > 0; i--) {
			led0g = i;
			led1g = 255;
			sendAndWait();
		}
		_delay_ms(PAUSE);
    }
}

Po wgraniu poniższej pętli do AVRa uruchomiłem dynię i… poszedłem łuskać pestki do suszenia :)

Miłej zabawy!

Komentarze

Podobne wpisy