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
UART: setting baudrate, stopbits, and parity on runtime #5899
Conversation
baudrate, stopbits, and parity (only implemented cc2538)
I don't like this: a) baudrate configuration: There are not many use cases, where one would like to re-configure this settings often. If this is needed, one can always re-call |
I have a specific use case for this API extension: a Modbus RTU master with a RS-485 transceiver. I need different parity setting (depending on the slave devices) and the flush function is essential too. Because the bus works in half-duplex mode, I need to change the transceiver direction manually (using a GPIO pin). For this, I need to guarantee, that the last byte has been sent to the bus before switching the direction. I agree, that the baudrate configuration is a code duplication; my intention was to separate the baudrate reconfiguration from dangerous actions (setting the callback pointer) and other initialization stuff. Implementing this extension only in cpu/board specific code would not allow writing portable applications needing this functions (e.g. my Modbus master). |
I have implemented something alike for a RS-485/Modbus RTU project as well, so I understand the requirement, but I'm reluctant on triggering a full API change... I would separate these features into a separate Please separate the flash implementation from the UART proposed changes, it would be great to have in a different PR 😄 |
I think, implementing this as an extended API within a seperate module would be the best solution (e.g. So, I will change my code (and remove the flash code; sorry, was the wrong branch...). |
drivers/include/periph/uart.h
Outdated
uart_parity_none = 0, | ||
uart_parity_even = 1, | ||
uart_parity_odd = 2, | ||
} uart_parity; |
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.
Please suffix typedef'ed typenames with _t
@geith: Good to know your use case! In general I completely agree, that we should supply this functionality independent of a platform. To clarify:
As a solution, I suggest we do the following:
typedef enum {
UART_NONE,
UART_ODD,
UART_EVEN
} uart_parity_t;
int uart_mode(uint8_t databits, uint8_t stopbits, uart_parity_t parity); Now this allows for (runtime) re-configuration of the parameters of interest, while still keeping the interface slim. Also, existing drivers do not need to be touched. What do you think? |
@haukepetersen: Your suggested API function would be okay for me, but in my opinion, the The function This behavior is absolutely okay for most use cases, but I need to synchronize my program with the moment when the TX buffer is empty again; if I would switch the transceiver direction immediately after So, would you be okay with an API extension following your suggestion plus the |
The question is, which constraints we want to put on the Thinking about it now, I think you are right and a flush function would make sense. I think I would prefer a name as |
I agree, from the performance perstpective, |
drivers/include/periph/uart.h
Outdated
@@ -94,6 +94,17 @@ typedef unsigned int uart_t; | |||
/** @} */ | |||
|
|||
/** | |||
* @brief parity mode enum |
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.
maybe something like Available parity modes
?
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.
Just Parity modes
? The availability depends on hardware and driver implementation.
drivers/include/periph/uart.h
Outdated
@@ -163,6 +174,30 @@ void uart_poweron(uart_t uart); | |||
*/ | |||
void uart_poweroff(uart_t uart); | |||
|
|||
/** | |||
* @brief set UART mode |
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.
s/@brief set UART mode/@brief Confiure UART mode/
drivers/include/periph/uart.h
Outdated
uint8_t stopbits, uart_parity_t parity); | ||
|
||
/** | ||
* @brief flush UART TX buffer |
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.
drivers/include/periph/uart.h
Outdated
* @return -2 on inapplicable parity | ||
*/ | ||
int uart_mode(uart_t uart, uint8_t databits, | ||
uint8_t stopbits, uart_parity_t parity); |
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.
indention
drivers/include/periph/uart.h
Outdated
/** | ||
* @brief flush UART TX buffer | ||
* | ||
* wait until UART TX buffer is empty |
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.
maybe better: 'Block until TX buffer is empty and all bytes have been shifted out.'
Could you also add some more detailed documentation for:
Thanks! |
Yes, if you want just this PR dealt with I can work it into the short term schedule. Otherwise, I would like to propose an update to the uart API at some point (maybe next month) if this can wait until then. |
Agreed, makes sense: let's wait for this. |
This PR is surely still very valid. As quick recap on the state of discussion (might be subjective), we are discussing two separate issues:
Regarding 1.An agreement for the need of this functionality is reached, so it is rather the question on 'how' to implement it. Thinking about it, I propose the following code: typedef enum {
UART_8N1,
UART_8E1,
UART_8O1,
UART_8N2,
...
} uart_mode_t;
int uart_mode(uart_t uart, uart_mode_t mode); Of course following the existing concept of allowing CPU implementation to override that type definition. This would enable for significantly more efficient UART driver implementations (as register values can be encoded directly in the enum value -> less ugly swich-case blocks). Now when implementing this new functionality for existing uart drivers, I propose the approach similar to the one taken in #9845, using a temporary feature (e.g. On top, when we touch existing implementations, it would be IMHO be further beneficial to add a static Regarding 2Personally, I am still in favor of a synchronous For the naming, I personally would simply call that function So here is my proposal for the next steps:
|
@haukepetersen |
rather 3 stop bit modes (0-2) -> so it would be 180 modes altogether. Probably some less, as certain combinations (e.g. 9 data bytes + parity) are not possible, right?! So I guess my idea was not so great after all... I am trying to think of something that could still enable us to efficiently code the mode() function. How about something in the direction of: typedef enum {
UART_DATABITS_3,
UART_DATABITS_4,
...
UART_DATABITS_9,
} uart_databits_t;
typedef enum {
UART_PARITY_NONE,
...
} uart_parity_t;
typedef enum {
UART_STOPBITS_0,
...
} uart_stopbits_t;
int uart_mode(uart_t dev, uart_databits_t databits, uart_parity_t parity, uart_stopbits_t stopbits); This would still enable us to encode CPU specific register contents into the enum values (allowing us to apply them directly), while circumventing the 'state explosion' in the enum definitions. Alternatively even we could even do something like // condition when defining values for the 3 enums:
// UART_DATABITS_8 := must be 0 for all platforms
// UART_PARITY_NONE := must be 0 for all platforms
// UART_STOPBITS_1 := must be 0 for all platforms
int uart_mode(uart_t dev, unsigned mode);
// usage:
uart_mode(dev, 0); //-> 8N1
uart_mode(dev, (UART_DATABITS_5 | UART_PARITY_EVEN)); // -> 5E1 |
I don't know if here is the place to start the discussion but I would throw in typedef enum {
UART_DATABITS_8,
UART_DATABITS_5,
UART_DATABITS_6,
UART_DATABITS_7,
UART_DATABITS_9
} uart_databits_t;
typedef enum {
UART_PARITY_NONE,
UART_PARITY_EVEN,
UART_PARITY_ODD,
} uart_parity_t;
typedef enum {
UART_STOPBITS_0,
UART_STOPBITS_1,
UART_STOPBITS_2,
} uart_stopbits_t;
typedef struct {
uart_databits_t databits: 3;
uart_parity_t parity: 2;
uart_stopbits_t stop: 2;
} uart_config; On that not why not put all args in a struct (so baud, dev, and callback too)? Makes saving the config state easy. I think that is what the STMCUBE HIL tries to do. We can also have nice packing with the bitfields and type safety with the enums. If we make all default values zero then if users don't want non-standard settings they don't need to change the values. Maybe we even have a #ifdef macro for extended API vs basic one so code size doesn't go crazy. ...Maybe a bit much. |
|
@MrKevinWeiss that does unfortunately not work, as it would take away the possibility of encoding register bit values in the enums. Let me elaborate in a small example: take the So we can simply do // in the nrf52/include/periph_cpu.h
#define HAVE_UART_PARITY_T
typedef enum {
UART_PARITY_NONE = 0x00,
UART_PARITY_EVEN = (0x07 << 1),
UART_PARITY_X = 0x20, // something that lets us read this as not-supported value
...
} uart_parity_t;
// in the uart driver
int uart_mode(..., uart_partity_t parity, ...)
{
/* check if given parity value is actually supported */
if (partiy & 0xe0) {
return UART_NOMODE;
}
...
NRF_UART->CONFIG = parity; // no need for shifting, masking, switch-case, etc...
...
} Now as the actual used values for the enum members are highly dependent on the actual CPU, one can not make any assumptions on their structure, so joining them will not work...
Major reason: efficiency. Minor: consistency. I have been playing with that approach quite a bit when initially designing the periph interfaces. But that approach proved to be quite inefficient in terms of memory usage and runtime overhead. Just look at the code size overhead of the STM cube library... At least back then, you could easily add ~5K ROM to any standard RIOT example when using that library. |
So would the enums be declared in each cpu or in the API (ie. drivers/include/periph/uart.h)? |
Heh true the STM cube is big and heavy.
You could make the bitfields big enough to fit the encodings or just not use a bitfield and it would be the same as the enum. I guess was thinking to add the enums to switches (or if's) only if they don't fit into a exploitable pattern but if the majority will have to be "mapped" then I can see how it isn't worth it. Should we find a proper place to have this discussion? |
it is worth it :-) On top, this concept is already heavily used throughout the peripheral drivers, and we should be consistent (which is and has always been an important point in the periph interface's design).
I think this is the proper place, isn't it? |
There would be a generic definition in Take as example the |
Ok, thanks for the info, that clarifies the enum choice. What about the passing several parameters by multiple arguments vs structs? |
Quote "Heh true the STM cube is big and heavy." The two reasons I don't like it are (i) it is inefficient in both memory and runtime overheads, and (ii) we should stay consistent with the rest of the periph APIs |
maybe to give some quick example to underline this: // say we have the parity that we need to change programatically, the rest is constant
uart_mode_cfg_t cfg = { .databits = UART_DATABITS8, .stopbits = UART_STOPBITS_2, .parity = some_parity };
uart_mode(dev, &cfg);
// vs
uart_mode(dev, UART_DATABITS8, some_parity, UART_STOPBITS_2); To the best of my knowledge (so when I tried it last), the compiler does not optimize both approaches the same way, so that the first approach takes more instructions and also more stack space. |
@haukepetersen I've cooked a patch defining enums for parity, databits etc. both in
What do you think about it? |
Don't like it, I think they should stay in the board/CPU specific code path and be configured statically.
I only partly understand this question. Which parameters do you mean exactly? Concerning the default parameters: from my perspective |
@haukepetersen I was talking about 8n1 params only, not the baudrate. But I see your point and will change my upcoming PR accordingly. |
I suppose all params can be either added statically in the periph_config or with an extra function (uart_mode). Can anyone think of practical reasons to need any of the parameters runtime configurable? Moving the setting to a seperate function does help minimal code size if it isn't used. If it had to be implemented in the cpu during init it would always be included. |
Hmm I suppose any type of controller like plc's, I have worked on industrial products that had some parity and baud parameters configurable runtime. I think 👍 to the uart_mode added to the api. |
Well now that #10743 is merged maybe we can update the cc2538 to fit the uart_mode api! |
@geith If you are not planning to update I can take it over? |
@MrKevinWeiss I'm currently not active on RIOT (have other priorities); so you are welcome to take over that part. |
Taken over with #11503. Thanks! |
added UART functions for additional runtime configuration an TX buffer flushing (only implemented for cc2538)