All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.diozero.ws281xj.spi.WS281xSpi Maven / Gradle / Ivy

There is a newer version: 1.4.0
Show newest version
package com.diozero.ws281xj.spi;

/*-
 * #%L
 * Organisation: mattjlewis
 * Project:      Device I/O Zero - WS281x Java Wrapper
 * Filename:     WS281xSpi.java  
 * 
 * This file is part of the diozero project. More information about this project
 * can be found at http://www.diozero.com/
 * %%
 * Copyright (C) 2016 - 2017 mattjlewis
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */

import java.util.Arrays;

import org.pmw.tinylog.Logger;

import com.diozero.api.SPIConstants;
import com.diozero.api.SpiClockMode;
import com.diozero.api.SpiDevice;
import com.diozero.util.SleepUtil;
import com.diozero.ws281xj.LedDriverInterface;
import com.diozero.ws281xj.StripType;

/**
 * References:
 * https://os.mbed.com/users/JacobBramley/code/PixelArray/file/47802e75974e/neopixel.cpp
 * https://github.com/jgarff/rpi_ws281x/blob/master/ws2811.c
 */
public class WS281xSpi implements LedDriverInterface {
	private static final int LED_COLOURS = 4;
	private static final int LED_RESET_uS = 55;
	/* Minimum time to wait for reset to occur in nanoseconds. */
	private static final int LED_RESET_WAIT_TIME = 300_000;
	
	// Symbol definitions
	private static final byte SYMBOL_HIGH = 0b110;
	private static final byte SYMBOL_LOW = 0b100;
	// Symbol definitions for software inversion (PCM and SPI only)
	private static final byte SYMBOL_HIGH_INV = 0b001;
	private static final byte SYMBOL_LOW_INV = 0b011;
	
	/* 4 colours (R, G, B + W), 8 bits per byte, 3 symbols per bit + 55uS low for reset signal */
	
	private SpiDevice device;
	private Protocol protocol;
	private StripType stripType;
	private int numLeds;
	private long lastRenderTime;
	private int renderWaitTime;
	// Brightness value between 0 and 255
	private int brightness;
	private int[] leds;
	private byte[] gamma;
	private byte[] pixelRaw;
	
	public WS281xSpi(int controller, int chipSelect, StripType stripType, int numLeds, int brightness) {
		this(controller, chipSelect, Protocol.PROTOCOL_800KHZ, stripType, numLeds, brightness);
	}

	public WS281xSpi(int controller, int chipSelect, Protocol protocol, StripType stripType, int numLeds,
			int brightness) {
		device = new SpiDevice(controller, chipSelect, protocol.getFrequency() * 3, SpiClockMode.MODE_0,
				SPIConstants.DEFAULT_LSB_FIRST);
		
		this.protocol = protocol;
		this.stripType = stripType;
		this.numLeds = numLeds;
		this.brightness = brightness & 0xff;
		leds = new int[numLeds];
		gamma = new byte[256];
		
		// Set default uncorrected gamma table
		for (int x=0; x<256; x++) {
			gamma[x] = (byte) x;
		}

		// Allocate SPI transmit buffer (same size as PCM)
		pixelRaw = new byte[PCM_BYTE_COUNT(numLeds, protocol.getFrequency())];
		
		// 1.25us per bit (1250ns)
		renderWaitTime = numLeds * stripType.getColourCount() * 8 * 1250 + LED_RESET_WAIT_TIME;
	}
	
	@Override
	public void close() {
		Logger.debug("Turning all off before close");
		allOff();
		device.close();
	}
	
	@Override
	public int getNumPixels() {
		return numLeds;
	}
	
	@Override
	public int getPixelColour(int pixel) {
		return leds[pixel];
	}
	
	@Override
	public void setPixelColour(int pixel, int colour) {
		leds[pixel] = colour;
	}
	
