Skip to content

Instantly share code, notes, and snippets.

@scamiv
Created March 20, 2018 23:39
Show Gist options
  • Save scamiv/b931b307afaead5a0f43e6eb2bc57e08 to your computer and use it in GitHub Desktop.
Save scamiv/b931b307afaead5a0f43e6eb2bc57e08 to your computer and use it in GitHub Desktop.
ws2812Driver.cpp mask nmi
/*
ESP8266_ArtNetNode v2.0.0
Copyright (c) 2016, Matthew Tong
https://github.com/mtongnz/ESP8266_ArtNetNode_v2
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program.
If not, see http://www.gnu.org/licenses/
*/
// Timings from here:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
#include "ws2812Driver.h"
#include <interrupts.h>
ws2812Driver::ws2812Driver() {
_pixels[0] = 0;
_pixels[1] = 0;
}
void ws2812Driver::setStrip(uint8_t port, uint8_t pin, uint16_t size, uint16_t config) {
_pin[port] = pin;
_pixels[port] = size * 3;
_config[port] = config;
pinMode(_pin[port], OUTPUT);
digitalWrite(_pin[port], LOW);
clearBuffer(port);
// Clear the strip
byte* b = buffer[port];
//doPixel(b, _pin[port], PIX_MAX_BUFFER_SIZE);
}
void ws2812Driver::updateStrip(uint8_t port, uint16_t size, uint16_t config) {
size = size * 3;
// Clear the strip if it's shorter than our current strip
if (size < _pixels[port] || _config[port] != config) {
clearBuffer(port, size);
// Wait for last pixel packet to finish it's latch time
while (_nextPix > millis())
yield();
byte* b = buffer[port];
//doPixel(b, _pin[port], _pixels[port]);
// Allow at least 50 us with LOW to make LEDs latch data
_nextPix = millis() + 5;
}
_pixels[port] = size;
_config[port] = config;
}
uint8_t* ws2812Driver::getBuffer(uint8_t port) {
return buffer[port];
}
void ws2812Driver::clearBuffer(uint8_t port, uint16_t start) {
memset(&buffer[port][start], 0, PIX_MAX_BUFFER_SIZE - start);
}
void ws2812Driver::setBuffer(uint8_t port, uint16_t startChan, uint8_t* data, uint16_t size) {
uint8_t* a = buffer[port];
memcpy(&a[startChan], data, size);
}
byte ws2812Driver::setPixel(uint8_t port, uint16_t pixel, uint8_t r, uint8_t g, uint8_t b) {
uint8_t* a = buffer[port];
uint16_t chan = pixel * 3;
// ws2812 is GRB ordering
a[chan + 1] = r;
a[chan] = g;
a[chan + 2] = b;
}
byte ws2812Driver::setPixel(uint8_t port, uint16_t pixel, uint32_t colour) {
setPixel(port, pixel, ((colour >> 16) & 0xFF), ((colour >> 8) & 0xFF), (colour & 0xFF));
}
uint32_t ws2812Driver::getPixel(uint8_t port) {
uint8_t* b = buffer[port];
uint16_t chan = _pixels[port] * 3;
// ws2812 is GRB ordering - return RGB
return ((b[chan + 1] << 16) | (b[chan] << 8) | (b[chan+2]));
}
uint16_t ws2812Driver::numPixels(uint8_t port) {
return _pixels[port] / 3;
}
#define xt_disable_interrupts(state, level) __asm__ __volatile__("rsil %0," __STRINGIFY(level) "; esync; isync; dsync" : "=a" (state))
#define xt_enable_interrupts(state) __asm__ __volatile__("wsr %0,ps; esync" :: "a" (state) : "memory")
#define XCHAL_NMI_VECOFS 0x00000020
#define PS_EXCM_SHIFT 4
bool ws2812Driver::show() {
yield();
if (_nextPix > millis())
return 0;
if (_pixels[0] == 0 && _pixels[1] == 0)
return 1;
byte* b0 = buffer[0];
byte* b1 = buffer[1];
uint32_t state;
void *nmi_isr;
xt_disable_interrupts(state, 15);
if (_pixels[0] == 0)
doPixel(b1, _pin[1], _pixels[1]);
else if (_pixels[1] == 0)
doPixel(b0, _pin[0], _pixels[0]);
else if (_pixels[1] > _pixels[0])
doPixelDouble(b0, _pin[0], b1, _pin[1], _pixels[1]);
else
doPixelDouble(b0, _pin[0], b1, _pin[1], _pixels[0]);
_nextPix = millis() + PIX_LATCH_TIME;
xt_enable_interrupts(state);
return 1;
}
void ICACHE_RAM_ATTR ws2812Driver::doPixel(byte* data, uint8_t pin, uint16_t numBytes) {
uint8_t a, b, c, d, f;
uint32_t cc1, cc2;
pin = (1 << pin);
asm volatile (
"MOVI %[r_set], 0x60000304;"
//"RSIL %[r_int], 15;" // disable interrupts
"doNextByteSingle:"
"BEQZ %[r_num_bytes], doExitSingle;" // exit if all bytes sent
"L8UI %[r_data], %[r_data_array], 0;" // Load array element
"ADDI.N %[r_data_array], %[r_data_array], 1;" // Move pointer to next array element
"ADDI.N %[r_num_bytes], %[r_num_bytes], -1;" // Decrement number of bytes left
"MOVI %[r_bit], 0x80;" // Set our bitmask
"sendNextBitSingle:"
"BALL %[r_data], %[r_bit], doOne;" // check if bit is one -> doOne
"j doZero;" // doZero if it's not
"doNextBitSingle:"
"SRLI %[r_bit], %[r_bit], 1;" // shift bitmask right
"BEQZ %[r_bit], doNextByteSingle;" // get the next byte if all bits done
"j sendNextBitSingle;" // send the next bit
"SingleLoop:"
"RSR %[r_cc2], CCOUNT;" // Get clock cycles
"BGE %[r_cc1], %[r_cc2], SingleLoop;" // If finishtime >= nowtime -> loop again
"j doNextBitSingle;" // otherwise do the next bit
"doOne:"
"S16I %[r_pin], %[r_set], 0;" // set
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 95;" // add to cycles for delay - works at 100
"SingleOneLoop:"
"RSR %[r_cc2], CCOUNT;" // Get clock cycles
"BGE %[r_cc1], %[r_cc2], SingleOneLoop;" // If finishtime >= nowtime -> loop again
"S16I %[r_pin], %[r_set], 4;" // clear
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 61;" // add to cycles for delay - works at 67
"BEQZ %[r_allow_int], SingleLoop;" // if allowInt equals false, jump without enabling interrupts
// "RSIL %[r_int], 0;" // enable interrupts again
// "NOP;" // 1 clock for any interrupts to run
// "RSIL %[r_int], 15;" // disable interrupts
"j SingleLoop;"
"doZero:"
"S16I %[r_pin], %[r_set], 0;" // set
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 40;" // add to cycles for delay - works at 50
"SingleZeroLoop:"
"RSR %[r_cc2], CCOUNT;" // Get clock cycles
"BGE %[r_cc1], %[r_cc2], SingleZeroLoop;" // If finishtime >= nowtime -> loop again
"S16I %[r_pin], %[r_set], 4;" // clear
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 85;" // add to cycles for delay - works at 105
"BEQZ %[r_allow_int], SingleLoop;" // if allowInt equals false, jump without enabling interrupts
// "RSIL %[r_int], 0;" // enable interrupts again
// "NOP;" // 1 clock for any interrupts to run
//"RSIL %[r_int], 15;" // disable interrupts
"j SingleLoop;"
// Our exit point
"doExitSingle:"
//"RSIL %[r_int], 0;" // enable interrupts again
: [r_allow_int] "+r" (allowInterruptSingle), [r_int] "+r" (f), [r_cc1] "+r" (cc1), [r_cc2] "+r" (cc2), [r_set] "+r" (a), [r_bit] "+r" (b), [r_byte_count] "+r" (c), [r_data] "+r" (d), [r_pin] "+r" (pin), [r_data_array] "+r" (&data[0]), [r_num_bytes] "+r" (numBytes)
);
}
void ICACHE_RAM_ATTR ws2812Driver::doPixelDouble(byte* data1, uint8_t pin1, byte* data2, uint8_t pin2, uint16_t numBytes) {
uint8_t a, b, c, d, e, f;
uint32_t cc1, cc2;
pin1 = (1 << pin1);
pin2 = (1 << pin2);
//irq is already disabled, lets mask the non-maskable interrupt, this can lead to wdt resets on race. Should trigger nmi first.
//https://www.esp8266.com/viewtopic.php?f=9&t=3979&start=20
void *nmi_isr;
__asm__ __volatile__ (
"j function_entry\n"
".align 128\n"
"vecbase_mod:\n"
"nop\n"
".align 16\n"
"debug_exception_mod:\n"
"nop\n"
".align 16\n"
"nmi_exception_mod:\n"
"wsr.excsave3 a0\n" // preserve original a0 register
"rsr.epc3 a0\n" // get PC from before interrupt
"jx a0\n" // and jump back there
".align 16\n" //xxxx
"kernel_exception_mod:\n"
"nop\n"
".align 16\n" //xxxx
"nop\n"
".align 16\n"
"user_exception_mod:\n"
"nop\n"
".align 16\n" //xxxx
"nop\n"
".align 16\n"
"double_exception_mod:\n"
"nop\n"
".align 16\n" //xxxx
"nop\n"
".align 16\n"
"panic_exception_mod:\n"
"nop\n"
"function_entry:\n"
"rsr.vecbase %0\n"
"movi a2, vecbase_mod\n"
"wsr.vecbase a2\n"
: "=r" (nmi_isr)
:
: "a2", "memory"
);
asm volatile (
"MOVI %[r_set], 0x60000304;"
// "RSIL %[r_int], 15;" // disable interrupts
"doNextByteDouble:"
"BEQZ %[r_num_bytes], doExitDouble;" // exit if all bytes sent
"L8UI %[r_data1], %[r_data_array1], 0;" // Load array elements
"L8UI %[r_data2], %[r_data_array2], 0;"
"ADDI.N %[r_data_array1], %[r_data_array1], 1;" // Move pointer to next array element
"ADDI.N %[r_data_array2], %[r_data_array2], 1;"
"ADDI.N %[r_num_bytes], %[r_num_bytes], -1;" // Decrement number of bytes left
"MOVI %[r_bit], 0x80;" // Set our bitmask
"sendNextBitDouble:"
"BALL %[r_data1], %[r_bit], doOne1Check2;" // check if bit is one -> doOne
"j doZero1Check2;" // doZero if it's not
"doNextBitDouble:"
"SRLI %[r_bit], %[r_bit], 1;" // shift bitmask right
"BEQZ %[r_bit], doNextByteDouble;" // get the next byte if all bits done
"j sendNextBitDouble;" // send the next bit
"doOne1Check2:"
"BALL %[r_data2], %[r_bit], doOneOne;" // Both bits are one -> doOneOne
"j doOneZero;" // data1 is one, data2 is zero -> doOneZero
"doZero1Check2:"
"BALL %[r_data2], %[r_bit], doZeroOne;" // data1 is zero, data2 is one -> doZeroOne
"j doZeroZero;" // both bits are zero -> doZeroZero
"DoubleLoop:"
"RSR %[r_cc2], CCOUNT;" // Get clock cycles
"BGE %[r_cc1], %[r_cc2], DoubleLoop;" // If finishtime >= nowtime -> loop again
"j doNextBitDouble;" // otherwise do the next bit
"doOneOne:"
"S16I %[r_pin1], %[r_set], 0;" // set
"S16I %[r_pin2], %[r_set], 0;" // set
"MEMW;"
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop;"
"S16I %[r_pin1], %[r_set], 4;" // clear
"S16I %[r_pin2], %[r_set], 4;" // clear
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 65;" // add to cycles for delay
"BEQZ %[r_allow_int], DoubleLoop;" // if allowInt equals false, jump without enabling interrupts
// "RSIL %[r_int], 0;" // enable interrupts again
// "NOP;" // 1 clock for any interrupts to run
//"RSIL %[r_int], 15;" // disable interrupts
"j DoubleLoop;"
"doZeroZero:"
"S16I %[r_pin1], %[r_set], 0;" // set
"S16I %[r_pin2], %[r_set], 0;" // set
"MEMW;"
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"S16I %[r_pin1], %[r_set], 4;" // clear
"S16I %[r_pin2], %[r_set], 4;" // clear
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 105;" // add to cycles for delay
"BEQZ %[r_allow_int], DoubleLoop;" // if allowInt equals false, jump without enabling interrupts
// "RSIL %[r_int], 0;" // enable interrupts again
// "NOP;" // 1 clock for any interrupts to run
// "RSIL %[r_int], 15;" // disable interrupts
"j DoubleLoop;"
"doOneZero:"
"S16I %[r_pin1], %[r_set], 0;" // set
"S16I %[r_pin2], %[r_set], 0;" // set
"MEMW;"
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"S16I %[r_pin2], %[r_set], 4;" // clear
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"S16I %[r_pin1], %[r_set], 4;" // clear
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 65;" // add to cycles for delay
"BEQZ %[r_allow_int], DoubleLoop;" // if allowInt equals false, jump without enabling interrupts
// "RSIL %[r_int], 0;" // enable interrupts again
//"NOP;" // 1 clock for any interrupts to run
//"RSIL %[r_int], 15;" // disable interrupts
"j DoubleLoop;"
"doZeroOne:"
"S16I %[r_pin1], %[r_set], 0;" // set
"S16I %[r_pin2], %[r_set], 0;" // set
"MEMW;"
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"S16I %[r_pin1], %[r_set], 4;" // clear
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"_nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; _nop; "
"S16I %[r_pin2], %[r_set], 4;" // clear
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 65;" // add to cycles for delay
"BEQZ %[r_allow_int], DoubleLoop;" // if allowInt equals false, jump without enabling interrupts
// "RSIL %[r_int], 0;" // enable interrupts again
// "NOP;" // 1 clock for any interrupts to run
//"RSIL %[r_int], 15;" // disable interrupts
"j DoubleLoop;"
// Our exit point
"doExitDouble:"
// "RSIL %[r_int], 15;" // disable interrupts
: [r_allow_int] "+r" (allowInterruptDouble), [r_int] "+r" (f), [r_cc1] "+r" (cc1), [r_cc2] "+r" (cc2), [r_set] "+r" (a), [r_bit] "+r" (b), [r_byte_count] "+r" (c), [r_data1] "+r" (d), [r_pin1] "+r" (pin1), [r_data_array1] "+r" (&data1[0]), [r_data2] "+r" (e), [r_pin2] "+r" (pin2), [r_data_array2] "+r" (&data2[0]), [r_num_bytes] "+r" (numBytes)
);
//restore nmi
__asm__ __volatile__ (
"wsr.vecbase %0\n" // restore original vecbase
"rsr.ps a2\n" // check if we're running unter exception
"bbci a2, %2, function_end\n" // indicated by EXCM bit in the PS register
"rsr.excsave3 a0\n" // restore original a0
"movi a2, function_end\n"
"wsr.epc3 a2\n" // generate return vector when original NMI returns
"addi %0, %0, %1\n" // add offset to vecbase to get NMI ISR
"jx %0\n" // and jump to the original NMI ISR
"function_end:\n"
:
: "r" (nmi_isr), "i" (XCHAL_NMI_VECOFS), "i" (PS_EXCM_SHIFT)
: "a2", "memory"
);
}
void ICACHE_RAM_ATTR ws2812Driver::doAPA106(byte* data, uint8_t pin, uint16_t numBytes) {
uint8_t a, b, c, d, f;
uint32_t cc1, cc2, cc3;
pin = (1 << pin);
InterruptLock lock;
asm volatile (
"MOVI %[r_set], 0x60000304;"
"RSIL %[r_int], 15;" // disable interrupts
"doNextByte106:"
"BEQZ %[r_num_bytes], doExit106;" // exit if all bytes sent
"L8UI %[r_data], %[r_data_array], 0;" // Load array element
"ADDI.N %[r_data_array], %[r_data_array], 1;" // Move pointer to next array element
"ADDI.N %[r_num_bytes], %[r_num_bytes], -1;" // Decrement number of bytes left
"MOVI %[r_bit], 0x80;" // Set our bitmask
"sendNextBit106:"
"RSR %[r_cc3], CCOUNT;" // Get clock cycles
"ADDI %[r_cc3], %[r_cc3], 230;" // add to cycles - total bit length
"BALL %[r_data], %[r_bit], doOne106;" // check if bit is one -> doOne
"j doZero106;" // doZero if it's not
"doNextBit106:"
"SRLI %[r_bit], %[r_bit], 1;" // shift bitmask right
"BEQZ %[r_bit], doNextByte106;" // get the next byte if all bits done
"j sendNextBit106;" // send the next bit
"Loop106:"
"RSR %[r_cc2], CCOUNT;" // Get clock cycles
"BGE %[r_cc3], %[r_cc2], Loop106;" // If finishtime >= nowtime -> loop again
"j doNextBit106;" // otherwise do the next bit
"doOne106:"
"S16I %[r_pin], %[r_set], 0;" // set pin
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 185;" // add to cycles for delay
"OneLoop106:"
"RSR %[r_cc2], CCOUNT;" // Get clock cycles
"BGE %[r_cc1], %[r_cc2], OneLoop106;" // If finishtime >= nowtime -> loop again
"S16I %[r_pin], %[r_set], 4;" // clear pin
"MEMW;"
"j Loop106;"
"doZero106:"
"S16I %[r_pin], %[r_set], 0;" // set pin
"MEMW;"
"RSR %[r_cc1], CCOUNT;" // get clock cycles
"ADDI %[r_cc1], %[r_cc1], 40;" // add to cycles for delay
"ZeroLoop106:"
"RSR %[r_cc2], CCOUNT;" // Get clock cycles
"BGE %[r_cc1], %[r_cc2], ZeroLoop106;" // If finishtime >= nowtime -> loop again
"S16I %[r_pin], %[r_set], 4;" // clear pin
"MEMW;"
"j Loop106;"
// Our exit point
"doExit106:"
"RSIL %[r_int], 0;" // enable interrupts again
: [r_int] "+r" (f), [r_cc1] "+r" (cc1), [r_cc2] "+r" (cc2), [r_cc3] "+r" (cc3), [r_set] "+r" (a), [r_bit] "+r" (b), [r_byte_count] "+r" (c), [r_data] "+r" (d), [r_pin] "+r" (pin), [r_data_array] "+r" (&data[0]), [r_num_bytes] "+r" (numBytes)
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment