====== Beteiligte ====== * CoRe * dd2 * mrm2m * draradech ====== Idee ====== Led-Cube 4x4x4 mit blauen Leds, gesteuert von einem Arduino-Mega. Im weiteren Verlauf soll der Arduino über RS232 mit Daten versorgt werden. Aktuell umgesetzt: Sprektrumsanzeige (Mic-In -> PulseaudioSample -> C-Programm -> Python-Script -> Seriell an Arduino) -> Funktioniert BounceBall Sinuskurve ====== Umsetzung ====== == Erste Ebene == {{:projekte:led_cube:2013-05-25_19.54.31.jpg?200|}} == 4 Ebenen == {{:projekte:led_cube:2013-05-25_21.50.43.jpg?200|}} == Cube fertig == {{:projekte:led_cube:2013-05-25_23.06.52.jpg?200|}} == Verlängerung 16 Leds pro Ebene == {{:projekte:led_cube:2013-05-25_23.06.59.jpg?200|}} == Verlängerung 4 Ebenen == {{:projekte:led_cube:2013-05-25_23.07.09.jpg?200|}} == Hardware komplett mit Arduino Mega == {{:projekte:led_cube:2013-05-25_23.36.30.jpg?200|}} == Hardware komplett mit Arduino Mega == {{:projekte:led_cube:2013-05-25_23.36.22.jpg?200|}} == Erster Funktionstest == {{:projekte:led_cube:2013-05-26_00.22.07.jpg?200|}} == Version 1 (4x4 Grün) um Grundlagen von Charlieplexing zu verstehen == {{:projekte:led_cube:2013-05-26_02.17.28.jpg?200|}} ===== Probleme ===== ===== Code ===== ==== Arduino ==== Der Cube kann z.B. über einen Arduino angesteuert werden, wofür unten passender Code gepasted ist. Der Code läuft ohne Anpassungen auf folgendem Setup: * Arduino Mega 2560 * Ebenen (von unten nach oben, z = 0..3) an Pins A0, A1, A2, A3 * LEDs erste Reihe (y = 0) an Pins 22, 23, 24, 25 * LEDs zweite Reihe (y = 1) an Pins 26, 27, 28, 29 * LEDs dritte Reihe (y = 2) an Pins 30, 31, 32, 33 * LEDs letzte Reihe (y = 3) an Pins 34, 35, 36, 37 /* cube */ #define LAYERs 4 #define COLs 4 #define ROWs 4 /* global variable definitions */ unsigned char LEDs[LAYERs][COLs * ROWs]; unsigned char pwmpos = 0; // actual step of the PWM unsigned char layer = 1; // actual controlled layer /* * cmp_carry_shift_left compares the two parameters * which results in the carry bit to be set/unset * that carry bit is rotated into para_io * the complete function consumes only 2 cycles */ static inline unsigned char cmp_carry_shift_left ( unsigned char para_io, unsigned char cmp_para_1, unsigned char cmp_para_2 ) { asm volatile ( // compare the parameters // (get the carry flag set/unset) "cp %r[cmp2], %r[cmp1]" "\n\t" // rotate carry bit into paramter "rol %r[out]" "\n\t" // output operand : [out] "+r" (para_io) // input operands : [in] "r" (para_io), [cmp1] "r" (cmp_para_1), [cmp2] "r" (cmp_para_2) ); return para_io; } /* * The timer3 overflow interrupt routine builds the pin states * to be output. the actual output is done in the beginning * (of the next execution) of the routine to achieve a more * constant time behaviour */ ISR ( SIG_OVERFLOW3 ) { static unsigned char porta, portc, portf; PORTB = 0x80; // turn on pin B7 for measuring the time consumption PORTA = porta; // turn on the pins of PORTA and B in resull of PORTC = portc; // the previous cycles outcome PORTF = portf; pwmpos++; if ( pwmpos >= 128 ) pwmpos = 0; if ( pwmpos == 0 ) { layer++; if (layer == LAYERs) { layer = 0; PORTB |= 0x10; // get an impulse on Pin B4 as debug output PORTB &= ~0x10; // -"- PORTA = 0; PORTC = 0; } portf = 1 << layer; PORTB |= 0x40; // get an impulse on Pin B6 as debug output PORTB &= ~0x40; // -"- } porta = cmp_carry_shift_left ( porta, LEDs[layer][ 7], pwmpos ); // LED at A7 porta = cmp_carry_shift_left ( porta, LEDs[layer][ 6], pwmpos ); // LED at A6 porta = cmp_carry_shift_left ( porta, LEDs[layer][ 5], pwmpos ); porta = cmp_carry_shift_left ( porta, LEDs[layer][ 4], pwmpos ); porta = cmp_carry_shift_left ( porta, LEDs[layer][ 3], pwmpos ); porta = cmp_carry_shift_left ( porta, LEDs[layer][ 2], pwmpos ); porta = cmp_carry_shift_left ( porta, LEDs[layer][ 1], pwmpos ); porta = cmp_carry_shift_left ( porta, LEDs[layer][ 0], pwmpos ); // LED at A0 portc = cmp_carry_shift_left ( portc, LEDs[layer][15], pwmpos ); // LED at C7 portc = cmp_carry_shift_left ( portc, LEDs[layer][14], pwmpos ); // LED at C6 portc = cmp_carry_shift_left ( portc, LEDs[layer][13], pwmpos ); portc = cmp_carry_shift_left ( portc, LEDs[layer][12], pwmpos ); portc = cmp_carry_shift_left ( portc, LEDs[layer][11], pwmpos ); portc = cmp_carry_shift_left ( portc, LEDs[layer][10], pwmpos ); portc = cmp_carry_shift_left ( portc, LEDs[layer][ 9], pwmpos ); portc = cmp_carry_shift_left ( portc, LEDs[layer][ 8], pwmpos ); // LED at C0 PORTB &= ~0x80; } void serialEvent() { static unsigned char RX_pos = 0; unsigned char rx_char; PORTB |= 0x08; // turn on B3 as debug output rx_char = (unsigned char)Serial.read(); if ( rx_char == 0xFF ) { // if FF is rxed, a new buffer is expected RX_pos = 0; } else { ( (unsigned char *) LEDs )[RX_pos] = rx_char % 128; RX_pos = ( RX_pos >= LAYERs * COLs * ROWs ) ? 0 : RX_pos + 1; } PORTB &= ~0x08; // turn off B3 as debug output } void setup() { unsigned char i; // Initialize port directions and output values DDRA = 0xFF; PORTA = 0xFF; // first 8 LEDs DDRC = 0xFF; PORTC = 0xFF; // second 8 LEDs DDRF = 0x0F; PORTF = 0x01; // 4 layers DDRB = 0xFF; PORTB = 0x00; // debug output LEDs // Initialize timer TCCR3A = (1 << WGM31) | (1 << WGM30) | (0x00 << COM3B0); TCCR3B = (0x01 << CS30) | (1 << WGM32) | (1 << WGM33); TIMSK3 |= 1 << TOIE3; OCR3A = 0xFF; // */ // init LEDs array for ( i = 0; i < LAYERs * COLs * ROWs; i++ ) ( (unsigned char *) LEDs )[i] = 1; // Enable interrupts sei(); // listen on RS232 for new data Serial.begin(115200); } void loop() { PORTB ^= 0x02; // debug output (toggles pin B1 if nothing else is done) } ==== PC ==== Der mit dem Compilat des obigen Codes laufende Arduino macht nichts anderes, als alle LEDs auf der untersten Helligkeitsstufe anzusteuern, solange nichts anderes über die serielle Schnittstelle herreinkommt. Am einfachsten wird der Arduino über ein Script gefüttern, das auf dem Gerät am anderen Ende der USB-Leitung hängt. Das erwartete Datenformat sieht so aus: * ein FF-Byte am Anfang setzt den Pointer des zu überschreibenden Bytes auf den Anfang zurück * 64 weitere Bytes enthalten die Helligkeitswerte, je einer pro LED (der obige Code stellt 0x00 bis 0x7F unterschiedlich hell dar - 0x7F..0xFE bedeuten 100% angeschaltet) Die angehängten Scripte lassen eine Kugelwelle durch den Cube wabern bzw. stellen den unix-timestamp darauf dar. #! /usr/bin/env python from serial import serialposix as ser import time import math import random serport = ser.Serial ( port='/dev/ttyACM0', baudrate=115200 ) time.sleep ( 1 ) # wait for the Arduino bootloader to quit def linear ( val ): return max ( 0, min ( 127, int ( 127 * val**2 ) ) ) def picfunc ( pix, t ): return picfunc_xyz ( pix % 4, (pix / 4) % 4, (pix / 16) % 4, t ) def picfunc_xyz ( x, y, z, t ): return linear ( math.sin ( 2 * t + (x + y + z)/3. ) ** 2 ) t = 0.0 try: while True: time.sleep ( 0.01 ) t += 0.01 serport.write ( chr ( 255 ) + ''.join ( map ( chr, [linear ( random.random ( ) ** 256 ) for x in range ( 64 )] ) ) ) except KeyboardInterrupt: serport.write ( chr ( 255 ) + ''.join ( map ( chr, [0 for x in range ( 64 )] ) ) ) serport.close ( ) #! /usr/bin/env python from serial import serialposix as ser import time import math serport = ser.Serial ( port='/dev/ttyACM0', baudrate=115200 ) time.sleep ( 1 ) # wait for the Arduino bootloader to quit pix_buffer = [0. for i in range ( 64 )] def linear ( val ): return max ( 0, min ( 127, int ( 127 * val**2 ) ) ) def picfunc ( pix, t ): pix_buffer[pix] += ( 0.6 * ( 1 & ( int ( time.time ( ) ) >> (63 - pix) ) ) - pix_buffer[pix] ) / 20 return linear ( pix_buffer[pix] ) t = 0.0 try: while True: time.sleep ( 0.01 ) t += 0.01 serport.write ( chr ( 255 ) + ''.join ( map ( chr, [picfunc ( x, t ) for x in range ( 64 )] ) ) ) except KeyboardInterrupt: serport.write ( chr ( 255 ) + ''.join ( map ( chr, [0 for x in range ( 64 )] ) ) ) serport.close ( )