Skip to content
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

Node support for ws2812 #24

Open
johnnyman727 opened this issue Apr 29, 2015 · 54 comments
Open

Node support for ws2812 #24

johnnyman727 opened this issue Apr 29, 2015 · 54 comments
Assignees
Milestone

Comments

@johnnyman727
Copy link
Contributor

Gotta get those Neopixels™ working.

@flaki
Copy link
Contributor

flaki commented Aug 3, 2015

How can I help with this?
I have an 8x8 Neopixel which I could get working with my Espruino Pico, but the same naïve SPI-based approach somehow didn't work for the T2 — is there anything I'm missing, or could it be that the Espruino has some kind of firmware magic to support this?

I realized that the T1 was using custom firmware support to make this work, do we need the same approach or the current setup is more flexible in this regard?

@johnnyman727
Copy link
Contributor Author

Yes, we need to write some custom firmware. Here are some notes I've taken:

  • Here is how I implemented the driver for the LPC1830 on T1 so you can get an idea how these sorts of drivers are written.
  • Apparently, Adafruit has Neopixel support for the Arduino Zero which also uses the SAMD21 microcontroller but unfortunately, it's convoluted and not pretty. I don't think we want to do it the same way they did (because, frankly, I'm not sure how their solution works).
  • We probably want to use the Timer/Counter for Control Applications (TCC) peripheral in chapter 30 of the SAMD21 datasheet.
  • PA04 (Port B MOSI), PA05 (Port B SCK), PA07 (Port B MISO), PA08 (Port B SDA), PA09 (Port B SCL), PA10 (Port B G1), PA11 (Port B G2), PA12 (Port A SDA), PA13 (Port A SCL), PA14 (PORT A G1), PA15 (Port A G2), PB02 (Port A MOSI), PB22 (Port A MISO), PB23 (Port A SCK), PB08 (Port A G3) {Basically all the GPIOs except Port B G3} can be used for Neopixels. I worked through this by comparing the Tessel 2 schematic with the pin descriptions on page 21 of the datasheet. Note that the schematic is one revision out of date but I don't think any pinouts changed - we should confirm this!
  • The general strategy here is to be able to DMA the LED buffer into a double buffered TCC that modulates a "PWM" signal at the frequency the WS2812 expects. If the current byte is a 1, it outputs an "on" PWM duty cycle and if it's a 0, it outputs an "off" PWM duty cycle (this makes more sense if you read my blog post above).

Will research a bit more later.

@johnnyman727
Copy link
Contributor Author

Starting this work here.

@flaki
Copy link
Contributor

flaki commented Aug 11, 2015

Ah! Just seen the PR! My microcontroller skills are a bit dented at this level (honest, I tried looking at the SAMD21 datasheet...), so I resort to believing you that this is going to be great 😜

@johnnyman727
Copy link
Contributor Author

That's probably more trust than I deserve :)

@abritinthebay
Copy link

My two requests:

  1. Make as much of the API GPIO Pin agnostic (ie - you can specify what pin to use for each function). This will enable SO MANY THINGS.
  2. Expose every method to JS! That way pure JS abstractions are infinitely easier (and you have to do less work maintaining/fixing later).

@nodebotanist
Copy link
Contributor

So I am working on this but I've never written this kind of firmware before so I'm frantically reading to get caught up. Any and all assistance is appreciated.

@johnnyman727
Copy link
Contributor Author

@nodebotanist have you looked through the readings I suggested here? It's pretty dense and I, frankly, didn't understand all of it. If you have specific questions after looking through those sections, call out me or @kevinmehall and we might be able to help.

@yoitsro
Copy link

yoitsro commented Jun 7, 2016

@nodebotanist @johnnyman727 Pals, I'm on board with this! I really want to help, but my C knowledge is limited. Getting started with it all is confusing to say the least. Has anyone else been working on this and made any progress?

@yoitsro
Copy link

yoitsro commented Jun 8, 2016

@abritinthebay
Copy link

If we can break out all possible C APIs to JS then I think the community creating libraries to have opinionated syntax is best.

I know I'd be happy to do so - I'm just no C programmer so will be happy to write JS libraries ;)

@rwaldron
Copy link
Contributor

rwaldron commented Jun 9, 2016

