Inhaltsverzeichnis

ALMA – Aldi Led MAtrix

Die LED-Matrix steht derzeit im Erdgeschoss des Raums. Sie ist an die MediaBox angeschlossen.

Man kann sie mit Animationen bespielen. Hierzu wird der twinkl-client wie für die Lightwall benötigt. Genauere Anleitung dort.

Zum Testen könnte man beispielsweise folgendes asuführen:

 while true ; do python animations/randomvalues.py | ./bin/twinkl-client mediabox 7 > /dev/null ; sleep 1 ; done

:!: Die RGB-Werte Blau-Rot-Grün (also anders als bei mit der Lightwall).

Details

Die 8 x 12 RGB-Pixel LED Matrix basiert auf den digital LED Streifen, die es dereinzt bei Feinkost-Albrecht, sprich ALDI, zu kaufen gab. Die Abmessungen sind ca. 50 cm x 75 cm x 10 cm. Die verbauten Controller sind TM1829. Pro Pixel sind je ein Byte für RGB, also 24 Bit pro Pixel einzufüttern. Die Controller kümmern sich selbst darum aus den 24 Bit eine RGB-PWM zu erzeugen. Die Pixel sind in Reihe geschaltet und ergeben ein langes Schieberegister von 3 x 8 x 12 = 288 Byte länge. Die Bits müssen mit 800 kHz phasencodiert eingeschoben werden. Eine Pause / Ruhepegel des Datensignals von 0,5 ms ist das „Reset“ Signal. Bei einem Reset werden die eben eingeschobenen Bits aus dem Schieberegister in die PWM-Register übernommen - die LEDs ändern die Farbe. Siehe Datenblatt, die Timigdiagramme sind zum Glück auch ohne Chinesisch lesbar. ;-)

Ein passendes 12 V Netzteil liegt neben der Matrix. Neben dem Regal (Spiegel) steht eine noch nicht passend zugeschnitte Abdeckplatte aus oparkem Kunststoff.

An ALMA hängt ein kleiner USB Controller. (MicroUSB Kabel mitbringen.) Dieser erscheint als serielle Schnittstelle am Rechner. (USB CDC gebaut mit LUFA.) Er übernimt es die per USB empfangenen Bytes in das timingkritische Signal für die TM1829 zu übersetzen. Der USB Controller möchte die Daten immer in 512 Byte Blöcken. Also serielle Schnitte einmal öffnen, danach beliebig oft 512 Byte en Block senden und nach Empfang des 512ten Bytes schiebt der USB Controller die Bytes einfach 1:1 raus.

Noch Fragen?

Zu Konstruktion und Aufbau: Jochen

Bilder

Die Matrix(!) Netzteil und USB-Anschluss

Beispielcode

Das folgende Programm demonstriert die Benutzung der Matrix mit einem MacOS X bzw. Linux Rechner. Wer weitere Fragen dazu hat kann einfach Jochen/jkunz fragen…