	/**
	 * Pixels are sent as follows:
	 * - The first transmitted pixel is the pixel closest to the transmitter.
	 * - The most significant bit is always sent first.
	 *
	 * g7,g6,g5,g4,g3,g2,g1,g0,r7,r6,r5,r4,r3,r2,r1,r0,b7,b6,b5,b4,b3,b2,b1,b0
	 * \_____________________________________________________________________/
	 *                           |      _________________...
	 *                           |     /   __________________...
	 *                           |    /   /   ___________________...
	 *                           |   /   /   /
	 *                          GRB,GRB,GRB,GRB,...
	 *
	 * For BYTE_ORDER_RGB, the order of the first two bytes are reversed.
	 */
	@Override
	public void render() {
		int bitpos = 7;
		int bytepos = 0;
		int scale = brightness + 1;
		//int array_size = stripType.getWhiteShift() == 0 ? 3 : 4; // RGB or RGBW
		int colour_count = stripType.getColourCount();
	
		for (int i=0; i> stripType.getRedShift()) & 0xff) * scale) >> 8],
				gamma[(((leds[i] >> stripType.getGreenShift()) & 0xff) * scale) >> 8],
				gamma[(((leds[i] >> stripType.getBlueShift()) & 0xff) * scale) >> 8],
				gamma[(((leds[i] >> stripType.getWhiteShift()) & 0xff) * scale) >> 8]
			};
			
			// Colour
			for (int j=0; j=0; k--) {
					int symbol = ((colour[j] & (1 << k)) != 0) ? SYMBOL_HIGH : SYMBOL_LOW;
					
					// Symbol
					for (int l=2; l>=0; l--) {
						/*
						volatile byte *byteptr = &pxl_raw[bytepos];
						*byteptr &= ~(1 << bitpos);
						if (symbol & (1 << l)) {
							*byteptr |= (1 << bitpos);
						}
						*/
						pixelRaw[bytepos] &= ~(1 << bitpos);
						if ((symbol & (1 << l)) != 0) {
							pixelRaw[bytepos] |= (1 << bitpos);
						}
						
						bitpos--;
						if (bitpos < 0) {
							bytepos++;
							bitpos = 7;
						}
					}
				}
			}
		}
		
		if (lastRenderTime != 0) {
			int diff = (int) (System.nanoTime() - lastRenderTime);
			if (renderWaitTime > diff) {
				SleepUtil.sleepNanos(renderWaitTime - diff);
			}
		}
		
		device.write(pixelRaw);
		lastRenderTime = System.nanoTime();
	}

	@Override
	public void allOff() {
		Arrays.fill(leds, 0);
		render();
	}
	
	private static final int LED_BIT_COUNT(int numLeds, int frequency) {
		return (numLeds * LED_COLOURS * 8 * 3) + ((LED_RESET_uS * (frequency * 3)) / 1000000);
	}

	private static final int PCM_BYTE_COUNT(int numLeds, int frequency) {
		return (((LED_BIT_COUNT(numLeds, frequency) >> 3) & ~0x7) + 4) + 4;
	}
	
	public static enum Protocol {
		/**
		 * 
		 * 800kHz bit encodings:
		 *  '0': ----________
		 *  '1': --------____
		 * The period is 1.25us, giving a basic frequency of 800kHz.
		 * Getting the mark-space ratio right is trickier, though. There are a number
		 * of different timings, and the correct (documented) values depend on the
		 * controller chip.
		 *
		 * The _real_ timing restrictions are much simpler though, and someone has
		 * published a lovely analysis here:
		 *   http://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/
		 *
		 * In summary:
		 * - The period should be at least 1.25us.
		 * - The '0' high time can be anywhere from 0.0625us to 0.5us.
		 * - The '1' high time should be longer than 0.625us.
		 *
		 * These constraints are easy to meet by splitting each bit into three and packing them into SPI packets.
		 *  '0': 100  mark: 0.42us, space: 0.83us
		 *  '1': 110  mark: 0.83us, space: 0.42us
		 */
		PROTOCOL_800KHZ(800_000),
		/**
		 * 400kHz bit encodings:
		 *  '0': --________
		 *  '1': -----_____
		 *
		 * Timing requirements are derived from this document:
		 *   http://www.adafruit.com/datasheets/WS2811.pdf
		 *
		 * The period is 2.5us, and we use a 10-bit packet for this encoding:
		 *  '0': 1100000000  mark: 0.5us, space: 2us
		 *  '1': 1111100000  mark: 1.25us, space: 1.25us
		 */
		PROTOCOL_400KHZ(400_000);
		
		int frequency;
		private Protocol(int frequency) {
			this.frequency = frequency;
		}
		
		public int getFrequency() {
			return frequency;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy