-
Notifications
You must be signed in to change notification settings - Fork 69
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
Hardware flow control for serial interfaces #202
base: main
Are you sure you want to change the base?
Hardware flow control for serial interfaces #202
Conversation
Here is my attempt at adding support for hardware flow control in serial interfaces. |
Sounds like a nice first step. |
140cda4
to
b3c5d3d
Compare
I have a device (that's why I am interested in adding this feature) but I have a bit of a problem trying the new version of stm32f7xx-hal with it.
I am new to rust and this one looks bewildering. It appears to suggest that I need an // NOTE(unsafe) atomic write to stateless register
// NOTE(write_volatile) 8-bit write that's not possible through the svd2rust API
unsafe { ptr::write_volatile(&(*USART::ptr()).tdr as *const _ as *mut _, byte) }
Ok(()) |
I think I understand your point about needing RTS and CTS pins to be declared. After all, there may be another device configured that would also use those pins, which is not OK. I would also need to add lines similar to these: impl PinTx<USART1> for gpio::PA9<Alternate<7>> {}
impl PinTx<USART1> for gpio::PB6<Alternate<7>> {}
impl PinTx<USART2> for gpio::PA2<Alternate<7>> {}
impl PinTx<USART2> for gpio::PD5<Alternate<7>> {} I suppose I can look these up from stm32cubeide. However, I do not quite understand the meaning of numbers in /// Some alternate mode (type state)
pub struct Alternate<const A: u8, Otype = PushPull>(PhantomData<Otype>); But the meaning of |
Could you make a simple repro case? |
For reference, this builds fine on my side: https://github.com/maximeborges/stm32f7-demo-project |
Usually people are using some macro to define all possible combinations of pins- with or without CTS/RTS for example in this case. This is a bit tricky to implement if you don't have much experience, but could be a great way to learn macros also (that's basically how I started a few years ago). Some other implementations are using
What I do is look at the datasheet of the chips in the whole family. Usually they are mostly the same (emphasis on mostly), so I first implement it for the one I'm using in my project, do some basic tests there, then quickly check the other chips if there is some variations.
|
The "assigning to (I see this error in the continuous integration test on the PR that I just submitted, as well -- but only in the nightly run.) |
I've figured out more about the new error. The code (at least in one place) is trying to write a byte to the transmit data register on the usart peripheral. It's trying to get the compiler to emit a byte sized write operation because that's atomic, and the rest of the register needs to be preserved (the bits are all defined as reserved in the reference manual). But the svd2rust generated code only allows 32-but-wide read/modify/write sequences. And the pointer to the RegisterBlock is const, so doing an offset and making it mutable is still undefined behavior. It may work to use inline assembly here, or add an API to svd2rust, or find another way to generate the address of the tdr register. I don't think an UnsafeCell / RefCell will help (like the error message says) because the different calls to the ptr() associated fn will get different ref cells, and won't share the ref count. It would be nice if the bits() method from svd2rust could be made safe for a case like this where the bit field covers 8 or 16 bits, and have the generated code use an 8 or 16 bit write to memory... |
b3c5d3d
to
b865df4
Compare
I see how to add CTS and RTS pins and was about to wire them in somehow but after a bit of thinking about it I am not sure which approach is nice. An USART/UART, depending on its capabilities, can run in one of several modes. Some of those modes do not use anything other than TX and RX. Some use CTS or RTS or both. There is also an RS485 mode which uses RTS pin but calls it "DE". Some UARTs can do IrDA or SmartCard protocol. The first thought was to add an enum UARTMode which would select between the modes, but not every mode is supported by every UART. The second though was that each mode can be a trait and it may be possible to assign those traits to different UARTs. That would ensure that the correct modes of operation with the correct pins correspond to appropriate UARTs. However, at some point the mode needs to be used to configure the UART and that has to be generic. Traits are not classes, however. So I cannot declare a generic UARTMode with a method "enable" and have traits which implement it (with their specific implementations of "enable"). Can I? Or perhaps I can have an enum UARTMode parametrised by UART instance and somehow link mode traits to its values so that those values could exist only for some of UARTs? |
I think the way the L4 HAL is doing it might be the way to go. This means that one would have to re-configure the UART driver with the right pins to change the mode, but I think that would be the only "safe" way of doing it without messing with the borrow checker. |
Oh also, the implementation from the esp32-hal could be a nice source of inspiration: https://github.com/esp-rs/esp-hal/blob/main/esp-hal-common/src/uart.rs |
I think it would be great to get it done properly. The current version of the change is rather limiting. There is an idea to introduce traits which correspond to different capabilities of UARTs and declare which UARTs implement which traits. The traits would then provide customised constructors as a replacement for the current and generic "new". Meanwhile, there is something I do not quite understand. The specification for stm32f7xx devices says that the same UART can be used with multiple TX and RX pins. The code matches that. For example, UART4 can use PA0 and PC10 pins as TX, and PA1 and PC11 pins as RX. When I look in STM32 IDE, I can configure only PA1 for RX and PA0 for TX. There is no PC10 and PC11 in sight. So frankly, I do not understand how this is supposed to work. But I do suspect that you cannot, for example, configure UART4 with PA1 for RX and PC10 for TX. It can probably work only for specific pairs: PA0+PA1 and PC10+PC11. The current code, the way I am reading it, would allow to construct UART4 with any combinations of RX and TX pins. |
This is a draft.
I've tried a few things but so far it does not work the way I wanted it to. The general idea was the following:
However, type inference does not work well (no idea why) and the API becomes slightly confusing.
This is not clear to me. Surely it should be able to infer the type as the call passes the pins into the method: let serial = Serial::new_async_uart_no_flwctl(
p.USART1,
(tx, rx),
&clocks,
serial::Config {
// Default to 115_200 bauds
..Default::default()
},
); Also, it is not obvious how to properly borrow CTS and RTS pins in case they were provided. Finally, the idea that any combinations of RX, TX, CTS, and RTS pins are valid bothers me a bit. It feels like only explicitly vetted combinations should be allowed. |
I have added a bit more code. For example, there it a way to configure an UART in IrDA mode. Please let me know what you think of the approach. Is it horrible or not? For the time being, I have not updated most of the examples. The only one I touched was serial_echo. |
Which MCU were you using when looking for the pins in stm32ide? The datasheet for the stm32f767 seems to say that both pins are available for the peripheral, and from experience, it should just work as expected. We could expect one or two reference of the family to NOT match that, but I didn't dive into that yet.
As far as I remember, pins are working pretty independent of each others when there is multiple combinations possible like here.
The usual way of dealing with that is to add the traits to use stm32f7xx_hal::prelude::*; |
Did you have a look at the STM32L1 implementation? |
I have stm32f722ze. Finally I see how it works. In STM32 CubeIDE when I enable UART4 the pins are shown as PA0+PA1. There is no obvious way to change it there. However, if I click on PC10 pin then I can assign it to be UART4_TX. In that case I end up with UART4 configured as PC10+PA1. |
The implementation in STM32L1 looks interesting. I think I understand most of it but I am not sure the solution is optimal either. Here are my concerns:
No solution that comes to my mind feels satisfactory so far. The existing one is flexible and provides a bit of rigor, but fails to do what's right with borrowing. Also, the amount of code is non-trivial. |
By the way, I have added this as in-code comments, but should say it in here too: it seems UART8 was missing from the original code. Shall I add it? |
Sure. Can you put it in another PR? |
Also, in my already rather large PR there is a controversial change: I have renamed template parameters USART to just U in several places. This was to free up USART for the name of a trait but also to avoid a perception that it has to be specifically USART rather than some kind of UART. Are you OK with that or not really? |
Yes all good for me. It's just a generic argument name so nothing critical or API-breaking. |
I agree that it's not optimal; also F7 seems to support more modes. I guess that's also why nobody really tried to implement that properly before, there is a lot of stuff to be implemented ^^'
I kinda like the approach from the
About this approach, we don't have to be limited that by just 3
I think we could split the UART trait into UART and USART, so we could implement them only on relevant peripherals, i.e. : pub trait UART<U: Instance, PINS: Pins<U>>;
impl <PINS: Pins<UART4>> UART<UART4, PINS> for Serial<UART4, PINS> {}
impl <PINS: Pins<UART5>> UART<UART5, PINS> for Serial<UART5, PINS> {}
impl <PINS: Pins<UART7>> UART<UART7, PINS> for Serial<UART7, PINS> {}
impl <PINS: Pins<USART1>> UART<USART1, PINS> for Serial<USART1, PINS> {}
impl <PINS: Pins<USART2>> UART<USART2, PINS> for Serial<USART2, PINS> {}
impl <PINS: Pins<USART3>> UART<USART3, PINS> for Serial<USART3, PINS> {}
impl <PINS: Pins<USART6>> UART<USART6, PINS> for Serial<USART6, PINS> {} becomes: pub trait UART<U: Instance, PINS: Pins<U>>;
pub trait USART<U: Instance, PINS: Pins<U>>: UART<U, PINS>;
impl <PINS: Pins<UART4>> UART<UART4, PINS> for Serial<UART4, PINS> {}
impl <PINS: Pins<UART5>> UART<UART5, PINS> for Serial<UART5, PINS> {}
impl <PINS: Pins<UART7>> UART<UART7, PINS> for Serial<UART7, PINS> {}
impl <PINS: Pins<USART1>> UART<USART1, PINS> for Serial<USART1, PINS> {}
impl <PINS: Pins<USART2>> UART<USART2, PINS> for Serial<USART2, PINS> {}
impl <PINS: Pins<USART3>> UART<USART3, PINS> for Serial<USART3, PINS> {}
impl <PINS: Pins<USART6>> UART<USART6, PINS> for Serial<USART6, PINS> {}
impl <PINS: Pins<USART1>> USART<USART1, PINS> for Serial<USART1, PINS> {}
impl <PINS: Pins<USART2>> USART<USART2, PINS> for Serial<USART2, PINS> {}
impl <PINS: Pins<USART3>> USART<USART3, PINS> for Serial<USART3, PINS> {}
impl <PINS: Pins<USART6>> USART<USART6, PINS> for Serial<USART6, PINS> {} (not sure if the supertrait would actually be useful in
Since this peripheral is more complex than most of the other UART implementation I've seen, it's a bit hard to imagine what would be the ideal way of settings things up. I don't have much experience with anything other than basic RX/TX and some RTC/CTS. |
usart.cr1.write(|w| w.deat().bits(0)); // setting the assertion time | ||
usart.cr1.write(|w| w.dedt().bits(0)); // setting the de-assertion time |
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.
Sequence of write
operations of same register looks strange. It rewrites whole register with reset value + modified bits.
Should there be modify
? Or a single write
operation?
There several such places.
No description provided.