Why does this need to be in t2-firmware at all? The C code can be written as a compiled module that gets built through our pre compilation system, right? Not all Tessels will want this and shouldn't have the additional burden unless they opt-in. This issue was filed way before we had any compiled module support, and now we have strong compiled module support.

@abritinthebay
Copy link

A fair point.

@yoitsro
Copy link

yoitsro commented Jun 10, 2016

Agreed. Any chance we could get an official repo up on Tessel's GitHub account for this and we'll take the chat there?

@johnnyman727
Copy link
Contributor Author

@rwaldron this can't be a pre-compiled module because that mechanism is used to patch in binaries that run on the MediaTek. Neopixel firmware would need to run on the SAMD21 and we don't have a mechanism for updating that binary on the fly.

@abritinthebay
Copy link

ooo well then it really does need to be in the binary.

Either way - as long as the API is exposed to either C hooks or JS the actual writing of interface and control libraries can be written by the community.

@dougalcampbell
Copy link

dougalcampbell commented Jul 22, 2016

So, there's still not any real traction on this yet? Could anything be gleaned from FastLED and its SAMD support (Arduino Zero, MKR1000 and Wino)?

@johnnyman727
Copy link
Contributor Author

@dougalcampbell I don't think anyone is working on WS2812 support at the moment but @nodebotanist recently got Dotstars (very similar to WS2812 but with a "better" interface) working on T2: https://twitter.com/nodebotanist/status/753590795823964161

@abritinthebay
Copy link

abritinthebay commented Jul 26, 2016

Dotstars have a "better" interface but are a worse LED solution overall and less flexible (and less popular) as they only come in strips, which are much less useful generally.

NeoPixel support is important to be taken credibly in a maker-scene that is rife with them.

If the hardware can be controlled by JS with sufficient clock accuracy there's no reason why this even has to be a C module. Anyone know if that's possible?

@abritinthebay
Copy link

for example: this library can control them over an I2C interface: does the Tessel 2 have that/break it out at all?

https://github.com/ajfisher/node-pixel

@nodebotanist
Copy link
Contributor

Hey so that demeaning and explanatory tone does not help. I personally am I
aware of the importance of Neopixel support. I had dotstars and they
worked, and Jon was just pointing out a temporary alternative.

Second, if we didn't plan to figure this out at some point we'd have closed
the ticket. It's more complicated than it looks on the surface. We know
because we've been trying to find ways to tackle it for months.

--Kas

On Tuesday, July 26, 2016, Gregory Wild-Smith notifications@github.com
wrote:

for example: this library can control them over an I2C interface: does the
Tessel 2 have that/break it out at all?

https://github.com/ajfisher/node-pixel


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#24 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAJFvJ3R8E_gtkBUlHE8_rGuyy3Afi09ks5qZkL7gaJpZM4ELwVT
.

@abritinthebay
Copy link

Demeaning is pointing out that the dotstars are not a replacement for neopixels and asking about about actual solutions to the task at hand... ooookay.

What a toxic attitude you have.

@dougalcampbell
Copy link

@johnnyman727: Thanks for pointing out the DotStar work by @nodebotanist. So there's probably hope for APA102 strips, which also have a clock line.

@abritinthebay: That node-pixel solution is using I2C to talk to an Arduino, which is actually driving the pixels. But then the t2 isn't actually driving the pixels, it's just doing communication. It's a solution, but the real goal will be a native implementation.

I'm passingly familiar with the real-time timing constraints that make WS2812 difficult. Does the t2 give access to DMA in a way that you could implement a driver similar the one for RasPi?

@abritinthebay
Copy link

abritinthebay commented Jul 26, 2016

Well at least you agree...

