Skip to content

Add generalised spi driver for clockless LEDs - #112

Merged
ahdinosaur merged 36 commits into
ahdinosaur:mainfrom
mlandauer:clockless-spi-driver-rebased
Oct 12, 2026
Merged

ahdinosaur merged 36 commits into
ahdinosaur:mainfrom
mlandauer:clockless-spi-driver-rebased

Conversation

@mlandauer

Copy link
Copy Markdown
Contributor

This addresses #12

The implementation is currently pretty long winded for what it actually does (to my taste anyway). Most of this is due to hoops we're having to jump through to calculate buffer sizes at compile time (See #107)

Documentation is still to come but I thought showing this to you earlier rather than later was probably a good idea!

I haven't changed anything to make it easier to build the examples (there are 2 now) in blinksy-esp. Happy to add that if it makes sense. The new example isn't actually building against blinksy-esp but rather is building against blinksy but is esp specific. If there's a better place to put that example...

I'm somewhat surprised at how large this PR has become. It certainly was not my intention. I don't like the way the blocking and async code is duplicated and I don't like that there are two buffer sizes that have to be calculated. The builder makes it slightly less messy.

The usage of the new api goes something like this:

const SPI_FREQ_HZ: u32 = clockless_spi_ideal_frequency_hz::<Led>(150);

// Create spi that implements embedded_hal::spi::SpiBus trait or embedded_hal_async::spi::SpiBus (if using async feature)
// and set its frequency to SPI_FREQ_HZ
// let spi = ...

let writer = ClocklessSpiBuilder::default()
       .with_spi(spi)
       .with_freq_hz(SPI_FREQ_HZ)
       .with_buffer_size::<{ clockless_spi_buffer_size::<Led, SpiDma<Async>, _>(Layout::PIXEL_COUNT, SPI_FREQ_HZ) }>()
       .with_pulse_size::<{ clockless_spi_pulse_size::<Led, SpiDma<Async>, _>(SPI_FREQ_HZ) }>()
       .with_async()
       .build();

This addresses ahdinosaur#12

