-
Notifications
You must be signed in to change notification settings - Fork 35
RFC: use Rust edition 2018 and add timer proxy #5
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
Switch to Rust Edition 2018. Update major dependencies including embedded-hal, embedded-hal-mock, cortex-m. Signed-off-by: Sergey Matyukevich <[email protected]>
Signed-off-by: Sergey Matyukevich <[email protected]>
Hi Sergey, thanks for your PR! The first patch looks great, I'd like to merge that independently of the second one. With the timer proxy, I am not sure if that is a good idea. Consider two drivers independently setting up the count-down in parallel and then waiting on it. This will lead to the
What is your exact use-case for the timer-proxy? I believe we have (again) hit the deeper lying problem, of which
The problem is unique to Embedded Rust in a way because we enforce these strict ownership rules. In C, sharing was just done without much thinking, which lead to a great number of bugs. A common "solution" is to just add a mutex for the peripheral in question and hope this is enough. For a simple bus it is, which is why this approach works for Please don't misunderstand: I definitely see the need for sharing a timer peripheral between multiple tasks / drivers. I see two solutions for this: A) Global MutexStuffing the entire timer into a mutex, without a proxy. This would ensure each user locks the entire timer for the duration where it needs the peripheral. It would definitely be a 100% safe implementation but has the huge drawback that there cannot be parallel count-downs from multiple users at the same time. B)
|
Hi @Rahix In brief, my primary use-case for shared timer is to use it for clock generation in bitbang-hal. Example (single shared timer for bitbang i2c and spi) can be found here Regards, |
Hi @Rahix
Regards, |
Hi Sergey,
Please do!
I don't think so. With shared peripherals you have to think in a preemptive context: At any time an interrupt might be triggered and modify the peripheral before returning execution in your routines. This is perfectly safe with the abstraction Take another look at the diagram I drew. Both tasks call This kind of race-condition is what Rust is trying to prevent with its ownership system so Looking at I think you are trying to use fn task_foo() {
let timer = SharedTimer::new(...);
let mut serial = Serial::new(..., timer);
let mut i2c = I2c::new(..., timer);
/* Now use the serial port for some time, but **NOT** the i2c */
timer.start(115200.hz());
writeln!(&mut serial, "Hello!\r\n");
/* Now use the i2c bus for some time */
timer.start(400.khz())
i2c.write(0x3A, &[0xDE, 0xAD, 0xBE, 0xEF]);
} In this example, there are no race-conditions possible, but Rust can't know that. IMO this is a shortcoming of the definition of the fn task_foo() {
let timer = SharedTimer::new(...);
let mut serial = Serial::new(..., timer);
let mut i2c = I2c::new(..., timer);
timer.start(400.khz())
i2c.write(0x3A, &[0xDE, 0xAD, 0xBE, 0xEF]);
// Forgot to add the t.start() here, oops
// timer.start(115200.hz());
// Now the serial is sending way too fast and the other end won't be able
// to decode the data
writeln!(&mut serial, "Hello!\r\n");
} Instead, in a safe implementation it would probably look more like this: let timer = ...; /* No SharedTimer this time */
let serial = Serial::new(..., 115200.hz());
let i2c = I2c::new(..., 400.khz());
/* Acquire the timer for serial for some time: */
let mut serial = serial.take_timer(timer);
/* Timer is now moved into the serial driver,
Rust will prevent access from anyone else */
writeln!(&mut serial, "Hello!\r\n");
/* Get back the timer. After this, the serial can no longer be written to */
let (serial, timer) = serial.release_timer();
/* Move timer into i2c driver, to allow accessing the bus */
let mut i2c = i2c.take_timer(timer);
i2c.write(0x3A, &[0xDE, 0xAD, 0xBE, 0xEF]);
/* Stop using i2c */
let (i2c, timer) = i2c.release_timer(); This approach is called the type-state pattern. I have linked it to a more detailed example. |
Hi @Rahix Ok, I see the point now. Suggested timer proxy indeed can not be used to clock In my particular case I have a single hardware timer suitable for clocking multiple P.S. Regards, |
The idea with shared access to hardware timer using shared-bus proxy did not work well enough. See the following PR for details: Rahix/shared-bus#5 So for now use TIM2 for display and TIM3 for accelerometer. However this will have to be changed since TIM3 is needed for brush motors PWM. Signed-off-by: Sergey Matyukevich <[email protected]>
Hi @Rahix
Here is a PR that includes two patches:
Regards,
Sergey