-
Notifications
You must be signed in to change notification settings - Fork 174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Driver for WS2812B LEDs #76
base: master
Are you sure you want to change the base?
Changes from 42 commits
06f4d0a
3b2c280
8014560
943c4ef
1a6e170
cc4546a
66c2f1b
1f8a673
6c40fb5
d5a8e97
8414469
35aeb68
52d14f2
899b21d
9e2bb56
a7e3786
4b24e66
c14727d
e4b45d9
9839ced
64bb7ae
2d21d5f
0bd5ba6
64eabee
8bb4a72
dae568a
d567e9a
ed0eb24
1fd826b
0b687fc
6efc128
22bae36
6bee650
13c9221
c395031
bc23345
34ccd6e
94dbdb9
4da9d60
3d10410
c582583
7ef19f5
bb7b936
37d29b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
WS2812B LED driver for Android Things | ||
===================================== | ||
|
||
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 | ||
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 `<version>` matches the last version of the driver available on [jcenter][jcenter]. | ||
|
||
``` | ||
dependencies { | ||
compile 'com.google.android.things.contrib:driver-ws2812b:<version>' | ||
} | ||
``` | ||
|
||
### Sample usage | ||
|
||
```java | ||
import com.google.android.things.contrib.driver.ws2812b.Ws2812b; | ||
|
||
// Access the LED strip: | ||
|
||
Ws2812b mWs2812b; | ||
|
||
try { | ||
mWs2812b = new Ws2812b(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 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: | ||
|
||
<p align="center"> | ||
<img align="center" src="https://rawgit.com/Ic-ks/contrib-drivers/master/ws2812b/ws2812b-timings.svg"/> | ||
</p> | ||
|
||
* 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 way to send such short timed pulses having _different durations_ via the Android Things API. | ||
|
||
### The Serial Peripheral Interface | ||
|
||
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: | ||
|
||
<p align="center"> | ||
<img align="center" src="https://rawgit.com/Ic-ks/contrib-drivers/master/ws2812b/ws2812b-bit-pattern.svg"/> | ||
</p> | ||
|
||
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): | ||
|
||
<p align="center"> | ||
<img align="center" src="http://latex.codecogs.com/gif.latex?f%3D%5Cfrac%7B1%20%7D%7B417%20%5Ccdot%2010%5E%7B-9%7D%7DHz"/> | ||
</p> | ||
|
||
### 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 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 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 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 | ||
---------- | ||
* 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 | ||
------- | ||
|
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* 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.5-devpreview' | ||
compile 'com.android.support:support-annotations:24.2.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' | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
TYPE="RGB LED strip" | ||
ARTIFACT_VERSION=0.1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
rootProject.buildFileName = '../publish.gradle' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<!-- | ||
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. | ||
--> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
package="com.google.android.things.contrib.driver.ws2812b"> | ||
<application> | ||
<uses-library android:name="com.google.android.things"/> | ||
</application> | ||
</manifest> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
class ColorChannelSequence { | ||
@SuppressWarnings("WeakerAccess") | ||
public static final int RGB = 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of supressing the warning, you can change these to package access There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These constants are used as second parameter in the public accessible constructor of the Ws2812b class: public Ws2812b(String spiBusPort, @ColorChannelSequence.Sequence int colorChannelSequence) throws IOException {
this (spiBusPort, new ColorToBitPatternConverter(colorChannelSequence));
} The failure was that the whole ColorChannelSequence class must be public accessible. Fixed: 37d29b8 |
||
@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}; | ||
|
||
@Retention(SOURCE) | ||
@IntDef({RGB, RBG, GRB, GBR, BRG, BGR}) | ||
@SuppressWarnings("WeakerAccess") | ||
public @interface Sequence {} | ||
|
||
interface Sequencer | ||
{ | ||
int rearrangeColorChannels(@ColorInt int color); | ||
} | ||
|
||
@NonNull | ||
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.ALL_SEQUENCES)); | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.google.android.things.contrib.driver.ws2812b; | ||
|
||
|
||
import android.support.annotation.ColorInt; | ||
import android.support.annotation.NonNull; | ||
import android.support.annotation.Size; | ||
import android.support.annotation.VisibleForTesting; | ||
|
||
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; | ||
|
||
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 | ||
*/ | ||
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!"); | ||
} | ||
|
||
byte[] bitPatterns = new byte[colors.length * 8]; | ||
|
||
int i = 0; | ||
for (int color : colors) { | ||
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; | ||
} | ||
return bitPatterns; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
drivers in this repo now have required=false
<uses-library android:required="false" android:name="com.google.android.things"/>
to allow for any application using them to not be filtered from the playstore (if for example they wrote an app that targets phones and an IoT device)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed: bb7b936