Build your own Output Expander Firmware!

Here’s the code I changed to get the Output Expander to work with the WS2814s. @wizard, this is a good reference for adding official support for them, as it’s tested and works. I made these changes in app.c. I’m only including the handleIncoming() function here. For the rest go to Github (see above)


static inline void handleIncomming() {
	uartResetCrc();
	//look for the 4 byte magic header sequence
	if (uartGetc() == 'U' && uartGetc() == 'P' && uartGetc() == 'X' && uartGetc() == 'L') {
		uint8_t channel = uartGetc();
		uint8_t recordType = uartGetc();
		lastDataMs = ms; //notice that we see some data
		switch (recordType) {
		case SET_CHANNEL_WS2812: {
			//read in the header
			PBWS2812Channel ch;
			uartRead(&ch, sizeof(PBWS2812Channel));

			if (ch.numElements < 3 || ch.numElements > 4)
				return;
			if (ch.pixels * ch.numElements > BYTES_PER_CHANNEL)
				return;

			//check that it's addressed to us and remap
			channel = remapFrameChannel(channel);

			// Original code
			// uint8_t or = ch.or;
			// uint8_t og = ch.og;
			// uint8_t ob = ch.ob;
			// uint8_t ow = ch.ow;
			/// Original code

			// Scott Mauer's hack for WS2814s
			// Set your PB and Output Expander for RGBW
			// This will re-arrange the colors to be correct
			uint8_t or = ch.og;
			uint8_t og = ch.ob;
			uint8_t ob = ch.ow;
			uint8_t ow = ch.or;

			uint8_t elements[4];

			uint32_t * dst = bitBuffer;
			int stride = 2*ch.numElements;
			for (int i = 0; i < ch.pixels; i++) {
				elements[or] = uartGetc();
				elements[og] = uartGetc();
				elements[ob] = uartGetc();
				if (ch.numElements == 4) {
					elements[ow] = uartGetc();
				}
				//this will ignore channel > 7
				bitConverter(dst, channel, elements, ch.numElements);
				dst += stride;
			}

			volatile uint32_t crcExpected = uartGetCrc();
			volatile uint32_t crcRead;
			uartRead((void *) &crcRead, sizeof(crcRead));

			if (channel < 8) { //check that it was addressed to us (not 0xff)
				int blocksToZero;
				if (crcExpected == crcRead) {
					if (channels[channel].type == SET_CHANNEL_WS2812
							&& (ch.pixels * ch.numElements >=
									channels[channel].ws2812Channel.pixels * channels[channel].ws2812Channel.numElements)
						) {
						blocksToZero = 0;
					} else {
						//we need to zero out previous data if the data received was less than last time
						blocksToZero = BYTES_PER_CHANNEL - ch.numElements * ch.pixels;
					}

					channels[channel].type = SET_CHANNEL_WS2812;
					channels[channel].ws2812Channel = ch;

					lastValidChannelMs = ms;
				} else {
					//garbage data, disable the channel, zero everything.
					//its better to let the LEDs keep the previous values than draw garbage.
					debugStats.crcErrors++;
					channels[channel].type = SET_CHANNEL_WS2812;
					memset(&channels[channel].ws2812Channel, 0, sizeof(channels[0].ws2812Channel));
					blocksToZero = BYTES_PER_CHANNEL;
				}
				//zero out any remaining data in the buffer for this channel
				if (blocksToZero > 0)
					bitSetZeros(bitBuffer + (BYTES_PER_CHANNEL - blocksToZero)*2, channel, blocksToZero);
			}
			break;
		}
		case DRAW_ALL: {
			uint32_t crcExpected = uartGetCrc();
			uint32_t crcRead;
			uartRead(&crcRead, sizeof(crcRead));
			if (crcExpected == crcRead) {
				startDrawingChannles();
			} else {
				debugStats.crcErrors++;
			}
			break;
		}
		case SET_CHANNEL_APA102_DATA: {
			//read in the header
			PBAPA102DataChannel ch;
			uartRead(&ch, sizeof(ch));
			if (ch.frequency == 0)
				return;
			//make sure we're not getting more data than we can handle
			if ((ch.pixels+2) * 4 > BYTES_PER_CHANNEL)
				return;

			//check that it's addressed to us and remap
			channel = remapFrameChannel(channel);

			uint8_t or = ch.or;
			uint8_t og = ch.og;
			uint8_t ob = ch.ob;

			uint8_t elements[4] = {0,0,0,0};
			uint32_t * dst = bitBuffer;

			//start frame
			bitConverter(dst, channel, elements, 4);
			dst += 8;

			for (int i = 0; i < ch.pixels; i++) {
				elements[or+1] = uartGetc();
				elements[og+1] = uartGetc();
				elements[ob+1] = uartGetc();
				elements[0] = uartGetc() | 0xe0;
				bitConverter(dst, channel, elements, 4);
				dst += 8;
			}

			//end frame
			elements[0] = 0xff;
			elements[1] = elements[2] = elements[3] = 0;
			bitConverter(dst, channel, elements, 4);

			volatile uint32_t crcExpected = uartGetCrc();
			volatile uint32_t crcRead;
			uartRead((void *) &crcRead, sizeof(crcRead));

			if (channel < 8) { //check that it was addressed to us (not 0xff)
				int blocksToZero;
				if (crcExpected == crcRead) {
					if (channels[channel].type == SET_CHANNEL_APA102_DATA
							&& (ch.pixels >= channels[channel].apa102DataChannel.pixels)
						) {
						blocksToZero = 0;
					} else {
						//we need to zero out previous data if the data received was less than last time
						blocksToZero = BYTES_PER_CHANNEL - ch.pixels * 4;
					}

					channels[channel].type = SET_CHANNEL_APA102_DATA;
					channels[channel].apa102DataChannel = ch;

					lastValidChannelMs = ms;
				} else {
					//garbage data, disable the channel, zero everything.
					//its better to let the LEDs keep the previous values than draw garbage.
					debugStats.crcErrors++;
					channels[channel].type = SET_CHANNEL_APA102_DATA;
					memset(&channels[channel].apa102DataChannel, 0, sizeof(channels[0].apa102DataChannel));
					blocksToZero = BYTES_PER_CHANNEL;
				}
				//zero out any remaining data in the buffer for this channel
				//TODO FIXME apa102 zeros will cause a start frame or maybe draw black? might not be what we want. set to all ones instead?
				if (blocksToZero > 0)
					bitSetZeros(bitBuffer + (BYTES_PER_CHANNEL - blocksToZero)*2, channel, blocksToZero);
			}
			break;
		}
		case SET_CHANNEL_APA102_CLOCK: {
			//read in the header
			PBAPA102ClockChannel ch;
			uartRead(&ch, sizeof(ch));
			if (ch.frequency == 0)
				return;

			//check that it's addressed to us and remap
			channel = remapFrameChannel(channel);

			volatile uint32_t crcExpected = uartGetCrc();
			volatile uint32_t crcRead;
			uartRead((void *) &crcRead, sizeof(crcRead));

			if (channel < 8) { //check that it was addressed to us (not 0xff)
				int blocksToZero;
				if (crcExpected == crcRead) {
					if (channels[channel].type == SET_CHANNEL_APA102_CLOCK) {
						blocksToZero = 0;
					} else {
						//we need to zero out previous data
						blocksToZero = BYTES_PER_CHANNEL;
					}

					channels[channel].type = SET_CHANNEL_APA102_CLOCK;
					channels[channel].apa102ClockChannel = ch;

					lastValidChannelMs = ms;
				} else {
					//garbage data, disable the channel, zero everything. Some apa102 channels could be without clock, so should remain unchanged
					debugStats.crcErrors++;
					channels[channel].type = SET_CHANNEL_APA102_CLOCK;
					memset(&channels[channel].apa102ClockChannel, 0, sizeof(channels[0].apa102ClockChannel));
					blocksToZero = BYTES_PER_CHANNEL;
				}
				//zero out any remaining data in the buffer for this channel
				if (blocksToZero > 0)
					bitSetZeros(bitBuffer + (BYTES_PER_CHANNEL - blocksToZero)*2, channel, blocksToZero);
			}
			break;
		}
		default:
			//unsupported frame type or garbage frame, just wait for the next one
			break;
		}

	} else {
		debugStats.frameMisses++;
	}
}