LowCostLightPaint
Nach dem sehr positiven Anklang des Lightpainting Sticks wurden wir bereits öfters von Leuten angeschrieben, die eine ähnliche Variante nachbauen wollten. Wenn jedoch nicht ausreichende Elektronik- und Programmiererfahrungen für teils notwendige Anpassungen vorhanden sind, ist unser Lightpainting Stick schwer nachzubauen. Vor Kurzem haben wir jedoch gesehen, dass der Franzis Verlag ein kleines LED Shield vertreibt ("Blinkenlight Shield mit 20 LEDs für Arduino"), das auch von Arduino Anfängern zum Ausprobieren genutzt werden kann. Dieses ist für 20-25 Euro bei zahlreichen großen Versendern zu bekommen (einfach mal in die Suchmaschine des Vertrauens eingeben).
Um zu wissen was wir den LightPaint-Interessenten empfehlen haben wir das auch mal ausprobiert und im Gegensatz zur Nutzung mit einem Arduino einen Fez Domino Controller genutzt...weil er gerade rumlag und im Gegensatz zum Arduino auch direkt mit SD-Karten- und USB-Stick-Unterstützung herkommt.
Das Blinkenlight Shield ist ein simples LED Shield, das auf einen Arduino kompatiblen Controller die 14 Digital IO Pins und die 6 Analog IO Pins für die direkte Ansteuerung der 20 LEDs nutzt. Dies hat den Vorteil, dass keine zusätzliche Logik programmiert werden muss (I2C, SPI, Schieberegister,...) und somit für Anfänger auch einfach zu verstehen und einzusteigen ist. Der Nachteil ist jedoch ganz klar, dass alle Pins eines Standard-Arduinos genutzt werden und weitere Addons wie z.B. SD-Karten Zugriff nicht realisiert werden können. Hierfür muss dann entweder ein größerer Arduino-kompatibler Controller her (Arduino Mega, Duo,...) oder ein ganz anderes Modell wie unserer Fez Domino mit integriertem SD- und USB- Filesystemzugriff.
Wir möchten das Laden von Bilddaten von Speichermedium nutzen, um vorher gezeichnete Bilder auszugeben. Hierfür unterstützt der Code das Laden von Bitmap-Bildern und Extraktion der byte-Daten. Der aktuelle Code unterstützt 24bpp Bitmaps (8 Bits per Farbe). Dieses Format kann auch mit einfachster Bildbearbeitungssoftware erstellt werden. Bei einer Höhe von 20 LEDs werden natürlich auch nur Bilder mit einer Pixelhöhe von 20 Pixeln unterstützt. Fehler im Bildladen oder bei Bildern, die nicht 20 Pixel Höhe haben, werden durch den Controller und den LEDs durch paarweise abwechselndes Blinken signalisiert.
Der erste einfache Code zum Laden eines Bitmaps "1.bmp" von einem angehängten USB Stick sieht folgendermaßen aus:
using System.Threading; using System.IO.Ports; using System.Text; using Microsoft.SPOT; using Microsoft.SPOT.IO; using GHIElectronics.NETMF.FEZ; using GHIElectronics.NETMF.IO; using GHIElectronics.NETMF.USBHost; using System.IO; using System; using Microsoft.SPOT.Presentation; using Microsoft.SPOT.Hardware; using Microsoft.SPOT.Presentation.Media; namespace LightPaint.NET { struct tagBITMAPFILEHEADER { public Int16 bfType; //specifies the file type public Int32 bfSize; //specifies the size in bytes of the bitmap file public Int16 bfReserved1; //reserved; must be 0 public Int16 bfReserved2; //reserved; must be 0 public Int32 bOffBits; //species the offset in bytes from the bitmapfileheader to the bitmap bits } struct tagBITMAPINFOHEADER { public Int32 biSize; //specifies the number of bytes required by the struct public Int64 biWidth; //specifies width in pixels public Int64 biHeight; //species height in pixels public Int16 biPlanes; //specifies the number of color planes, must be 1 public Int16 biBitCount; //specifies the number of bit per pixel public Int32 biCompression;//spcifies the type of compression public Int32 biSizeImage; //size of image in bytes public Int64 biXPelsPerMeter; //number of pixels per meter in x axis public Int64 biYPelsPerMeter; //number of pixels per meter in y axis public Int32 biClrUsed; //number of colors used by th ebitmap public Int32 biClrImportant; //number of colors that are important } public class Program { static PersistentStorage ps; static OutputPort[] leds = new OutputPort[20]; static void DeviceConnectedEvent(GHIElectronics.NETMF.USBHost.USBH_Device device) { if (device.TYPE == USBH_DeviceType.MassStorage) { Debug.Print("USB Mass Storage detected..."); ps = new PersistentStorage(device); ps.MountFileSystem(); string rootDirectory = VolumeInfo.GetVolumes()[0].RootDirectory; FileStream inStreamBitmap = new FileStream(rootDirectory + @"\1.bmp", FileMode.Open, FileAccess.Read); Byte[] buffer = new Byte[inStreamBitmap.Length]; inStreamBitmap.Read(buffer, 0, (int)(inStreamBitmap.Length)); inStreamBitmap.Close(); //FileStream inStreamSpeed = new FileStream(rootDirectory + @"\1.txt", FileMode.Open, FileAccess.Read); //inStreamBitmap.Read(buffer, 0, (int)(inStreamBitmap.Length)); tagBITMAPINFOHEADER bitmapInfoHeader = new tagBITMAPINFOHEADER(); Byte[] bitmapData = LoadBitmapFile(buffer, ref bitmapInfoHeader); if ((bitmapData == null) || (bitmapInfoHeader.biHeight != 20)) { for (int y = 0; y < 20; ++y) { if (y % 2 == 0) leds[y].Write(true); else leds[y].Write(false); } } while (true) { for (int x = 0; x < bitmapInfoHeader.biWidth; ++x) { for (int y = 0; y < bitmapInfoHeader.biHeight; ++y) { checkElementAndSetLED(bitmapData[(y * bitmapInfoHeader.biWidth + x) * 3], leds[y]); } Thread.Sleep(10); } } } } static void checkElementAndSetLED(Byte value, OutputPort port) { if (value > 127) port.Write(true); else port.Write(false); } public static void Main() { bool ledState = false; leds[0] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di0, ledState); leds[1] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di1, ledState); leds[2] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di2, ledState); leds[3] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di3, ledState); leds[4] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di4, ledState); leds[5] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di5, ledState); leds[6] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di6, ledState); leds[7] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di7, ledState); leds[8] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di8, ledState); leds[9] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di9, ledState); leds[10] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di10, ledState); leds[11] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di11, ledState); leds[12] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di12, ledState); leds[13] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.Di13, ledState); leds[14] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.An0, ledState); leds[15] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.An1, ledState); leds[16] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.An2, ledState); leds[17] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.An3, ledState); leds[18] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.An4, ledState); leds[19] = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.An5, ledState); USBHostController.DeviceConnectedEvent += DeviceConnectedEvent; Thread.Sleep(System.Threading.Timeout.Infinite); } static Byte[] LoadBitmapFile(Byte[] buffer, ref tagBITMAPINFOHEADER bitmapInfoHeader) { if (buffer.Length < 62) return null; tagBITMAPFILEHEADER fileHeader = new tagBITMAPFILEHEADER(); fileHeader.bfType = BitConverter.ToInt16(new Byte[] { buffer[0], buffer[1] }, 0); fileHeader.bfSize = BitConverter.ToInt32(new Byte[] { buffer[2], buffer[3], buffer[4], buffer[5] }, 0); fileHeader.bfReserved1 = BitConverter.ToInt16(new Byte[] { buffer[6], buffer[7] }, 0); fileHeader.bfReserved2 = BitConverter.ToInt16(new Byte[] { buffer[8], buffer[9] }, 0); fileHeader.bOffBits = BitConverter.ToInt32(new Byte[] { buffer[10], buffer[11], buffer[12], buffer[13] }, 0); bitmapInfoHeader.biSize = BitConverter.ToInt32(new Byte[] { buffer[14], buffer[15], buffer[16], buffer[17] }, 0); bitmapInfoHeader.biWidth = BitConverter.ToInt32(new Byte[] { buffer[18], buffer[19], buffer[20], buffer[21] }, 0); bitmapInfoHeader.biHeight = BitConverter.ToInt32(new Byte[] { buffer[22], buffer[23], buffer[24], buffer[25] }, 0); bitmapInfoHeader.biPlanes = BitConverter.ToInt16(new Byte[] { buffer[26], buffer[27] }, 0); bitmapInfoHeader.biBitCount = BitConverter.ToInt16(new Byte[] { buffer[28], buffer[29] }, 0); bitmapInfoHeader.biCompression = BitConverter.ToInt32(new Byte[] { buffer[30], buffer[31], buffer[32], buffer[33] }, 0); bitmapInfoHeader.biSizeImage = BitConverter.ToInt32(new Byte[] { buffer[34], buffer[35], buffer[36], buffer[37] }, 0); bitmapInfoHeader.biXPelsPerMeter = BitConverter.ToInt64(new Byte[] { buffer[38], buffer[39], buffer[40], buffer[41], buffer[42], buffer[43], buffer[44], buffer[45] }, 0); bitmapInfoHeader.biYPelsPerMeter = BitConverter.ToInt64(new Byte[] { buffer[46], buffer[47], buffer[48], buffer[49], buffer[50], buffer[51], buffer[52], buffer[53] }, 0); bitmapInfoHeader.biClrUsed = BitConverter.ToInt32(new Byte[] { buffer[54], buffer[55], buffer[56], buffer[57] }, 0); bitmapInfoHeader.biClrImportant = BitConverter.ToInt32(new Byte[] { buffer[58], buffer[59], buffer[60], buffer[61] }, 0); //verify that this is a bmp file by check bitmap id if (fileHeader.bfType != 0x4D42) return null; //allocate enough memory for the bitmap image data Byte[] bitmapImage = new Byte[bitmapInfoHeader.biSizeImage]; Array.Copy(buffer, fileHeader.bOffBits, bitmapImage, 0, bitmapInfoHeader.biSizeImage); return bitmapImage; } } }
Mehr Infos und vor allem Bilder folgen demnächst!