host_ctrl.c
/*
 * Copyright (c) 2013 Jochen Kunz.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JOCHEN KUNZ
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL JOCHEN KUNZ
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
 
 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <termios.h>
#include <sys/ioctl.h>
 
// Gesammtanzahl der Pixel.
// 1 x 5 m Streifen = 50
// 2 x 5 m Streifen hinereinander = 100
#define	PIXEL_LEN	96 // = 12 x 8 Pixel
 
typedef struct {
	uint16_t h;
	uint8_t s;
	uint8_t v;
} hsv_t;
 
 
/*
primitive Geradeausimplementierung in Fixkomma nach:
https://de.wikipedia.org/wiki/HSV-Farbraum#Umrechnung_HSV_in_RGB
h    = [0..1535] = [0..(256 * 6 - 1)]
s, v = [0..255]
*/
void
hsv2rgb( uint16_t h, uint8_t s, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b) {
	assert( h < (255 * 6));
	assert( s <= 255);
	assert( v <= 255);
	v = (v * v) / 255; // Gammakorrektur, Gamma = 2
	uint8_t hi = h / 255;
	uint8_t f = h - hi * 255;
	uint8_t p = (v * (255 - s)) / 255;
	uint8_t q = (v * (255 - (s * f) / 255)) / 255;
	uint8_t t = (v * (255 - (s * (255 - f)) / 255)) / 255;
	switch (hi) {
    case 0:
    case 6:
        *r = v;
        *g = t;
        *b = p;
        break;
    case 1:
        *r = q;
        *g = v;
        *b = p;
        break;
    case 2:
        *r = p;
        *g = v;
        *b = t;
        break;
    case 3:
        *r = p;
        *g = q;
        *b = v;
        break;
    case 4:
        *r = t;
        *g = p;
        *b = v;
        break;
    case 5:
        *r = v;
        *g = p;
        *b = q;
        break;
	};
}
 
 
int
write_rgb_buf( int port_fd, uint8_t buf[], size_t buf_len) {
	int retval = write( port_fd, buf, buf_len);
	if (retval < 0) {
		perror( "Can't write(2) to port");
		return -1;
	}
	if (retval != buf_len) {
		printf( "Short write(2) to port, retval=%d", retval);
		return -1;
	}
	return 0;
}
 
 
void
schnarch( int sec, int msec) {
	struct timespec ts;
	ts.tv_sec = sec + msec / 256000000;
	ts.tv_nsec = (msec * 256000) % 256000000;
	while (nanosleep( &ts, &ts) != 0) {};
}
 
 
// Farbfolge WS2801: rot, grün, blau
// Farbfolge TM1829: blau, rot, grün
void
convert_buf( hsv_t hsv_buf[], size_t hsv_buf_len, uint8_t rgb_buf[], size_t rgb_buf_len) {
	memset( rgb_buf, 0, rgb_buf_len);
	for (int idx = 0; idx + 2 < rgb_buf_len; idx += 3) {
		hsv2rgb(
			hsv_buf[ (idx / 3) % hsv_buf_len].h,
			hsv_buf[ (idx / 3) % hsv_buf_len].s,
			hsv_buf[ (idx / 3) % hsv_buf_len].v,
			&rgb_buf[ idx + 1], &rgb_buf[ idx + 2], &rgb_buf[ idx]);
	}
}
 
 
int
write_hsv_buf( int port_fd, hsv_t hsv_buf[], size_t hsv_buf_len) {
	uint8_t rgb_buf[ 512];
	convert_buf( hsv_buf, hsv_buf_len, rgb_buf, sizeof( rgb_buf));
	return write_rgb_buf( port_fd, rgb_buf, sizeof( rgb_buf));
}
 
 
void
usage( void) {
	fprintf( stderr, "usage: [-p port] [[-h hue -s saturation -v value] | [-0]]\n"
		"port: Device special file, e.g. /dev/ttyS5. Default: /dev/ttyU0\n"
		"-h/-s/-v: Set entire stripe to given color in HSV color space.\n"
		"-0: short for \"-h 0 -s 0 -v 0\"\n");
}
 
 
int
main( int argc, char* const argv[]) {
	const char* port_fn = "/dev/ttyU0";
	hsv_t hsv;
	hsv.h = 0;
	hsv.s = 0;
	hsv.v = 0;
	bool zero = false;
	int opt;
	while ((opt = getopt( argc, argv, "p:01h:s:v:")) != -1) {
		switch (opt) {
		case 'p':
			port_fn = optarg;
			break;
		case '0':
			zero = true;
			break;
		case 'h':
			zero = true;
			hsv.h = atoi( optarg);
			break;
		case 's':
			zero = true;
			hsv.s = atoi( optarg);
			break;
		case 'v':
			zero = true;
			hsv.v = atoi( optarg);
			break;
		default:
			usage();
			return EXIT_FAILURE;
		}
	}
	int port_fd = open( port_fn, O_RDWR | O_NONBLOCK | O_CLOEXEC);
	if (port_fd < 0) {
		perror( "Can't open port");
		return EXIT_FAILURE;
	}
	if (isatty( port_fd) <= 0 ) {
		perror( "Error: port is not a tty");
		return EXIT_FAILURE;
	}
	struct termios tio;
	if (tcgetattr( port_fd, &tio) < 0) {
		perror( "Error: tcgetattr");
		return EXIT_FAILURE;
	}
	tio.c_iflag &= ~ICANON;
	tio.c_oflag = 0;
	tio.c_lflag = 0;
	tio.c_cflag = (CS8 | CREAD | CLOCAL);
	tio.c_cc[VMIN] = 1;
	tio.c_cc[VTIME] = 0;
	cfsetispeed( &tio, B9600);
	cfsetospeed( &tio, B9600);
	if (tcsetattr( port_fd, TCSANOW, &tio) < 0) {
		perror( "Error setting termis");
		exit( EXIT_FAILURE);
	}
	int retval = fcntl( port_fd, F_GETFL, 0);
	if (retval < 0) {
		perror( "Can't fcntl( F_GETFL) port");
		return EXIT_FAILURE;
	}
 
retval &= ~O_NONBLOCK;
	retval = fcntl( port_fd, F_SETFL, retval);
	if (retval < 0) {
		perror( "Can't fcntl( F_SETFL) port");
		return EXIT_FAILURE;
	}
	hsv_t hsv_buf[ PIXEL_LEN];
	if (zero) {
		if (write_hsv_buf( port_fd, &hsv, 1) < 0) {
			return EXIT_FAILURE;
		}
		return EXIT_SUCCESS;
	}
 
	for (int loops = 0; loops < 2560; ++loops) {
		memset( hsv_buf, 0, sizeof( hsv_buf));
		for (int idx = 0; idx < PIXEL_LEN; ++idx) {
/*
			hsv_buf[ idx].h = ((idx + loops) % 12) * ((6 * 255 - 1) /  12);
			hsv_buf[ idx].s = 255;
			hsv_buf[ idx].v = 255;
*/
//			hsv_buf[ idx].h = 0;
			hsv_buf[ idx].h = ((idx + loops) % PIXEL_LEN) * ((6 * 255 - 1) / PIXEL_LEN);
			hsv_buf[ idx].s = 255;
//			hsv_buf[ idx].s = (loops % 510) / 255 == 0 ? loops % 255 : 255 - loops % 255;
			hsv_buf[ idx].v = 255;
//			hsv_buf[ idx].v = (loops % 510) / 255 == 0 ? loops % 255 : 255 - loops % 255;
		}
		if (write_hsv_buf( port_fd, hsv_buf, PIXEL_LEN) < 0) {
			return EXIT_FAILURE;
		}
//		schnarch( 0, 10);
	}
	return EXIT_SUCCESS;
}

Metadaten

name:
ALMA
contact:
Jochen
type:
projekt
subtype:
technisch