-
Notifications
You must be signed in to change notification settings - Fork 194
RFC: Multi CS Spi Implementation #150
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
Conversation
Imo it's rather inconvenient that you have to create all the devices upfront. One should be able to create a bus then create devices using the bus. |
I see what you mean. The current implementation does this to make it a simpler implementation. It is easy to add the functionality to add new devices. But i hesitated a bit because of the following implications:
I think the 2nd point would only not apply if you have some sort of plugable spi devices or your devices needs two different configurations for initialization. Do we need to support such a case is a question. If we implement an option do add devices question is how this should change the interface. What do you think? |
Another problem when allowing an dedicated rm of a device -> Because the identifier for an device is just an i32 this is a weak link. An device could be removed but someone still wants to talk to that device using an i32. This call would fail. Here the simplicity of the i32 don't look to good, we than need to inform the cally about that. So allowing an rm of a device needs the existence of an stronger link. But the question is does we really need to rm devices at runtime separated from removing the bus? If we dont need it we should be fine they way it is. Currently all devices gets removed when the bus gets removed. |
This is required for SD cards. When an SD card is to be used, it has to be configured to be in SPI mode, doing this requires sending some SPI signals to it much slower than usual, after which the SD card can be communicated with in SPI mode and at much higher speeds.
Yes I imagine this would be another guard type like so. let bus = SpiMasterDriver::new(.....)?;
let device1 = SpiDevice::new(&bus, cs1)?;
let device2 = SpiDevice::new(&bus, cs2)?;
I don't understand the need for a runtime check or iteration. Each device can keep track of it's own stuff and the bus doesn't need to iterate.
Yes. My SD card example requires this at least.
An issue with using an |
I missed this when I initially looked at the PR, so I was confused at the device list tracking but I see the goal now. Another way to surpass the esp-idf limit of 3 devices per bus is to do software CS. This means creating the spi bus without the CS pin, then manually toggling the CS pin outside the driver. embedded-hal allows for this with the |
Thank you for your feedback. I tried really hard to create something that works without the i32 id's and have a similar interface like you proposed. I have a first working version now ready 🎉 |
Current Interface looks something like the following
|
It looks like the only use of the global static is to make sure that all devices a free'd before the bus is free'd. |
Let me know (here or on Matrix) if you need any help. I'd like this PR to land ASAP or within the next few weeks before the next version is released. This is a fantastic opportunity to make breaking changes to the existing SPI interface since the next version will already contain breaking changes. |
I got it down now for the most part. It is fully functional and i tested it for 11 parallel SPI devices. Usage Interface currently looks like the following:
pros of the current design:
cons :
there are still some cleanups to do but there is one other problem i have to investigate... i noticed when a device gets removed from the bus via esp-idf spi_bus_remove_device() fn the cs_line gets driven low for a short amout of time. that would be not accaptable when frequent device removes are happening. for compairsion i will later implement a version with software managed cs pins. after that i will have a look to make the driver async rdy |
It's possible to improve the lifetime situation still. Second improvement is to remove the forced use of |
Need i didn't now about Borrow. I will try to implement it asap. Thanks for the Input 🥰 |
hmm i am kinda stuck, i don't think it is possible to use Borrow. Or maybe i am not understanding it correctly. The problem where it all breakes apart is that i want to store the Borrow in the struct and that is not easyly possible in this special situation. Maybe i am not understanding it correctly but here is an example: If only one struct has a reference to another struct this works as expected
But my problem is because we also have a reference from slave inside master this breakes. I got the following circular problem. No i need to make Master Generic over slave -> this feeds back to slave that needs now a generic Master.
can you think of an solution to this problem ? |
Well, the ideal solution would be to remove the tracking completely and leave the user to handle unlimited CS pins themselves. 😛 The solution you're looking for is to not hold a reference to the device directly in the bus but instead hold a reference to the data inside the device that you need in the bus i.e. the |
thats not the problem here. the problem arises because one device needs to revoke an handle from another device when there is currently no empty handle free. so there have to be a place for all the devices to look at and revoke a handle from another device. otherwise every SpiDevice works complitly autonomous. Alternativly each devices has to give up its handle after usage. but this would make it more bad lots of device_rm_from_bus calls 😭 i dont like a software cs solution, its to "blocking" for me . but maybe when making it all async i just dont have to care 🙈 |
Sorry, I missed the fact that the So instead of pub struct EspSpiDevice{
handle: RefCell<Option<spi_device_handle_t>>,
// .....
}
pub struct SpiMasterDriver {
devices: HashMap<i32, RefCell<Weak<EspSpiDevice>>>,
// ....
}
let device: Arc<EspSpiDevice> you can just do this pub struct EspSpiDevice {
handle: Arc<RefCell<Option<spi_device_handle_t>>>,
// .....
}
pub struct SpiMasterDriver {
devices: HashMap<i32, RefCell<Weak<RefCell<Option<spi_device_handle_t>>>>>,
// ....
}
let device: EspSpiDevice this way you don't need the master to be generic based on the device. |
Well, what you currently have is kinda software CS, since you're having to destroy the device and recreate it, no? Though I suppose it's slightly better than plain software CS. |
The trick is that i dont destroy devices. All devices life as long as they like only there handles get set to None from other Devices if it is needed. That is the hole "trick". (Or do you mean when releasing in RTOS?) Since SPI is inherently Serial it is only possible that only one Device can send at a Time. If Device 4 sends for a very large time, all others have to wait for it. That's why i guard the SpiMasterDriver with a Mutex on transaction, while transaction is going no other device can switch out a handle if it has none. There is no ques option since in the current state the driver already only utilize a blocking transfer with |
At least its not a total disaster:
How does this sound ? question is now how can we detect how many SpiDevices are there? Would like to have a nice compile time solution. |
Yes, 4 devices on 4 different threads. Though since the ESP is limited to 2 cores with cooperative scheduling it's probably not possible. Though it becomes possible when this becomes async and uses interrupt instead.
Yes but ideally we want to queue up all the transactions ahead of time to reduce software overhead in the peripheral. Only matters for DMA though, which you don't have yet but it'll have to be added.
Where is this? I don't see this guard being used during the transaction. The guard is only used to get a handle.
Ohhhh, you should've said so. I would've been more helpful with the idea if you did.
Good and you can still implement the "hole trick" on top (as a wrapper) of what you've just described. I'm happy to help with this as well as I think it's a cool feature but one that should be optional.
I'd say not to bother with this. For what I currently have in mind. I think this PR should just be simple multi device implementation with hardware CS (and no tracking). let spi = SpiMaster2::<SPI2>::new(spi, sclk, tx, Some(rx));
let mut slave1 = SpiSlave::new(&spi, cs_pins.pop().unwrap(), conf_list[0])?;
let mut slave2 = SpiSlave::new(&spi, cs_pins.pop().unwrap(), conf_list[1])?;
let mut slave3 = SpiSlave::new(&spi, cs_pins.pop().unwrap(), conf_list[2])?; Then in a separate file in this PR (or another PR), we can have a pooling struct. let spi = SpiMaster2::<SPI2>::new(spi, sclk, tx, Some(rx));
let spi = SpiMasterPool::new(spi);
let mut slave1 = SpiPoolSlave::new(&spi, cs_pins.pop().unwrap(), conf_list[0])?;
let mut slave2 = SpiPoolSlave::new(&spi, cs_pins.pop().unwrap(), conf_list[1])?;
let mut slave3 = SpiPoolSlave::new(&spi, cs_pins.pop().unwrap(), conf_list[2])?;
let mut slave4 = SpiPoolSlave::new(&spi, cs_pins.pop().unwrap(), conf_list[3])?;
This way we can have the best of both worlds! |
src/spi2_pool.rs
Outdated
pub struct SpiConfigPool<'d, T> { | ||
shared_handles: HashMap<u32, spi_device_handle_t>, | ||
master: T, | ||
_p: PhantomData<&'d ()>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you're using the raw spi_device_handle_t
, were you not able to do this?
pub struct SpiConfigPool<'d, T> {
shared_handles: HashMap<u32, EspSpiDevice<'d, T>>,
master: T,
_p: PhantomData<&'d ()>,
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you're using the raw
spi_device_handle_t
, were you not able to do this?pub struct SpiConfigPool<'d, T> { shared_handles: HashMap<u32, EspSpiDevice<'d, T>>, master: T, _p: PhantomData<&'d ()>, }
yeah that was the initial plan. i switched it back here to the raw handle because of some design decision
- because of the cs_glitches i got when i frequently removed an device handle with spi_bus_remove_device() i currently opted out of the idea of switching out the spi_device_handle_t actively ( this is a deeper dive first):
- for regular usage one can now use SpiMasterDriver with EspSpiDevice witch uses hardware cs
- redefining the purpose of the SpiConfigPool: The SpiConfigPool gets you up to 3/6 independent "transmission configs" ( an spi_device_handle_t).
- because each "transmission configs" is now shared it should not allow to have an cs pin set to anything -> an EspSpiDevice allows to set an cs_pin,
- its probably less resource intense to just store just the handle
- the transaction from EspSpiDevice and the SpiPoolDevice are different. The First must call SpiBusMasterDriver with hardware_cs true / the later with false. SpiPoolDevice needs to manage the cs pin here now actively -> want the high/low calls close to the actual transaction. but yes yours look much nicer without so much duplication 😺
- because of that i didn't think about actually using an EspSpiDevice here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay I see. So for the new idea, what's the different between this and the simple hardware CS option? Does this give you unlimited devices but subsets of them share a spi_device_handle_t
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay I see. So for the new idea, what's the different between this and the simple hardware CS option? Does this give you unlimited devices but subsets of them share a
spi_device_handle_t
?
yes that's the basic idea. but the nice thing is you can use it in two ways here. the first is unlimited devices. but you can also use it for the reverse. if you have one device that needed different configs you can now just change configs without switching the actual config / handle of the device. an config switch is normally also an handle switch witch lets the cs_pin in an glitchy state currently. this does prevent it. want a different config to your device? just change the config_id in your SpiPoolDevice to one of the created configs in the SpiConfPool. All at the cost of using software cs
src/spi2_pool.rs
Outdated
impl<'d, T> SpiConfigPool<'d, T> { | ||
pub fn new(master: T, device_configs: HashMap<u32, config::Config>) -> Result<Self, EspError> | ||
where | ||
T: Borrow<SpiMasterDriver<'d>> + 'd, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be BorrowMut
as you want an exclusive ref to the driver for pooling.
T: Borrow<SpiMasterDriver<'d>> + 'd, | |
T: BorrowMut<SpiMasterDriver<'d>> + 'd, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes and no ? 😄 technically you could have a subset of devices that uses pooling and some that are plain EspSpiDevices. i don't require it exclusively. like 2 normal ones and 6 devices that share one handle. with this you could use some devices with hardware cs and the rest with software cs no ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure but how does the SpiConfigPool
know how many devices are available for pooling then? Especially since this number could change at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure but how does the
SpiConfigPool
know how many devices are available for pooling then? Especially since this number could change at runtime.
how many configs are available uses the same restriction as creating normal EspSpiDevices. you crate an SpiConfigPool with an set of configs. one should be able to change an config but not alter the numbers of configs. the user must ensure that in total all numbers of EspSpiDevices in parallel with the number of configs given to the SpiConfigPool creation does not exide 3 / 6 devices. you than get en EspError if you use more than that. but yeah would be nice to have that sorted out at compile time.
i saw some crazy shit with constant generics combined with FnOnce impl where one can construct functions that are only callable N times and that is checked at compile time !!!
src/spi2_pool.rs
Outdated
let device_handle:&spi_device_handle_t = pool.shared_handles.get(&self.config_id).unwrap(); | ||
|
||
// lock the bus through the mutex in SpiMasterDriver | ||
let master: &SpiMasterDriver = pool.master.borrow(); | ||
// should we check for poison here? | ||
let driver_lock = master.handle.lock().unwrap(); | ||
|
||
//self.pin_driver.set_low()?; | ||
|
||
//let handle = self.handle.clone().into_inner().unwrap(); | ||
let mut bus = SpiBusMasterDriver { | ||
handle: *device_handle, | ||
trans_len: SOC_SPI_MAXIMUM_BUFFER_SIZE as usize, | ||
hardware_cs: false, | ||
_p: PhantomData, | ||
}; | ||
self.pin_driver.set_low()?; | ||
let trans_result = f(&mut bus); | ||
|
||
let finish_result = bus.finish(); | ||
|
||
// Flush whatever is pending. | ||
// Note that this is done even when an error is returned from the transaction. | ||
let flush_result = bus.flush(); | ||
|
||
self.pin_driver.set_high()?; | ||
|
||
drop(driver_lock); | ||
println!("after lock drop"); | ||
let result = trans_result?; | ||
println!("after trans result"); | ||
flush_result?; | ||
println!("flush and finish"); | ||
finish_result?; | ||
println!("end Transaction"); | ||
Ok(result) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the change in my first comment, you'd be able to simplify this whole function to just this.
let device_handle:&spi_device_handle_t = pool.shared_handles.get(&self.config_id).unwrap(); | |
// lock the bus through the mutex in SpiMasterDriver | |
let master: &SpiMasterDriver = pool.master.borrow(); | |
// should we check for poison here? | |
let driver_lock = master.handle.lock().unwrap(); | |
//self.pin_driver.set_low()?; | |
//let handle = self.handle.clone().into_inner().unwrap(); | |
let mut bus = SpiBusMasterDriver { | |
handle: *device_handle, | |
trans_len: SOC_SPI_MAXIMUM_BUFFER_SIZE as usize, | |
hardware_cs: false, | |
_p: PhantomData, | |
}; | |
self.pin_driver.set_low()?; | |
let trans_result = f(&mut bus); | |
let finish_result = bus.finish(); | |
// Flush whatever is pending. | |
// Note that this is done even when an error is returned from the transaction. | |
let flush_result = bus.flush(); | |
self.pin_driver.set_high()?; | |
drop(driver_lock); | |
println!("after lock drop"); | |
let result = trans_result?; | |
println!("after trans result"); | |
flush_result?; | |
println!("flush and finish"); | |
finish_result?; | |
println!("end Transaction"); | |
Ok(result) | |
} | |
let device = &pool.shared_handles.get(&self.config_id).unwrap(); | |
device.transaction(f); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as stated above. this is much nicer but i need the difference from EspSpiDevice because this is software_cs. Software_cs need a dfferent SpiBusMasterDriver flag + needs tight setting over the pins. Have to think how to still reduce some of the duplication efficiently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood
Api example for the SpiConfigPool with SpiPoolDevice
|
.cargo/config.toml
Outdated
@@ -1,3 +1,11 @@ | |||
[build] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not belong to this PR.
Cargo.toml
Outdated
@@ -31,6 +31,7 @@ heapless = "0.7" | |||
embassy-sync = { version = "0.1", optional = true, git = "https://github.com/ivmarkov/embassy" } | |||
edge-executor = { version = "0.3", optional = true, default-features = false } | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
src/peripherals.rs
Outdated
@@ -44,6 +46,8 @@ pub struct Peripherals { | |||
pub spi1: spi::SPI1, | |||
#[cfg(not(feature = "riscv-ulp-hal"))] | |||
pub spi2: spi::SPI2, | |||
pub spi22: spi2::SPI2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very confusing. What does spi22
mean? What does spi2v2
mean?
Also:
- you need
#[cfg(not(feature = "riscv-ulp-hal"))]
or this code will break when compiling for the ULP HAL - How can I have 3 different SPI2 peripherals? What happens if I use all three simultaneously? As in
spi2
spi22
andspi_v2
? Would that even work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah sorry to confuse you here. it was just a hack to outsource the current dev of the driver. after finishing the new driver api i will merge it into the spi.rs than this will be all gone. ( and you don't need to review a wall of code 😄 ). When i created the draft i started with a spi_v2.rs. This is now gone i should have removed it 😸 I split it now into two files because of two different feauteres: spi2.rs witch is mostly an update to the current spi.rs will bring Mutli Cs / Multi Device. spi2_pool is the new thing where you can share rtos device handles with multiple devices to allow more than the limit of 3 or 6 devices, or alternativly just to have an mechanism to seemingly switch already created device_configs on an device( one device needs different bus speeds like an sdcard on creation).
src/spi2.rs
Outdated
use core::marker::PhantomData; | ||
use core::ptr; | ||
|
||
use std::borrow::Borrow; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not OK. Use these from the alloc
crate. For mutex - use the mutex supplied by esp-idf-hal
itself. For now, the policy is to avoid STD
types where possible. it is another question whether this will pay off in any way, but for now we are doing it.
Also - and as a matter of fact, - we also try to avoid the alloc
module in esp-idf-hal
, whenever possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep it was laziness here( i had not the time to look at the non std variants so far but it was on the radar for the finalization)
src/spi2.rs
Outdated
@@ -0,0 +1,681 @@ | |||
//! SPI peripheral control |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a general question as to the approach here: why are we creating a completely new implementation of SPI, in its own module? Given that the SPI impl in master
is not compatible with the one in the release
branch, I think we should just modify the existing implementation in a way that it has the extra capabilities you suggest. As long as it is still possible to implement the e-hal 1 and e-hal 0.2 traits on top of it, that is. This would also save me the effort of looking at a wall of code, which surely is 80% the same as the one inthe current driver, and trying to figure out where you've done changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah i was planing to exactly does this. to make my life easier i was just developing in a parallel file to fetch other potential upstream changes to spi.rs. sorry to confuse you here
src/spi2.rs
Outdated
use esp_idf_sys::*; | ||
|
||
use crate::delay::BLOCK; | ||
use crate::gpio::{self, InputPin, OutputPin, Output,InputOutput, Pin,AnyIOPin, AnyOutputPin, IOPin, PinDriver}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, my code is still ugly 🙈 it was not 100 % ready for a review
src/spi2_pool.rs
Outdated
use crate::peripheral::{Peripheral, PeripheralRef}; | ||
use crate::spi2::{config, SpiBusMasterDriver, SpiMasterDriver}; | ||
|
||
use std::borrow::Borrow; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above for STD usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep
src/spi2_pool.rs
Outdated
const ESP_MAX_SPI_DEVICES: usize = 6; | ||
|
||
pub struct SpiConfigPool<'d, T> { | ||
shared_handles: HashMap<u32, spi_device_handle_t>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HashMap is an overkill, unless you plan to use tens to hundreds of devices here. And requires STD. Why not a BTreeMap
or even a simple array of (u32, spi_device_handle_t)
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, original the concept was kinda different, you had a central pool where all the devices would be subscribed and one device could yank another devices handle and get its own. the driver worked, but calling rtos spi_bus_remove_device & spi_bus_add_device will make the hardware managed cs go wild(pulling it low for a short amount )on transition from an spi_cs_pin to an normal pin again.
now the shared handles can possibly be <= ESP_MAX_SPI_DEVICES and not " all the subscribed devices", so using a simpler data struct now makes sense.
Users should be able to add or remove shared_handles at runtime. A fixed size array was a bit unergonomic. but certainly its possible now. but the EspPoolDevice has to search now every element of the array . alternatively one can make something like a mapping inside the EspPoolDevice that maps its ID to an array Index and now just store an shared_handles[spi_device_handle_t]; or a BTreeMap as you suggested. in the end i will hope to get rid of all the std
If somebody wants just one device (with or without a CS pin) and does not want to share, he/she should use the
That's exactly what I meant and that would be great.
Why?
I do not understand this. All
OK but this is even more complex, and... would that even work?
I do not understand this. In my simple mind, the user will create a pool around a specific
If a custom, user-supplied wrapper on top of
I think we can somehow meet in the middle. I think a good path forward would be to: |
let driver = SpiMasterDriver::new(.....)?;
let bus = EspSpiBus::new(&mut bus, &config)?; // No CS pin specified.
bus.transfer(...)?;
bus.write(...)?; User can then use https://github.com/Rahix/shared-bus to share the bus and get unlimited devices with software/manual CS? |
works only for one thread on spi though ... my current solution is much slimmer and works everywhere and i dont think it works with multiple configs |
I'm not suggesting a replacement to It just seems very strange to have a |
did you have a chance to look inside the spi_pool.rs ? thats essentially what the spi_pool is. its just 2 structs and 150 lines that does the wrapping without being a full wrapper rather an optimized impl and also provided the locking itself. i can now implement it very easily myself now though. i just think its a nice helper and other's might appreciate it + it can be only this way if its inside the hal but if my life depend on it i can live without it 😺 and try to wrap around it myself . |
And your |
What I don't like about
What I would do, if we really want this "logical CS" utility:
|
src/spi.rs
Outdated
} | ||
|
||
pub fn host(&self) -> spi_host_device_t { | ||
self.host as _ | ||
pub fn cs_gpio_number(&self) -> i32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
host
and device
methods seem to be removed now? Or did I misread? From my POV they were there so that in case you want to use the Rust SPI driver for the most part, but then just need "this little thing" to be done with an unsafe call to the actual esp-idf-sys
raw bindings - you could still do this. I see less of a need to do this for the CS pin, as the CS pin is passed from user code, so I would retire cs_gpio_number
and restore the other two methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
included again
@Vollbrecht I was hoping I can release the Btw, I would like to do a couple of renames once the "pool" problem is addressed, but that's the easy part, and can be done post-merge. |
yeah, got you. will try to get an update later today |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've left a ton of requests for minor changes in the form of renames for clarity.
Please go through those, as at one place, I noticed a buglet, and also - amongst the renames - there are a few requests to fix the 'd
lifetimes (getting rid of _p
at one place; replacing &'d ()
with &'d mut ()
as it should really be.
Once the renames and small fixes are in place, I think we should really merge.
@Dominaezzz - I really hope, that the SD card support can be modelled by something like an SpiSdCardMasterDriver
which takes a mutable Borrow
over SpiMasterDriver
.
src/spi.rs
Outdated
if let Some(delay) = self.pre_delay { | ||
Ets::delay_us(delay); | ||
} | ||
let result = device.transaction(f)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using ?
here is NOK. You have to keep the Result
without checking, then toggle the CS pin, and only then inspect the result with ?
.
Two additional options regarding the master/slave dichotomy:
Now that I think of it, I'm heavily leaning towards Option 2. It is just that most of the SPI/I2C use cases are targeting the master mode. So perhaps we can just skip "Master" from everywhere and assume it is "master" in that it does not have the "Slave" keyword? Last but not least, this is what ESP IDF does, in terms of naming conventions for SPI. So if we go with Option 2:
Oh - and by the way - for |
If it not clear, thus Btw, to be able to implement the |
src/spi.rs
Outdated
if let Some(delay) = self.pre_delay { | ||
Ets::delay_us(delay); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering the placement of these delays, couldn't the user just add this delay themselves in the transaction
function? I don't see why it has do be done by esp-idf-hal
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the point for this is, that if you are using an hardware library that expects an embedded_hal SpiDevice you just can give it this Device. The hardware driver manages the SpiDevice -> you have no way of hooking in the transaction yourself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question become here if one writes an hardware driver for x, should he assume that the embedded_hal device that he receive is an devices with managed cs or not. If he specify that he would like to have an managed cs device and it needed an delay between cs and clk than you would need this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The e-hal traits do not assume or specify in any way how the SPI protocol is implemented. So as long as it is behaving as expected - managed CS or not - user should not care. So I'm fine with these delays actually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardware driver manages the SpiDevice -> you have no way of hooking in the transaction yourself
The nice thing about the SpiDevice
trait is that it can be implemented by anyone. Which means anyone can simply make a wrapper to add this delay themselves and it's only a couple lines of code.
question become here if one writes an hardware driver for x, should he assume that the embedded_hal device that he receive is an devices with managed cs or not
Yes. The Spi traits explicitly state that SpiDevice
implementations take care of toggling the CS pin. If the driver wants to manage the CS pin then the SpiBus
trait should be used instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question become here if one writes an hardware driver for x, should he assume that the embedded_hal device that he receive is an devices with managed cs or not
Yes. The Spi traits explicitly state that
SpiDevice
implementations take care of toggling the CS pin. If the driver wants to manage the CS pin then theSpiBus
trait should be used instead.
This is not how I interpret the spec, sorry. SpiBus
is here for bitbanging and all sorts of tricks on top of the SPI protocol that - besides - also require an exclusive access to the bus.
With SpiSoftCsDeviceDriver
we are really only manually manipulating the CS signal and nothing else. And then again - given that from the user code POV, the behavior of SpiSoftCsDeviceDriver
is indistinguishable from SpiDeviceDriver
, then why not supporting it to the full extent?
I think removing the CS pin delays, yet keeping the "pool" (what is now the SpiSoftCsDeviceDriver
+ SpiSharedDeviceDriver
combo) is probably the worst. Either we provide "software" CS support, or we don't. But no in-between.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not how I interpret the spec, sorry. SpiBus is here for bitbanging and all sorts of tricks on top of the SPI protocol that - besides - also require an exclusive access to the bus.
bitbanging is one use case. SD cards is another. Display+Touch screen combo drivers is yet another.
https://github.com/rust-embedded/embedded-hal/blob/master/embedded-hal/src/spi.rs#L96
It's all about whether exclusive access is required and it just so happens that software CS required exclusive access. (Hence the locking we have here and in esp-idf).
I think we're on the same page here but using different words.
I think removing the CS pin delays, yet keeping the "pool" (what is now the
SpiSoftCsDeviceDriver
+SpiSharedDeviceDriver
combo) is probably the worst. Either we provide "software" CS support, or we don't. But no in-between.
Oh okay, I kinda see what you're getting at.
First I want to say, does software CS actually require delays? Or is this just a convenience thing?
I see it as a convenience which is why I don't like it but from what you're saying it sounds like it required?
Though, now that the CS pin has been made optional, I think "software" CS feature should be removed then 😅. It should be easy for users to DIY it now.
A mutex (Critical section) is used here but some users might actually want RefCell
. At the end of the day, increasing the device limit isn't about multithreading, it's about sharing, which Rust itself has multiple tools for.
So the code here is quite an opinionated feature.
@ivmarkov I got that part with the pub lock Fn. My problem is i already take it currently as an owned value. so i see no difference there. to not get confused, i need the generic because SpiDeviceDriver allows to get the SpiDriver by Borrow -> so if i want to safe it as i do it right now or if i save it in an UnsafeCell i still need to provide the generic right? |
Thanks to both of you for driving this to completion! |
Design Goals:
Current Implementation Details:
- The Driver itself stores all devices itself in an HashMap. This allows to reference all Devices by an id( currently the cs pin number as i32)- Pros: enables easy iteration over devices and a single device can be referred to with there id
- Cons:
- the type of the collection is of SpiSlave -> This approach dont allow different SpiDevice impl in a single collection
- the Driver still cant work with other Drivers that impl an SpiDevice
The Device id is an i32 that represent the cs pin number
Allowing more than 3 Devices per bus comes with the overhead of continuously keeping track of the registered bus_devices. To make it a bit more refined a VeqDeque is used to remove the oldest not used device from the bus. If a Device is used multiple times in a row it can still reuse the spi_device_handle given by rtos
don't support DMA
all possible Devices will be created when
SpiMasterDriver::new()
is called. no adding or removing devices after that.Current Usage Example:
Open Questions
Todo