====== 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 ( )