Look, I appreciate the work you've put in here -seriously - but I'm also trying to find solutions to this problem as well (it's important to projects I work on/prototype). A reply (not by you) that said "try something else that doesn't actually solve the problem space" wasn't actually as helpful as it thought. I pointed the issues with that solution out - not that it was a bad thing by itself (dotstars are neat, limited atm, but very neat).

Wasn't being demeaning - I was explaining why it wasn't a complete solution. Rather than just rudely say "it doesn't fix the problem" I tried to explain why.

I guess as you are even more invested than me you're better qualified to answer the question:

What is the best starting point to get support for them? T1 had support and if anything was more complex and abstracted away from the hardware so what's the difference between T1 and T2 that's the blocker here?

@HipsterBrown
Copy link
Contributor

HipsterBrown commented Jul 26, 2016

Just putting a reminder in this issue that Tessel has a Code of Conduct (https://github.com/tessel/project/blob/master/CONDUCT.md) we expect all participants to follow.

@abritinthebay A lot of prior research has been done around this issue already, see #24 (comment) as an example. I appreciate you participating and checking in on the progress. We are still working on solutions, including a working proof of concept with Dotstars. Let's keep this conversation constructive.

@johnnyman727
Copy link
Contributor Author

johnnyman727 commented Jul 26, 2016

Meta Comment: the Tessel Steering Committee discussed the conflict that arose over this issue at the weekly Steering Committee meeting and you can read the notes here.

From a technical perspective, animations (on any LED hardware) can be a tricky task - they are not just timing-dependent but are relatively memory-intensive. The tl;dr is that I think it's a non-trivial problem to get Neopixels working, they may never work on T2 without changing the system architecture, and Dotstars are a reasonable alternative (only currently for projects that require strips as @abritinthebay points out).

From my experience, Tessel 2's timing resolution from JavaScript is on the milliseconds scale. I don't think it will likely work out if we try to bit bang out the WS2812 protocol from JavaScript. One productive step for a community member to take, would be to confirm that with an o-scope/logic analyzer.

We could potentially do this at the required resolution by writing a driver in the firmware using the SAMD21s TCC peripheral. I outlined that strategy in an early comment here. We went this route with Tessel 1 using the NXP microcontroller's SCT peripheral instead of Atmel's (almost) equivalent TCC.

However, even if we had that driver written now, we could have issues with running smooth animations because of scheduling limitations. Tessel 1 was unique in that we had a single microcontroller doing everything: it allocated the memory necessary for the animation, setup the TCC configuration to modulate the output pin, and handled hardware interrupts when it was finished processing each bit of data. Tessel 2 is more complex; here is the process that would have to go down to animate Neopixels:

  • Memory is allocated in the MediaTek's RAM when the JS code is executed. This is done automatically by Node/V8/OpenWRT
  • The tessel driver writes the hypothetical NEOPIXEL_ANIMATE command and all the animation data to the Unix domain socket that bridges communication between OpenWRT and the SAMD21 firmware
  • At some point in the future as determined by the OpenWRT scheduler, the domain socket will service the data will continue to pass it over SPI to the Atmel SAMD21
  • The SAMD21 SPI hardware interrupt will get hit and it will begin parsing the command and data in the packet
  • The SAMD21 will configure the TCC, pointing the peripheral to the animation data in RAM and begin the animation

The biggest question mark in my mind, is how well it will actually handle those animations. The SPI data packet for each command is currently a maximum of 256 bytes so all of the animation data would likely have to be written over multiple (many?) transactions (assuming #115 gets merged first). We can experiment with making that size bigger but I'm not sure how much, if any, RAM is available on the SAMD21. The microcontroller on T1 had significantly more RAM so this wasn't as much of an issue.

The TCC (I believe) also has an option for double buffering so the contents from the first batch of animation data can be sent out on the wire while the second batch of data is coming in over SPI and being configured with the TCC.

As you can see, there are a lot of factors at play here: RAM size determines how much of an animation we can animate at a time and therefore how long the animation persists, while the OpenWRT scheduler determines how often the SPI daemon sends over that data. It's totally possible that it will all work smoothly but it's difficult to determine without actually building it out which is a pretty significant time investment.

@abritinthebay does that answer your question? I don't mean to be a downer here, I just think this feature would be difficult to add given the T2 architecture. I'm happy to help/support anyone who wants to work on any of the following tasks that could help move it along:

@abritinthebay
Copy link

Yes, it does. Thank you. Exhaustively even.

I think, outside of the internals of the Tessel team, this may have been under-communicated. It my feel like very old/common information but it's not really well detailed on this thread (until now).

Thank you. It's disappointing to hear the Tessel 2 may be a non-starter for many of the projects I was hoping to use it for (given the T1 had support), but I understand. I guess I will have to rethink how Tessel fits into my projects and plans for now.

@dougalcampbell
Copy link

dougalcampbell commented Jul 26, 2016

@johnnyman727 Thanks for those details! I'm practically certain that you'd never be able to make a 100% JS solution work. The timing requires precision down to about 250ns. I found the Just In Time site to be helpful in understanding the challenges.

Personally, I wouldn't care too much about an animation API, as such. If I could just feed a bunch of RGB values into a buffer array, all I'd really need would be a function that would feed the buffer to the strip via the driver, and another function that would clear the strip (latch the data pin low long enough for a reset to black). Even if managing "animation" myself by manipulating the buffer is slow, it would be better than nothing. 😄

I wish I knew enough about the low-level hardware/C/asm to offer help with the coding. I know a little about hardware, and I can understand almost all of what's going on in the assembly on the Just In Time site above, but I'm mostly a high-level guy.

@johnnyman727
Copy link
Contributor Author

I think, outside of the internals of the Tessel team, this may have been under-communicated. It my feel like very old/common information but it's not really well detailed on this thread (until now).

@abritinthebay Sorry about that. In all honesty, I hadn't thought through the implementations thoroughly enough until recently. @kevinmehall might have some tips on how a better implementation or point out mistakes in my logic.

@johnnyman727 Thanks for those details! I'm practically certain that you'd never be able to make a 100% JS solution work. The timing requires precision down to about 250ns. I found the Just In Time site to be helpful in understanding the challenges.

@dougalcampbell that sounds reasonable to me. I also documented the process of building support for WS2812 on T1 a couple of years ago here. That could be useful for understanding the type of driver that needs to be written (but it will be a little bit different since it's a different MCU).

@ajfisher
Copy link

To provide some additional context to the challenge of this and some solutions too. Apologies for length - there's quite a few considerations in here.

On current hardware you cannot guarantee the timing constraints needed in JS to reliably get a length of pixels to update on anything longer than a handful. The timing cycles are short yes, but more importantly they don't have a lot of slack in them to mark when a frame is complete. The implication of this is that whilst you may get it to work for less than a handful of LEDs you can't guarantee you'll keep the timing over a long length as you pass the data through the length.

This means the update cycle will occur when you're only half way through pumping the data along and not only will half your length of LEDs not light up, the other half will be out of position.

One inopportune GC or a process switch and you can drop the whole frame through a mistimed latch - this happens surprisingly often with long lengths of pixels. Great if you like glitch, not so handy if you want to do something useful or accurate.

Most importantly it's sub microsecond accuracy that is needed - at a resolution of about 100ns to get by from whatever language you are going to use.

There were points about not needing animation and the like, that's all fine but the reality is that for a viable solution to work you need to be able to individually address any given pixel at a reasonable framerate for updates. If you only want to have a whole strip that is one colour then there are far cheaper solutions to this with straight RGB LED strips with 3 analog inputs. On the other hand if you do your animation at the JS level which is also viable, you then have to pump all of that data down to the pins as a buffer. This is possible but lowers your throughput.

To do this accurately you need to do it as close to the metal as possible which in the case of the tessel would mean extending the firmware to make it work. I haven't got a tessel 2 yet (Australia - price & timing to get them still means I'm waiting) so I can't determine exactly what would be needed to add a module to it but can guess it's non-trivial and will be tessel-specific.

The idea of extending core firmware was discussed long and hard by a group of Johnny-Five developers and users during and after RobotsConf in 2014. In the end we abandoned the idea of natively extending firmata because of the resources that are taken up to manage large numbers of pixels.

What needs to be considered is not just how long it can take to update a frame that contains lots of pixels but also the memory overhead required to hold the state of those pixels (1 byte per colour channel per pixel). You can very quickly consume the entire resources of an MCU and not leave it capable of doing anything else.

DotStars and other types of controllable RGB LED are better, however they are typically much more expensive ($1 per pixel is very expensive - I can't afford to spend literally thousands of dollars on LEDs for one display), limited in form factor etc.

The approach taken by the J5 team was to offload the heavy lifting to an I2C Backpack. This is what node-pixel does and created the purpose of the Interchange project.

The node-pixel I2C backpack can control several hundred pixels at high frame rates and only requires changed data to be sent to the backpack to deal with framing and timing. The current price on a pro-mini or nano is sub $2. You also get the benefit of being able to use node-pixel which will make your life easier too.

Whilst "native" solutions are handy, the tessel isn't the only SBC in the ecosystem that wants to use controllable RGB LEDs so if people are interested in writing and optimising firmware then I'd love to have you over in the node-pixel project where there are serious optimisation and firmware tasks still needing to be done to eke every last drop out of the backpack solution that will work for every board that can use I2C (and using NodeBots or not).

Having spent the last several years working with these pixels at large and small scale on a variety of platforms, I'm happy to help (once I can get my hands on a tessel) however I still believe that a board-agnostic approach is more viable in the longer term for the ecosystem as a whole.

@dougalcampbell
Copy link

@ajfisher: I appreciate that node-pixel offers a solution now. But there are going to be people (like myself) who are surprised that the t2 doesn't have "out of the box" support for NeoPixels. Especially when they can be driven on something like a 8MHz ATtiny85. Of course, I realize that t2 is a different platform, with different constraints, and a different ecosystem.

Expectations aside, I'm pretty sure it should be possible to implement something in the t2 firmware that can drive some reasonable number of pixels ("reasonable" defined mostly by memory limits), without concerns about starting a new update halfway through the last cycle or something. I don't know enough about how things fit together to say that with 100% certainty, but given what I've read about drivers for the RasPi, BeagleBone, etc. It looks like the general strategy is to use DMA and PWM, converting the actual bytes into a stream of 1s and 0s that can approximate the WS2812 timing. For example, this RasPi driver transforms every source 1 bit into 1 1 0, and every 0 bit to 1 0 0.

I gather that the SAMD chip is what drives the hardware ports on the t2, while the MediaTek chip handles OpenWRT, wifi, JavaScript, and such. I would think the general strategy could be something like:

  • JavaScript neopixel init function would create an Array (or Buffer?), and instruct SAMD to reserve memory, too.
  • JS code would write pixel color data to array (setPixel( pixnum, rgbColor), etc.)
  • JS code would call a send() function which would transfer the values to the SAMD buffer, and SAMD would use DMA/PWM to drive the strip.
  • JS code could call a clear() function which tells the SAMD to simply hold the output GPIO low for an appropriate time.

The trickiest part, obviously is the SAMD-side of the send functionality. For example, I don't know how the RasPi driver manages driving large numbers of pixels without halting all other code from running or losing its timing. But there seems to be some trick to it.

@kevinmehall
Copy link
Member

@dougalcampbell, that's basically correct. T2 has the SAMD21 coprocessor for exactly these kinds of low-level timing-critical code; it just takes someone interested in implementing firmware for it. Hardware resources aren't necessarily limiting there. It's significantly more powerful than the Arduino Uno, for instance, and it has a DMA controller, which is key for keeping timing without completely blocking execution.

There are two firmware steps needed to add support:

  • Right now, the port command buffer only holds a single 255-byte packet before it has to ask the SoC for more data. There's a lot of RAM available that should be used for a ring buffer of command packets so it can go longer without relying on the less-predictable timing of the SoC. This would also improve performance with large I2C/SPI/UART transfers, as you might need to talk to an offboard LED controller.
  • A way to clock out a waveform at a fixed frequency. One option is to modify the existing SPI code to never pause between DMA buffers by setting up double-buffered / chained DMA, and using the SPI MOSI line to drive a single Neopixel strand. Another is to configure the TCC in pattern generation mode, with DMA writing a value for each pin at each timer tick to the pattern register, which would drive up to 8 parallel Neopixel strands with no additional overhead.

Then JS code could prepare the waveform for a frame in a buffer, and send it over to the coprocessor to be played back on the pins with exact timing.

@qimyi
Copy link

qimyi commented Dec 10, 2016

I was able to use the SPI interface clocking at 2.4MHz to control a Neopixel strip but was only able to address about 20 LEDs because of the buffer size limitation. @kevinmehall 's suggestion to use chained DMA sounds like a plan. I'll see if my limited knowledge is sufficient to come up with something to contribute here.

@dougalcampbell
Copy link

I just noticed that #115 was merged and closed recently.
I know it's only a tiny piece of what's needed, but is there any chance somebody has time to look back into this and the DMA portion?

@nodebotanist
Copy link
Contributor

nodebotanist commented Mar 6, 2018

So this finally came out of that giant jar of side project ideas to work on while (for lack of wanting to reveal more) coding to distract myself.

I'll be noodling with this based on @PaintYourDragon 's work with the Adafruit Feather M0: https://learn.adafruit.com/dma-driven-neopixels/overview

This may provide a little more stability over using the SPI functions (mostly avoiding edge cases or folks trying to mod the SPI to meet neopixel's timing demands)

@JaredSartin
Copy link

@qimyi - how did you manage that? I need to address only 12. Anything to share?

@nodebotanist
Copy link
Contributor

Currently investigating the Adafruit PXL8 library on an Adafruit Feather running on a SAMD21 chip, if it works I might need guidance to get the lib on the T2 but I think this could work.

@nodebotanist
Copy link
Contributor

Okay, so the lib works perfectly on SAM21 via Adafruit Feather M0. Need guidance as to how to run a C lib on the T2 to test the library on the SAMD21 on the T2. @johnnyman727 @flaki @Frijol any suggestions on where to start?

@nodebotanist
Copy link
Contributor

@qimyi - how did you manage that? I need to address only 12. Anything to share?

I couldn't get that lib to work even after a LOT of trying

@HipsterBrown
Copy link
Contributor

@nodebotanist You should be able to update the SAMD21 using t2 update --firmware-path path/to/local/binary . I'm not sure if you need to integrate it into the C firmware first or if you can just push just the PXL8 related code.

@nodebotanist
Copy link
Contributor

Thanks @HipsterBrown I'll try that tomorrow!

@JaredSartin
Copy link

@qimyi - how did you manage that? I need to address only 12. Anything to share?

I couldn't get that lib to work even after a LOT of trying

You are talking about https://github.com/qimyi/party-lights ?

@nodebotanist
Copy link
Contributor

@qimyi - how did you manage that? I need to address only 12. Anything to share?

I couldn't get that lib to work even after a LOT of trying

You are talking about https://github.com/qimyi/party-lights ?

Yes

@nodebotanist
Copy link
Contributor

So I tried to wire up the NeoPXL8 lib and I don't think just using that library is the right approach. However, I think we can use the knowledge within it to add DMA-ready WS2812 support to the T2 firmware.

This is still far from finished, but we're making some good progress.

@HipsterBrown
Copy link
Contributor

@nodebotanist Thanks for your continued work on this front. 😄

@nodebotanist
Copy link
Contributor

nodebotanist commented Oct 22, 2018

Can someone tell me what the div argument byte required for CMD_ENABLE_SPI is? Source:

// 1 byte for mode, 1 byte for freq, 1 byte for div

@HipsterBrown
Copy link
Contributor

HipsterBrown commented Oct 22, 2018

@nodebotanist It appears to be used when enabling the serial clock ->

void sercom_clock_enable(SercomId id, uint32_t clock_channel, u8 divider) {
// prevent this clock write from changing any other clocks
PM->APBCMASK.reg |= 1 << (PM_APBCMASK_SERCOM0_Pos + id);
if (clock_channel != 0) {
// clock generators 3-8 have 8 division factor bits - DIV[7:0]
gclk_enable(clock_channel, GCLK_SOURCE_DFLL48M, divider);
}

Used in the node module: https://github.com/tessel/t2-firmware/blob/master/node/tessel-export.js#L1091-L1135

@kevinmehall should know more about that

@kevinmehall
Copy link
Member

IIRC the SERCOM module is limited in how low its frequency can go relative to its input clock, so to get lower baud rates, you have to divide the input clock to the SERCOM.

@nodebotanist
Copy link
Contributor

Okay took a new approach with FastLED.

Notes are here:https://github.com/nodebotanist/T2-Investigation-Notes/blob/master/notes/2018-12-15.md
Fork of FastLED here: https://github.com/nodebotanist/FastLED-Tessel

Making slow but reasonable progress. Will alert when FastLED is at least compiling and running on the T2 itself.

@econic
Copy link

econic commented Jan 18, 2019

@nodebotanist I'm really looking forward to your work and would love to hear of any updates! 👍

@nodebotanist
Copy link
Contributor

Still going! A little slow due to new job onboarding. Working on FastLED firrmware in the T2 firmware still. Will post soon!

@yoitsro
Copy link

yoitsro commented Nov 23, 2019

Hey @nodebotanist. Thank you for all your work on this so far! How are you getting on with this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests