rp-hal
rp-hal copied to clipboard
No way to be generic over components at runtime
For example:
UartPeripheralis generic overUartDevice, beingUART0orUART1, as well asValidUartPinout.Spiis generic overSpiDevice, beingSPI0orSPI1- PWM
Sliceis generic overSliceId, beingPwm{n}, andSliceMode.
I've been walking around in circles trying to make a program that's flexible on which physical GPIO pins these peripherals are connected to at runtime, among other things, but this typelevel programming really shoots me in the foot, and makes things harder than they have any right to be.
GPIO provides AnyPin, but I can't find anything similar for these generics.
I don't know if this is connected to this, but it sounds like the issue I had. It was impossible to make a simple "write/read stuff from some UART" like this:
fn setup_hc05<P>(
uart: &mut rp_pico::hal::uart::UartPeripheral<
rp_pico::hal::uart::Enabled,
UART0???, // I don't want any specific UART here, I do not care...
P??????>) // This type parameter I never figured out...
{
uart.write_full_blocking("AT\r\n".as_bytes());
uart.write_full_blocking("AT-NAME?\r\n".as_bytes());
if uart.uart_is_readable() {
// ...
}
}
I ended up copy/pasting everything back into main()... an alternative would be to write all more or less generic code into a macro. But that is not how I envision clean code.
To be brutally honest: this typelevel programming makes things worse more than it makes things better. I understand where it's coming from, with Rust's compile time guarantees, but it's not really something that makes sense to enforce where it's being used here.
It ends up stopping the minority of people who don't understand and use a pin twice, or use UART9, but it forces everyone else to use copious amounts of completely unnecessary generics when they want to encapsulate anything in their program. It's far more a burden than a tool, which is probably why almost all of the examples do not encapsulate anything, but just store things as variables in main and use Rust's type inference (not viable for large programs)
I have found it completely impossible to be generic at run time rather than build time, even though there is functionally no limitation for this, and the SDK library finds no issues exposing multiple hardware instances, because of their shared functionality. This is outside the scope of typelevel programming, because it would require completely sidestepping it.
Distinguishing between an enabled or disabled peripheral with typelevel programming makes sense, but enforcing a specific pin combination or a specific peripheral number (UART0/UART1) doesn't make sense when they share functionality. It is too restrictive for little benefit for the reasons I have discussed.
My personal opinion formed from trying to use this library is that typelevel programming has caused me a lot of pain, and no good.
These are just my experiences on typelevel programming. I appreciate the work put into the library, please don't take this too seriously :)
The peripherals implement the embedded-hal traits. You are supposed to (where possible) write code that targets those traits, so that your code is generic over every HAL (not just the peripherals in this HAL). @WeirdConstructor: check out barafael's hc12 crate for inspiration In particular this section maps in the embedded-hal serial read and write traits. There's a lot of drivers listed on the awesome embedded rust page, those are what I look to for how to solve these sorts of problems.
@AlexApps99: I've honestly never had to switch some code between multiple peripherals of the same type on the same board at runtime. That's a pretty interesting problem, and I don't know if there's any easy way to solve it? Maybe have all the peripherals in one struct and impl the required traits on that? It would be tough.
Yeah, it would require a pretty big redo of the API, which is probably too late at this point. It might be nice to have a secondary, "type-erased" lower level API (similar to what the Pico SDK provides) that could be used to implement the current higher level typed API, so both use cases can be fulfilled, but that's still a lot of work.
Looks like this works fine:
use hal::uart;
fn write_something<D, P>(uart: &mut uart::UartPeripheral<uart::Enabled, D, P>)
where
D: uart::UartDevice,
P: uart::ValidUartPinout<D>
{
uart.write_full_blocking(b"something");
}
Although as others mentioned above, using one of the embedded HAL traits would be better, since it would work on any device, not just rp2040.