From 06f4d0a4a67ff64543e4c338cc0c41cfec0c6119 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 28 Jun 2017 22:47:47 +0200 Subject: [PATCH 01/35] Initial commit --- ws2812b/README.md | 23 + ws2812b/build.gradle | 34 ++ ws2812b/gradle.properties | 2 + ws2812b/publish-settings.gradle | 1 + ws2812b/src/main/AndroidManifest.xml | 22 + .../ColorValueBitPatternConverter.java | 53 +++ .../contrib/driver/ws2812b/Ws2812b.java | 194 +++++++++ ws2812b/ws2812b-bit-pattern.svg | 397 ++++++++++++++++++ ws2812b/ws2812b-timings.svg | 337 +++++++++++++++ 9 files changed, 1063 insertions(+) create mode 100644 ws2812b/README.md create mode 100644 ws2812b/build.gradle create mode 100644 ws2812b/gradle.properties create mode 100644 ws2812b/publish-settings.gradle create mode 100644 ws2812b/src/main/AndroidManifest.xml create mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorValueBitPatternConverter.java create mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java create mode 100644 ws2812b/ws2812b-bit-pattern.svg create mode 100644 ws2812b/ws2812b-timings.svg diff --git a/ws2812b/README.md b/ws2812b/README.md new file mode 100644 index 0000000..38d38a5 --- /dev/null +++ b/ws2812b/README.md @@ -0,0 +1,23 @@ +# readme-svg-test +How does it work +--------------------- +The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long data block. The transmission of these bits is done by sending a chain of high and low voltage pulses to the data line of the LED controller. +Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. Both pulses must have an exact specified duration, so that they are recognized as 1 or 0 bit: + + + +* A 1 bit is defined by a high voltage pulse with a duration of 850 ns which is followed by a low voltage pulse of 400 ns +* A 0 bit is defined by a high voltage pulse with a duration of 400 ns which is followed by a low voltage pulse of 850 ns +* Each pulse can have a deviation of +/- 150 ns + +At the moment there is no direct solution to send such short timed pulses with **different durations** by the API of Android Things. There is however the [Serial Peripheral Interface (SPI)](https://developer.android.com/things/sdk/pio/spi.html) which is only able to send short timed pulses with the **exact same** duration: +* This duration is indirectly defined by the frequency of the SPI. +* A transmitted 1 bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout +* A transmitted 0 bit results in a short low voltage pulse at the MOSI pinout + +Now the solution gets within reach. To control WS2812B LEDs by the SPI we must find an assembly of SPI bits (bit pattern) and a frequency so that this bit pattern is recognized as one WS2812B bit. +This approach is using an assembly of 3 bits to represent 1 WS2812B bit: + + + +The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. You could create also a more accurate bit patterns which consists of more than 3 bits. But the more bits you use to express one WS2812b bit, the less is the number of controllable LEDs. Because the SPI is using a fixed sized byte buffer to send the data. diff --git a/ws2812b/build.gradle b/ws2812b/build.gradle new file mode 100644 index 0000000..f05e799 --- /dev/null +++ b/ws2812b/build.gradle @@ -0,0 +1,34 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 24 + buildToolsVersion '24.0.3' + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 24 + versionCode 1 + versionName "1.0" + } +} + +dependencies { + provided 'com.google.android.things:androidthings:0.4-devpreview' + compile 'com.android.support:support-annotations:25.3.1' +} diff --git a/ws2812b/gradle.properties b/ws2812b/gradle.properties new file mode 100644 index 0000000..34f9583 --- /dev/null +++ b/ws2812b/gradle.properties @@ -0,0 +1,2 @@ +TYPE="RGB LED strip" +ARTIFACT_VERSION=0.1 diff --git a/ws2812b/publish-settings.gradle b/ws2812b/publish-settings.gradle new file mode 100644 index 0000000..f0318e9 --- /dev/null +++ b/ws2812b/publish-settings.gradle @@ -0,0 +1 @@ +rootProject.buildFileName = '../publish.gradle' diff --git a/ws2812b/src/main/AndroidManifest.xml b/ws2812b/src/main/AndroidManifest.xml new file mode 100644 index 0000000..312af20 --- /dev/null +++ b/ws2812b/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorValueBitPatternConverter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorValueBitPatternConverter.java new file mode 100644 index 0000000..02ffece --- /dev/null +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorValueBitPatternConverter.java @@ -0,0 +1,53 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.support.annotation.IntRange; +import android.util.SparseArray; + +class ColorValueBitPatternConverter +{ + private static final byte BIT_PATTERN_01 = (byte) 142; + private static final byte BIT_PATTERN_10 = (byte) 132; + private static final byte BIT_PATTERN_11 = (byte) 238; + private static final byte BIT_PATTERN_00 = (byte) 136; + + private SparseArray colorValueToBitPatternMap = new SparseArray<>(); + + ColorValueBitPatternConverter() + { + for (int colorValue = 0; colorValue < 256; colorValue++) + { + byte[] bitPattern = new byte [4]; + String binaryString = toEightBitBinaryString(colorValue); + bitPattern[0] = convertToBitPattern(binaryString.subSequence(0, 2).toString()); + bitPattern[1] = convertToBitPattern(binaryString.subSequence(2, 4).toString()); + bitPattern[2] = convertToBitPattern(binaryString.subSequence(4, 6).toString()); + bitPattern[3] = convertToBitPattern(binaryString.subSequence(6, 8).toString()); + colorValueToBitPatternMap.put(colorValue, bitPattern); + } + } + + private static String toEightBitBinaryString(@IntRange(from = 0, to = 255) int value) + { + StringBuilder stringBuilder = new StringBuilder(Integer.toBinaryString(value)); + while (stringBuilder.length() < 8) + { + stringBuilder.insert(0, '0'); + } + return stringBuilder.toString(); + } + + private static byte convertToBitPattern(String binaryString) + { + return + "01".equals(binaryString) ? BIT_PATTERN_01 : + "10".equals(binaryString) ? BIT_PATTERN_10 : + "11".equals(binaryString) ? BIT_PATTERN_11 : + BIT_PATTERN_00; + } + + byte[] convertColorValue(@IntRange(from = 0, to = 255) int colorValue) + { + return colorValueToBitPatternMap.get(colorValue); + } +} diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java new file mode 100644 index 0000000..f6c4536 --- /dev/null +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java @@ -0,0 +1,194 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.ws2812b; + +import android.graphics.Color; +import android.support.annotation.ColorInt; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import com.google.android.things.pio.PeripheralManagerService; +import com.google.android.things.pio.SpiDevice; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Device driver for WS2812B LEDs using SPI. + * + * For more information on SPI, see: + * https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus + * For information on the WS2812B protocol, see: + * https://cpldcpu.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/ + */ + +@SuppressWarnings({"unused", "WeakerAccess"}) +public class Ws2812b implements AutoCloseable { + private static final String TAG = "Ws2812b"; + + /** + * Color ordering for the RGB LED messages; the most common modes are BGR and RGB. + */ + @Retention(SOURCE) + @IntDef({RGB, RBG, GRB, GBR, BRG, BGR}) + public @interface LedMode {} + public static final int RGB = 0; + public static final int RBG = 1; + public static final int GRB = 2; + public static final int GBR = 3; + public static final int BRG = 4; + public static final int BGR = 5; + + private final ColorValueBitPatternConverter bitPatternConverter = new ColorValueBitPatternConverter(); + + // RGB LED strip configuration that must be provided by the caller. + @LedMode + private int mLedMode; + + // For peripherals access + private SpiDevice mDevice = null; + + /** + * Create a new WS2812B driver. + * + * @param spiBusPort Name of the SPI bus + */ + public Ws2812b(String spiBusPort) throws IOException { + this(spiBusPort, GRB); + } + + /** + * Create a new WS2812B driver. + * + * @param spiBusPort Name of the SPI bus + * @param ledMode The {@link LedMode} indicating the red/green/blue byte ordering for the device. + */ + public Ws2812b(String spiBusPort, @LedMode int ledMode) throws IOException { + mLedMode = ledMode; + PeripheralManagerService pioService = new PeripheralManagerService(); + mDevice = pioService.openSpiDevice(spiBusPort); + try { + configure(mDevice); + } catch (IOException|RuntimeException e) { + try { + close(); + } catch (IOException|RuntimeException ignored) { + } + throw e; + } + } + + /** + * Create a new Apa102 driver. + * + * @param device {@link SpiDevice} where the LED strip is attached to. + * @param ledMode The {@link LedMode} indicating the red/green/blue byte ordering for the device. + */ + @VisibleForTesting + /*package*/ Ws2812b(SpiDevice device, @LedMode int ledMode) throws IOException { + mLedMode = ledMode; + mDevice = device; + configure(mDevice); + } + + private void configure(SpiDevice device) throws IOException { + // Note: You may need to set bit justification for your board. + // mDevice.setBitJustification(SPI_BITJUST); + device.setFrequency(3225806); + device.setMode(SpiDevice.MODE1); + device.setBitsPerWord(8); + } + + /** + * Writes the current RGB Led data to the peripheral bus. + * @param colors An array of integers corresponding to a {@link Color}. + * @throws IOException + */ + public void write(@ColorInt int[] colors) throws IOException { + if (mDevice == null) { + throw new IllegalStateException("SPI device not open"); + } + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream( ); + for (int color : colors) + { + writeColorPattern(outputStream, color); + } + + byte[] colorPattern = outputStream.toByteArray(); + + mDevice.write(colorPattern, colorPattern.length); + } + + private void writeColorPattern(@NonNull ByteArrayOutputStream outputStream, @ColorInt int color) throws IOException + { + byte[] red = bitPatternConverter.convertColorValue(Color.red(color)); + byte[] green = bitPatternConverter.convertColorValue(Color.green(color)); + byte[] blue = bitPatternConverter.convertColorValue(Color.blue(color)); + + switch (mLedMode) + { + case BGR: + outputStream.write( blue ); + outputStream.write( green ); + outputStream.write( red ); + break; + case BRG: + outputStream.write( blue ); + outputStream.write( red ); + outputStream.write( green ); + break; + case GBR: + outputStream.write( green ); + outputStream.write( blue ); + outputStream.write( red ); + break; + case GRB: + outputStream.write( green ); + outputStream.write( red ); + outputStream.write( blue ); + break; + case RBG: + outputStream.write( red ); + outputStream.write( blue ); + outputStream.write( green ); + break; + case RGB: + outputStream.write( red ); + outputStream.write( green ); + outputStream.write( blue ); + break; + } + } + + /** + * Releases the SPI interface and related resources. + */ + @Override + public void close() throws IOException { + if (mDevice != null) { + try { + mDevice.close(); + } finally { + mDevice = null; + } + } + } +} diff --git a/ws2812b/ws2812b-bit-pattern.svg b/ws2812b/ws2812b-bit-pattern.svg new file mode 100644 index 0000000..7aa6ce7 --- /dev/null +++ b/ws2812b/ws2812b-bit-pattern.svg @@ -0,0 +1,397 @@ + + + +image/svg+xml417ns1 bit + + +417ns1 bit +417ns0 bit +834ns +1 pattern +417ns1 bit +417ns0 bit +417ns0 bit +834ns +0 pattern + \ No newline at end of file diff --git a/ws2812b/ws2812b-timings.svg b/ws2812b/ws2812b-timings.svg new file mode 100644 index 0000000..3c62fd0 --- /dev/null +++ b/ws2812b/ws2812b-timings.svg @@ -0,0 +1,337 @@ + + + +image/svg+xml0code +1code +RETcode +T0H +T0L +T1H +T1L +Treset +400ns +850ns +850ns +400ns +< 50μs + + + \ No newline at end of file From 3b2c2806c7ce9f76852959152fdcd40fca2d4a23 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 29 Jun 2017 01:12:36 +0200 Subject: [PATCH 02/35] Replaced bit pattern converting algorithm --- settings.gradle | 1 + .../driver/ws2812b/BitPatternWriter.java | 114 ++++++++++++++ .../ws2812b/ColorToBitPatternConverter.java | 145 ++++++++++++++++++ .../ColorValueBitPatternConverter.java | 53 ------- .../contrib/driver/ws2812b/Ws2812b.java | 80 ++-------- 5 files changed, 275 insertions(+), 118 deletions(-) create mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java create mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java delete mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorValueBitPatternConverter.java diff --git a/settings.gradle b/settings.gradle index e1d773e..6745a23 100644 --- a/settings.gradle +++ b/settings.gradle @@ -28,5 +28,6 @@ include ':tm1637' include ':ssd1306' include ':rainbowhat' include ':sensehat' +include ':ws2812b' include ':testingutils' diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java new file mode 100644 index 0000000..d1c99d5 --- /dev/null +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java @@ -0,0 +1,114 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.support.annotation.IntRange; + +class BitPatternWriter +{ + private static final int MAX_NUMBER_OF_SUPPORTED_LEDS = 512; + private static final int NUMBER_OF_BYTES_PER_RGB_COLOR = 8; + private static final int ONE_BYTE_BIT_MASKS[] = new int [] {128, 64, 32, 16, 8, 4, 2, 1}; + + private final byte[] destinationByteBuffer; + private int currentByteBufferIndex; + private int bitMaskIndex; + private int currentWrittenByte; + + BitPatternWriter(@IntRange(from = 1, to = MAX_NUMBER_OF_SUPPORTED_LEDS) int numberOfLeds) + { + if (numberOfLeds > MAX_NUMBER_OF_SUPPORTED_LEDS) + { + throw new IllegalArgumentException("Only " + MAX_NUMBER_OF_SUPPORTED_LEDS + " LEDs are supported. A Greater Number (" + numberOfLeds + ") will result in SPI errors!"); + } + + // 3 source bits will be represented by 1 destination byte: + // - The first 2 source bits are represented by 3 destination bits + // - The last source bit is represented by 2 destination bits and 1 pause bit + // - The pause bit is automatically sent between each destination byte (it is always 0) + // + // => 3 src bits = 1 dst byte + // => 24 src = 8 dst bytes + // => 1 RGB LED color = 8 dst bytes + + int numberOfDestinationBytes = numberOfLeds * NUMBER_OF_BYTES_PER_RGB_COLOR; + + destinationByteBuffer = new byte[numberOfDestinationBytes]; + currentByteBufferIndex = 0; + bitMaskIndex = 0; + currentWrittenByte = 0; + } + + /** + * Converts one value of a channel of a RGB color into a bit pattern and writes the bit pattern in a byte buffer + * @param colorChannelValue The value of one channel of a RGB color + */ + void writeBitPatternToBuffer(@IntRange(from = 0, to = 255) int colorChannelValue) + { + // Highest byte bit + int mask = 128; + + for (int i = 0; i < 8; i++) + { + if ((colorChannelValue & mask) == mask) + { + writeOneBitPattern(); + } + else + { + writeZeroBitPattern(); + } + + colorChannelValue = colorChannelValue << 1; + } + } + + /** + * Writes the bit pattern for a 0 bit which is represented by a 1, 0, 0 + */ + private void writeZeroBitPattern() + { + currentWrittenByte = currentWrittenByte | ONE_BYTE_BIT_MASKS[bitMaskIndex++]; // Write 1 + bitMaskIndex++; // Do nothing means write 0 + bitMaskIndex++; // Do nothing means write 0 + + storeValueAndShiftToNextBitsInBuffer(); + } + + /** + * Writes the bit pattern for a 1 bit which is represented by a 1, 1, 0 + */ + private void writeOneBitPattern() + { + currentWrittenByte = currentWrittenByte | ONE_BYTE_BIT_MASKS[bitMaskIndex++]; // Write 1 + currentWrittenByte = currentWrittenByte | ONE_BYTE_BIT_MASKS[bitMaskIndex++]; // Write 1 + bitMaskIndex++; // Do nothing means write 0 + + storeValueAndShiftToNextBitsInBuffer(); + } + + private void storeValueAndShiftToNextBitsInBuffer() + { + // There is no need to to store the pause bit. It can be ignored + if (bitMaskIndex % 8 == 1) + { + destinationByteBuffer[currentByteBufferIndex] = (byte) currentWrittenByte; + currentByteBufferIndex++; + bitMaskIndex = 0; + currentWrittenByte = 0; + } + } + + void finishBitPatternBuffer() + { + if (currentByteBufferIndex < destinationByteBuffer.length) + { + destinationByteBuffer[currentByteBufferIndex] = (byte) currentWrittenByte; + } + } + + byte [] getBitPatternBuffer() + { + return destinationByteBuffer; + } + +} diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java new file mode 100644 index 0000000..8f9b31d --- /dev/null +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java @@ -0,0 +1,145 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.graphics.Color; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; + +import java.util.Arrays; + +import static com.google.android.things.contrib.driver.ws2812b.Ws2812b.*; + + +class ColorToBitPatternConverter +{ + @Ws2812b.LedMode + private final int mLedMode; + private static final int [] SUPPORTED_MODES = {RGB, RBG, GRB, GBR, BRG, BGR}; + + + ColorToBitPatternConverter(int ledMode) + { + this.mLedMode = ledMode; + } + + @NonNull + byte[] convertColorsToBitPattern(@NonNull @ColorInt int colors[]) + { + byte [] convertedColors; + switch (mLedMode) + { + case BGR: + convertedColors = convertToBgr(colors); + break; + case BRG: + convertedColors = convertToBrg(colors); + break; + case GBR: + convertedColors = convertToGbr(colors); + break; + case GRB: + convertedColors = convertToGrb(colors); + break; + case RBG: + convertedColors = convertToRbg(colors); + break; + case Ws2812b.RGB: + convertedColors = convertToRgb(colors); + break; + default: + throw new IllegalStateException("This LED mode is not supported. Chosen mode: " + mLedMode + ". Supported modes: " + Arrays.toString(SUPPORTED_MODES)); + } + return convertedColors; + + } + + private byte[] convertToBgr(@ColorInt int colors[]) + { + BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); + + for (int color : colors) + { + bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); + } + bitPatternWriter.finishBitPatternBuffer(); + + return bitPatternWriter.getBitPatternBuffer(); + } + + private byte[] convertToBrg(@ColorInt int colors[]) + { + BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); + + for (int color : colors) + { + bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); + } + bitPatternWriter.finishBitPatternBuffer(); + + return bitPatternWriter.getBitPatternBuffer(); + } + + private byte[] convertToGbr(@ColorInt int colors[]) + { + BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); + + for (int color : colors) + { + bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); + } + bitPatternWriter.finishBitPatternBuffer(); + + return bitPatternWriter.getBitPatternBuffer(); + } + + private byte[] convertToGrb(@ColorInt int colors[]) + { + BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); + + for (int color : colors) + { + bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); + } + bitPatternWriter.finishBitPatternBuffer(); + + return bitPatternWriter.getBitPatternBuffer(); + } + + private byte[] convertToRbg(@ColorInt int colors[]) + { + BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); + + for (int color : colors) + { + bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); + } + bitPatternWriter.finishBitPatternBuffer(); + + return bitPatternWriter.getBitPatternBuffer(); + } + + private byte[] convertToRgb(@ColorInt int colors[]) + { + BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); + + for (int color : colors) + { + bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); + bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); + } + bitPatternWriter.finishBitPatternBuffer(); + + return bitPatternWriter.getBitPatternBuffer(); + } +} diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorValueBitPatternConverter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorValueBitPatternConverter.java deleted file mode 100644 index 02ffece..0000000 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorValueBitPatternConverter.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.google.android.things.contrib.driver.ws2812b; - - -import android.support.annotation.IntRange; -import android.util.SparseArray; - -class ColorValueBitPatternConverter -{ - private static final byte BIT_PATTERN_01 = (byte) 142; - private static final byte BIT_PATTERN_10 = (byte) 132; - private static final byte BIT_PATTERN_11 = (byte) 238; - private static final byte BIT_PATTERN_00 = (byte) 136; - - private SparseArray colorValueToBitPatternMap = new SparseArray<>(); - - ColorValueBitPatternConverter() - { - for (int colorValue = 0; colorValue < 256; colorValue++) - { - byte[] bitPattern = new byte [4]; - String binaryString = toEightBitBinaryString(colorValue); - bitPattern[0] = convertToBitPattern(binaryString.subSequence(0, 2).toString()); - bitPattern[1] = convertToBitPattern(binaryString.subSequence(2, 4).toString()); - bitPattern[2] = convertToBitPattern(binaryString.subSequence(4, 6).toString()); - bitPattern[3] = convertToBitPattern(binaryString.subSequence(6, 8).toString()); - colorValueToBitPatternMap.put(colorValue, bitPattern); - } - } - - private static String toEightBitBinaryString(@IntRange(from = 0, to = 255) int value) - { - StringBuilder stringBuilder = new StringBuilder(Integer.toBinaryString(value)); - while (stringBuilder.length() < 8) - { - stringBuilder.insert(0, '0'); - } - return stringBuilder.toString(); - } - - private static byte convertToBitPattern(String binaryString) - { - return - "01".equals(binaryString) ? BIT_PATTERN_01 : - "10".equals(binaryString) ? BIT_PATTERN_10 : - "11".equals(binaryString) ? BIT_PATTERN_11 : - BIT_PATTERN_00; - } - - byte[] convertColorValue(@IntRange(from = 0, to = 255) int colorValue) - { - return colorValueToBitPatternMap.get(colorValue); - } -} diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java index f6c4536..fb646c9 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java @@ -19,12 +19,10 @@ import android.graphics.Color; import android.support.annotation.ColorInt; import android.support.annotation.IntDef; -import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import com.google.android.things.pio.PeripheralManagerService; import com.google.android.things.pio.SpiDevice; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.annotation.Retention; @@ -56,15 +54,11 @@ public class Ws2812b implements AutoCloseable { public static final int BRG = 4; public static final int BGR = 5; - private final ColorValueBitPatternConverter bitPatternConverter = new ColorValueBitPatternConverter(); - - // RGB LED strip configuration that must be provided by the caller. - @LedMode - private int mLedMode; - // For peripherals access private SpiDevice mDevice = null; + private final ColorToBitPatternConverter colorToBitPatternConverter; + /** * Create a new WS2812B driver. * @@ -81,7 +75,7 @@ public Ws2812b(String spiBusPort) throws IOException { * @param ledMode The {@link LedMode} indicating the red/green/blue byte ordering for the device. */ public Ws2812b(String spiBusPort, @LedMode int ledMode) throws IOException { - mLedMode = ledMode; + colorToBitPatternConverter = new ColorToBitPatternConverter(ledMode); PeripheralManagerService pioService = new PeripheralManagerService(); mDevice = pioService.openSpiDevice(spiBusPort); try { @@ -96,24 +90,27 @@ public Ws2812b(String spiBusPort, @LedMode int ledMode) throws IOException { } /** - * Create a new Apa102 driver. + * Create a new WS2812B driver. * * @param device {@link SpiDevice} where the LED strip is attached to. * @param ledMode The {@link LedMode} indicating the red/green/blue byte ordering for the device. */ @VisibleForTesting /*package*/ Ws2812b(SpiDevice device, @LedMode int ledMode) throws IOException { - mLedMode = ledMode; + colorToBitPatternConverter = new ColorToBitPatternConverter(ledMode); mDevice = device; configure(mDevice); } private void configure(SpiDevice device) throws IOException { - // Note: You may need to set bit justification for your board. - // mDevice.setBitJustification(SPI_BITJUST); - device.setFrequency(3225806); - device.setMode(SpiDevice.MODE1); + + double durationOfOneBitInNs = 417.0; + double durationOfOneBitInS = durationOfOneBitInNs * Math.pow(10, -9); + int frequencyInHz = (int) Math.round(1.0 / durationOfOneBitInS); + + device.setFrequency(frequencyInHz); device.setBitsPerWord(8); + device.setDelay(0); } /** @@ -123,59 +120,12 @@ private void configure(SpiDevice device) throws IOException { */ public void write(@ColorInt int[] colors) throws IOException { if (mDevice == null) { - throw new IllegalStateException("SPI device not open"); + throw new IllegalStateException("SPI device not opened"); } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream( ); - for (int color : colors) - { - writeColorPattern(outputStream, color); - } - - byte[] colorPattern = outputStream.toByteArray(); + byte[] convertedColors = colorToBitPatternConverter.convertColorsToBitPattern(colors); - mDevice.write(colorPattern, colorPattern.length); - } - - private void writeColorPattern(@NonNull ByteArrayOutputStream outputStream, @ColorInt int color) throws IOException - { - byte[] red = bitPatternConverter.convertColorValue(Color.red(color)); - byte[] green = bitPatternConverter.convertColorValue(Color.green(color)); - byte[] blue = bitPatternConverter.convertColorValue(Color.blue(color)); - - switch (mLedMode) - { - case BGR: - outputStream.write( blue ); - outputStream.write( green ); - outputStream.write( red ); - break; - case BRG: - outputStream.write( blue ); - outputStream.write( red ); - outputStream.write( green ); - break; - case GBR: - outputStream.write( green ); - outputStream.write( blue ); - outputStream.write( red ); - break; - case GRB: - outputStream.write( green ); - outputStream.write( red ); - outputStream.write( blue ); - break; - case RBG: - outputStream.write( red ); - outputStream.write( blue ); - outputStream.write( green ); - break; - case RGB: - outputStream.write( red ); - outputStream.write( green ); - outputStream.write( blue ); - break; - } + mDevice.write(convertedColors, convertedColors.length); } /** From 8014560ec1fd36089608fcb9724aadf32e58392b Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Mon, 14 Aug 2017 22:40:46 +0200 Subject: [PATCH 03/35] Added comments, variable name refactoring, formatation --- .../driver/ws2812b/BitPatternWriter.java | 112 +++++++++--------- .../ws2812b/ColorToBitPatternConverter.java | 52 +++----- .../contrib/driver/ws2812b/Ws2812b.java | 13 +- 3 files changed, 79 insertions(+), 98 deletions(-) diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java index d1c99d5..7cd1a3f 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java @@ -3,58 +3,59 @@ import android.support.annotation.IntRange; -class BitPatternWriter -{ +class BitPatternWriter { private static final int MAX_NUMBER_OF_SUPPORTED_LEDS = 512; private static final int NUMBER_OF_BYTES_PER_RGB_COLOR = 8; - private static final int ONE_BYTE_BIT_MASKS[] = new int [] {128, 64, 32, 16, 8, 4, 2, 1}; + private static final int ONE_BYTE_BIT_MASKS[] = new int[]{128, 64, 32, 16, 8, 4, 2, 1}; - private final byte[] destinationByteBuffer; - private int currentByteBufferIndex; - private int bitMaskIndex; - private int currentWrittenByte; + private final byte[] mDestinationByteBuffer; + private int mCurrentByteBufferIndex; + private int mBitMaskIndex; + private int mCurrentWrittenByte; - BitPatternWriter(@IntRange(from = 1, to = MAX_NUMBER_OF_SUPPORTED_LEDS) int numberOfLeds) - { - if (numberOfLeds > MAX_NUMBER_OF_SUPPORTED_LEDS) - { + /** + * Converts bits of a color value with two bit patterns to bits comprehensible for a WS2812B + * controller. To convert and write a color value call {@link #writeBitPatternToBuffer(int)}. + * These converted bits are written to a buffer. The buffer is a simple byte array + * which can be retrieved with {@link #getBitPatternBuffer()} after + * {@link #finishBitPatternBuffer()} was called + * 1 src bit + * 3 source bits will be represented by 1 destination byte: + * - The first 2 source bits are represented by 3 destination bits + * - The last source bit is represented by 2 destination bits and 1 pause bit + * - The pause bit is automatically sent between each destination byte (it is always 0) + * + * => 3 src bits = 1 dst byte + * => 24 src bits = 8 dst bytes + * => 1 RGB LED color = 8 dst bytes + * @param numberOfLeds number of LEDs of the + */ + BitPatternWriter(@IntRange(from = 1, to = MAX_NUMBER_OF_SUPPORTED_LEDS) int numberOfLeds) { + if (numberOfLeds > MAX_NUMBER_OF_SUPPORTED_LEDS) { throw new IllegalArgumentException("Only " + MAX_NUMBER_OF_SUPPORTED_LEDS + " LEDs are supported. A Greater Number (" + numberOfLeds + ") will result in SPI errors!"); } - // 3 source bits will be represented by 1 destination byte: - // - The first 2 source bits are represented by 3 destination bits - // - The last source bit is represented by 2 destination bits and 1 pause bit - // - The pause bit is automatically sent between each destination byte (it is always 0) - // - // => 3 src bits = 1 dst byte - // => 24 src = 8 dst bytes - // => 1 RGB LED color = 8 dst bytes - int numberOfDestinationBytes = numberOfLeds * NUMBER_OF_BYTES_PER_RGB_COLOR; - destinationByteBuffer = new byte[numberOfDestinationBytes]; - currentByteBufferIndex = 0; - bitMaskIndex = 0; - currentWrittenByte = 0; + mDestinationByteBuffer = new byte[numberOfDestinationBytes]; + mCurrentByteBufferIndex = 0; + mBitMaskIndex = 0; + mCurrentWrittenByte = 0; } /** * Converts one value of a channel of a RGB color into a bit pattern and writes the bit pattern in a byte buffer + * * @param colorChannelValue The value of one channel of a RGB color */ - void writeBitPatternToBuffer(@IntRange(from = 0, to = 255) int colorChannelValue) - { + void writeBitPatternToBuffer(@IntRange(from = 0, to = 255) int colorChannelValue) { // Highest byte bit int mask = 128; - for (int i = 0; i < 8; i++) - { - if ((colorChannelValue & mask) == mask) - { + for (int i = 0; i < 8; i++) { + if ((colorChannelValue & mask) == mask) { writeOneBitPattern(); - } - else - { + } else { writeZeroBitPattern(); } @@ -65,11 +66,10 @@ void writeBitPatternToBuffer(@IntRange(from = 0, to = 255) int colorChannelValue /** * Writes the bit pattern for a 0 bit which is represented by a 1, 0, 0 */ - private void writeZeroBitPattern() - { - currentWrittenByte = currentWrittenByte | ONE_BYTE_BIT_MASKS[bitMaskIndex++]; // Write 1 - bitMaskIndex++; // Do nothing means write 0 - bitMaskIndex++; // Do nothing means write 0 + private void writeZeroBitPattern() { + mCurrentWrittenByte = mCurrentWrittenByte | ONE_BYTE_BIT_MASKS[mBitMaskIndex++]; // Write 1 + mBitMaskIndex++; // Do nothing means write 0 + mBitMaskIndex++; // Do nothing means write 0 storeValueAndShiftToNextBitsInBuffer(); } @@ -77,38 +77,32 @@ private void writeZeroBitPattern() /** * Writes the bit pattern for a 1 bit which is represented by a 1, 1, 0 */ - private void writeOneBitPattern() - { - currentWrittenByte = currentWrittenByte | ONE_BYTE_BIT_MASKS[bitMaskIndex++]; // Write 1 - currentWrittenByte = currentWrittenByte | ONE_BYTE_BIT_MASKS[bitMaskIndex++]; // Write 1 - bitMaskIndex++; // Do nothing means write 0 + private void writeOneBitPattern() { + mCurrentWrittenByte = mCurrentWrittenByte | ONE_BYTE_BIT_MASKS[mBitMaskIndex++]; // Write 1 + mCurrentWrittenByte = mCurrentWrittenByte | ONE_BYTE_BIT_MASKS[mBitMaskIndex++]; // Write 1 + mBitMaskIndex++; // Do nothing means write 0 storeValueAndShiftToNextBitsInBuffer(); } - private void storeValueAndShiftToNextBitsInBuffer() - { + private void storeValueAndShiftToNextBitsInBuffer() { // There is no need to to store the pause bit. It can be ignored - if (bitMaskIndex % 8 == 1) - { - destinationByteBuffer[currentByteBufferIndex] = (byte) currentWrittenByte; - currentByteBufferIndex++; - bitMaskIndex = 0; - currentWrittenByte = 0; + if (mBitMaskIndex % 8 == 1) { + mDestinationByteBuffer[mCurrentByteBufferIndex] = (byte) mCurrentWrittenByte; + mCurrentByteBufferIndex++; + mBitMaskIndex = 0; + mCurrentWrittenByte = 0; } } - void finishBitPatternBuffer() - { - if (currentByteBufferIndex < destinationByteBuffer.length) - { - destinationByteBuffer[currentByteBufferIndex] = (byte) currentWrittenByte; + void finishBitPatternBuffer() { + if (mCurrentByteBufferIndex < mDestinationByteBuffer.length) { + mDestinationByteBuffer[mCurrentByteBufferIndex] = (byte) mCurrentWrittenByte; } } - byte [] getBitPatternBuffer() - { - return destinationByteBuffer; + byte[] getBitPatternBuffer() { + return mDestinationByteBuffer; } } diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java index 8f9b31d..9bc32ae 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java @@ -10,24 +10,20 @@ import static com.google.android.things.contrib.driver.ws2812b.Ws2812b.*; -class ColorToBitPatternConverter -{ +class ColorToBitPatternConverter { @Ws2812b.LedMode private final int mLedMode; - private static final int [] SUPPORTED_MODES = {RGB, RBG, GRB, GBR, BRG, BGR}; + private static final int[] SUPPORTED_MODES = {RGB, RBG, GRB, GBR, BRG, BGR}; - ColorToBitPatternConverter(int ledMode) - { + ColorToBitPatternConverter(@LedMode int ledMode) { this.mLedMode = ledMode; } @NonNull - byte[] convertColorsToBitPattern(@NonNull @ColorInt int colors[]) - { - byte [] convertedColors; - switch (mLedMode) - { + byte[] convertColorsToBitPattern(@NonNull @ColorInt int colors[]) { + byte[] convertedColors; + switch (mLedMode) { case BGR: convertedColors = convertToBgr(colors); break; @@ -53,12 +49,10 @@ byte[] convertColorsToBitPattern(@NonNull @ColorInt int colors[]) } - private byte[] convertToBgr(@ColorInt int colors[]) - { + private byte[] convertToBgr(@ColorInt int colors[]) { BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - for (int color : colors) - { + for (int color : colors) { bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); @@ -68,12 +62,10 @@ private byte[] convertToBgr(@ColorInt int colors[]) return bitPatternWriter.getBitPatternBuffer(); } - private byte[] convertToBrg(@ColorInt int colors[]) - { + private byte[] convertToBrg(@ColorInt int colors[]) { BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - for (int color : colors) - { + for (int color : colors) { bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); @@ -83,12 +75,10 @@ private byte[] convertToBrg(@ColorInt int colors[]) return bitPatternWriter.getBitPatternBuffer(); } - private byte[] convertToGbr(@ColorInt int colors[]) - { + private byte[] convertToGbr(@ColorInt int colors[]) { BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - for (int color : colors) - { + for (int color : colors) { bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); @@ -98,12 +88,10 @@ private byte[] convertToGbr(@ColorInt int colors[]) return bitPatternWriter.getBitPatternBuffer(); } - private byte[] convertToGrb(@ColorInt int colors[]) - { + private byte[] convertToGrb(@ColorInt int colors[]) { BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - for (int color : colors) - { + for (int color : colors) { bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); @@ -113,12 +101,10 @@ private byte[] convertToGrb(@ColorInt int colors[]) return bitPatternWriter.getBitPatternBuffer(); } - private byte[] convertToRbg(@ColorInt int colors[]) - { + private byte[] convertToRbg(@ColorInt int colors[]) { BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - for (int color : colors) - { + for (int color : colors) { bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); @@ -128,12 +114,10 @@ private byte[] convertToRbg(@ColorInt int colors[]) return bitPatternWriter.getBitPatternBuffer(); } - private byte[] convertToRgb(@ColorInt int colors[]) - { + private byte[] convertToRgb(@ColorInt int colors[]) { BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - for (int color : colors) - { + for (int color : colors) { bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java index fb646c9..5de0e90 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java @@ -57,7 +57,7 @@ public class Ws2812b implements AutoCloseable { // For peripherals access private SpiDevice mDevice = null; - private final ColorToBitPatternConverter colorToBitPatternConverter; + private final ColorToBitPatternConverter mColorToBitPatternConverter; /** * Create a new WS2812B driver. @@ -73,9 +73,11 @@ public Ws2812b(String spiBusPort) throws IOException { * * @param spiBusPort Name of the SPI bus * @param ledMode The {@link LedMode} indicating the red/green/blue byte ordering for the device. + * @throws IOException if the initialization of the SpiDevice fails + * */ public Ws2812b(String spiBusPort, @LedMode int ledMode) throws IOException { - colorToBitPatternConverter = new ColorToBitPatternConverter(ledMode); + mColorToBitPatternConverter = new ColorToBitPatternConverter(ledMode); PeripheralManagerService pioService = new PeripheralManagerService(); mDevice = pioService.openSpiDevice(spiBusPort); try { @@ -97,7 +99,7 @@ public Ws2812b(String spiBusPort, @LedMode int ledMode) throws IOException { */ @VisibleForTesting /*package*/ Ws2812b(SpiDevice device, @LedMode int ledMode) throws IOException { - colorToBitPatternConverter = new ColorToBitPatternConverter(ledMode); + mColorToBitPatternConverter = new ColorToBitPatternConverter(ledMode); mDevice = device; configure(mDevice); } @@ -116,20 +118,21 @@ private void configure(SpiDevice device) throws IOException { /** * Writes the current RGB Led data to the peripheral bus. * @param colors An array of integers corresponding to a {@link Color}. - * @throws IOException + * @throws IOException if writing to the SPi device fails */ public void write(@ColorInt int[] colors) throws IOException { if (mDevice == null) { throw new IllegalStateException("SPI device not opened"); } - byte[] convertedColors = colorToBitPatternConverter.convertColorsToBitPattern(colors); + byte[] convertedColors = mColorToBitPatternConverter.convertColorsToBitPattern(colors); mDevice.write(convertedColors, convertedColors.length); } /** * Releases the SPI interface and related resources. + * @throws IOException if the SpiDevice is already closed */ @Override public void close() throws IOException { From 1a6e1707c42e203dfacb82fa2d22f1df72c65ce5 Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Thu, 17 Aug 2017 20:37:07 +0200 Subject: [PATCH 04/35] Added tests: Close SPI device Close SPI device twice Write red to SPI device --- ws2812b/build.gradle | 24 ++- .../contrib/driver/ws2812b/ColorMock.java | 80 ++++++++++ .../contrib/driver/ws2812b/Ws2812bTest.java | 137 ++++++++++++++++++ 3 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java diff --git a/ws2812b/build.gradle b/ws2812b/build.gradle index f05e799..e41ad57 100644 --- a/ws2812b/build.gradle +++ b/ws2812b/build.gradle @@ -17,18 +17,30 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 24 - buildToolsVersion '24.0.3' + compileSdkVersion 25 + buildToolsVersion '25.0.3' defaultConfig { - minSdkVersion 24 - targetSdkVersion 24 + minSdkVersion 25 + targetSdkVersion 25 versionCode 1 versionName "1.0" } } +repositories { // + jcenter() // < + maven { // < CAN BE DELETED IF contrib-drivers was updated + url "https://maven.google.com" // < + } // +} + dependencies { - provided 'com.google.android.things:androidthings:0.4-devpreview' - compile 'com.android.support:support-annotations:25.3.1' + provided 'com.google.android.things:androidthings:0.5-devpreview' + compile 'com.android.support:support-annotations:25.4.0' + + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:1.10.19' + testCompile 'org.powermock:powermock-module-junit4:1.6.6' + testCompile 'org.powermock:powermock-api-mockito:1.6.6' } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java new file mode 100644 index 0000000..a182d2b --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.things.contrib.driver.ws2812b; + +import android.graphics.Color; + +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; + +import static org.mockito.Matchers.anyInt; + +class ColorMock { + static void mockStatic() { + PowerMockito.mockStatic(Color.class); + + mockRed(); + mockGreen(); + mockBlue(); + mockArgb(); + } + + private static void mockArgb() { + Mockito.when(Color.argb(anyInt(), anyInt(), anyInt(), anyInt())).thenAnswer(new Answer() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + int a = invocation.getArgumentAt(0, Integer.class); + int r = invocation.getArgumentAt(1, Integer.class); + int g = invocation.getArgumentAt(2, Integer.class); + int b = invocation.getArgumentAt(3, Integer.class); + return (a << 24) | (r << 16) | (g << 8) | b; + } + }); + } + + private static void mockBlue() { + Mockito.when(Color.blue(anyInt())).thenAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + int c = invocation.getArgumentAt(0, Integer.class); + return c & 0xff; + } + }); + } + + private static void mockGreen() { + Mockito.when(Color.green(anyInt())).thenAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + int c = invocation.getArgumentAt(0, Integer.class); + return (c >> 8) & 0xff; + } + }); + } + + private static void mockRed() { + Mockito.when(Color.red(anyInt())).thenAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + int c = invocation.getArgumentAt(0, Integer.class); + return (c >> 16) & 0xff; + } + }); + } +} diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java new file mode 100644 index 0000000..c7449ea --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java @@ -0,0 +1,137 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.graphics.Color; + +import com.google.android.things.pio.SpiDevice; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + + +@RunWith(PowerMockRunner.class) +@PrepareForTest(android.graphics.Color.class) +public class Ws2812bTest { + @Mock + private SpiDevice mSpiDevice; + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Rule + public ExpectedException mExpectedException = ExpectedException.none(); + + @Test + public void close() throws IOException { + Ws2812b leds = new Ws2812b(mSpiDevice, Ws2812b.RGB); + leds.close(); + Mockito.verify(mSpiDevice).close(); + } + + @Test + public void close_safeToCallTwice() throws IOException { + Ws2812b ws2812b = new Ws2812b(mSpiDevice, Ws2812b.RBG); + ws2812b.close(); + ws2812b.close(); + // Check if the inner SPI device was only closed once + Mockito.verify(mSpiDevice, Mockito.times(1)).close(); + } + + @Test + public void writeRed() throws IOException { + ColorMock.mockStatic(); + + int color = Color.RED; + + Ws2812b ws2812b = new Ws2812b(mSpiDevice, Ws2812b.RBG); + ws2812b.write(new int [] {Color.RED}); + + List bitPatterns = new ArrayList<>(); + List oneBitPattern = Arrays.asList(true, true, false); + List zeroBitPattern = Arrays.asList(true, false, false); + + List originalBits = new ArrayList<>(); + + int highestBit = 1<<23; + + for (int i = 0; i < 24; i++) { + List bitPattern = (color & highestBit) == highestBit ? oneBitPattern : zeroBitPattern; + bitPatterns.addAll(bitPattern); + originalBits.add((color & highestBit) == highestBit); + color = color << 1; + } + + Iterator iterator = bitPatterns.iterator(); + int i = 0; + while (iterator.hasNext()) { + iterator.next(); + if (i == 8) + { + iterator.remove(); + i = 0; + continue; + } + i++; + } + + + int [] masks = { + 0b1000_0000, + 0b0100_0000, + 0b0010_0000, + 0b0001_0000, + 0b0000_1000, + 0b0000_0100, + 0b0000_0010, + 0b0000_0001, + }; + + byte [] bytes = new byte[bitPatterns.size() / 8]; + byte currentByte = 0; + i = 0; + int j = 0; + for (Boolean bitValue : bitPatterns) { + if (bitValue) + { + currentByte |= masks[i]; + } + if (i == 7) + { + bytes[j++] = currentByte; + currentByte = 0; + i = 0; + continue; + } + i++; + } + + + + + + + Mockito.verify(mSpiDevice).write(bytes, bytes.length); + } + + private List flatten(List> bitPatterns) { + List flattenBitPatterns = new ArrayList<>(); + for (List bitPattern : bitPatterns) { + flattenBitPatterns.addAll(bitPattern); + } + return flattenBitPatterns; + } +} From cc4546a591861227542d4694f5d5859a34774974 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 21 Aug 2017 20:44:26 +0200 Subject: [PATCH 05/35] Added additional tests Added a faster strategy calculate the bit patterns --- gradle/wrapper/gradle-wrapper.properties | 4 +- .../driver/ws2812b/BitPatternHolder.java | 145 ++++++++++++++++++ .../driver/ws2812b/BitPatternWriter.java | 9 +- .../driver/ws2812b/ColorOrderShifter.java | 42 +++++ .../driver/ws2812b/BitPatternHolderTest.java | 55 +++++++ .../contrib/driver/ws2812b/ColorMock.java | 13 ++ .../driver/ws2812b/ColorOrderShifterTest.java | 99 ++++++++++++ .../contrib/driver/ws2812b/Ws2812bTest.java | 23 ++- 8 files changed, 373 insertions(+), 17 deletions(-) create mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolder.java create mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifter.java create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolderTest.java create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifterTest.java diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6f14fa2..537ab17 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Jan 25 12:34:01 PST 2017 +#Tue Aug 15 21:09:08 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolder.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolder.java new file mode 100644 index 0000000..488db78 --- /dev/null +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolder.java @@ -0,0 +1,145 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.support.annotation.IntRange; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * Map which stores bit patterns for each possible 12 bit number -> (numbers from 0 to 4096) + */ +public class BitPatternHolder { + private static final int BIGGEST_12_BIT_NUMBER = (1 << 12) - 1; // 2¹² - 1 = 4095 + private static final List BIT_PATTERN_FOR_ZERO_BIT = Arrays.asList(true, false, false); + private static final List BIT_PATTERN_FOR_ONE_BIT = Arrays.asList(true, true, false); + private static final int ONE_BYTE_BIT_MASKS[] = new int[]{ 0b10000000, + 0b01000000, + 0b00100000, + 0b00010000, + 0b00001000, + 0b00000100, + 0b00000010, + 0b00000001}; + + private final Storage bitPatternStorage; + + public BitPatternHolder() { + this(new DefaultStorage()); + } + + @VisibleForTesting + /*package*/ BitPatternHolder(@NonNull Storage storage) { + this.bitPatternStorage = storage; + fillBitPatternCache(); + } + + /** + * Returns for each possible 12 bit integer an corresponding bit pattern as byte array. + * Throws an {@link IllegalArgumentException} if the integer is using more than 12 bit. + * + * @param twelveBitValue A 12 bit integer (from 0 to 4095) + * @return The corresponding bit pattern as byte array + */ + @NonNull + public byte[] getBitPattern(@IntRange(from = 0, to = BIGGEST_12_BIT_NUMBER) int twelveBitValue) { + byte[] bitPatternByteArray = bitPatternStorage.get(twelveBitValue); + if (bitPatternByteArray == null) + { + throw new IllegalArgumentException("Only values from 0 to " + BIGGEST_12_BIT_NUMBER + " are allowed. The passed input value was: " + twelveBitValue); + } + return bitPatternByteArray; + } + + private void fillBitPatternCache() { + for (int i = 0; i <= BIGGEST_12_BIT_NUMBER; i++) { + bitPatternStorage.put(i, calculateBitPatternByteArray(i)); + } + } + + private byte[] calculateBitPatternByteArray(@IntRange(from = 0, to = BIGGEST_12_BIT_NUMBER) int twelveBitNumber) { + List bitPatterns = new ArrayList<>(); + int highest12BitBitMask = 1 << 11; + for (int i = 0; i < 12; i++) { + if ((twelveBitNumber & highest12BitBitMask) == highest12BitBitMask) + { + bitPatterns.addAll(BIT_PATTERN_FOR_ONE_BIT); + } + else + { + bitPatterns.addAll(BIT_PATTERN_FOR_ZERO_BIT); + } + twelveBitNumber = twelveBitNumber << 1; + } + bitPatterns = removePauseBits(bitPatterns); + return convertBitPatternsToByteArray(bitPatterns); + } + + private List removePauseBits(List bitPatterns) { + Iterator iterator = bitPatterns.iterator(); + int i = 0; + while (iterator.hasNext()) { + iterator.next(); + if (i == 8) + { + iterator.remove(); + i = 0; + continue; + } + i++; + } + return bitPatterns; + } + + private byte[] convertBitPatternsToByteArray(List bitPatterns) { + + if (bitPatterns.size() != 32) + { + throw new IllegalStateException("Undefined bit pattern size"); + } + byte[] bitPatternsAsByteArray = new byte[4]; + bitPatternsAsByteArray [0] = convertBitPatternsToByte(bitPatterns.subList(0, 8)); + bitPatternsAsByteArray [1] = convertBitPatternsToByte(bitPatterns.subList(8, 16)); + bitPatternsAsByteArray [2] = convertBitPatternsToByte(bitPatterns.subList(16, 24)); + bitPatternsAsByteArray [3] = convertBitPatternsToByte(bitPatterns.subList(24, 32)); + return bitPatternsAsByteArray; + } + + private byte convertBitPatternsToByte(List bitPatterns) { + int bitPatternByte = 0; + for (int i = 0; i < 8; i++) { + if (bitPatterns.get(i)) + { + bitPatternByte |= ONE_BYTE_BIT_MASKS[i]; + } + } + return (byte) bitPatternByte; + } + + @VisibleForTesting + /*package*/ interface Storage + { + void put(int key, byte[] value); + byte[] get(int key); + } + + private static class DefaultStorage implements Storage + { + private final SparseArray sparseArray = new SparseArray<>(); + + @Override + public void put(int key, byte[] value) { + sparseArray.append(key, value); + } + + @Override + public byte[] get(int key) { + return sparseArray.get(key); + } + } +} diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java index 7cd1a3f..ca6185b 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java @@ -6,7 +6,14 @@ class BitPatternWriter { private static final int MAX_NUMBER_OF_SUPPORTED_LEDS = 512; private static final int NUMBER_OF_BYTES_PER_RGB_COLOR = 8; - private static final int ONE_BYTE_BIT_MASKS[] = new int[]{128, 64, 32, 16, 8, 4, 2, 1}; + private static final int ONE_BYTE_BIT_MASKS[] = new int[]{0b10000000, + 0b01000000, + 0b00100000, + 0b00010000, + 0b00001000, + 0b00000100, + 0b00000010, + 0b00000001}; private final byte[] mDestinationByteBuffer; private int mCurrentByteBufferIndex; diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifter.java new file mode 100644 index 0000000..1ccbc38 --- /dev/null +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifter.java @@ -0,0 +1,42 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.support.annotation.ColorInt; +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +public class ColorOrderShifter { + + @Retention(SOURCE) + @IntDef({RGB, RBG, GRB, GBR, BRG, BGR}) + public @interface Order {} + public static final int RGB = 1; + public static final int RBG = 2; + public static final int GRB = 3; + public static final int GBR = 4; + public static final int BRG = 5; + public static final int BGR = 6; + + public static int reorderColorToRbg(@ColorInt int color) { + return (color & 0xff0000) | ((color & 0xff00) >> 8) | ((color & 0xff) << 8); + } + + public static int reorderColorToGrb(@ColorInt int color) { + return ((color & 0xff0000) >> 8) | ((color & 0xff00) << 8) | (color & 0xff); + } + + public static int reorderColorToGbr(@ColorInt int color) { + return ((color & 0xff0000) >> 16) | ((color & 0xff00) << 8) | ((color & 0xff) << 8); + } + + public static int reorderColorToBrg(@ColorInt int color) { + return ((color & 0xff0000) >> 8) | ((color & 0xff00) >> 8) | ((color & 0xff) << 16); + } + + public static int reorderColorToBgr(@ColorInt int color) { + return ((color & 0xff0000) >> 16) | (color & 0xff00) | ((color & 0xff) << 16); + } +} diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolderTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolderTest.java new file mode 100644 index 0000000..9dff5dc --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolderTest.java @@ -0,0 +1,55 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.annotation.SuppressLint; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + + +@RunWith(PowerMockRunner.class) +public class BitPatternHolderTest { + + private BitPatternHolder bitPatternHolder; + + @Before + public void setUp() throws Exception { + bitPatternHolder = new BitPatternHolder(new BitPatternHolder.Storage() { + @SuppressLint("UseSparseArrays") // Sparse array is not available for Unit tests + private Map map = new HashMap<>(); + @Override + public void put(int key, byte[] value) { + map.put(key, value); + } + + @Override + public byte[] get(int key) { + return map.get(key); + } + }); + } + + @Test + public void getBitPattern() throws IOException { + double limit = Math.pow(2, 12); + for (int i = 0; i < limit; i++) { + //noinspection ConstantConditions + if (bitPatternHolder.getBitPattern(i) == null) + { + throw new AssertionError("Bit pattern not found for value: " + i); + } + } + } + + @Test (expected = IllegalArgumentException.class) + public void getBitPatternIllegalArgumentException() throws IOException { + int limit = (int) Math.pow(2, 12); + bitPatternHolder.getBitPattern(limit); + } +} diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java index a182d2b..eeacab0 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java @@ -32,9 +32,22 @@ static void mockStatic() { mockRed(); mockGreen(); mockBlue(); + mockRgb(); mockArgb(); } + private static void mockRgb() { + Mockito.when(Color.rgb(anyInt(), anyInt(), anyInt())).thenAnswer(new Answer() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + int r = invocation.getArgumentAt(0, Integer.class); + int g = invocation.getArgumentAt(1, Integer.class); + int b = invocation.getArgumentAt(2, Integer.class); + return (r << 16) | (g << 8) | b; + } + }); + } + private static void mockArgb() { Mockito.when(Color.argb(anyInt(), anyInt(), anyInt(), anyInt())).thenAnswer(new Answer() { @Override diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifterTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifterTest.java new file mode 100644 index 0000000..11f419b --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifterTest.java @@ -0,0 +1,99 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.graphics.Color; +import android.support.annotation.ColorInt; +import android.support.annotation.IntRange; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + + +@RunWith(PowerMockRunner.class) +@PrepareForTest(android.graphics.Color.class) +public class ColorOrderShifterTest { + + private int red; + private int green; + private int blue; + + @Before + public void setUp() { + ColorMock.mockStatic(); + + red = generateRandomColorValue(); + green = generateRandomColorValue(); + blue = generateRandomColorValue(); + } + + @Test + public void reorderToRbg(){ + + int rbgColor = ColorOrderShifter.reorderColorToRbg(Color.rgb(red, green, blue)); + + Assert.assertEquals(get1stColor(rbgColor), red); + Assert.assertEquals(get2ndColor(rbgColor), blue); + Assert.assertEquals(get3ndColor(rbgColor), green); + } + + @Test + public void reorderToGrb(){ + int grb = ColorOrderShifter.reorderColorToGrb(Color.rgb(red, green, blue)); + + Assert.assertEquals(get1stColor(grb), green); + Assert.assertEquals(get2ndColor(grb), red); + Assert.assertEquals(get3ndColor(grb), blue); + } + + @Test + public void reorderToGbr(){ + int gbrColor = ColorOrderShifter.reorderColorToGbr(Color.rgb(red, green, blue)); + + Assert.assertEquals(get1stColor(gbrColor), green); + Assert.assertEquals(get2ndColor(gbrColor), blue); + Assert.assertEquals(get3ndColor(gbrColor), red); + } + + @Test + public void reorderToBrg(){ + int brgColor = ColorOrderShifter.reorderColorToBrg(Color.rgb(red, green, blue)); + + Assert.assertEquals(get1stColor(brgColor), blue); + Assert.assertEquals(get2ndColor(brgColor), red); + Assert.assertEquals(get3ndColor(brgColor), green); + } + + @Test + public void reorderToBgr(){ + int bgrColor = ColorOrderShifter.reorderColorToBgr(Color.rgb(red, green, blue)); + + Assert.assertEquals(get1stColor(bgrColor), blue); + Assert.assertEquals(get2ndColor(bgrColor), green); + Assert.assertEquals(get3ndColor(bgrColor), red); + } + + @IntRange(from = 0, to = 255) + private int generateRandomColorValue() { + return (int) Math.round(Math.random() * 255); + } + + private static int get1stColor(@ColorInt int color) + { + return Color.red(color); + } + + private static int get2ndColor(@ColorInt int color) + { + return Color.green(color); + } + + private static int get3ndColor(@ColorInt int color) + { + return Color.blue(color); + } +} diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java index c7449ea..ef8ae96 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java @@ -37,8 +37,8 @@ public class Ws2812bTest { @Test public void close() throws IOException { - Ws2812b leds = new Ws2812b(mSpiDevice, Ws2812b.RGB); - leds.close(); + Ws2812b ws2812b = new Ws2812b(mSpiDevice, Ws2812b.RGB); + ws2812b.close(); Mockito.verify(mSpiDevice).close(); } @@ -64,14 +64,12 @@ public void writeRed() throws IOException { List oneBitPattern = Arrays.asList(true, true, false); List zeroBitPattern = Arrays.asList(true, false, false); - List originalBits = new ArrayList<>(); int highestBit = 1<<23; for (int i = 0; i < 24; i++) { List bitPattern = (color & highestBit) == highestBit ? oneBitPattern : zeroBitPattern; bitPatterns.addAll(bitPattern); - originalBits.add((color & highestBit) == highestBit); color = color << 1; } @@ -120,18 +118,15 @@ public void writeRed() throws IOException { } - - + int firstTwelveBit = 0x00FFF000; + int secondTwelveBit = 0x00000FFF; + int firstValue = (Color.RED & firstTwelveBit) >> 12; + int secondValue = Color.RED & secondTwelveBit; + BitPatternHolder bitPatternHolder = new BitPatternHolder(); + byte[] bitPattern = bitPatternHolder.getBitPattern(firstValue); + byte[] secondBitPattern = bitPatternHolder.getBitPattern(secondValue); Mockito.verify(mSpiDevice).write(bytes, bytes.length); } - - private List flatten(List> bitPatterns) { - List flattenBitPatterns = new ArrayList<>(); - for (List bitPattern : bitPatterns) { - flattenBitPatterns.addAll(bitPattern); - } - return flattenBitPatterns; - } } From 66c2f1bebd5689cfcd493278824d8a85f34c3e1b Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 28 Aug 2017 23:29:06 +0200 Subject: [PATCH 06/35] Using a map to cache and convert the bit patterns Added unit tests for this mapping strategy --- .../driver/ws2812b/BitPatternWriter.java | 115 ------------- .../driver/ws2812b/ColorChannelSequence.java | 90 ++++++++++ .../driver/ws2812b/ColorOrderShifter.java | 42 ----- .../ws2812b/ColorToBitPatternConverter.java | 160 +++++------------- ...va => TwelveBitIntToBitPatternMapper.java} | 38 +++-- .../contrib/driver/ws2812b/Ws2812b.java | 67 +++----- .../ws2812b/ColorChannelSequenceTest.java | 121 +++++++++++++ .../driver/ws2812b/ColorOrderShifterTest.java | 99 ----------- ...> TwelveBitIntToBitPatternMapperTest.java} | 26 ++- .../contrib/driver/ws2812b/Ws2812bTest.java | 97 +++-------- .../util/BitPatternTo12BitIntConverter.java | 76 +++++++++ .../driver/ws2812b/{ => util}/ColorMock.java | 6 +- .../util/SimpleBitPatternTestConverter.java | 86 ++++++++++ 13 files changed, 507 insertions(+), 516 deletions(-) delete mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java create mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java delete mode 100644 ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifter.java rename ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/{BitPatternHolder.java => TwelveBitIntToBitPatternMapper.java} (76%) create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java delete mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifterTest.java rename ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/{BitPatternHolderTest.java => TwelveBitIntToBitPatternMapperTest.java} (60%) create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/BitPatternTo12BitIntConverter.java rename ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/{ => util}/ColorMock.java (96%) create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleBitPatternTestConverter.java diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java deleted file mode 100644 index ca6185b..0000000 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternWriter.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.google.android.things.contrib.driver.ws2812b; - - -import android.support.annotation.IntRange; - -class BitPatternWriter { - private static final int MAX_NUMBER_OF_SUPPORTED_LEDS = 512; - private static final int NUMBER_OF_BYTES_PER_RGB_COLOR = 8; - private static final int ONE_BYTE_BIT_MASKS[] = new int[]{0b10000000, - 0b01000000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00000100, - 0b00000010, - 0b00000001}; - - private final byte[] mDestinationByteBuffer; - private int mCurrentByteBufferIndex; - private int mBitMaskIndex; - private int mCurrentWrittenByte; - - /** - * Converts bits of a color value with two bit patterns to bits comprehensible for a WS2812B - * controller. To convert and write a color value call {@link #writeBitPatternToBuffer(int)}. - * These converted bits are written to a buffer. The buffer is a simple byte array - * which can be retrieved with {@link #getBitPatternBuffer()} after - * {@link #finishBitPatternBuffer()} was called - * 1 src bit - * 3 source bits will be represented by 1 destination byte: - * - The first 2 source bits are represented by 3 destination bits - * - The last source bit is represented by 2 destination bits and 1 pause bit - * - The pause bit is automatically sent between each destination byte (it is always 0) - * - * => 3 src bits = 1 dst byte - * => 24 src bits = 8 dst bytes - * => 1 RGB LED color = 8 dst bytes - * @param numberOfLeds number of LEDs of the - */ - BitPatternWriter(@IntRange(from = 1, to = MAX_NUMBER_OF_SUPPORTED_LEDS) int numberOfLeds) { - if (numberOfLeds > MAX_NUMBER_OF_SUPPORTED_LEDS) { - throw new IllegalArgumentException("Only " + MAX_NUMBER_OF_SUPPORTED_LEDS + " LEDs are supported. A Greater Number (" + numberOfLeds + ") will result in SPI errors!"); - } - - int numberOfDestinationBytes = numberOfLeds * NUMBER_OF_BYTES_PER_RGB_COLOR; - - mDestinationByteBuffer = new byte[numberOfDestinationBytes]; - mCurrentByteBufferIndex = 0; - mBitMaskIndex = 0; - mCurrentWrittenByte = 0; - } - - /** - * Converts one value of a channel of a RGB color into a bit pattern and writes the bit pattern in a byte buffer - * - * @param colorChannelValue The value of one channel of a RGB color - */ - void writeBitPatternToBuffer(@IntRange(from = 0, to = 255) int colorChannelValue) { - // Highest byte bit - int mask = 128; - - for (int i = 0; i < 8; i++) { - if ((colorChannelValue & mask) == mask) { - writeOneBitPattern(); - } else { - writeZeroBitPattern(); - } - - colorChannelValue = colorChannelValue << 1; - } - } - - /** - * Writes the bit pattern for a 0 bit which is represented by a 1, 0, 0 - */ - private void writeZeroBitPattern() { - mCurrentWrittenByte = mCurrentWrittenByte | ONE_BYTE_BIT_MASKS[mBitMaskIndex++]; // Write 1 - mBitMaskIndex++; // Do nothing means write 0 - mBitMaskIndex++; // Do nothing means write 0 - - storeValueAndShiftToNextBitsInBuffer(); - } - - /** - * Writes the bit pattern for a 1 bit which is represented by a 1, 1, 0 - */ - private void writeOneBitPattern() { - mCurrentWrittenByte = mCurrentWrittenByte | ONE_BYTE_BIT_MASKS[mBitMaskIndex++]; // Write 1 - mCurrentWrittenByte = mCurrentWrittenByte | ONE_BYTE_BIT_MASKS[mBitMaskIndex++]; // Write 1 - mBitMaskIndex++; // Do nothing means write 0 - - storeValueAndShiftToNextBitsInBuffer(); - } - - private void storeValueAndShiftToNextBitsInBuffer() { - // There is no need to to store the pause bit. It can be ignored - if (mBitMaskIndex % 8 == 1) { - mDestinationByteBuffer[mCurrentByteBufferIndex] = (byte) mCurrentWrittenByte; - mCurrentByteBufferIndex++; - mBitMaskIndex = 0; - mCurrentWrittenByte = 0; - } - } - - void finishBitPatternBuffer() { - if (mCurrentByteBufferIndex < mDestinationByteBuffer.length) { - mDestinationByteBuffer[mCurrentByteBufferIndex] = (byte) mCurrentWrittenByte; - } - } - - byte[] getBitPatternBuffer() { - return mDestinationByteBuffer; - } - -} diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java new file mode 100644 index 0000000..014343c --- /dev/null +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java @@ -0,0 +1,90 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.support.annotation.ColorInt; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.util.Arrays; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +public class ColorChannelSequence { + @SuppressWarnings("WeakerAccess") + public static final int RGB = 1; + @SuppressWarnings("WeakerAccess") + public static final int RBG = 2; + @SuppressWarnings("WeakerAccess") + public static final int GRB = 3; + @SuppressWarnings("WeakerAccess") + public static final int GBR = 4; + @SuppressWarnings("WeakerAccess") + public static final int BRG = 5; + @SuppressWarnings("WeakerAccess") + public static final int BGR = 6; + @SuppressWarnings("WeakerAccess") + private static final int[] AVAILABLE_ORDER = {RGB, RBG, GRB, GBR, BRG, BGR}; + + @Retention(SOURCE) + @IntDef({RGB, RBG, GRB, GBR, BRG, BGR}) + @SuppressWarnings("WeakerAccess") + public @interface Sequence {} + + interface Sequencer + { + int rearrangeColorChannels(@ColorInt int color); + } + + @NonNull + public static Sequencer createSequencer(@Sequence int colorChannelSequence) + { + switch (colorChannelSequence) { + case BGR: + return new Sequencer() { + @Override + public int rearrangeColorChannels(int color) { + return ((color & 0xff0000) >> 16) | (color & 0xff00) | ((color & 0xff) << 16); + } + }; + case BRG: + return new Sequencer() { + @Override + public int rearrangeColorChannels(int color) { + return ((color & 0xff0000) >> 8) | ((color & 0xff00) >> 8) | ((color & 0xff) << 16); + } + }; + case GBR: + return new Sequencer() { + @Override + public int rearrangeColorChannels(int color) { + return ((color & 0xff0000) >> 16) | ((color & 0xff00) << 8) | ((color & 0xff) << 8); + } + }; + case GRB: + return new Sequencer() { + @Override + public int rearrangeColorChannels(int color) { + return ((color & 0xff0000) >> 8) | ((color & 0xff00) << 8) | (color & 0xff); + } + }; + case RBG: + return new Sequencer() { + @Override + public int rearrangeColorChannels(int color) { + return (color & 0xff0000) | ((color & 0xff00) >> 8) | ((color & 0xff) << 8); + } + }; + case RGB: + return new Sequencer() { + @Override + public int rearrangeColorChannels(int color) { + return color; + } + }; + default: + throw new IllegalArgumentException("Invalid color channel sequence: " + colorChannelSequence + ". Supported color channel sequences are: " + Arrays.toString(ColorChannelSequence.AVAILABLE_ORDER)); + } + } + +} diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifter.java deleted file mode 100644 index 1ccbc38..0000000 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifter.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.google.android.things.contrib.driver.ws2812b; - - -import android.support.annotation.ColorInt; -import android.support.annotation.IntDef; - -import java.lang.annotation.Retention; - -import static java.lang.annotation.RetentionPolicy.SOURCE; - -public class ColorOrderShifter { - - @Retention(SOURCE) - @IntDef({RGB, RBG, GRB, GBR, BRG, BGR}) - public @interface Order {} - public static final int RGB = 1; - public static final int RBG = 2; - public static final int GRB = 3; - public static final int GBR = 4; - public static final int BRG = 5; - public static final int BGR = 6; - - public static int reorderColorToRbg(@ColorInt int color) { - return (color & 0xff0000) | ((color & 0xff00) >> 8) | ((color & 0xff) << 8); - } - - public static int reorderColorToGrb(@ColorInt int color) { - return ((color & 0xff0000) >> 8) | ((color & 0xff00) << 8) | (color & 0xff); - } - - public static int reorderColorToGbr(@ColorInt int color) { - return ((color & 0xff0000) >> 16) | ((color & 0xff00) << 8) | ((color & 0xff) << 8); - } - - public static int reorderColorToBrg(@ColorInt int color) { - return ((color & 0xff0000) >> 8) | ((color & 0xff00) >> 8) | ((color & 0xff) << 16); - } - - public static int reorderColorToBgr(@ColorInt int color) { - return ((color & 0xff0000) >> 16) | (color & 0xff00) | ((color & 0xff) << 16); - } -} diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java index 9bc32ae..4132f43 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java @@ -1,129 +1,55 @@ package com.google.android.things.contrib.driver.ws2812b; -import android.graphics.Color; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; - -import java.util.Arrays; - -import static com.google.android.things.contrib.driver.ws2812b.Ws2812b.*; - - -class ColorToBitPatternConverter { - @Ws2812b.LedMode - private final int mLedMode; - private static final int[] SUPPORTED_MODES = {RGB, RBG, GRB, GBR, BRG, BGR}; - - - ColorToBitPatternConverter(@LedMode int ledMode) { - this.mLedMode = ledMode; - } - - @NonNull - byte[] convertColorsToBitPattern(@NonNull @ColorInt int colors[]) { - byte[] convertedColors; - switch (mLedMode) { - case BGR: - convertedColors = convertToBgr(colors); - break; - case BRG: - convertedColors = convertToBrg(colors); - break; - case GBR: - convertedColors = convertToGbr(colors); - break; - case GRB: - convertedColors = convertToGrb(colors); - break; - case RBG: - convertedColors = convertToRbg(colors); - break; - case Ws2812b.RGB: - convertedColors = convertToRgb(colors); - break; - default: - throw new IllegalStateException("This LED mode is not supported. Chosen mode: " + mLedMode + ". Supported modes: " + Arrays.toString(SUPPORTED_MODES)); +import android.support.annotation.Size; +import android.support.annotation.VisibleForTesting; + +public class ColorToBitPatternConverter { + private static final int MAX_NUMBER_OF_SUPPORTED_LEDS = 512; + private static final int FIRST_TWELVE_BIT_BIT_MASK = 0x00FFF000; + private static final int SECOND_TWELVE_BIT_BIT_MASK = 0x00000FFF; + + private final TwelveBitIntToBitPatternMapper mTwelveBitIntToBitPatternMapper; + private ColorChannelSequence.Sequencer colorChannelSequencer; + + public ColorToBitPatternConverter(@ColorChannelSequence.Sequence int colorChannelSequence) { + this(colorChannelSequence, new TwelveBitIntToBitPatternMapper()); + } + + @VisibleForTesting + ColorToBitPatternConverter(@ColorChannelSequence.Sequence int colorChannelSequence, @NonNull TwelveBitIntToBitPatternMapper twelveBitIntToBitPatternMapper) { + colorChannelSequencer = ColorChannelSequence.createSequencer(colorChannelSequence); + mTwelveBitIntToBitPatternMapper = twelveBitIntToBitPatternMapper; + } + + /** + * Converts the passed color array to a correlating byte array of bit patterns. These resulting + * bit patterns are readable by a WS2812B LED strip if they are sent by a SPI device with the + * right frequency. + * + * @param colors An array of color integers {@link ColorInt} + * @return Returns a byte array of correlating bit patterns + */ + public byte[] convertToBitPattern(@ColorInt @NonNull @Size(max = MAX_NUMBER_OF_SUPPORTED_LEDS) int[] colors) { + if (colors.length > MAX_NUMBER_OF_SUPPORTED_LEDS) { + throw new IllegalArgumentException("Only " + MAX_NUMBER_OF_SUPPORTED_LEDS + " LEDs are supported. A Greater Number (" + colors.length + ") will result in SPI errors!"); } - return convertedColors; - } - - private byte[] convertToBgr(@ColorInt int colors[]) { - BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); + byte[] bitPatterns = new byte[colors.length * 8]; + int i = 0; for (int color : colors) { - bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); + color = colorChannelSequencer.rearrangeColorChannels(color); + int firstValue = (color & FIRST_TWELVE_BIT_BIT_MASK) >> 12; + int secondValue = color & SECOND_TWELVE_BIT_BIT_MASK; + + System.arraycopy(mTwelveBitIntToBitPatternMapper.getBitPattern(firstValue), 0, bitPatterns, i, 4); + i += 4; + System.arraycopy(mTwelveBitIntToBitPatternMapper.getBitPattern(secondValue), 0, bitPatterns, i, 4); + i += 4; } - bitPatternWriter.finishBitPatternBuffer(); - - return bitPatternWriter.getBitPatternBuffer(); - } - - private byte[] convertToBrg(@ColorInt int colors[]) { - BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - - for (int color : colors) { - bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); - } - bitPatternWriter.finishBitPatternBuffer(); - - return bitPatternWriter.getBitPatternBuffer(); - } - - private byte[] convertToGbr(@ColorInt int colors[]) { - BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - - for (int color : colors) { - bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); - } - bitPatternWriter.finishBitPatternBuffer(); - - return bitPatternWriter.getBitPatternBuffer(); - } - - private byte[] convertToGrb(@ColorInt int colors[]) { - BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - - for (int color : colors) { - bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); - } - bitPatternWriter.finishBitPatternBuffer(); - - return bitPatternWriter.getBitPatternBuffer(); - } - - private byte[] convertToRbg(@ColorInt int colors[]) { - BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - - for (int color : colors) { - bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); - } - bitPatternWriter.finishBitPatternBuffer(); - - return bitPatternWriter.getBitPatternBuffer(); - } - - private byte[] convertToRgb(@ColorInt int colors[]) { - BitPatternWriter bitPatternWriter = new BitPatternWriter(colors.length); - - for (int color : colors) { - bitPatternWriter.writeBitPatternToBuffer(Color.red(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.green(color)); - bitPatternWriter.writeBitPatternToBuffer(Color.blue(color)); - } - bitPatternWriter.finishBitPatternBuffer(); - - return bitPatternWriter.getBitPatternBuffer(); + return bitPatterns; } } diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolder.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java similarity index 76% rename from ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolder.java rename to ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java index 488db78..c5df904 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolder.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java @@ -3,6 +3,7 @@ import android.support.annotation.IntRange; import android.support.annotation.NonNull; +import android.support.annotation.Size; import android.support.annotation.VisibleForTesting; import android.util.SparseArray; @@ -12,10 +13,20 @@ import java.util.List; /** - * Map which stores bit patterns for each possible 12 bit number -> (numbers from 0 to 4096) + * Creates a storage which maps any possible 12 bit sized integer to bit patterns. If a sequence of + * these bit patterns is sent to a WS2812b LED strip using SPI, the outcoming high and low voltage + * pulses are recognized as the original 12 bit integers.
+ * Converting algorithm:
+ * - 1 src bit is converted to a 3 bit long bit pattern
+ * - The 9th bit in a sequence of bit pattern is a pause bit and must be removed. The pause bit is + * automatically transmitted between every byte
+ * - This results in the fact that 3 source bits are converted to 1 destination byte
+ * + * => 3 src bits = 8 bit (1 dst byte)
+ * => 12 src bits = 32 bit (4 dst bytes)
*/ -public class BitPatternHolder { - private static final int BIGGEST_12_BIT_NUMBER = (1 << 12) - 1; // 2¹² - 1 = 4095 +public class TwelveBitIntToBitPatternMapper { + private static final int BIGGEST_12_BIT_NUMBER = 0B1111_1111_1111; private static final List BIT_PATTERN_FOR_ZERO_BIT = Arrays.asList(true, false, false); private static final List BIT_PATTERN_FOR_ONE_BIT = Arrays.asList(true, true, false); private static final int ONE_BYTE_BIT_MASKS[] = new int[]{ 0b10000000, @@ -27,28 +38,29 @@ public class BitPatternHolder { 0b00000010, 0b00000001}; - private final Storage bitPatternStorage; + private final Storage mBitPatternStorage; - public BitPatternHolder() { + public TwelveBitIntToBitPatternMapper() { this(new DefaultStorage()); } @VisibleForTesting - /*package*/ BitPatternHolder(@NonNull Storage storage) { - this.bitPatternStorage = storage; - fillBitPatternCache(); + TwelveBitIntToBitPatternMapper(@NonNull Storage storage) { + mBitPatternStorage = storage; + fillBitPatternStorage(); } /** - * Returns for each possible 12 bit integer an corresponding bit pattern as byte array. + * Returns for each possible 12 bit integer a corresponding sequence of bit pattern as byte array. * Throws an {@link IllegalArgumentException} if the integer is using more than 12 bit. * * @param twelveBitValue A 12 bit integer (from 0 to 4095) * @return The corresponding bit pattern as byte array */ @NonNull + @Size(value = 4) public byte[] getBitPattern(@IntRange(from = 0, to = BIGGEST_12_BIT_NUMBER) int twelveBitValue) { - byte[] bitPatternByteArray = bitPatternStorage.get(twelveBitValue); + byte[] bitPatternByteArray = mBitPatternStorage.get(twelveBitValue); if (bitPatternByteArray == null) { throw new IllegalArgumentException("Only values from 0 to " + BIGGEST_12_BIT_NUMBER + " are allowed. The passed input value was: " + twelveBitValue); @@ -56,12 +68,13 @@ public byte[] getBitPattern(@IntRange(from = 0, to = BIGGEST_12_BIT_NUMBER) int return bitPatternByteArray; } - private void fillBitPatternCache() { + private void fillBitPatternStorage() { for (int i = 0; i <= BIGGEST_12_BIT_NUMBER; i++) { - bitPatternStorage.put(i, calculateBitPatternByteArray(i)); + mBitPatternStorage.put(i, calculateBitPatternByteArray(i)); } } + @Size(value = 4) private byte[] calculateBitPatternByteArray(@IntRange(from = 0, to = BIGGEST_12_BIT_NUMBER) int twelveBitNumber) { List bitPatterns = new ArrayList<>(); int highest12BitBitMask = 1 << 11; @@ -96,6 +109,7 @@ private List removePauseBits(List bitPatterns) { return bitPatterns; } + @Size(value = 4) private byte[] convertBitPatternsToByteArray(List bitPatterns) { if (bitPatterns.size() != 32) diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java index 5de0e90..079baa0 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java @@ -16,17 +16,14 @@ package com.google.android.things.contrib.driver.ws2812b; -import android.graphics.Color; import android.support.annotation.ColorInt; -import android.support.annotation.IntDef; +import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; + import com.google.android.things.pio.PeripheralManagerService; import com.google.android.things.pio.SpiDevice; import java.io.IOException; -import java.lang.annotation.Retention; - -import static java.lang.annotation.RetentionPolicy.SOURCE; /** * Device driver for WS2812B LEDs using SPI. @@ -41,23 +38,9 @@ public class Ws2812b implements AutoCloseable { private static final String TAG = "Ws2812b"; - /** - * Color ordering for the RGB LED messages; the most common modes are BGR and RGB. - */ - @Retention(SOURCE) - @IntDef({RGB, RBG, GRB, GBR, BRG, BGR}) - public @interface LedMode {} - public static final int RGB = 0; - public static final int RBG = 1; - public static final int GRB = 2; - public static final int GBR = 3; - public static final int BRG = 4; - public static final int BGR = 5; - - // For peripherals access - private SpiDevice mDevice = null; - + @NonNull private final ColorToBitPatternConverter mColorToBitPatternConverter; + private SpiDevice mDevice = null; /** * Create a new WS2812B driver. @@ -65,23 +48,26 @@ public class Ws2812b implements AutoCloseable { * @param spiBusPort Name of the SPI bus */ public Ws2812b(String spiBusPort) throws IOException { - this(spiBusPort, GRB); + this(spiBusPort, new ColorToBitPatternConverter(ColorChannelSequence.GRB)); } /** * Create a new WS2812B driver. * * @param spiBusPort Name of the SPI bus - * @param ledMode The {@link LedMode} indicating the red/green/blue byte ordering for the device. + * @param colorChannelSequence The {@link ColorChannelSequence.Sequence} indicates the red/green/blue byte order for the LED strip. * @throws IOException if the initialization of the SpiDevice fails * */ - public Ws2812b(String spiBusPort, @LedMode int ledMode) throws IOException { - mColorToBitPatternConverter = new ColorToBitPatternConverter(ledMode); - PeripheralManagerService pioService = new PeripheralManagerService(); - mDevice = pioService.openSpiDevice(spiBusPort); + public Ws2812b(String spiBusPort, @ColorChannelSequence.Sequence int colorChannelSequence) throws IOException { + this (spiBusPort, new ColorToBitPatternConverter(colorChannelSequence)); + } + + private Ws2812b(String spiBusPort, @NonNull ColorToBitPatternConverter colorToBitPatternConverter) throws IOException { + mColorToBitPatternConverter = colorToBitPatternConverter; + mDevice = new PeripheralManagerService().openSpiDevice(spiBusPort); try { - configure(mDevice); + initSpiDevice(mDevice); } catch (IOException|RuntimeException e) { try { close(); @@ -91,20 +77,14 @@ public Ws2812b(String spiBusPort, @LedMode int ledMode) throws IOException { } } - /** - * Create a new WS2812B driver. - * - * @param device {@link SpiDevice} where the LED strip is attached to. - * @param ledMode The {@link LedMode} indicating the red/green/blue byte ordering for the device. - */ @VisibleForTesting - /*package*/ Ws2812b(SpiDevice device, @LedMode int ledMode) throws IOException { - mColorToBitPatternConverter = new ColorToBitPatternConverter(ledMode); + /*package*/ Ws2812b(SpiDevice device, @NonNull ColorToBitPatternConverter colorToBitPatternConverter) throws IOException { + mColorToBitPatternConverter = colorToBitPatternConverter; mDevice = device; - configure(mDevice); + initSpiDevice(mDevice); } - private void configure(SpiDevice device) throws IOException { + private void initSpiDevice(SpiDevice device) throws IOException { double durationOfOneBitInNs = 417.0; double durationOfOneBitInS = durationOfOneBitInNs * Math.pow(10, -9); @@ -116,17 +96,16 @@ private void configure(SpiDevice device) throws IOException { } /** - * Writes the current RGB Led data to the peripheral bus. - * @param colors An array of integers corresponding to a {@link Color}. - * @throws IOException if writing to the SPi device fails + * Transforms the passed color array and writes it to the SPI connected WS2812b LED strip. + * @param colors An array of 24 bit RGB color integers {@link ColorInt} + * @throws IOException if writing to the SPI device fails */ - public void write(@ColorInt int[] colors) throws IOException { + public void write(@NonNull @ColorInt int[] colors) throws IOException { if (mDevice == null) { throw new IllegalStateException("SPI device not opened"); } - byte[] convertedColors = mColorToBitPatternConverter.convertColorsToBitPattern(colors); - + byte[] convertedColors = mColorToBitPatternConverter.convertToBitPattern(colors); mDevice.write(convertedColors, convertedColors.length); } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java new file mode 100644 index 0000000..45ad936 --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java @@ -0,0 +1,121 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import android.graphics.Color; +import android.support.annotation.ColorInt; +import android.support.annotation.IntRange; + +import com.google.android.things.contrib.driver.ws2812b.util.ColorMock; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + + +@RunWith(PowerMockRunner.class) +@PrepareForTest(android.graphics.Color.class) +public class ColorChannelSequenceTest { + + private int red; + private int green; + private int blue; + + @Before + public void before() { + ColorMock.mockStatic(); + + red = generateRandomColorValue(); + green = generateRandomColorValue(); + blue = generateRandomColorValue(); + } + + @Test + public void reorderToRgb(){ + ColorChannelSequence.Sequencer sequencer = ColorChannelSequence.createSequencer(ColorChannelSequence.RGB); + int rgbColor = Color.rgb(red, green, blue); + int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); + + Assert.assertEquals(get1stColor(rearrangedColor), red); + Assert.assertEquals(get2ndColor(rearrangedColor), green); + Assert.assertEquals(get3ndColor(rearrangedColor), blue); + } + + @Test + public void reorderToRbg(){ + ColorChannelSequence.Sequencer sequencer = ColorChannelSequence.createSequencer(ColorChannelSequence.RBG); + int rgbColor = Color.rgb(red, green, blue); + int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); + + Assert.assertEquals(get1stColor(rearrangedColor), red); + Assert.assertEquals(get2ndColor(rearrangedColor), blue); + Assert.assertEquals(get3ndColor(rearrangedColor), green); + } + + @Test + public void reorderToGrb(){ + ColorChannelSequence.Sequencer sequencer = ColorChannelSequence.createSequencer(ColorChannelSequence.GRB); + int rgbColor = Color.rgb(red, green, blue); + int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); + + Assert.assertEquals(get1stColor(rearrangedColor), green); + Assert.assertEquals(get2ndColor(rearrangedColor), red); + Assert.assertEquals(get3ndColor(rearrangedColor), blue); + } + + @Test + public void reorderToGbr(){ + ColorChannelSequence.Sequencer sequencer = ColorChannelSequence.createSequencer(ColorChannelSequence.GBR); + int rgbColor = Color.rgb(red, green, blue); + int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); + + Assert.assertEquals(get1stColor(rearrangedColor), green); + Assert.assertEquals(get2ndColor(rearrangedColor), blue); + Assert.assertEquals(get3ndColor(rearrangedColor), red); + } + + @Test + public void reorderToBrg(){ + ColorChannelSequence.Sequencer sequencer = ColorChannelSequence.createSequencer(ColorChannelSequence.BRG); + int rgbColor = Color.rgb(red, green, blue); + int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); + + Assert.assertEquals(get1stColor(rearrangedColor), blue); + Assert.assertEquals(get2ndColor(rearrangedColor), red); + Assert.assertEquals(get3ndColor(rearrangedColor), green); + } + + @Test + public void reorderToBgr(){ + ColorChannelSequence.Sequencer sequencer = ColorChannelSequence.createSequencer(ColorChannelSequence.BGR); + int rgbColor = Color.rgb(red, green, blue); + int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); + + Assert.assertEquals(get1stColor(rearrangedColor), blue); + Assert.assertEquals(get2ndColor(rearrangedColor), green); + Assert.assertEquals(get3ndColor(rearrangedColor), red); + } + + @IntRange(from = 0, to = 255) + private int generateRandomColorValue() { + return (int) Math.round(Math.random() * 255); + } + + private static int get1stColor(@ColorInt int color) + { + return Color.red(color); + } + + private static int get2ndColor(@ColorInt int color) + { + return Color.green(color); + } + + private static int get3ndColor(@ColorInt int color) + { + return Color.blue(color); + } +} diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifterTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifterTest.java deleted file mode 100644 index 11f419b..0000000 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorOrderShifterTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.google.android.things.contrib.driver.ws2812b; - - -import android.graphics.Color; -import android.support.annotation.ColorInt; -import android.support.annotation.IntRange; - -import junit.framework.Assert; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - - -@RunWith(PowerMockRunner.class) -@PrepareForTest(android.graphics.Color.class) -public class ColorOrderShifterTest { - - private int red; - private int green; - private int blue; - - @Before - public void setUp() { - ColorMock.mockStatic(); - - red = generateRandomColorValue(); - green = generateRandomColorValue(); - blue = generateRandomColorValue(); - } - - @Test - public void reorderToRbg(){ - - int rbgColor = ColorOrderShifter.reorderColorToRbg(Color.rgb(red, green, blue)); - - Assert.assertEquals(get1stColor(rbgColor), red); - Assert.assertEquals(get2ndColor(rbgColor), blue); - Assert.assertEquals(get3ndColor(rbgColor), green); - } - - @Test - public void reorderToGrb(){ - int grb = ColorOrderShifter.reorderColorToGrb(Color.rgb(red, green, blue)); - - Assert.assertEquals(get1stColor(grb), green); - Assert.assertEquals(get2ndColor(grb), red); - Assert.assertEquals(get3ndColor(grb), blue); - } - - @Test - public void reorderToGbr(){ - int gbrColor = ColorOrderShifter.reorderColorToGbr(Color.rgb(red, green, blue)); - - Assert.assertEquals(get1stColor(gbrColor), green); - Assert.assertEquals(get2ndColor(gbrColor), blue); - Assert.assertEquals(get3ndColor(gbrColor), red); - } - - @Test - public void reorderToBrg(){ - int brgColor = ColorOrderShifter.reorderColorToBrg(Color.rgb(red, green, blue)); - - Assert.assertEquals(get1stColor(brgColor), blue); - Assert.assertEquals(get2ndColor(brgColor), red); - Assert.assertEquals(get3ndColor(brgColor), green); - } - - @Test - public void reorderToBgr(){ - int bgrColor = ColorOrderShifter.reorderColorToBgr(Color.rgb(red, green, blue)); - - Assert.assertEquals(get1stColor(bgrColor), blue); - Assert.assertEquals(get2ndColor(bgrColor), green); - Assert.assertEquals(get3ndColor(bgrColor), red); - } - - @IntRange(from = 0, to = 255) - private int generateRandomColorValue() { - return (int) Math.round(Math.random() * 255); - } - - private static int get1stColor(@ColorInt int color) - { - return Color.red(color); - } - - private static int get2ndColor(@ColorInt int color) - { - return Color.green(color); - } - - private static int get3ndColor(@ColorInt int color) - { - return Color.blue(color); - } -} diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolderTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java similarity index 60% rename from ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolderTest.java rename to ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java index 9dff5dc..1ab8f3f 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/BitPatternHolderTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java @@ -3,7 +3,9 @@ import android.annotation.SuppressLint; -import org.junit.Before; +import com.google.android.things.contrib.driver.ws2812b.util.BitPatternTo12BitIntConverter; + +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.modules.junit4.PowerMockRunner; @@ -13,14 +15,11 @@ import java.util.Map; -@RunWith(PowerMockRunner.class) -public class BitPatternHolderTest { - private BitPatternHolder bitPatternHolder; +@RunWith(PowerMockRunner.class) +public class TwelveBitIntToBitPatternMapperTest { - @Before - public void setUp() throws Exception { - bitPatternHolder = new BitPatternHolder(new BitPatternHolder.Storage() { + private TwelveBitIntToBitPatternMapper twelveBitIntToBitPatternMapper = new TwelveBitIntToBitPatternMapper(new TwelveBitIntToBitPatternMapper.Storage() { @SuppressLint("UseSparseArrays") // Sparse array is not available for Unit tests private Map map = new HashMap<>(); @Override @@ -33,23 +32,22 @@ public byte[] get(int key) { return map.get(key); } }); - } + @Test public void getBitPattern() throws IOException { + BitPatternTo12BitIntConverter converter = new BitPatternTo12BitIntConverter(); double limit = Math.pow(2, 12); for (int i = 0; i < limit; i++) { - //noinspection ConstantConditions - if (bitPatternHolder.getBitPattern(i) == null) - { - throw new AssertionError("Bit pattern not found for value: " + i); - } + byte[] bitPattern = twelveBitIntToBitPatternMapper.getBitPattern(i); + int originalValue = converter.convertBitPatternTo12BitInt(bitPattern); + Assert.assertEquals(originalValue, i); } } @Test (expected = IllegalArgumentException.class) public void getBitPatternIllegalArgumentException() throws IOException { int limit = (int) Math.pow(2, 12); - bitPatternHolder.getBitPattern(limit); + twelveBitIntToBitPatternMapper.getBitPattern(limit); } } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java index ef8ae96..b96ca18 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java @@ -1,8 +1,12 @@ package com.google.android.things.contrib.driver.ws2812b; +import android.annotation.SuppressLint; import android.graphics.Color; +import android.support.annotation.NonNull; +import com.google.android.things.contrib.driver.ws2812b.util.ColorMock; +import com.google.android.things.contrib.driver.ws2812b.util.SimpleBitPatternTestConverter; import com.google.android.things.pio.SpiDevice; import org.junit.Rule; @@ -17,10 +21,8 @@ import org.powermock.modules.junit4.PowerMockRunner; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; +import java.util.HashMap; +import java.util.Map; @RunWith(PowerMockRunner.class) @@ -37,14 +39,14 @@ public class Ws2812bTest { @Test public void close() throws IOException { - Ws2812b ws2812b = new Ws2812b(mSpiDevice, Ws2812b.RGB); + Ws2812b ws2812b = createWs2812BDevice(); ws2812b.close(); Mockito.verify(mSpiDevice).close(); } @Test public void close_safeToCallTwice() throws IOException { - Ws2812b ws2812b = new Ws2812b(mSpiDevice, Ws2812b.RBG); + Ws2812b ws2812b = createWs2812BDevice(); ws2812b.close(); ws2812b.close(); // Check if the inner SPI device was only closed once @@ -57,76 +59,31 @@ public void writeRed() throws IOException { int color = Color.RED; - Ws2812b ws2812b = new Ws2812b(mSpiDevice, Ws2812b.RBG); + Ws2812b ws2812b = createWs2812BDevice(); ws2812b.write(new int [] {Color.RED}); - List bitPatterns = new ArrayList<>(); - List oneBitPattern = Arrays.asList(true, true, false); - List zeroBitPattern = Arrays.asList(true, false, false); + byte[] bytes = new SimpleBitPatternTestConverter().constructBitPatterns(color); + Mockito.verify(mSpiDevice).write(bytes, bytes.length); + } - int highestBit = 1<<23; - - for (int i = 0; i < 24; i++) { - List bitPattern = (color & highestBit) == highestBit ? oneBitPattern : zeroBitPattern; - bitPatterns.addAll(bitPattern); - color = color << 1; - } + @NonNull + private Ws2812b createWs2812BDevice() throws IOException { + TwelveBitIntToBitPatternMapper patternMapper = new TwelveBitIntToBitPatternMapper(new TwelveBitIntToBitPatternMapper.Storage() { + @SuppressLint("UseSparseArrays") + private Map internalStorage = new HashMap<>(); - Iterator iterator = bitPatterns.iterator(); - int i = 0; - while (iterator.hasNext()) { - iterator.next(); - if (i == 8) - { - iterator.remove(); - i = 0; - continue; - } - i++; - } - - - int [] masks = { - 0b1000_0000, - 0b0100_0000, - 0b0010_0000, - 0b0001_0000, - 0b0000_1000, - 0b0000_0100, - 0b0000_0010, - 0b0000_0001, - }; - - byte [] bytes = new byte[bitPatterns.size() / 8]; - byte currentByte = 0; - i = 0; - int j = 0; - for (Boolean bitValue : bitPatterns) { - if (bitValue) - { - currentByte |= masks[i]; - } - if (i == 7) - { - bytes[j++] = currentByte; - currentByte = 0; - i = 0; - continue; + @Override + public void put(int key, byte[] value) { + internalStorage.put(key, value); } - i++; - } - - int firstTwelveBit = 0x00FFF000; - int secondTwelveBit = 0x00000FFF; - int firstValue = (Color.RED & firstTwelveBit) >> 12; - int secondValue = Color.RED & secondTwelveBit; - BitPatternHolder bitPatternHolder = new BitPatternHolder(); - byte[] bitPattern = bitPatternHolder.getBitPattern(firstValue); - byte[] secondBitPattern = bitPatternHolder.getBitPattern(secondValue); - - - Mockito.verify(mSpiDevice).write(bytes, bytes.length); + @Override + public byte[] get(int key) { + return internalStorage.get(key); + } + }); + ColorToBitPatternConverter converter = new ColorToBitPatternConverter(ColorChannelSequence.RBG, patternMapper); + return new Ws2812b(mSpiDevice, converter); } } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/BitPatternTo12BitIntConverter.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/BitPatternTo12BitIntConverter.java new file mode 100644 index 0000000..a0c1277 --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/BitPatternTo12BitIntConverter.java @@ -0,0 +1,76 @@ +package com.google.android.things.contrib.driver.ws2812b.util; + + +import android.support.annotation.NonNull; +import android.support.annotation.Size; + +import junit.framework.Assert; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +public class BitPatternTo12BitIntConverter { + + public int convertBitPatternTo12BitInt(@Size(value = 4) byte[] bitPatterns) + { + List booleanBitPatterns = convertToBooleanBitPatternWithMissingPauseBit(bitPatterns); + List originalBooleanBits = new ArrayList<>(); + ListIterator booleanBitPatternIterator = booleanBitPatterns.listIterator(); + + Assert.assertEquals(12 * 3, booleanBitPatterns.size()); + + for (int i = 0; i < 12; i++) { + boolean bit0 = booleanBitPatternIterator.next(); + boolean bit1 = booleanBitPatternIterator.next(); + boolean bit2 = booleanBitPatternIterator.next(); + + originalBooleanBits.add(isOneBitPattern(bit0, bit1, bit2)); + } + return convertTo12BitInt(originalBooleanBits); + } + + @NonNull + @Size(value = 9) + private List convertToBooleanBitPatternWithMissingPauseBit(@Size(value = 4) byte[] bitPatterns) { + List booleanBitPatterns = new ArrayList<>(); + for (byte bitPattern : bitPatterns) { + booleanBitPatterns.addAll(convertBitPatternTo12BitInt(bitPattern)); + // Add missing pause bit + booleanBitPatterns.add(false); + } + return booleanBitPatterns; + } + + private int convertTo12BitInt(@Size(value = 12) List originalBooleanBits) { + int converted = 0; + int highestBit = 1 << 11; + for (int i = 0; i < 12; i++) { + if (originalBooleanBits.get(i)) { + converted |= highestBit; + } + highestBit = highestBit >> 1; + } + return converted; + } + + private Boolean isOneBitPattern(boolean bit0, boolean bit1, boolean bit2) { + if (!bit0) + { + throw new AssertionError("First bit in pattern must always be true"); + } + return bit1 & !bit2; + } + + @Size(value = 8) + private List convertBitPatternTo12BitInt(int bitPattern) + { + List booleanBitPattern = new ArrayList<>(); + int oneByteBitMask = 0b1000_0000; + for (int i = 0; i < 8; i++) { + booleanBitPattern.add((oneByteBitMask & bitPattern) == oneByteBitMask); + bitPattern = bitPattern << 1; + } + return booleanBitPattern; + } +} diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorMock.java similarity index 96% rename from ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java rename to ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorMock.java index eeacab0..caff6df 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorMock.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorMock.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.things.contrib.driver.ws2812b; +package com.google.android.things.contrib.driver.ws2812b.util; import android.graphics.Color; @@ -25,8 +25,8 @@ import static org.mockito.Matchers.anyInt; -class ColorMock { - static void mockStatic() { +public class ColorMock { + public static void mockStatic() { PowerMockito.mockStatic(Color.class); mockRed(); diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleBitPatternTestConverter.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleBitPatternTestConverter.java new file mode 100644 index 0000000..b8d5eed --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleBitPatternTestConverter.java @@ -0,0 +1,86 @@ +package com.google.android.things.contrib.driver.ws2812b.util; + + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +public class SimpleBitPatternTestConverter { + private static final int [] ONE_BYTE_BIT_MASKS = { + 0b1000_0000, + 0b0100_0000, + 0b0010_0000, + 0b0001_0000, + 0b0000_1000, + 0b0000_0100, + 0b0000_0010, + 0b0000_0001, + }; + + public byte[] constructBitPatterns(int color) + { + List booleanBitPatterns = constructBooleanBitPatterns(color); + booleanBitPatterns = removePauseBits(booleanBitPatterns); + return convertToByteArray(booleanBitPatterns); + } + + @NonNull + private List constructBooleanBitPatterns(int color) { + ArrayList bitPatterns = new ArrayList<>(); + List oneBitPattern = Arrays.asList(true, true, false); + List zeroBitPattern = Arrays.asList(true, false, false); + + int highestBit = 1<<23; + + for (int i = 0; i < 24; i++) { + List bitPattern = (color & highestBit) == highestBit ? oneBitPattern : zeroBitPattern; + bitPatterns.addAll(bitPattern); + color = color << 1; + } + return bitPatterns; + } + + private List removePauseBits(List bitPatterns) { + Iterator iterator = bitPatterns.iterator(); + int i = 0; + while (iterator.hasNext()) { + iterator.next(); + if (i == 8) + { + iterator.remove(); + i = 0; + continue; + } + i++; + } + return bitPatterns; + } + + private byte[] convertToByteArray(List bitPatterns) { + int i; + + byte [] bytes = new byte[bitPatterns.size() / 8]; + byte currentByte = 0; + i = 0; + int j = 0; + for (Boolean bitValue : bitPatterns) { + if (bitValue) + { + currentByte |= ONE_BYTE_BIT_MASKS[i]; + } + if (i == 7) + { + bytes[j++] = currentByte; + currentByte = 0; + i = 0; + continue; + } + i++; + } + return bytes; + } + +} From 1f8a673b222fe1e32aa3be301441d7397437f093 Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Mon, 28 Aug 2017 23:47:12 +0200 Subject: [PATCH 07/35] Fixed warnings: Reduced all method scopes to the required minimum Minor renaming refactoring --- .../driver/ws2812b/ColorChannelSequence.java | 4 ++-- .../driver/ws2812b/ColorToBitPatternConverter.java | 6 +++--- .../ws2812b/TwelveBitIntToBitPatternMapper.java | 10 +++++----- .../driver/ws2812b/ColorChannelSequenceTest.java | 14 +++++++------- .../things/contrib/driver/ws2812b/Ws2812bTest.java | 4 ++-- ....java => SimpleColorToBitPatternConverter.java} | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) rename ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/{SimpleBitPatternTestConverter.java => SimpleColorToBitPatternConverter.java} (97%) diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java index 014343c..95a0494 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java @@ -10,7 +10,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; -public class ColorChannelSequence { +class ColorChannelSequence { @SuppressWarnings("WeakerAccess") public static final int RGB = 1; @SuppressWarnings("WeakerAccess") @@ -37,7 +37,7 @@ interface Sequencer } @NonNull - public static Sequencer createSequencer(@Sequence int colorChannelSequence) + static Sequencer createSequencer(@Sequence int colorChannelSequence) { switch (colorChannelSequence) { case BGR: diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java index 4132f43..8e9b1ff 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java @@ -6,7 +6,7 @@ import android.support.annotation.Size; import android.support.annotation.VisibleForTesting; -public class ColorToBitPatternConverter { +class ColorToBitPatternConverter { private static final int MAX_NUMBER_OF_SUPPORTED_LEDS = 512; private static final int FIRST_TWELVE_BIT_BIT_MASK = 0x00FFF000; private static final int SECOND_TWELVE_BIT_BIT_MASK = 0x00000FFF; @@ -14,7 +14,7 @@ public class ColorToBitPatternConverter { private final TwelveBitIntToBitPatternMapper mTwelveBitIntToBitPatternMapper; private ColorChannelSequence.Sequencer colorChannelSequencer; - public ColorToBitPatternConverter(@ColorChannelSequence.Sequence int colorChannelSequence) { + ColorToBitPatternConverter(@ColorChannelSequence.Sequence int colorChannelSequence) { this(colorChannelSequence, new TwelveBitIntToBitPatternMapper()); } @@ -32,7 +32,7 @@ public ColorToBitPatternConverter(@ColorChannelSequence.Sequence int colorChanne * @param colors An array of color integers {@link ColorInt} * @return Returns a byte array of correlating bit patterns */ - public byte[] convertToBitPattern(@ColorInt @NonNull @Size(max = MAX_NUMBER_OF_SUPPORTED_LEDS) int[] colors) { + byte[] convertToBitPattern(@ColorInt @NonNull @Size(max = MAX_NUMBER_OF_SUPPORTED_LEDS) int[] colors) { if (colors.length > MAX_NUMBER_OF_SUPPORTED_LEDS) { throw new IllegalArgumentException("Only " + MAX_NUMBER_OF_SUPPORTED_LEDS + " LEDs are supported. A Greater Number (" + colors.length + ") will result in SPI errors!"); } diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java index c5df904..f4749f0 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java @@ -25,7 +25,7 @@ * => 3 src bits = 8 bit (1 dst byte)
* => 12 src bits = 32 bit (4 dst bytes)
*/ -public class TwelveBitIntToBitPatternMapper { +class TwelveBitIntToBitPatternMapper { private static final int BIGGEST_12_BIT_NUMBER = 0B1111_1111_1111; private static final List BIT_PATTERN_FOR_ZERO_BIT = Arrays.asList(true, false, false); private static final List BIT_PATTERN_FOR_ONE_BIT = Arrays.asList(true, true, false); @@ -40,7 +40,7 @@ public class TwelveBitIntToBitPatternMapper { private final Storage mBitPatternStorage; - public TwelveBitIntToBitPatternMapper() { + TwelveBitIntToBitPatternMapper() { this(new DefaultStorage()); } @@ -54,12 +54,12 @@ public TwelveBitIntToBitPatternMapper() { * Returns for each possible 12 bit integer a corresponding sequence of bit pattern as byte array. * Throws an {@link IllegalArgumentException} if the integer is using more than 12 bit. * - * @param twelveBitValue A 12 bit integer (from 0 to 4095) - * @return The corresponding bit pattern as byte array + * @param twelveBitValue Any 12 bit integer (from 0 to 4095) + * @return The corresponding bit pattern as 4 byte sized array */ @NonNull @Size(value = 4) - public byte[] getBitPattern(@IntRange(from = 0, to = BIGGEST_12_BIT_NUMBER) int twelveBitValue) { + byte[] getBitPattern(@IntRange(from = 0, to = BIGGEST_12_BIT_NUMBER) int twelveBitValue) { byte[] bitPatternByteArray = mBitPatternStorage.get(twelveBitValue); if (bitPatternByteArray == null) { diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java index 45ad936..55b6b0a 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java @@ -41,7 +41,7 @@ public void reorderToRgb(){ Assert.assertEquals(get1stColor(rearrangedColor), red); Assert.assertEquals(get2ndColor(rearrangedColor), green); - Assert.assertEquals(get3ndColor(rearrangedColor), blue); + Assert.assertEquals(get3rdColor(rearrangedColor), blue); } @Test @@ -52,7 +52,7 @@ public void reorderToRbg(){ Assert.assertEquals(get1stColor(rearrangedColor), red); Assert.assertEquals(get2ndColor(rearrangedColor), blue); - Assert.assertEquals(get3ndColor(rearrangedColor), green); + Assert.assertEquals(get3rdColor(rearrangedColor), green); } @Test @@ -63,7 +63,7 @@ public void reorderToGrb(){ Assert.assertEquals(get1stColor(rearrangedColor), green); Assert.assertEquals(get2ndColor(rearrangedColor), red); - Assert.assertEquals(get3ndColor(rearrangedColor), blue); + Assert.assertEquals(get3rdColor(rearrangedColor), blue); } @Test @@ -74,7 +74,7 @@ public void reorderToGbr(){ Assert.assertEquals(get1stColor(rearrangedColor), green); Assert.assertEquals(get2ndColor(rearrangedColor), blue); - Assert.assertEquals(get3ndColor(rearrangedColor), red); + Assert.assertEquals(get3rdColor(rearrangedColor), red); } @Test @@ -85,7 +85,7 @@ public void reorderToBrg(){ Assert.assertEquals(get1stColor(rearrangedColor), blue); Assert.assertEquals(get2ndColor(rearrangedColor), red); - Assert.assertEquals(get3ndColor(rearrangedColor), green); + Assert.assertEquals(get3rdColor(rearrangedColor), green); } @Test @@ -96,7 +96,7 @@ public void reorderToBgr(){ Assert.assertEquals(get1stColor(rearrangedColor), blue); Assert.assertEquals(get2ndColor(rearrangedColor), green); - Assert.assertEquals(get3ndColor(rearrangedColor), red); + Assert.assertEquals(get3rdColor(rearrangedColor), red); } @IntRange(from = 0, to = 255) @@ -114,7 +114,7 @@ private static int get2ndColor(@ColorInt int color) return Color.green(color); } - private static int get3ndColor(@ColorInt int color) + private static int get3rdColor(@ColorInt int color) { return Color.blue(color); } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java index b96ca18..ff9cda6 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java @@ -6,7 +6,7 @@ import android.support.annotation.NonNull; import com.google.android.things.contrib.driver.ws2812b.util.ColorMock; -import com.google.android.things.contrib.driver.ws2812b.util.SimpleBitPatternTestConverter; +import com.google.android.things.contrib.driver.ws2812b.util.SimpleColorToBitPatternConverter; import com.google.android.things.pio.SpiDevice; import org.junit.Rule; @@ -62,7 +62,7 @@ public void writeRed() throws IOException { Ws2812b ws2812b = createWs2812BDevice(); ws2812b.write(new int [] {Color.RED}); - byte[] bytes = new SimpleBitPatternTestConverter().constructBitPatterns(color); + byte[] bytes = new SimpleColorToBitPatternConverter().constructBitPatterns(color); Mockito.verify(mSpiDevice).write(bytes, bytes.length); } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleBitPatternTestConverter.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java similarity index 97% rename from ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleBitPatternTestConverter.java rename to ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java index b8d5eed..d8bffb8 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleBitPatternTestConverter.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java @@ -8,7 +8,7 @@ import java.util.Iterator; import java.util.List; -public class SimpleBitPatternTestConverter { +public class SimpleColorToBitPatternConverter { private static final int [] ONE_BYTE_BIT_MASKS = { 0b1000_0000, 0b0100_0000, From 6c40fb5dea87100fc73dbee5232f637633883e1a Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Tue, 29 Aug 2017 21:19:02 +0200 Subject: [PATCH 08/35] Replaced Storage interface of TwelveBitIntToBitPatternMapper.java by a SparseArray --- .../TwelveBitIntToBitPatternMapper.java | 36 ++++--------------- .../ColorToBitPatternConverterTest.java | 11 ++++++ .../TwelveBitIntToBitPatternMapperTest.java | 20 ++--------- .../contrib/driver/ws2812b/Ws2812bTest.java | 19 ++-------- .../ws2812b/util/SparseArrayMockCreator.java | 36 +++++++++++++++++++ 5 files changed, 58 insertions(+), 64 deletions(-) create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverterTest.java create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SparseArrayMockCreator.java diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java index f4749f0..614f74b 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java @@ -37,16 +37,16 @@ class TwelveBitIntToBitPatternMapper { 0b00000100, 0b00000010, 0b00000001}; - - private final Storage mBitPatternStorage; + @NonNull + private final SparseArray mSparseArray; TwelveBitIntToBitPatternMapper() { - this(new DefaultStorage()); + this(new SparseArray(BIGGEST_12_BIT_NUMBER)); } @VisibleForTesting - TwelveBitIntToBitPatternMapper(@NonNull Storage storage) { - mBitPatternStorage = storage; + TwelveBitIntToBitPatternMapper(@NonNull final SparseArray sparseArray) { + mSparseArray = sparseArray; fillBitPatternStorage(); } @@ -60,7 +60,7 @@ class TwelveBitIntToBitPatternMapper { @NonNull @Size(value = 4) byte[] getBitPattern(@IntRange(from = 0, to = BIGGEST_12_BIT_NUMBER) int twelveBitValue) { - byte[] bitPatternByteArray = mBitPatternStorage.get(twelveBitValue); + byte[] bitPatternByteArray = mSparseArray.get(twelveBitValue); if (bitPatternByteArray == null) { throw new IllegalArgumentException("Only values from 0 to " + BIGGEST_12_BIT_NUMBER + " are allowed. The passed input value was: " + twelveBitValue); @@ -70,7 +70,7 @@ byte[] getBitPattern(@IntRange(from = 0, to = BIGGEST_12_BIT_NUMBER) int twelveB private void fillBitPatternStorage() { for (int i = 0; i <= BIGGEST_12_BIT_NUMBER; i++) { - mBitPatternStorage.put(i, calculateBitPatternByteArray(i)); + mSparseArray.append(i, calculateBitPatternByteArray(i)); } } @@ -134,26 +134,4 @@ private byte convertBitPatternsToByte(List bitPatterns) { } return (byte) bitPatternByte; } - - @VisibleForTesting - /*package*/ interface Storage - { - void put(int key, byte[] value); - byte[] get(int key); - } - - private static class DefaultStorage implements Storage - { - private final SparseArray sparseArray = new SparseArray<>(); - - @Override - public void put(int key, byte[] value) { - sparseArray.append(key, value); - } - - @Override - public byte[] get(int key) { - return sparseArray.get(key); - } - } } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverterTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverterTest.java new file mode 100644 index 0000000..dcfcca0 --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverterTest.java @@ -0,0 +1,11 @@ +package com.google.android.things.contrib.driver.ws2812b; + + +import org.junit.Test; + +public class ColorToBitPatternConverterTest { + + @Test + public void test() { + } +} diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java index 1ab8f3f..a41bf87 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java @@ -1,9 +1,8 @@ package com.google.android.things.contrib.driver.ws2812b; -import android.annotation.SuppressLint; - import com.google.android.things.contrib.driver.ws2812b.util.BitPatternTo12BitIntConverter; +import com.google.android.things.contrib.driver.ws2812b.util.SparseArrayMockCreator; import org.junit.Assert; import org.junit.Test; @@ -11,28 +10,13 @@ import org.powermock.modules.junit4.PowerMockRunner; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; @RunWith(PowerMockRunner.class) public class TwelveBitIntToBitPatternMapperTest { - private TwelveBitIntToBitPatternMapper twelveBitIntToBitPatternMapper = new TwelveBitIntToBitPatternMapper(new TwelveBitIntToBitPatternMapper.Storage() { - @SuppressLint("UseSparseArrays") // Sparse array is not available for Unit tests - private Map map = new HashMap<>(); - @Override - public void put(int key, byte[] value) { - map.put(key, value); - } - - @Override - public byte[] get(int key) { - return map.get(key); - } - }); - + private TwelveBitIntToBitPatternMapper twelveBitIntToBitPatternMapper = new TwelveBitIntToBitPatternMapper(SparseArrayMockCreator.createMockedSparseArray()); @Test public void getBitPattern() throws IOException { diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java index ff9cda6..020f238 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java @@ -1,12 +1,12 @@ package com.google.android.things.contrib.driver.ws2812b; -import android.annotation.SuppressLint; import android.graphics.Color; import android.support.annotation.NonNull; import com.google.android.things.contrib.driver.ws2812b.util.ColorMock; import com.google.android.things.contrib.driver.ws2812b.util.SimpleColorToBitPatternConverter; +import com.google.android.things.contrib.driver.ws2812b.util.SparseArrayMockCreator; import com.google.android.things.pio.SpiDevice; import org.junit.Rule; @@ -21,8 +21,6 @@ import org.powermock.modules.junit4.PowerMockRunner; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; @RunWith(PowerMockRunner.class) @@ -69,20 +67,7 @@ public void writeRed() throws IOException { @NonNull private Ws2812b createWs2812BDevice() throws IOException { - TwelveBitIntToBitPatternMapper patternMapper = new TwelveBitIntToBitPatternMapper(new TwelveBitIntToBitPatternMapper.Storage() { - @SuppressLint("UseSparseArrays") - private Map internalStorage = new HashMap<>(); - - @Override - public void put(int key, byte[] value) { - internalStorage.put(key, value); - } - - @Override - public byte[] get(int key) { - return internalStorage.get(key); - } - }); + TwelveBitIntToBitPatternMapper patternMapper = new TwelveBitIntToBitPatternMapper(SparseArrayMockCreator.createMockedSparseArray()); ColorToBitPatternConverter converter = new ColorToBitPatternConverter(ColorChannelSequence.RBG, patternMapper); return new Ws2812b(mSpiDevice, converter); } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SparseArrayMockCreator.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SparseArrayMockCreator.java new file mode 100644 index 0000000..adffd0c --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SparseArrayMockCreator.java @@ -0,0 +1,36 @@ +package com.google.android.things.contrib.driver.ws2812b.util; + + +import android.util.SparseArray; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.List; + +public class SparseArrayMockCreator { + + public static SparseArray createMockedSparseArray() { + + @SuppressWarnings("unchecked") + SparseArray sparseArray = Mockito.mock(SparseArray.class); + final ArgumentCaptor keyCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor valueCaptor = ArgumentCaptor.forClass(byte[].class); + + Mockito.doNothing().when(sparseArray).append(keyCaptor.capture(), valueCaptor.capture()); + + Mockito.when(sparseArray.get(Mockito.anyInt())).thenAnswer(new Answer() { + @Override + public byte[] answer(InvocationOnMock invocation) throws Throwable { + Integer key = invocation.getArgumentAt(0, Integer.class); + List allKeys = keyCaptor.getAllValues(); + int lastIndexOfKey = allKeys.lastIndexOf(key); + return lastIndexOfKey != -1 ? valueCaptor.getAllValues().get(lastIndexOfKey) : null; + } + }); + + return sparseArray; + } +} From d5a8e972e043bc1424aabe45a284bbdba238b93c Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Wed, 30 Aug 2017 20:42:21 +0200 Subject: [PATCH 09/35] Name refactoring and completion of ColorToBitPatternConverterTest.java --- .../driver/ws2812b/ColorChannelSequence.java | 6 +- .../ws2812b/ColorChannelSequenceTest.java | 66 +++++-------- .../ColorToBitPatternConverterTest.java | 99 ++++++++++++++++++- .../TwelveBitIntToBitPatternMapperTest.java | 6 +- .../driver/ws2812b/util/ColorUtil.java | 29 ++++++ ...r.java => ReverseBitPatternConverter.java} | 21 +++- 6 files changed, 174 insertions(+), 53 deletions(-) create mode 100644 ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorUtil.java rename ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/{BitPatternTo12BitIntConverter.java => ReverseBitPatternConverter.java} (77%) diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java index 95a0494..064c207 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java @@ -23,8 +23,8 @@ class ColorChannelSequence { public static final int BRG = 5; @SuppressWarnings("WeakerAccess") public static final int BGR = 6; - @SuppressWarnings("WeakerAccess") - private static final int[] AVAILABLE_ORDER = {RGB, RBG, GRB, GBR, BRG, BGR}; + @Sequence + private static final int[] ALL_SEQUENCES = {RGB, RBG, GRB, GBR, BRG, BGR}; @Retention(SOURCE) @IntDef({RGB, RBG, GRB, GBR, BRG, BGR}) @@ -83,7 +83,7 @@ public int rearrangeColorChannels(int color) { } }; default: - throw new IllegalArgumentException("Invalid color channel sequence: " + colorChannelSequence + ". Supported color channel sequences are: " + Arrays.toString(ColorChannelSequence.AVAILABLE_ORDER)); + throw new IllegalArgumentException("Invalid color channel sequence: " + colorChannelSequence + ". Supported color channel sequences are: " + Arrays.toString(ColorChannelSequence.ALL_SEQUENCES)); } } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java index 55b6b0a..f66ba56 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java @@ -2,10 +2,9 @@ import android.graphics.Color; -import android.support.annotation.ColorInt; -import android.support.annotation.IntRange; import com.google.android.things.contrib.driver.ws2812b.util.ColorMock; +import com.google.android.things.contrib.driver.ws2812b.util.ColorUtil; import junit.framework.Assert; @@ -25,12 +24,12 @@ public class ColorChannelSequenceTest { private int blue; @Before - public void before() { + public void setUp() { ColorMock.mockStatic(); - red = generateRandomColorValue(); - green = generateRandomColorValue(); - blue = generateRandomColorValue(); + red = ColorUtil.generateRandomColorValue(); + green = ColorUtil.generateRandomColorValue(); + blue = ColorUtil.generateRandomColorValue(); } @Test @@ -39,9 +38,9 @@ public void reorderToRgb(){ int rgbColor = Color.rgb(red, green, blue); int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); - Assert.assertEquals(get1stColor(rearrangedColor), red); - Assert.assertEquals(get2ndColor(rearrangedColor), green); - Assert.assertEquals(get3rdColor(rearrangedColor), blue); + Assert.assertEquals(ColorUtil.get1stColor(rearrangedColor), red); + Assert.assertEquals(ColorUtil.get2ndColor(rearrangedColor), green); + Assert.assertEquals(ColorUtil.get3rdColor(rearrangedColor), blue); } @Test @@ -50,9 +49,9 @@ public void reorderToRbg(){ int rgbColor = Color.rgb(red, green, blue); int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); - Assert.assertEquals(get1stColor(rearrangedColor), red); - Assert.assertEquals(get2ndColor(rearrangedColor), blue); - Assert.assertEquals(get3rdColor(rearrangedColor), green); + Assert.assertEquals(ColorUtil.get1stColor(rearrangedColor), red); + Assert.assertEquals(ColorUtil.get2ndColor(rearrangedColor), blue); + Assert.assertEquals(ColorUtil.get3rdColor(rearrangedColor), green); } @Test @@ -61,9 +60,9 @@ public void reorderToGrb(){ int rgbColor = Color.rgb(red, green, blue); int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); - Assert.assertEquals(get1stColor(rearrangedColor), green); - Assert.assertEquals(get2ndColor(rearrangedColor), red); - Assert.assertEquals(get3rdColor(rearrangedColor), blue); + Assert.assertEquals(ColorUtil.get1stColor(rearrangedColor), green); + Assert.assertEquals(ColorUtil.get2ndColor(rearrangedColor), red); + Assert.assertEquals(ColorUtil.get3rdColor(rearrangedColor), blue); } @Test @@ -72,9 +71,9 @@ public void reorderToGbr(){ int rgbColor = Color.rgb(red, green, blue); int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); - Assert.assertEquals(get1stColor(rearrangedColor), green); - Assert.assertEquals(get2ndColor(rearrangedColor), blue); - Assert.assertEquals(get3rdColor(rearrangedColor), red); + Assert.assertEquals(ColorUtil.get1stColor(rearrangedColor), green); + Assert.assertEquals(ColorUtil.get2ndColor(rearrangedColor), blue); + Assert.assertEquals(ColorUtil.get3rdColor(rearrangedColor), red); } @Test @@ -83,9 +82,9 @@ public void reorderToBrg(){ int rgbColor = Color.rgb(red, green, blue); int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); - Assert.assertEquals(get1stColor(rearrangedColor), blue); - Assert.assertEquals(get2ndColor(rearrangedColor), red); - Assert.assertEquals(get3rdColor(rearrangedColor), green); + Assert.assertEquals(ColorUtil.get1stColor(rearrangedColor), blue); + Assert.assertEquals(ColorUtil.get2ndColor(rearrangedColor), red); + Assert.assertEquals(ColorUtil.get3rdColor(rearrangedColor), green); } @Test @@ -94,28 +93,9 @@ public void reorderToBgr(){ int rgbColor = Color.rgb(red, green, blue); int rearrangedColor = sequencer.rearrangeColorChannels(rgbColor); - Assert.assertEquals(get1stColor(rearrangedColor), blue); - Assert.assertEquals(get2ndColor(rearrangedColor), green); - Assert.assertEquals(get3rdColor(rearrangedColor), red); + Assert.assertEquals(ColorUtil.get1stColor(rearrangedColor), blue); + Assert.assertEquals(ColorUtil.get2ndColor(rearrangedColor), green); + Assert.assertEquals(ColorUtil.get3rdColor(rearrangedColor), red); } - @IntRange(from = 0, to = 255) - private int generateRandomColorValue() { - return (int) Math.round(Math.random() * 255); - } - - private static int get1stColor(@ColorInt int color) - { - return Color.red(color); - } - - private static int get2ndColor(@ColorInt int color) - { - return Color.green(color); - } - - private static int get3rdColor(@ColorInt int color) - { - return Color.blue(color); - } } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverterTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverterTest.java index dcfcca0..bb9ce08 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverterTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverterTest.java @@ -1,11 +1,108 @@ package com.google.android.things.contrib.driver.ws2812b; +import android.graphics.Color; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.util.SparseArray; + +import com.google.android.things.contrib.driver.ws2812b.util.ColorUtil; +import com.google.android.things.contrib.driver.ws2812b.util.ReverseBitPatternConverter; +import com.google.android.things.contrib.driver.ws2812b.util.ColorMock; +import com.google.android.things.contrib.driver.ws2812b.util.SparseArrayMockCreator; + +import org.junit.Assert; import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +@RunWith(PowerMockRunner.class) +@PrepareForTest(android.graphics.Color.class) public class ColorToBitPatternConverterTest { + private final ReverseBitPatternConverter reverseConverter = new ReverseBitPatternConverter(); + + @Test + public void convertToBitPattern_Rgb() { + ColorMock.mockStatic(); + @ColorInt int randomColor = generateRandomColor(); + ColorToBitPatternConverter converter = createColorToBitPatternConverter(ColorChannelSequence.RGB); + byte[] bitPatterns = converter.convertToBitPattern(new int[]{randomColor}); + int reconstructedColor = reverseConverter.convertBitPatternTo24BitInt(bitPatterns); + Assert.assertEquals(reconstructedColor, randomColor); + } + @Test - public void test() { + public void convertToBitPattern_Rbg() { + ColorMock.mockStatic(); + @ColorInt int randomColor = generateRandomColor(); + ColorToBitPatternConverter converter = createColorToBitPatternConverter(ColorChannelSequence.RBG); + byte[] bitPatterns = converter.convertToBitPattern(new int[]{randomColor}); + int reconstructedColor = reverseConverter.convertBitPatternTo24BitInt(bitPatterns); + Assert.assertEquals(ColorUtil.get1stColor(reconstructedColor), Color.red(randomColor)); + Assert.assertEquals(ColorUtil.get2ndColor(reconstructedColor), Color.blue(randomColor)); + Assert.assertEquals(ColorUtil.get3rdColor(reconstructedColor), Color.green(randomColor)); + } + + + @Test + public void convertToBitPattern_Grb() { + ColorMock.mockStatic(); + @ColorInt int randomColor = generateRandomColor(); + ColorToBitPatternConverter converter = createColorToBitPatternConverter(ColorChannelSequence.GRB); + byte[] bitPatterns = converter.convertToBitPattern(new int[]{randomColor}); + int reconstructedColor = reverseConverter.convertBitPatternTo24BitInt(bitPatterns); + Assert.assertEquals(ColorUtil.get1stColor(reconstructedColor), Color.green(randomColor)); + Assert.assertEquals(ColorUtil.get2ndColor(reconstructedColor), Color.red(randomColor)); + Assert.assertEquals(ColorUtil.get3rdColor(reconstructedColor), Color.blue(randomColor)); + } + + @Test + public void convertToBitPattern_Gbr() { + ColorMock.mockStatic(); + @ColorInt int randomColor = generateRandomColor(); + ColorToBitPatternConverter converter = createColorToBitPatternConverter(ColorChannelSequence.GBR); + byte[] bitPatterns = converter.convertToBitPattern(new int[]{randomColor}); + int reconstructedColor = reverseConverter.convertBitPatternTo24BitInt(bitPatterns); + Assert.assertEquals(ColorUtil.get1stColor(reconstructedColor), Color.green(randomColor)); + Assert.assertEquals(ColorUtil.get2ndColor(reconstructedColor), Color.blue(randomColor)); + Assert.assertEquals(ColorUtil.get3rdColor(reconstructedColor), Color.red(randomColor)); + } + + @Test + public void convertToBitPattern_Brg() { + ColorMock.mockStatic(); + @ColorInt int randomColor = generateRandomColor(); + ColorToBitPatternConverter converter = createColorToBitPatternConverter(ColorChannelSequence.BRG); + byte[] bitPatterns = converter.convertToBitPattern(new int[]{randomColor}); + int reconstructedColor = reverseConverter.convertBitPatternTo24BitInt(bitPatterns); + Assert.assertEquals(ColorUtil.get1stColor(reconstructedColor), Color.blue(randomColor)); + Assert.assertEquals(ColorUtil.get2ndColor(reconstructedColor), Color.red(randomColor)); + Assert.assertEquals(ColorUtil.get3rdColor(reconstructedColor), Color.green(randomColor)); + } + + @Test + public void convertToBitPattern_Bgr() { + ColorMock.mockStatic(); + @ColorInt int randomColor = generateRandomColor(); + ColorToBitPatternConverter converter = createColorToBitPatternConverter(ColorChannelSequence.BGR); + byte[] bitPatterns = converter.convertToBitPattern(new int[]{randomColor}); + int reconstructedColor = reverseConverter.convertBitPatternTo24BitInt(bitPatterns); + Assert.assertEquals(ColorUtil.get1stColor(reconstructedColor), Color.blue(randomColor)); + Assert.assertEquals(ColorUtil.get2ndColor(reconstructedColor), Color.green(randomColor)); + Assert.assertEquals(ColorUtil.get3rdColor(reconstructedColor), Color.red(randomColor)); + } + + @ColorInt + private int generateRandomColor() { + return Color.rgb((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255)); + } + + @NonNull + private ColorToBitPatternConverter createColorToBitPatternConverter(@ColorChannelSequence.Sequence int sequence) { + SparseArray mockedSparseArray = SparseArrayMockCreator.createMockedSparseArray(); + TwelveBitIntToBitPatternMapper bitPatternMapper = new TwelveBitIntToBitPatternMapper(mockedSparseArray); + return new ColorToBitPatternConverter(sequence, bitPatternMapper); } } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java index a41bf87..da84028 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapperTest.java @@ -1,7 +1,7 @@ package com.google.android.things.contrib.driver.ws2812b; -import com.google.android.things.contrib.driver.ws2812b.util.BitPatternTo12BitIntConverter; +import com.google.android.things.contrib.driver.ws2812b.util.ReverseBitPatternConverter; import com.google.android.things.contrib.driver.ws2812b.util.SparseArrayMockCreator; import org.junit.Assert; @@ -20,11 +20,11 @@ public class TwelveBitIntToBitPatternMapperTest { @Test public void getBitPattern() throws IOException { - BitPatternTo12BitIntConverter converter = new BitPatternTo12BitIntConverter(); + ReverseBitPatternConverter reverseConverter = new ReverseBitPatternConverter(); double limit = Math.pow(2, 12); for (int i = 0; i < limit; i++) { byte[] bitPattern = twelveBitIntToBitPatternMapper.getBitPattern(i); - int originalValue = converter.convertBitPatternTo12BitInt(bitPattern); + int originalValue = reverseConverter.convertBitPatternTo12BitInt(bitPattern); Assert.assertEquals(originalValue, i); } } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorUtil.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorUtil.java new file mode 100644 index 0000000..87bd01d --- /dev/null +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorUtil.java @@ -0,0 +1,29 @@ +package com.google.android.things.contrib.driver.ws2812b.util; + + +import android.graphics.Color; +import android.support.annotation.ColorInt; +import android.support.annotation.IntRange; + +public class ColorUtil { + + @IntRange(from = 0, to = 255) + public static int generateRandomColorValue() { + return (int) Math.round(Math.random() * 255); + } + + public static int get1stColor(@ColorInt int color) + { + return Color.red(color); + } + + public static int get2ndColor(@ColorInt int color) + { + return Color.green(color); + } + + public static int get3rdColor(@ColorInt int color) + { + return Color.blue(color); + } +} diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/BitPatternTo12BitIntConverter.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ReverseBitPatternConverter.java similarity index 77% rename from ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/BitPatternTo12BitIntConverter.java rename to ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ReverseBitPatternConverter.java index a0c1277..0d876d7 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/BitPatternTo12BitIntConverter.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ReverseBitPatternConverter.java @@ -1,6 +1,7 @@ package com.google.android.things.contrib.driver.ws2812b.util; +import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Size; @@ -10,10 +11,24 @@ import java.util.List; import java.util.ListIterator; -public class BitPatternTo12BitIntConverter { +public class ReverseBitPatternConverter { - public int convertBitPatternTo12BitInt(@Size(value = 4) byte[] bitPatterns) - { + @IntRange(from = 0, to = (1 << 24) -1) + public int convertBitPatternTo24BitInt(@Size(value = 8) byte[] bitPatterns) { + byte[] firstBitPatterns = new byte[4]; + byte[] secondBitPatterns = new byte[4]; + + System.arraycopy(bitPatterns, 0, firstBitPatterns, 0, 4); + System.arraycopy(bitPatterns, 4, secondBitPatterns, 0, 4); + + int first12BitInt = convertBitPatternTo12BitInt(firstBitPatterns); + int second12BitInt = convertBitPatternTo12BitInt(secondBitPatterns); + + return (first12BitInt << 12) | second12BitInt; + } + + @IntRange(from = 0, to = (1 << 12) -1) + public int convertBitPatternTo12BitInt(@Size(value = 4) byte[] bitPatterns) { List booleanBitPatterns = convertToBooleanBitPatternWithMissingPauseBit(bitPatterns); List originalBooleanBits = new ArrayList<>(); ListIterator booleanBitPatternIterator = booleanBitPatterns.listIterator(); From 8414469c2386660bf91c0e3d32f3179686d9e37a Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Wed, 30 Aug 2017 23:46:40 +0200 Subject: [PATCH 10/35] Updated README.md and ws2812b-timings.svg --- ws2812b/README.md | 20 + ws2812b/ws2812b-timings-svg-objects.svg | 450 +++++++++++++++ ws2812b/ws2812b-timings.svg | 724 ++++++++++++++---------- 3 files changed, 893 insertions(+), 301 deletions(-) create mode 100644 ws2812b/ws2812b-timings-svg-objects.svg diff --git a/ws2812b/README.md b/ws2812b/README.md index 38d38a5..ef3127f 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -21,3 +21,23 @@ This approach is using an assembly of 3 bits to represent 1 WS2812B bit: The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. You could create also a more accurate bit patterns which consists of more than 3 bits. But the more bits you use to express one WS2812b bit, the less is the number of controllable LEDs. Because the SPI is using a fixed sized byte buffer to send the data. + + +Copyright 2016 Google Inc. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you under the Apache License, Version 2.0 (the "License"); you may not +use this file except in compliance with the License. You may obtain a copy of +the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. + +[jcenter]: https://bintray.com/google/androidthings/contrib-driver-ws2812b/_latestVersion \ No newline at end of file diff --git a/ws2812b/ws2812b-timings-svg-objects.svg b/ws2812b/ws2812b-timings-svg-objects.svg new file mode 100644 index 0000000..1270a59 --- /dev/null +++ b/ws2812b/ws2812b-timings-svg-objects.svg @@ -0,0 +1,450 @@ + + + +image/svg+xml0 bit +1 bit +400ns +850ns +400ns + + +850ns +high voltage +high voltage +low voltage +low voltage + \ No newline at end of file diff --git a/ws2812b/ws2812b-timings.svg b/ws2812b/ws2812b-timings.svg index 3c62fd0..88c8afe 100644 --- a/ws2812b/ws2812b-timings.svg +++ b/ws2812b/ws2812b-timings.svg @@ -1,337 +1,459 @@ - - image/svg+xml0code -1code -RETcode -T0H -T0L -T1H -T1L -Treset -400ns -850ns -850ns -400ns -< 50μs - + id="tspan4259" /> - \ No newline at end of file + id="tspan4263" /> + \ No newline at end of file From 9e2bb569aa66c52c66bd32e2000b13ebc9f1d891 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Thu, 31 Aug 2017 00:14:55 +0200 Subject: [PATCH 11/35] Update README.md Replaced timing SVG --- ws2812b/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index ef3127f..6ba6488 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -4,7 +4,7 @@ How does it work The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long data block. The transmission of these bits is done by sending a chain of high and low voltage pulses to the data line of the LED controller. Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. Both pulses must have an exact specified duration, so that they are recognized as 1 or 0 bit: - + * A 1 bit is defined by a high voltage pulse with a duration of 850 ns which is followed by a low voltage pulse of 400 ns * A 0 bit is defined by a high voltage pulse with a duration of 400 ns which is followed by a low voltage pulse of 850 ns @@ -40,4 +40,4 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -[jcenter]: https://bintray.com/google/androidthings/contrib-driver-ws2812b/_latestVersion \ No newline at end of file +[jcenter]: https://bintray.com/google/androidthings/contrib-driver-ws2812b/_latestVersion From a7e3786cd3435113a5f715c1ccaf04ce18e36682 Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Thu, 31 Aug 2017 01:12:46 +0200 Subject: [PATCH 12/35] Updated SVGs --- ws2812b/ws2812b-bit-pattern-svg-objects.svg | 555 +++++++++++++++ ws2812b/ws2812b-bit-pattern.svg | 740 +++++++++++++------- ws2812b/ws2812b-timings-svg-objects.svg | 42 +- ws2812b/ws2812b-timings.svg | 350 +++++---- 4 files changed, 1248 insertions(+), 439 deletions(-) create mode 100644 ws2812b/ws2812b-bit-pattern-svg-objects.svg diff --git a/ws2812b/ws2812b-bit-pattern-svg-objects.svg b/ws2812b/ws2812b-bit-pattern-svg-objects.svg new file mode 100644 index 0000000..7391faf --- /dev/null +++ b/ws2812b/ws2812b-bit-pattern-svg-objects.svg @@ -0,0 +1,555 @@ + + + +image/svg+xml1 pattern = 110 +417ns + + +417ns +417ns +834ns +0 bit +1 bit +1 bit +0 pattern = 100 +417ns +417ns +417ns +834ns +0 bit +0 bit +1 bit + + \ No newline at end of file diff --git a/ws2812b/ws2812b-bit-pattern.svg b/ws2812b/ws2812b-bit-pattern.svg index 7aa6ce7..a6d6b7b 100644 --- a/ws2812b/ws2812b-bit-pattern.svg +++ b/ws2812b/ws2812b-bit-pattern.svg @@ -13,50 +13,85 @@ version="1.1" inkscape:version="0.91 r13725" xml:space="preserve" - width="202.29182" - height="168.62419" - viewBox="0 0 202.29182 168.62418" + width="302.82693" + height="163.81889" + viewBox="0 0 302.82693 163.81888" sodipodi:docname="ws2812b-bit-pattern.svg">image/svg+xml417ns1 bit - 417ns1 bit -417ns0 bit -834ns -1 pattern -417ns1 bit -417ns0 bit -417ns0 bit -834ns -0 pattern + id="tspan5951" + x="289.8848" + y="-746.44556" /> \ No newline at end of file diff --git a/ws2812b/ws2812b-timings-svg-objects.svg b/ws2812b/ws2812b-timings-svg-objects.svg index 1270a59..f9f919a 100644 --- a/ws2812b/ws2812b-timings-svg-objects.svg +++ b/ws2812b/ws2812b-timings-svg-objects.svg @@ -14,8 +14,8 @@ inkscape:version="0.91 r13725" xml:space="preserve" width="205.84219" - height="143.79297" - viewBox="0 0 205.84219 143.79296" + height="175.37582" + viewBox="0 0 205.84219 175.37581" sodipodi:docname="ws2812b-timings-svg-objects.svg">image/svg+xml0 bit 400ns 850ns high voltage @@ -406,14 +406,14 @@ xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:32px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="113.4515" - y="-674.60376" + y="-724.203" id="text8620-6" sodipodi:linespacing="125%" transform="scale(1,-1)">high voltage @@ -421,14 +421,14 @@ xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:32px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="178.25151" - y="-674.60376" + y="-669.80383" id="text8620-1" sodipodi:linespacing="125%" transform="scale(1,-1)">low voltage diff --git a/ws2812b/ws2812b-timings.svg b/ws2812b/ws2812b-timings.svg index e58901d..d4e4774 100644 --- a/ws2812b/ws2812b-timings.svg +++ b/ws2812b/ws2812b-timings.svg @@ -14,8 +14,8 @@ inkscape:version="0.91 r13725" xml:space="preserve" width="205.84219" - height="143.79297" - viewBox="0 0 205.84219 143.79296" + height="175.37582" + viewBox="0 0 205.84219 175.37581" sodipodi:docname="ws2812b-timings.svg">image/svg+xml + + \ No newline at end of file + id="path6552" /> \ No newline at end of file From c14727db55f74a874ca8f5ffdb520e35ccba2cf0 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Thu, 31 Aug 2017 01:13:57 +0200 Subject: [PATCH 13/35] Update README.md --- ws2812b/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 6ba6488..51f02b2 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -18,7 +18,7 @@ At the moment there is no direct solution to send such short timed pulses with * Now the solution gets within reach. To control WS2812B LEDs by the SPI we must find an assembly of SPI bits (bit pattern) and a frequency so that this bit pattern is recognized as one WS2812B bit. This approach is using an assembly of 3 bits to represent 1 WS2812B bit: - + The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. You could create also a more accurate bit patterns which consists of more than 3 bits. But the more bits you use to express one WS2812b bit, the less is the number of controllable LEDs. Because the SPI is using a fixed sized byte buffer to send the data. From e4b45d9a3ee29275df6f7a27f5f104e41aaf0570 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Thu, 31 Aug 2017 01:15:58 +0200 Subject: [PATCH 14/35] Update README.md Fixed wrong SVG filename --- ws2812b/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 51f02b2..b44825e 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -18,7 +18,7 @@ At the moment there is no direct solution to send such short timed pulses with * Now the solution gets within reach. To control WS2812B LEDs by the SPI we must find an assembly of SPI bits (bit pattern) and a frequency so that this bit pattern is recognized as one WS2812B bit. This approach is using an assembly of 3 bits to represent 1 WS2812B bit: - + The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. You could create also a more accurate bit patterns which consists of more than 3 bits. But the more bits you use to express one WS2812b bit, the less is the number of controllable LEDs. Because the SPI is using a fixed sized byte buffer to send the data. From 9839cedfdb58867247b4d8444f21c785ccb4c621 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Tue, 5 Sep 2017 00:49:20 +0200 Subject: [PATCH 15/35] Update README.md --- ws2812b/README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index b44825e..6f30995 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -10,7 +10,7 @@ Each separate bit is hereby defined by a high voltage pulse which is followed by * A 0 bit is defined by a high voltage pulse with a duration of 400 ns which is followed by a low voltage pulse of 850 ns * Each pulse can have a deviation of +/- 150 ns -At the moment there is no direct solution to send such short timed pulses with **different durations** by the API of Android Things. There is however the [Serial Peripheral Interface (SPI)](https://developer.android.com/things/sdk/pio/spi.html) which is only able to send short timed pulses with the **exact same** duration: +At the moment there is no direct solution to send such short timed pulses with **different durations** by the API of Android Things. There is however the [Serial Peripheral Interface (SPI)](https://developer.android.com/things/sdk/pio/spi.html) which can send bits as pulses with the **exact same** duration: * This duration is indirectly defined by the frequency of the SPI. * A transmitted 1 bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout * A transmitted 0 bit results in a short low voltage pulse at the MOSI pinout @@ -20,8 +20,21 @@ This approach is using an assembly of 3 bits to represent 1 WS2812B bit: -The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. You could create also a more accurate bit patterns which consists of more than 3 bits. But the more bits you use to express one WS2812b bit, the less is the number of controllable LEDs. Because the SPI is using a fixed sized byte buffer to send the data. +The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. You could create also a more accurate bit patterns which consists of more than 3 bits. But the more bits you use to express one WS2812b bit, the less is the number of controllable LEDs. Because the SPI is using a fixed sized byte buffer to send the data. +If the the SPI sends more than 8 bits in a row, a pause bit is automatically sent after the 8th bit. This pause bit can be understood as 0 bit between the 8th and the 9th bit. What implications does that have for our bit pattern conversion? Fortunately, this results only in a removing of the 9th bit, because every 9th bit is a 0 bit after we converted a arbitrary bit sequence. For example, if we take the following source bit sequence: 001 and converts it with our bit patterns it will result in: 100 100 11~~0.~~ As you can see the 9th bit is a 0 bit and can be removed because it will be sent automatically by the SPI device. This 9th 0 bit exists for any possible 3 bit long source sequence after the conversation: + +| Source bit sequence | Destination bit sequence | +| ------------------- |:------------------------:| +| 111 | 110 110 11~~0~~ | +| 011 | 100 110 11~~0~~ | +| 001 | 100 100 11~~0~~ | +| 000 | 100 100 10~~0~~ | +| 010 | 100 110 10~~0~~ | +| 100 | 110 100 10~~0~~ | +| 110 | 110 110 10~~0~~ | + +With this in mind any possible 24 bit color can be represented by 8 times 8 converted bits. Which means that 8 bytes must be sent to set the color of 1 LED. Copyright 2016 Google Inc. From 64bb7aee4ea1e4639dd8d6f0bcc1fcc848b98266 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Wed, 6 Sep 2017 01:08:42 +0200 Subject: [PATCH 16/35] Update README.md --- ws2812b/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 6f30995..a4ea879 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -1,28 +1,28 @@ # readme-svg-test How does it work --------------------- -The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long data block. The transmission of these bits is done by sending a chain of high and low voltage pulses to the data line of the LED controller. +The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long data block. The transmission of these bits is done by sending a chain of high and low voltage pulses over the data line of the LED controller. Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. Both pulses must have an exact specified duration, so that they are recognized as 1 or 0 bit: -* A 1 bit is defined by a high voltage pulse with a duration of 850 ns which is followed by a low voltage pulse of 400 ns * A 0 bit is defined by a high voltage pulse with a duration of 400 ns which is followed by a low voltage pulse of 850 ns +* A 1 bit is defined by a high voltage pulse with a duration of 850 ns which is followed by a low voltage pulse of 400 ns * Each pulse can have a deviation of +/- 150 ns -At the moment there is no direct solution to send such short timed pulses with **different durations** by the API of Android Things. There is however the [Serial Peripheral Interface (SPI)](https://developer.android.com/things/sdk/pio/spi.html) which can send bits as pulses with the **exact same** duration: +At the moment there is no direct solution to send such short timed pulses with **different durations** by the API of Android Things. There is however the [Serial Peripheral Interface (SPI)](https://developer.android.com/things/sdk/pio/spi.html) which can send bits as voltage pulses. Whereas each single bit results in a pulse of the **exact same** duration. + * This duration is indirectly defined by the frequency of the SPI. * A transmitted 1 bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout * A transmitted 0 bit results in a short low voltage pulse at the MOSI pinout -Now the solution gets within reach. To control WS2812B LEDs by the SPI we must find an assembly of SPI bits (bit pattern) and a frequency so that this bit pattern is recognized as one WS2812B bit. -This approach is using an assembly of 3 bits to represent 1 WS2812B bit: +Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of volatage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. Based on these bit patterns we are able to convert any color data to a sequence of bits which are recognizable by the WS2812B controller when it is sent over SPI. This library is using a 3 bit sized bit pattern to convert one input bit: The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. You could create also a more accurate bit patterns which consists of more than 3 bits. But the more bits you use to express one WS2812b bit, the less is the number of controllable LEDs. Because the SPI is using a fixed sized byte buffer to send the data. -If the the SPI sends more than 8 bits in a row, a pause bit is automatically sent after the 8th bit. This pause bit can be understood as 0 bit between the 8th and the 9th bit. What implications does that have for our bit pattern conversion? Fortunately, this results only in a removing of the 9th bit, because every 9th bit is a 0 bit after we converted a arbitrary bit sequence. For example, if we take the following source bit sequence: 001 and converts it with our bit patterns it will result in: 100 100 11~~0.~~ As you can see the 9th bit is a 0 bit and can be removed because it will be sent automatically by the SPI device. This 9th 0 bit exists for any possible 3 bit long source sequence after the conversation: +If the the SPI sends more than 8 bits in a row, a short break in form of a low voltage pulse is done automatically. This pause pulse has the same duration as every other bit. It can be understood as inserted 0 bit between the 8th and the 9th bit. This implicates that every 9th bit should be a 0 bit, if we want to remove it without data corruption. Fortunately, every 9th bit is a 0 bit if u have a look into the table below: | Source bit sequence | Destination bit sequence | | ------------------- |:------------------------:| @@ -34,7 +34,7 @@ If the the SPI sends more than 8 bits in a row, a pause bit is automatically sen | 100 | 110 100 10~~0~~ | | 110 | 110 110 10~~0~~ | -With this in mind any possible 24 bit color can be represented by 8 times 8 converted bits. Which means that 8 bytes must be sent to set the color of 1 LED. +With this in mind we can represent 3 source bits by 1 destination byte. So any possible 24 bit color needs to be converted to 8 byte sized sequence of bit patterns. Copyright 2016 Google Inc. From 2d21d5fd4611ce6a812b28c6905a1ccdae858a1d Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Sat, 9 Sep 2017 18:19:40 +0200 Subject: [PATCH 17/35] Updated compileSdkVersion, minSdkVersion, targetSdkVersion and buildToolsVersion to 26 --- ws2812b/build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ws2812b/build.gradle b/ws2812b/build.gradle index e41ad57..e0127d5 100644 --- a/ws2812b/build.gradle +++ b/ws2812b/build.gradle @@ -17,12 +17,12 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 25 - buildToolsVersion '25.0.3' + compileSdkVersion 26 + buildToolsVersion '26.0.1' defaultConfig { - minSdkVersion 25 - targetSdkVersion 25 + minSdkVersion 26 + targetSdkVersion 26 versionCode 1 versionName "1.0" } @@ -37,7 +37,7 @@ repositories { // dependencies { provided 'com.google.android.things:androidthings:0.5-devpreview' - compile 'com.android.support:support-annotations:25.4.0' + compile 'com.android.support:support-annotations:26.0.2' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' From 0bd5ba6a95bd6c2917edc2fb12ec5735e8260ccb Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 17 Sep 2017 21:08:30 +0200 Subject: [PATCH 18/35] Updated support-annotations library to 26.1.0 --- ws2812b/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ws2812b/build.gradle b/ws2812b/build.gradle index e0127d5..9c17c2d 100644 --- a/ws2812b/build.gradle +++ b/ws2812b/build.gradle @@ -37,7 +37,7 @@ repositories { // dependencies { provided 'com.google.android.things:androidthings:0.5-devpreview' - compile 'com.android.support:support-annotations:26.0.2' + compile 'com.android.support:support-annotations:26.1.0' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' From 64eabeecaf426f90aa2b3a17a57ead009a661799 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Tue, 19 Sep 2017 01:27:50 +0200 Subject: [PATCH 19/35] Update README.md --- ws2812b/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index a4ea879..9285798 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -1,8 +1,8 @@ # readme-svg-test How does it work --------------------- -The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long data block. The transmission of these bits is done by sending a chain of high and low voltage pulses over the data line of the LED controller. -Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. Both pulses must have an exact specified duration, so that they are recognized as 1 or 0 bit: +The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long block of data. The transmission of these bits is done by sending a chain of high and low voltage pulses over the data line of the LED controller. +Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. Both pulses must have an exact specified duration with a small deviation, so that they are recognized as 1 or 0 bit: @@ -16,13 +16,13 @@ At the moment there is no direct solution to send such short timed pulses with * * A transmitted 1 bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout * A transmitted 0 bit results in a short low voltage pulse at the MOSI pinout -Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of volatage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. Based on these bit patterns we are able to convert any color data to a sequence of bits which are recognizable by the WS2812B controller when it is sent over SPI. This library is using a 3 bit sized bit pattern to convert one input bit: +Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of voltage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. Based on these bit patterns we are able to convert any color data to a sequence of bits which are recognizable by the WS2812B controller when it is sent over SPI. This library is using a 3 bit sized bit pattern to convert one input bit: -The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. You could create also a more accurate bit patterns which consists of more than 3 bits. But the more bits you use to express one WS2812b bit, the less is the number of controllable LEDs. Because the SPI is using a fixed sized byte buffer to send the data. +The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. It is possible to create a more accurate bit pattern with more than 3 bits, but the greater size of the bit pattern, the faster is the fixed size SPI buffer full and the less is the number of controllable LEDs. -If the the SPI sends more than 8 bits in a row, a short break in form of a low voltage pulse is done automatically. This pause pulse has the same duration as every other bit. It can be understood as inserted 0 bit between the 8th and the 9th bit. This implicates that every 9th bit should be a 0 bit, if we want to remove it without data corruption. Fortunately, every 9th bit is a 0 bit if u have a look into the table below: +The last problem we must solve, is the low voltage pause between each transmitted byte: If the the SPI sends more than 8 bits in a row, a short break in form of a low voltage pulse is done automatically. This pause pulse has the same duration as the bits, which are really sent. So it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. To keep our data safe from corruption we must handle this "inserted" bit. Fortunately, any arbitrary sequence of our described bit patterns are resulting in a row of bits where every 9th bit is a 0 bit. So the simple solution is to remove this last bit. The table below shows this fact: | Source bit sequence | Destination bit sequence | | ------------------- |:------------------------:| From dae568a6a1f8404eb9795ac3d84e9fddfaed18d8 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Mon, 9 Oct 2017 21:55:53 +0200 Subject: [PATCH 20/35] Update README.md --- ws2812b/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 9285798..732acdb 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -2,7 +2,7 @@ How does it work --------------------- The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long block of data. The transmission of these bits is done by sending a chain of high and low voltage pulses over the data line of the LED controller. -Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. Both pulses must have an exact specified duration with a small deviation, so that they are recognized as 1 or 0 bit: +Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. The recognition as 0 or 1 bit is defined by the timings of these pulses: @@ -16,13 +16,13 @@ At the moment there is no direct solution to send such short timed pulses with * * A transmitted 1 bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout * A transmitted 0 bit results in a short low voltage pulse at the MOSI pinout -Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of voltage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. Based on these bit patterns we are able to convert any color data to a sequence of bits which are recognizable by the WS2812B controller when it is sent over SPI. This library is using a 3 bit sized bit pattern to convert one input bit: +Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of voltage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. Based on these bit patterns we are able to convert any color data to a sequence of bits which are recognizable by the WS2812B controller when it is sent by SPI. This library is using a 3 bit sized bit pattern to convert one input bit: The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. It is possible to create a more accurate bit pattern with more than 3 bits, but the greater size of the bit pattern, the faster is the fixed size SPI buffer full and the less is the number of controllable LEDs. -The last problem we must solve, is the low voltage pause between each transmitted byte: If the the SPI sends more than 8 bits in a row, a short break in form of a low voltage pulse is done automatically. This pause pulse has the same duration as the bits, which are really sent. So it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. To keep our data safe from corruption we must handle this "inserted" bit. Fortunately, any arbitrary sequence of our described bit patterns are resulting in a row of bits where every 9th bit is a 0 bit. So the simple solution is to remove this last bit. The table below shows this fact: +The last problem we must solve, is the low voltage pause between each transmitted byte: If the the SPI sends more than 8 bits in a row, a short break in form of a low voltage pulse is done automatically. This pause pulse has the same duration as the bits, which are really sent. So it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. To keep our data safe from corruption we must handle this "inserted" bit. Fortunately, any arbitrary sequence of our described bit patterns are resulting in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the following table: | Source bit sequence | Destination bit sequence | | ------------------- |:------------------------:| From d567e9a062cf546325aa3b73f254001a577b116c Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Mon, 9 Oct 2017 22:55:12 +0200 Subject: [PATCH 21/35] Update README.md --- ws2812b/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 732acdb..055d61d 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -34,7 +34,7 @@ The last problem we must solve, is the low voltage pause between each transmitte | 100 | 110 100 10~~0~~ | | 110 | 110 110 10~~0~~ | -With this in mind we can represent 3 source bits by 1 destination byte. So any possible 24 bit color needs to be converted to 8 byte sized sequence of bit patterns. +With this in mind we can represent 3 source bits by 1 destination byte. So any possible 24 bit color needs to be converted to 8 byte sized sequence of bit patterns. To prevent excessive memory usage this driver stores and maps only 12 bit numbers to their corresponding 4 byte sized bit patterns ([TwelveBitIntToBitPatternMapper.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java)). The full 8 byte sized bit pattern for a 24 bit color data is then constructed by two 4 byte sized bit patterns ([ColorToBitPatternConverter.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java)). Last but not least, most WS2812B controllers expect GRB as order of the incoming color. So a reordering from RGB to the expected order must be done before the bit pattern conversion ([ColorChannelSequence.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java)). Copyright 2016 Google Inc. From ed0eb24ba7d999642b3a31f52fc8d5c8757d9c1b Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Wed, 11 Oct 2017 22:39:32 +0200 Subject: [PATCH 22/35] Fixed a unit test bug and unit test refactoring --- .../TwelveBitIntToBitPatternMapper.java | 24 +++--- .../ws2812b/ColorChannelSequenceTest.java | 3 +- .../contrib/driver/ws2812b/Ws2812bTest.java | 21 +++--- .../driver/ws2812b/util/ColorUtil.java | 9 +++ .../util/ReverseBitPatternConverter.java | 11 ++- .../SimpleColorToBitPatternConverter.java | 75 ++++++++++--------- 6 files changed, 78 insertions(+), 65 deletions(-) diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java index 614f74b..703c12e 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java @@ -29,14 +29,14 @@ class TwelveBitIntToBitPatternMapper { private static final int BIGGEST_12_BIT_NUMBER = 0B1111_1111_1111; private static final List BIT_PATTERN_FOR_ZERO_BIT = Arrays.asList(true, false, false); private static final List BIT_PATTERN_FOR_ONE_BIT = Arrays.asList(true, true, false); - private static final int ONE_BYTE_BIT_MASKS[] = new int[]{ 0b10000000, - 0b01000000, - 0b00100000, - 0b00010000, - 0b00001000, - 0b00000100, - 0b00000010, - 0b00000001}; + private static final int ONE_BYTE_BIT_MASKS[] = new int[]{ 0B10000000, + 0B01000000, + 0B00100000, + 0B00010000, + 0B00001000, + 0B00000100, + 0B00000010, + 0B00000001}; @NonNull private final SparseArray mSparseArray; @@ -79,12 +79,10 @@ private byte[] calculateBitPatternByteArray(@IntRange(from = 0, to = BIGGEST_12_ List bitPatterns = new ArrayList<>(); int highest12BitBitMask = 1 << 11; for (int i = 0; i < 12; i++) { - if ((twelveBitNumber & highest12BitBitMask) == highest12BitBitMask) - { + if ((twelveBitNumber & highest12BitBitMask) == highest12BitBitMask){ bitPatterns.addAll(BIT_PATTERN_FOR_ONE_BIT); } - else - { + else{ bitPatterns.addAll(BIT_PATTERN_FOR_ZERO_BIT); } twelveBitNumber = twelveBitNumber << 1; @@ -114,7 +112,7 @@ private byte[] convertBitPatternsToByteArray(List bitPatterns) { if (bitPatterns.size() != 32) { - throw new IllegalStateException("Undefined bit pattern size"); + throw new IllegalArgumentException("Undefined bit pattern size: Expected size is 32. Passed size: " + bitPatterns.size()); } byte[] bitPatternsAsByteArray = new byte[4]; bitPatternsAsByteArray [0] = convertBitPatternsToByte(bitPatterns.subList(0, 8)); diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java index f66ba56..2d07c75 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequenceTest.java @@ -6,8 +6,7 @@ import com.google.android.things.contrib.driver.ws2812b.util.ColorMock; import com.google.android.things.contrib.driver.ws2812b.util.ColorUtil; -import junit.framework.Assert; - +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java index 020f238..cb763e2 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/Ws2812bTest.java @@ -1,10 +1,10 @@ package com.google.android.things.contrib.driver.ws2812b; -import android.graphics.Color; import android.support.annotation.NonNull; import com.google.android.things.contrib.driver.ws2812b.util.ColorMock; +import com.google.android.things.contrib.driver.ws2812b.util.ColorUtil; import com.google.android.things.contrib.driver.ws2812b.util.SimpleColorToBitPatternConverter; import com.google.android.things.contrib.driver.ws2812b.util.SparseArrayMockCreator; import com.google.android.things.pio.SpiDevice; @@ -37,14 +37,14 @@ public class Ws2812bTest { @Test public void close() throws IOException { - Ws2812b ws2812b = createWs2812BDevice(); + Ws2812b ws2812b = createWs2812BTestDevice(); ws2812b.close(); Mockito.verify(mSpiDevice).close(); } @Test public void close_safeToCallTwice() throws IOException { - Ws2812b ws2812b = createWs2812BDevice(); + Ws2812b ws2812b = createWs2812BTestDevice(); ws2812b.close(); ws2812b.close(); // Check if the inner SPI device was only closed once @@ -52,23 +52,22 @@ public void close_safeToCallTwice() throws IOException { } @Test - public void writeRed() throws IOException { + public void write_randomColors() throws IOException { ColorMock.mockStatic(); - int color = Color.RED; + int[] randomColors = ColorUtil.generateRandomColors(100); + byte[] bytes = new SimpleColorToBitPatternConverter().convertColorsToBitPattern(randomColors); - Ws2812b ws2812b = createWs2812BDevice(); - ws2812b.write(new int [] {Color.RED}); - - byte[] bytes = new SimpleColorToBitPatternConverter().constructBitPatterns(color); + Ws2812b ws2812b = createWs2812BTestDevice(); + ws2812b.write(randomColors); Mockito.verify(mSpiDevice).write(bytes, bytes.length); } @NonNull - private Ws2812b createWs2812BDevice() throws IOException { + private Ws2812b createWs2812BTestDevice() throws IOException { TwelveBitIntToBitPatternMapper patternMapper = new TwelveBitIntToBitPatternMapper(SparseArrayMockCreator.createMockedSparseArray()); - ColorToBitPatternConverter converter = new ColorToBitPatternConverter(ColorChannelSequence.RBG, patternMapper); + ColorToBitPatternConverter converter = new ColorToBitPatternConverter(ColorChannelSequence.RGB, patternMapper); return new Ws2812b(mSpiDevice, converter); } } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorUtil.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorUtil.java index 87bd01d..3d601e6 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorUtil.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ColorUtil.java @@ -7,6 +7,15 @@ public class ColorUtil { + @SuppressWarnings("SameParameterValue") + public static int []generateRandomColors(int numberOfRandomColors) { + int[] randomColors = new int[numberOfRandomColors]; + for (int i = 0; i < randomColors.length; i++) { + randomColors[i] = generateRandomColorValue(); + } + return randomColors; + } + @IntRange(from = 0, to = 255) public static int generateRandomColorValue() { return (int) Math.round(Math.random() * 255); diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ReverseBitPatternConverter.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ReverseBitPatternConverter.java index 0d876d7..4bc2e92 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ReverseBitPatternConverter.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/ReverseBitPatternConverter.java @@ -5,9 +5,8 @@ import android.support.annotation.NonNull; import android.support.annotation.Size; -import junit.framework.Assert; - import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.ListIterator; @@ -33,7 +32,7 @@ public int convertBitPatternTo12BitInt(@Size(value = 4) byte[] bitPatterns) { List originalBooleanBits = new ArrayList<>(); ListIterator booleanBitPatternIterator = booleanBitPatterns.listIterator(); - Assert.assertEquals(12 * 3, booleanBitPatterns.size()); + checkArraySizeOrThrow(booleanBitPatterns, 12 * 3); for (int i = 0; i < 12; i++) { boolean bit0 = booleanBitPatternIterator.next(); @@ -88,4 +87,10 @@ private List convertBitPatternTo12BitInt(int bitPattern) } return booleanBitPattern; } + + private static void checkArraySizeOrThrow(Collection collection, int size) { + if (collection.size() != size) { + throw new IllegalArgumentException("Array must have size " + size); + } + } } diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java index d8bffb8..8e98c59 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java @@ -9,34 +9,27 @@ import java.util.List; public class SimpleColorToBitPatternConverter { - private static final int [] ONE_BYTE_BIT_MASKS = { - 0b1000_0000, - 0b0100_0000, - 0b0010_0000, - 0b0001_0000, - 0b0000_1000, - 0b0000_0100, - 0b0000_0010, - 0b0000_0001, - }; + private static final List ONE_BIT_PATTERN = Arrays.asList(true, true, false); + private static final List ZERO_BIT_PATTERN = Arrays.asList(true, false, false); - public byte[] constructBitPatterns(int color) + public byte[] convertColorsToBitPattern(int [] colors) { - List booleanBitPatterns = constructBooleanBitPatterns(color); + List booleanBitPatterns = new ArrayList<>(); + for (int color : colors) { + booleanBitPatterns.addAll(constructBooleanBitPatterns(color)); + } booleanBitPatterns = removePauseBits(booleanBitPatterns); - return convertToByteArray(booleanBitPatterns); + return convertBitPatternToByteArray(booleanBitPatterns); } @NonNull private List constructBooleanBitPatterns(int color) { ArrayList bitPatterns = new ArrayList<>(); - List oneBitPattern = Arrays.asList(true, true, false); - List zeroBitPattern = Arrays.asList(true, false, false); int highestBit = 1<<23; for (int i = 0; i < 24; i++) { - List bitPattern = (color & highestBit) == highestBit ? oneBitPattern : zeroBitPattern; + List bitPattern = (color & highestBit) == highestBit ? ONE_BIT_PATTERN : ZERO_BIT_PATTERN; bitPatterns.addAll(bitPattern); color = color << 1; } @@ -48,8 +41,7 @@ private List removePauseBits(List bitPatterns) { int i = 0; while (iterator.hasNext()) { iterator.next(); - if (i == 8) - { + if (i == 8){ iterator.remove(); i = 0; continue; @@ -59,28 +51,39 @@ private List removePauseBits(List bitPatterns) { return bitPatterns; } - private byte[] convertToByteArray(List bitPatterns) { - int i; + private byte[] convertBitPatternToByteArray(List bitPatterns) { + List> eightBitPatterns = splitInEightBitPatterns(bitPatterns); + byte [] bytes = new byte[eightBitPatterns.size()]; + int i = 0; - byte [] bytes = new byte[bitPatterns.size() / 8]; - byte currentByte = 0; - i = 0; - int j = 0; - for (Boolean bitValue : bitPatterns) { - if (bitValue) - { - currentByte |= ONE_BYTE_BIT_MASKS[i]; - } - if (i == 7) - { - bytes[j++] = currentByte; - currentByte = 0; - i = 0; - continue; + for (List eightBitPattern : eightBitPatterns) { + int highestBit = 0b10000000; + byte currentByte = 0; + for (Boolean booleanBit : eightBitPattern) { + if (booleanBit) + { + currentByte |= highestBit; + } + highestBit = highestBit >> 1; } - i++; + bytes[i++] = currentByte; } return bytes; } + @NonNull + private List> splitInEightBitPatterns(List bitPatterns) { + List> eightBitPatterns = new ArrayList<>(); + int index = 0; + int numberOfBits = bitPatterns.size(); + while (index < numberOfBits && index + 8 <= numberOfBits) { + eightBitPatterns.add(bitPatterns.subList(index, index + 8)); + index += 8; + } + if (index != numberOfBits) { + throw new IllegalStateException("Wrong number of bit pattern size: " + numberOfBits); + } + return eightBitPatterns; + } + } From 0b687fceaaad541a147c0110d7464aa947cc0874 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Thu, 12 Oct 2017 21:19:51 +0200 Subject: [PATCH 23/35] Update README.md --- ws2812b/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 055d61d..5ca697e 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -16,11 +16,14 @@ At the moment there is no direct solution to send such short timed pulses with * * A transmitted 1 bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout * A transmitted 0 bit results in a short low voltage pulse at the MOSI pinout -Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of voltage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. Based on these bit patterns we are able to convert any color data to a sequence of bits which are recognizable by the WS2812B controller when it is sent by SPI. This library is using a 3 bit sized bit pattern to convert one input bit: +Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of voltage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. With these two bit patterns we are able to convert every bit of an arbitrary array of color data. If then the converted data sent by SPI to the WS2812B controller, the controller will recognize the orignal color and the LEDs will shine in this colors. A possible solution for the wanted bit patterns, are two 3 bit sized patterns. -The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. It is possible to create a more accurate bit pattern with more than 3 bits, but the greater size of the bit pattern, the faster is the fixed size SPI buffer full and the less is the number of controllable LEDs. +The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. It is possible to create a more accurate bit pattern with more than 3 bits, but the greater size of the bit pattern, the faster is the fixed size SPI buffer full and the less is the number of controllable LEDs. +The wanted frequency results from the 417ns: + +![equation](http://latex.codecogs.com/gif.latex?f%3D%5Cfrac%7B1%20%7D%7B417%20%5Ccdot%2010%5E%7B-9%7D%7DHz) The last problem we must solve, is the low voltage pause between each transmitted byte: If the the SPI sends more than 8 bits in a row, a short break in form of a low voltage pulse is done automatically. This pause pulse has the same duration as the bits, which are really sent. So it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. To keep our data safe from corruption we must handle this "inserted" bit. Fortunately, any arbitrary sequence of our described bit patterns are resulting in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the following table: From 6efc12854754da57273c5b671e318acd4cbafaa1 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Fri, 13 Oct 2017 01:45:01 +0200 Subject: [PATCH 24/35] Update README.md --- ws2812b/README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 5ca697e..02a548a 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -4,7 +4,9 @@ How does it work The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long block of data. The transmission of these bits is done by sending a chain of high and low voltage pulses over the data line of the LED controller. Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. The recognition as 0 or 1 bit is defined by the timings of these pulses: - +

+ +

* A 0 bit is defined by a high voltage pulse with a duration of 400 ns which is followed by a low voltage pulse of 850 ns * A 1 bit is defined by a high voltage pulse with a duration of 850 ns which is followed by a low voltage pulse of 400 ns @@ -16,16 +18,19 @@ At the moment there is no direct solution to send such short timed pulses with * * A transmitted 1 bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout * A transmitted 0 bit results in a short low voltage pulse at the MOSI pinout -Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of voltage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. With these two bit patterns we are able to convert every bit of an arbitrary array of color data. If then the converted data sent by SPI to the WS2812B controller, the controller will recognize the orignal color and the LEDs will shine in this colors. A possible solution for the wanted bit patterns, are two 3 bit sized patterns. +Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of voltage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. With these two bit patterns we would able to convert every bit of an arbitrary array of color data. If then the converted data is sent to the WS2812B controller by SPI, the controller will recognize the orignal color and the LEDs will shine in this colors. A possible solution for this approach, are the two 3 bit sized patterns below: - +

+ +

-The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. It is possible to create a more accurate bit pattern with more than 3 bits, but the greater size of the bit pattern, the faster is the fixed size SPI buffer full and the less is the number of controllable LEDs. -The wanted frequency results from the 417ns: +The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. It is possible to create a more accurate bit pattern with more than 3 bits, but the greater size of the bit pattern, the faster is the fixed size SPI buffer full and the less is the number of controllable LEDs. With regard to the frequency, the calculation is done by dividing 1 by the duration of 1 bit (417ns): -![equation](http://latex.codecogs.com/gif.latex?f%3D%5Cfrac%7B1%20%7D%7B417%20%5Ccdot%2010%5E%7B-9%7D%7DHz) +

+ +

-The last problem we must solve, is the low voltage pause between each transmitted byte: If the the SPI sends more than 8 bits in a row, a short break in form of a low voltage pulse is done automatically. This pause pulse has the same duration as the bits, which are really sent. So it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. To keep our data safe from corruption we must handle this "inserted" bit. Fortunately, any arbitrary sequence of our described bit patterns are resulting in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the following table: +One last problem remains, however: the low voltage pause between each transmitted word: If the the SPI sends more than the chosen number of bits per word, a short break in form of a low voltage pulse is done automatically. This break marks the end of every transmitted word and has the same duration as a single bit. If we would keep this pause pulse unhandled, a correct data transmission would be impossible. By considering a word size of 8 bits (maximum size) it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. Fortunately, any arbitrary sequence of our described bit patterns are resulting in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the following table: | Source bit sequence | Destination bit sequence | | ------------------- |:------------------------:| From 22bae36043bba859fbeacc4eba255a18be52e20c Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Fri, 13 Oct 2017 17:51:29 +0200 Subject: [PATCH 25/35] Update README.md --- ws2812b/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 02a548a..811bbbb 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -12,7 +12,7 @@ Each separate bit is hereby defined by a high voltage pulse which is followed by * A 1 bit is defined by a high voltage pulse with a duration of 850 ns which is followed by a low voltage pulse of 400 ns * Each pulse can have a deviation of +/- 150 ns -At the moment there is no direct solution to send such short timed pulses with **different durations** by the API of Android Things. There is however the [Serial Peripheral Interface (SPI)](https://developer.android.com/things/sdk/pio/spi.html) which can send bits as voltage pulses. Whereas each single bit results in a pulse of the **exact same** duration. +At the moment there is no direct solution to send such short timed pulses with **different durations** by the API of Android Things. There is however the [Serial Peripheral Interface (SPI)](https://developer.android.com/things/sdk/pio/spi.html) which can send bits as voltage pulses. In the context of SPI it should be noted that each sent bit has a specified and **exact same duration**. * This duration is indirectly defined by the frequency of the SPI. * A transmitted 1 bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout @@ -30,7 +30,7 @@ The deviation from the WS2812B specified pulse duration is -16 or rather +17 nan

-One last problem remains, however: the low voltage pause between each transmitted word: If the the SPI sends more than the chosen number of bits per word, a short break in form of a low voltage pulse is done automatically. This break marks the end of every transmitted word and has the same duration as a single bit. If we would keep this pause pulse unhandled, a correct data transmission would be impossible. By considering a word size of 8 bits (maximum size) it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. Fortunately, any arbitrary sequence of our described bit patterns are resulting in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the following table: +One last problem remains, however: the low voltage pause between each transmitted SPI word: If the the SPI sends more than the chosen number of bits per word, a short break in form of a low voltage pulse is done automatically. This break marks the end of every transmitted word and has the same duration as a single bit. If we would keep this pause pulse unhandled, a correct data transmission would be impossible. By considering a word size of 8 bits (maximum size) it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. Fortunately, any arbitrary sequence of our described bit patterns results in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the following table: | Source bit sequence | Destination bit sequence | | ------------------- |:------------------------:| From 6bee65050725831a0418fb1c575db75b6096944d Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Fri, 13 Oct 2017 19:36:58 +0200 Subject: [PATCH 26/35] Update README.md --- ws2812b/README.md | 84 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 811bbbb..10733e6 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -1,4 +1,59 @@ -# readme-svg-test +WS2812B LED driver for Android Things +===================================== + +This driver supports WS2812B RGB LEDs (maybe WS1812S and SK6812 run too). + +NOTE: these drivers are not production-ready. They are offered as sample +implementations of Android Things user space drivers for common peripherals +as part of the Developer Preview release. There is no guarantee +of correctness, completeness or robustness. + +How to use the driver +--------------------- + +### Gradle dependency + +To use the `WS2812B` driver, simply add the line below to your project's `build.gradle`, +where `` matches the last version of the driver available on [jcenter][jcenter]. + +``` +dependencies { + compile 'com.google.android.things.contrib:driver-ws2812b:' +} +``` + +### Sample usage + +```java +import com.google.android.things.contrib.driver.ws2812b.WS2812B; + +// Access the LED strip: + +WS2812B mWs2812b; + +try { + mWs2812b = new Apa102(spiBusName); +} catch (IOException e) { + // couldn't configure the device... +} + +// Light it up! + +int[] colors = new int[] {Color.RED, Color.GREEN, Color.BLUE}; +try { + mWs2812b.write(colors); +} catch (IOException e) { + // error setting LEDs +} + +// Close the LED strip when finished: + +try { + mWs2812b.close(); +} catch (IOException e) { + // error closing LED strip +} +``` How does it work --------------------- The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long block of data. The transmission of these bits is done by sending a chain of high and low voltage pulses over the data line of the LED controller. @@ -32,17 +87,22 @@ The deviation from the WS2812B specified pulse duration is -16 or rather +17 nan One last problem remains, however: the low voltage pause between each transmitted SPI word: If the the SPI sends more than the chosen number of bits per word, a short break in form of a low voltage pulse is done automatically. This break marks the end of every transmitted word and has the same duration as a single bit. If we would keep this pause pulse unhandled, a correct data transmission would be impossible. By considering a word size of 8 bits (maximum size) it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. Fortunately, any arbitrary sequence of our described bit patterns results in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the following table: -| Source bit sequence | Destination bit sequence | -| ------------------- |:------------------------:| -| 111 | 110 110 11~~0~~ | -| 011 | 100 110 11~~0~~ | -| 001 | 100 100 11~~0~~ | -| 000 | 100 100 10~~0~~ | -| 010 | 100 110 10~~0~~ | -| 100 | 110 100 10~~0~~ | -| 110 | 110 110 10~~0~~ | - -With this in mind we can represent 3 source bits by 1 destination byte. So any possible 24 bit color needs to be converted to 8 byte sized sequence of bit patterns. To prevent excessive memory usage this driver stores and maps only 12 bit numbers to their corresponding 4 byte sized bit patterns ([TwelveBitIntToBitPatternMapper.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java)). The full 8 byte sized bit pattern for a 24 bit color data is then constructed by two 4 byte sized bit patterns ([ColorToBitPatternConverter.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java)). Last but not least, most WS2812B controllers expect GRB as order of the incoming color. So a reordering from RGB to the expected order must be done before the bit pattern conversion ([ColorChannelSequence.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java)). +| Source bit sequence | Resulting bit patterns | Removed trailing zeros | +| ------------------- |:----------------------:|:------------------------:| +| 111 | 110 110 11**0** | 110 110 11 | +| 011 | 100 110 11**0** | 100 110 11 | +| 001 | 100 100 11**0** | 100 100 11 | +| 000 | 100 100 10**0** | 100 100 10 | +| 010 | 100 110 10**0** | 100 110 10 | +| 100 | 110 100 10**0** | 110 100 10 | +| 110 | 110 110 10**0** | 110 110 10 | + +With this in mind we can represent 3 source bits by 1 destination byte. So any possible 24 bit color needs to be converted to a 8 byte sized sequence of bit patterns (24 / 3 = 8 ). + +To prevent excessive memory usage this driver stores and maps only 12 bit numbers to their corresponding 4 byte sized bit patterns ([TwelveBitIntToBitPatternMapper.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java)). The full 8 byte sized bit pattern for a 24 bit color data is then constructed by two 4 byte sized bit patterns ([ColorToBitPatternConverter.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java)). Last but not least, most WS2812B controllers expect GRB as order of the incoming color. So a reordering from RGB to the expected order must be done before the bit pattern conversion ([ColorChannelSequence.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java)). + +License +------- Copyright 2016 Google Inc. From 13c9221a5320c585784d30c924471cc7a75adab8 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Fri, 13 Oct 2017 19:42:36 +0200 Subject: [PATCH 27/35] Update README.md --- ws2812b/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 10733e6..e9482c2 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -1,7 +1,7 @@ WS2812B LED driver for Android Things ===================================== -This driver supports WS2812B RGB LEDs (maybe WS1812S and SK6812 run too). +This driver supports WS2812B LEDs (maybe WS1812S and SK6812 run too). NOTE: these drivers are not production-ready. They are offered as sample implementations of Android Things user space drivers for common peripherals @@ -25,14 +25,14 @@ dependencies { ### Sample usage ```java -import com.google.android.things.contrib.driver.ws2812b.WS2812B; +import com.google.android.things.contrib.driver.ws2812b.Ws2812b; // Access the LED strip: -WS2812B mWs2812b; +Ws2812b mWs2812b; try { - mWs2812b = new Apa102(spiBusName); + mWs2812b = new Ws2812b(spiBusName); } catch (IOException e) { // couldn't configure the device... } @@ -85,9 +85,9 @@ The deviation from the WS2812B specified pulse duration is -16 or rather +17 nan

-One last problem remains, however: the low voltage pause between each transmitted SPI word: If the the SPI sends more than the chosen number of bits per word, a short break in form of a low voltage pulse is done automatically. This break marks the end of every transmitted word and has the same duration as a single bit. If we would keep this pause pulse unhandled, a correct data transmission would be impossible. By considering a word size of 8 bits (maximum size) it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. Fortunately, any arbitrary sequence of our described bit patterns results in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the following table: +One last problem remains, however: the low voltage pause between each transmitted SPI word: If the the SPI sends more than the chosen number of bits per word, a short break in form of a low voltage pulse is done automatically. This break marks the end of every transmitted word and has the same duration as a single bit. If we would keep this pause pulse unhandled, a correct data transmission would be impossible. By considering a word size of 8 bits (maximum size) it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. Fortunately, any arbitrary sequence of our described bit patterns results in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the table below: -| Source bit sequence | Resulting bit patterns | Removed trailing zeros | +| Source bit sequence | Resulting bit patterns | Removed trailing 0 bit | | ------------------- |:----------------------:|:------------------------:| | 111 | 110 110 11**0** | 110 110 11 | | 011 | 100 110 11**0** | 100 110 11 | From c395031d4e16555509198d4c6cf4ebb5bdc02cea Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Fri, 13 Oct 2017 20:05:55 +0200 Subject: [PATCH 28/35] Update README.md --- ws2812b/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ws2812b/README.md b/ws2812b/README.md index e9482c2..366a219 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -101,6 +101,12 @@ With this in mind we can represent 3 source bits by 1 destination byte. So any p To prevent excessive memory usage this driver stores and maps only 12 bit numbers to their corresponding 4 byte sized bit patterns ([TwelveBitIntToBitPatternMapper.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java)). The full 8 byte sized bit pattern for a 24 bit color data is then constructed by two 4 byte sized bit patterns ([ColorToBitPatternConverter.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java)). Last but not least, most WS2812B controllers expect GRB as order of the incoming color. So a reordering from RGB to the expected order must be done before the bit pattern conversion ([ColorChannelSequence.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java)). +References +---------- +* https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ +* https://cpldcpu.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/ +* https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf + License ------- From bc233454692421d4a4e1e36946577736f696ec3d Mon Sep 17 00:00:00 2001 From: bert2 Date: Sun, 15 Oct 2017 20:26:49 +0200 Subject: [PATCH 29/35] Improve README and SVG images. --- ws2812b/README.md | 62 +- ws2812b/ws2812b-bit-pattern-svg-objects.svg | 182 +++-- ws2812b/ws2812b-bit-pattern.svg | 704 ++++++++++---------- ws2812b/ws2812b-timings-svg-objects.svg | 173 +++-- ws2812b/ws2812b-timings.svg | 484 +++++++------- 5 files changed, 800 insertions(+), 805 deletions(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 366a219..9e0a190 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -54,52 +54,64 @@ try { // error closing LED strip } ``` -How does it work ---------------------- -The WS2812B LED controller needs 24 bits of data (8 bit per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long block of data. The transmission of these bits is done by sending a chain of high and low voltage pulses over the data line of the LED controller. -Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. The recognition as 0 or 1 bit is defined by the timings of these pulses: + +How it works +------------ + +### The WS2812B data format + +The WS2812B LED controller needs 24 bits of data (8 bits per color channel) to set the color of one LED. Every further LED of a strip needs another 24 bit long block of data. The transmission of these bits is done by sending a chain of high and low voltage pulses over the data line of the LED controller. +Each separate bit is hereby defined by a high voltage pulse which is followed by a low voltage pulse. The recognition as 0- or 1-bit is defined by the timings of these pulses:

-* A 0 bit is defined by a high voltage pulse with a duration of 400 ns which is followed by a low voltage pulse of 850 ns -* A 1 bit is defined by a high voltage pulse with a duration of 850 ns which is followed by a low voltage pulse of 400 ns -* Each pulse can have a deviation of +/- 150 ns +* The 0-bit is defined by a high voltage pulse with a duration of 400 ns which is followed by a low voltage pulse of 850 ns +* The 1-bit is defined by a high voltage pulse with a duration of 850 ns which is followed by a low voltage pulse of 400 ns +* Each pulse can have a deviation of +/- 150 ns -At the moment there is no direct solution to send such short timed pulses with **different durations** by the API of Android Things. There is however the [Serial Peripheral Interface (SPI)](https://developer.android.com/things/sdk/pio/spi.html) which can send bits as voltage pulses. In the context of SPI it should be noted that each sent bit has a specified and **exact same duration**. +At the moment there is no way to send such short timed pulses having _different durations_ via the Android Things API. -* This duration is indirectly defined by the frequency of the SPI. -* A transmitted 1 bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout -* A transmitted 0 bit results in a short low voltage pulse at the MOSI pinout +### The Serial Peripheral Interface -Now the solution gets within reach. To control WS2812B LEDs by the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency so that each of these bit patterns results in a sequence of voltage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. With these two bit patterns we would able to convert every bit of an arbitrary array of color data. If then the converted data is sent to the WS2812B controller by SPI, the controller will recognize the orignal color and the LEDs will shine in this colors. A possible solution for this approach, are the two 3 bit sized patterns below: +The [Serial Peripheral Interface (SPI)](https://developer.android.com/things/sdk/pio/spi.html) can send bits as voltage pulses. Those pulses all have the same specified duration. + +* The pulse duration is indirectly defined by the frequency of the SPI +* A transmitted 1-bit results in a short high voltage pulse at the SPI MOSI (Master Out Slave In) pinout +* A transmitted 0-bit results in a short low voltage pulse at the MOSI pinout + +### Approximating the WS2812B data format using SPI + +In order to control WS2812B LEDs via the SPI, we must find two assemblies of bits (hereinafter bit pattern) and a frequency such that each of these bit patterns results in a sequence of voltage pulses which are recognized as 0 or 1 bit by the receiving WS2812B controller. With these two bit patterns we are able to convert every bit of an arbitrary array of color data. If then the converted data is sent to the WS2812B controller by SPI, the controller will recognize the orignal color and light up the LEDs accordingly. A possible solution for this approach are the two 3 bit sized patterns below:

-The deviation from the WS2812B specified pulse duration is -16 or rather +17 nanoseconds which is within the allowed range of +/-150ns. It is possible to create a more accurate bit pattern with more than 3 bits, but the greater size of the bit pattern, the faster is the fixed size SPI buffer full and the less is the number of controllable LEDs. With regard to the frequency, the calculation is done by dividing 1 by the duration of 1 bit (417ns): +The deviation from the WS2812B specified pulse duration is -16 ns and +17 ns respectively, which is within the allowed range of +/- 150 ns. It is possible to create a more accurate bit pattern with more than 3 bits, but increasing the size of the bit pattern als reduces the number of controllable LEDs as the fixed-size SPI buffer fills up more quickly. The appropriate frequency is defined by the duration of 1 bit (417 ns):

-One last problem remains, however: the low voltage pause between each transmitted SPI word: If the the SPI sends more than the chosen number of bits per word, a short break in form of a low voltage pulse is done automatically. This break marks the end of every transmitted word and has the same duration as a single bit. If we would keep this pause pulse unhandled, a correct data transmission would be impossible. By considering a word size of 8 bits (maximum size) it can be understood as a automatically inserted 0 bit between the 8th and the 9th bit. Fortunately, any arbitrary sequence of our described bit patterns results in a row of bits where every 9th bit is a 0 bit. So a simple solution is the removing of this last bit like shown in the table below: +### Handling pauses between SPI words + +SPI will automatically insert low voltage pauses between transmitted words. This short break marks the end of a transmitted word and has the same duration as a single bit. If left unhandled, those pauses would interfere with our approximated data format and lead to incorrect colors. By considering a word size of 8 bits (maximum size), the pause can be understood as an automatically inserted 0-bit between the 8th and the 9th bit. Fortunately, all possible bit sequences yield a bit pattern where every 9th bit is a 0-bit. Hence an easy solution is to discard the last bit like shown in the table below: -| Source bit sequence | Resulting bit patterns | Removed trailing 0 bit | -| ------------------- |:----------------------:|:------------------------:| -| 111 | 110 110 11**0** | 110 110 11 | -| 011 | 100 110 11**0** | 100 110 11 | -| 001 | 100 100 11**0** | 100 100 11 | -| 000 | 100 100 10**0** | 100 100 10 | -| 010 | 100 110 10**0** | 100 110 10 | -| 100 | 110 100 10**0** | 110 100 10 | -| 110 | 110 110 10**0** | 110 110 10 | +| Source bit sequence | Resulting bit pattern | Without trailing 0-bit | +| ------------------- |:----------------------:|:------------------------:| +| 000 | 100 100 10**0** | 100 100 10 | +| 001 | 100 100 11**0** | 100 100 11 | +| 010 | 100 110 10**0** | 100 110 10 | +| 011 | 100 110 11**0** | 100 110 11 | +| 100 | 110 100 10**0** | 110 100 10 | +| 110 | 110 110 10**0** | 110 110 10 | +| 111 | 110 110 11**0** | 110 110 11 | -With this in mind we can represent 3 source bits by 1 destination byte. So any possible 24 bit color needs to be converted to a 8 byte sized sequence of bit patterns (24 / 3 = 8 ). +With this in mind we can represent three source bits using one destination byte. So any possible 24 bit color needs to be converted to a 8 byte sized sequence of bit patterns (24 / 3 = 8). -To prevent excessive memory usage this driver stores and maps only 12 bit numbers to their corresponding 4 byte sized bit patterns ([TwelveBitIntToBitPatternMapper.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java)). The full 8 byte sized bit pattern for a 24 bit color data is then constructed by two 4 byte sized bit patterns ([ColorToBitPatternConverter.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java)). Last but not least, most WS2812B controllers expect GRB as order of the incoming color. So a reordering from RGB to the expected order must be done before the bit pattern conversion ([ColorChannelSequence.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java)). +To prevent excessive memory usage this driver stores and maps only 12 bit numbers to their corresponding 4 byte sized bit patterns ([TwelveBitIntToBitPatternMapper.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/TwelveBitIntToBitPatternMapper.java)). The full 8 byte sized bit pattern for 24 bits of color data is then constructed by two 4 byte sized bit patterns ([ColorToBitPatternConverter.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorToBitPatternConverter.java)). Last but not least, most WS2812B controllers expect GRB as order of the incoming color. So a reordering from RGB to the expected order must be done before the bit pattern conversion ([ColorChannelSequence.java](/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java)). References ---------- diff --git a/ws2812b/ws2812b-bit-pattern-svg-objects.svg b/ws2812b/ws2812b-bit-pattern-svg-objects.svg index 7391faf..b5e3164 100644 --- a/ws2812b/ws2812b-bit-pattern-svg-objects.svg +++ b/ws2812b/ws2812b-bit-pattern-svg-objects.svg @@ -11,15 +11,15 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="svg3355" version="1.1" - inkscape:version="0.91 r13725" + inkscape:version="0.92.1 r15371" xml:space="preserve" - width="302.82693" - height="163.81889" + width="323.01541" + height="174.74016" viewBox="0 0 302.82693 163.81888" sodipodi:docname="ws2812b-bit-pattern-svg-objects.svg">image/svg+xml
1 pattern = 110 + x="-12.371832" + style="font-size:14px;line-height:1.25">1-pattern = 110
417ns + style="font-size:12px;line-height:1.25">417 ns + y="-680.18903" + style="font-size:32px;line-height:1.25">  + y="-758.32349" + style="font-size:32px;line-height:1.25">  417ns + style="font-size:12px;line-height:1.25">417 ns 417ns + style="font-size:12px;line-height:1.25">417 ns 834ns + style="font-size:12px;line-height:1.25">834 ns 0 bit + style="font-size:12px;line-height:1.25">0 bit 1 bit + style="font-size:12px;line-height:1.25">1 bit 1 bit + style="font-size:12px;line-height:1.25">1 bit 0 pattern = 100 + x="-12.371832" + style="font-size:14px;line-height:1.25">0-pattern = 100 417ns + style="font-size:12px;line-height:1.25">417 ns 417ns + style="font-size:12px;line-height:1.25">417 ns 417ns + style="font-size:12px;line-height:1.25">417 ns 834ns + style="font-size:12px;line-height:1.25">834 ns 0 bit + style="font-size:12px;line-height:1.25">0 bit 0 bit + style="font-size:12px;line-height:1.25">0 bit 1 bit + style="font-size:12px;line-height:1.25">1 bit + y="-746.44556" + style="font-size:32px;line-height:1.25"> 
\ No newline at end of file diff --git a/ws2812b/ws2812b-bit-pattern.svg b/ws2812b/ws2812b-bit-pattern.svg index a6d6b7b..0bbad4a 100644 --- a/ws2812b/ws2812b-bit-pattern.svg +++ b/ws2812b/ws2812b-bit-pattern.svg @@ -11,10 +11,10 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="svg3355" version="1.1" - inkscape:version="0.91 r13725" + inkscape:version="0.92.1 r15371" xml:space="preserve" - width="302.82693" - height="163.81889" + width="323.01541" + height="174.74016" viewBox="0 0 302.82693 163.81888" sodipodi:docname="ws2812b-bit-pattern.svg"> - -
-
\ No newline at end of file + d="m 105.33341,-765.328 1.9336,-0.94336 h 0.19336 v 6.70898 q 0,0.66797 0.0527,0.83203 0.0586,0.16407 0.23437,0.25196 0.17579,0.0879 0.71485,0.0996 v 0.21679 h -2.98828 v -0.21679 q 0.5625,-0.0117 0.72656,-0.0937 0.16406,-0.0879 0.22852,-0.22852 0.0645,-0.14648 0.0645,-0.86133 v -4.28906 q 0,-0.86719 -0.0586,-1.11328 -0.041,-0.1875 -0.15234,-0.27539 -0.10547,-0.0879 -0.25781,-0.0879 -0.2168,0 -0.60352,0.18164 z" + style="font-size:12px;line-height:1.25" + id="path288" />
\ No newline at end of file diff --git a/ws2812b/ws2812b-timings-svg-objects.svg b/ws2812b/ws2812b-timings-svg-objects.svg index f9f919a..3d2691b 100644 --- a/ws2812b/ws2812b-timings-svg-objects.svg +++ b/ws2812b/ws2812b-timings-svg-objects.svg @@ -11,11 +11,11 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="svg3355" version="1.1" - inkscape:version="0.91 r13725" + inkscape:version="0.92.1 r15371" xml:space="preserve" - width="205.84219" - height="175.37582" - viewBox="0 0 205.84219 175.37581" + width="286.31165" + height="187.06755" + viewBox="0 0 268.41717 175.37581" sodipodi:docname="ws2812b-timings-svg-objects.svg">image/svg+xml0 bit + style="font-size:14px;line-height:1.25">0-bit 1 bit + x="60.09333" + style="font-size:14px;line-height:1.25">1-bit 400ns + style="font-size:12px;line-height:1.25">400 ns 850ns + style="font-size:12px;line-height:1.25">850 ns 400ns + style="font-size:12px;line-height:1.25">400 ns + y="-680.18903" + style="font-size:32px;line-height:1.25">  + y="-758.32349" + style="font-size:32px;line-height:1.25">  850ns + style="font-size:12px;line-height:1.25">850 ns high voltage high voltage low voltage low voltage
\ No newline at end of file diff --git a/ws2812b/ws2812b-timings.svg b/ws2812b/ws2812b-timings.svg index d4e4774..09f1521 100644 --- a/ws2812b/ws2812b-timings.svg +++ b/ws2812b/ws2812b-timings.svg @@ -11,11 +11,11 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="svg3355" version="1.1" - inkscape:version="0.91 r13725" + inkscape:version="0.92.1 r15371" xml:space="preserve" - width="205.84219" - height="175.37582" - viewBox="0 0 205.84219 175.37581" + width="286.31165" + height="187.06755" + viewBox="0 0 268.41717 175.37581" sodipodi:docname="ws2812b-timings.svg">image/svg+xml - - \ No newline at end of file + id="path394" />
\ No newline at end of file From 94dbdb9abd05b0092d97749df57356ac5ebebc78 Mon Sep 17 00:00:00 2001 From: Ic-ks Date: Sun, 15 Oct 2017 18:28:48 +0200 Subject: [PATCH 30/35] Update README.md --- ws2812b/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ws2812b/README.md b/ws2812b/README.md index 9e0a190..d036c48 100644 --- a/ws2812b/README.md +++ b/ws2812b/README.md @@ -89,7 +89,7 @@ In order to control WS2812B LEDs via the SPI, we must find two assemblies of bit

-The deviation from the WS2812B specified pulse duration is -16 ns and +17 ns respectively, which is within the allowed range of +/- 150 ns. It is possible to create a more accurate bit pattern with more than 3 bits, but increasing the size of the bit pattern als reduces the number of controllable LEDs as the fixed-size SPI buffer fills up more quickly. The appropriate frequency is defined by the duration of 1 bit (417 ns): +The deviation from the WS2812B specified pulse duration is -16 ns and +17 ns respectively, which is within the allowed range of +/- 150 ns. It is possible to create a more accurate bit pattern with more than 3 bits, but increasing the size of the bit pattern also reduces the number of controllable LEDs as the fixed-size SPI buffer fills up more quickly. The appropriate frequency is defined by the duration of 1 bit (417 ns):

From 4da9d60cd988f4792b99450652d1ebfabeebfe09 Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Sun, 15 Oct 2017 18:30:56 +0200 Subject: [PATCH 31/35] Auto stash before merge of "master" and "origin/master" Minor refactoring --- .../things/contrib/driver/ws2812b/Ws2812b.java | 1 + .../util/SimpleColorToBitPatternConverter.java | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java index 079baa0..cdcada0 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/Ws2812b.java @@ -31,6 +31,7 @@ * For more information on SPI, see: * https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus * For information on the WS2812B protocol, see: + * https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ * https://cpldcpu.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/ */ diff --git a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java index 8e98c59..cb078be 100644 --- a/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java +++ b/ws2812b/src/test/java/com/google/android/things/contrib/driver/ws2812b/util/SimpleColorToBitPatternConverter.java @@ -52,19 +52,18 @@ private List removePauseBits(List bitPatterns) { } private byte[] convertBitPatternToByteArray(List bitPatterns) { - List> eightBitPatterns = splitInEightBitPatterns(bitPatterns); + List> eightBitPatterns = splitInEightBitParts(bitPatterns); byte [] bytes = new byte[eightBitPatterns.size()]; int i = 0; for (List eightBitPattern : eightBitPatterns) { - int highestBit = 0b10000000; + int currentBitMask = 0b10000000; byte currentByte = 0; for (Boolean booleanBit : eightBitPattern) { - if (booleanBit) - { - currentByte |= highestBit; + if (booleanBit) { + currentByte |= currentBitMask; } - highestBit = highestBit >> 1; + currentBitMask >>= 1; } bytes[i++] = currentByte; } @@ -72,7 +71,7 @@ private byte[] convertBitPatternToByteArray(List bitPatterns) { } @NonNull - private List> splitInEightBitPatterns(List bitPatterns) { + private List> splitInEightBitParts(List bitPatterns) { List> eightBitPatterns = new ArrayList<>(); int index = 0; int numberOfBits = bitPatterns.size(); From 3d1041017444cd02e66057233720a450f5a490fb Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Sun, 15 Oct 2017 19:11:10 +0200 Subject: [PATCH 32/35] Updated buildToolsVersion --- ws2812b/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ws2812b/build.gradle b/ws2812b/build.gradle index 9c17c2d..8f770f9 100644 --- a/ws2812b/build.gradle +++ b/ws2812b/build.gradle @@ -18,7 +18,7 @@ apply plugin: 'com.android.library' android { compileSdkVersion 26 - buildToolsVersion '26.0.1' + buildToolsVersion '26.0.2' defaultConfig { minSdkVersion 26 @@ -30,7 +30,7 @@ android { repositories { // jcenter() // < - maven { // < CAN BE DELETED IF contrib-drivers was updated + maven { // < CAN BE DELETED when contrib-drivers adds the google repo url "https://maven.google.com" // < } // } From c582583cd8f78a81fb78dd09887956a641b29138 Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Sun, 15 Oct 2017 20:53:22 +0200 Subject: [PATCH 33/35] Downgrade of compileSdkVersion, buildToolsVersion, minSdkVersion to 24 --- ws2812b/build.gradle | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/ws2812b/build.gradle b/ws2812b/build.gradle index 8f770f9..fcf746e 100644 --- a/ws2812b/build.gradle +++ b/ws2812b/build.gradle @@ -17,27 +17,20 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 26 - buildToolsVersion '26.0.2' + compileSdkVersion 24 + buildToolsVersion '24.0.3' defaultConfig { - minSdkVersion 26 - targetSdkVersion 26 + minSdkVersion 24 + targetSdkVersion 24 versionCode 1 versionName "1.0" } } -repositories { // - jcenter() // < - maven { // < CAN BE DELETED when contrib-drivers adds the google repo - url "https://maven.google.com" // < - } // -} - dependencies { provided 'com.google.android.things:androidthings:0.5-devpreview' - compile 'com.android.support:support-annotations:26.1.0' + compile 'com.android.support:support-annotations:24.2.0' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' From bb7b9361d825282451b1861f1924080ac7a74abc Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Mon, 20 Nov 2017 20:24:50 +0100 Subject: [PATCH 34/35] Added and changed uses-library's android:required field in AndroidManifest.xml to false: --- ws2812b/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ws2812b/src/main/AndroidManifest.xml b/ws2812b/src/main/AndroidManifest.xml index 312af20..9416d58 100644 --- a/ws2812b/src/main/AndroidManifest.xml +++ b/ws2812b/src/main/AndroidManifest.xml @@ -17,6 +17,6 @@ - + From 37d29b8aa479e04f4f2bc49f85ee4d3872daa808 Mon Sep 17 00:00:00 2001 From: Alexander Ehrhardt Date: Mon, 20 Nov 2017 20:58:34 +0100 Subject: [PATCH 35/35] Changed scope of the ColorChannelSequence class to public to allow a correct usage of the second constructor of the Ws2812b class --- .../contrib/driver/ws2812b/ColorChannelSequence.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java index 064c207..81fd0a5 100644 --- a/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java +++ b/ws2812b/src/main/java/com/google/android/things/contrib/driver/ws2812b/ColorChannelSequence.java @@ -10,18 +10,13 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; -class ColorChannelSequence { - @SuppressWarnings("WeakerAccess") +@SuppressWarnings("WeakerAccess") +public class ColorChannelSequence { public static final int RGB = 1; - @SuppressWarnings("WeakerAccess") public static final int RBG = 2; - @SuppressWarnings("WeakerAccess") public static final int GRB = 3; - @SuppressWarnings("WeakerAccess") public static final int GBR = 4; - @SuppressWarnings("WeakerAccess") public static final int BRG = 5; - @SuppressWarnings("WeakerAccess") public static final int BGR = 6; @Sequence private static final int[] ALL_SEQUENCES = {RGB, RBG, GRB, GBR, BRG, BGR};