The implementation is currently pretty long winded for what it actually
does (to my taste anyway). Most of this is due to hoops we're having to
jump through to calculate buffer sizes at compile time (See
ahdinosaur#107)

Documentation is still to come
@mlandauer

Copy link
Copy Markdown
Contributor Author

@ahdinosaur it would be great if you could let me know if this on the right track before I finish it off by adding documentation

@mlandauer

Copy link
Copy Markdown
Contributor Author

And I managed to completely fail to mention that I explained the motivation behind this in #12 (comment)

@ahdinosaur ahdinosaur left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thank you for this! 💜

Overall this looks great. I feel this definitely matches the vibes of the existing Blinksy code. I like your thinking in #12 (comment), thank you for sharing. I also feel like you "get" Blinksy, which gives me a good feeling.

I wouldn't worry about how many lines this adds. Yes there's some boilerplate, but I think that's okay.

I had a few nitpick comments, sorry, you can do with that as you want. 🤷‍♀️

> {
spi: Spi,
freq_hz: Freq,
_led: PhantomData<Led>,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't usually _ the PhantomData (e.g. https://github.com/ahdinosaur/blinksy/blob/main/blinksy/src/control.rs#L245 ), but also it doesn't really matter.

use crate::driver::ClocklessSpiAsync;
use crate::markers::{Async, Blocking};

pub struct ClocklessSpiBuilder<

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to see that you understood my builder pattern! 👍

It might be a lot of code, but it's mostly just boilerplate so I don't mind.

Led,
SpiWord,
Freq,
Dm,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's a Dm?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what I called Exec? https://github.com/ahdinosaur/blinksy/blob/main/blinksy/src/control.rs#L249. Might be best to be consistent.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh, Dm comes from the esp-hal crate and how they handle this same situation. Yeah I'd be keen to be consistent with Blinksy, even if I do use Dm here: https://github.com/ahdinosaur/blinksy/blob/main/esp/blinksy-esp/src/rmt.rs#L129

Led: ClocklessLed,
{
pub fn build(self) -> ClocklessSpi<BUFFER_SIZE, PULSE_SIZE, Led, Spi, SpiWord> {
assert!(BUFFER_SIZE > 0, "set buffer_size before calling build");

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

use bitvec::{array::BitArray, order::Msb0, slice::BitSlice};
use core::marker::PhantomData;

pub const fn freq_hz_to_duration_ns(freq_hz: u32) -> u32 {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally I like to order exports in a file like a house's intimacy gradient: most public at the top, most private at the bottom. These are all public, but I reckon the helpers should go at the bottom.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also 🤷‍♀️ , this file is more a collection of related things, so not a clear entrance. Feel free to ignore me.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But also, do these functions need to be pub? Why not pub(crate)?

Comment thread blinksy/src/driver/clockless/spi/mod.rs Outdated
duration_ns_to_freq_hz(max_clock_period_ns)
}

fn encode_spi_buffer<

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should go to the bottom.

.with_dma(p.DMA_CH0)
.with_buffers(dma_rx_buf, dma_tx_buf)
.into_async();
let writer = ClocklessSpiBuilder::default()

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all looks good. 👍

@mlandauer

Copy link
Copy Markdown
Contributor Author

Thanks @ahdinosaur for the feedback. All good suggestions. I've hopefully addressed them and added some documentation.

Looks like I've broken CI. ugh.

@mlandauer

Copy link
Copy Markdown
Contributor Author

I keep getting caught out by this. Keep forgetting that code in docs gets run. Silly me.

@mlandauer mlandauer changed the title WIP: Add generalised spi driver for clockless LEDs Add generalised spi driver for clockless LEDs Oct 8, 2026
@mlandauer

Copy link
Copy Markdown
Contributor Author

@ahdinosaur hopefully good for you to look over again now

@ahdinosaur ahdinosaur left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, thanks!

Minor docs feedback. 📝

use bitvec::{array::BitArray, order::Msb0, slice::BitSlice};
use core::marker::PhantomData;

/// Represents the timing (in number of SPI bits) to encode zero, one and reset LED signals

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oxford comma please 😅

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not my natural writing style but I'm happy to oblige ;-)

Comment thread blinksy/src/driver/clockless/spi/mod.rs Outdated
///
/// # Type Arguments
///
/// - `Led` - The LED protocol implementation (must implement ClocklessLed)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this (and the next lines) link to the trait(s)?

Comment thread blinksy/Cargo.toml Outdated
categories.workspace = true

[dependencies]
# radium 0.7 is a dependency of the latest released version of bitvec which breaks on esp32. So using an unreleased

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We won't be able to publish a new crate version until this is resolved. Although I haven't been publishing new crate versions lately, but that's on me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bit of show-stopper I think because it doesn't look like we could depend on a bitvec release for that fix getting done anytime soon. It's annoying.

Anyway, I've rejigged things to not depend on bitvec. It does tidy up the public interfaces just a tiny amount too even though what's under the hood is now more messy.

@mlandauer

Copy link
Copy Markdown
Contributor Author

@ahdinosaur hopefully ready for another look over

@mlandauer

Copy link
Copy Markdown
Contributor Author

I keep thinking I'm done and then I find a bug I hadn't spotted before. See the last commit 96c13f1

@ahdinosaur
ahdinosaur merged commit 3b71e11 into ahdinosaur:main Oct 12, 2026
6 checks passed
@ahdinosaur

Copy link
Copy Markdown
Owner

Merged, thanks @mlandauer ! 🎉

@mlandauer
mlandauer deleted the clockless-spi-driver-rebased branch October 12, 2026 04:43
@mlandauer

Copy link
Copy Markdown
Contributor Author

Thank you @ahdinosaur!

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

Successfully merging this pull request may close these issues.

2 participants