-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Description
Summary
In a project at my workplace, I'm integrating a RS-485 serial interface for use with multiple communications protocols. Since the device in question also lacks any other kind of interface, this same port doubles as the serial console. The hardware (nRF52840) has no native support for RS-485, it must be managed using GPIO lines. In my limited experience, very few devices on the market offer such support out-of-the-box.
At this time, Zephyr provides no means for interfacing to RS-485, and so I'm approaching this by writing a "wrapper" driver that sits on the UART interface "bus" in DeviceTree and exposes a UART interface for the application to use.
While interrupt-driven and asynchronous drivers exist, writing drivers for this interface is not trivial as the asynchronous I/O has to be co-ordinated with the control of nRE
and DE
lines on the RS-485 transceiver. Polling-mode transmissions are the easiest to get going in this context, but the standard interface is character-by-character, so to send, say "hello world"
, my code has to:
- drop
nRE
/ raiseDE
- wait for assertion/deassertion period of RS-485 transceiver
- transmit the character
- wait a bit to ensure we don't chop the character off
- drop
DE
/ raisenRE
- wait for assertion/deassertion period of RS-485 transciever
… for each character in the string.
This is very inefficient. The added delays may also break some time-sensitive protocols (e.g. Modbus/RTU). While interrupt driven I/O or DMA-driven I/O is undoubtedly a major step in the right direction, it also must be co-ordinated with control of the transceiver IC which is difficult to get right.
Describe the solution you'd like
A small expansion of the UART driver interface would allow for "burst" functions that send a stream of characters:
__syscall void uart_poll_burst_out(const struct device *dev,
const unsigned char *out_chars, int size);
__syscall void uart_poll_burst_out_u16(const struct device *dev,
const uint16_t *out_u16, int size);
This would be carried forward to the driver API:
/** @brief Driver API structure. */
__subsystem struct uart_driver_api {
/** Console I/O function */
int (*poll_in)(const struct device *dev, unsigned char *p_char);
void (*poll_out)(const struct device *dev, unsigned char out_char);
void (*poll_burst_out)(const struct device *dev, const unsigned char *out_chars, int size);
#ifdef CONFIG_UART_WIDE_DATA
int (*poll_in_u16)(const struct device *dev, uint16_t *p_u16);
void (*poll_out_u16)(const struct device *dev, uint16_t out_u16);
void (*poll_burst_out_u16)(const struct device *dev, const uint16_t *out_u16, int size);
#endif
}
A trivial wrapper can be inserted to "fill in" the missing function, so that drivers can supply either poll_out
or poll_burst_out
(and same for wide version):
static inline void z_impl_uart_poll_out(const struct device *dev, unsigned char out_char)
{
const struct uart_driver_api *api = (const struct uart_driver_api *)dev->api;
if (api->poll_out) {
api->poll_out(dev, out_char);
} else {
api->poll_burst_out(dev, &out_char, 1);
}
}
static inline void z_impl_uart_poll_burst_out(const struct device *dev, const unsigned char *out_chars, int size)
{
const struct uart_driver_api *api = (const struct uart_driver_api *)dev->api;
if (api->poll_burst_out) {
api->poll_burst_out(dev, out_chars, size);
} else {
while (size--) {
api->poll_out(dev, *out_chars++);
}
}
}
A driver author can choose to implement both if they choose, but this is unnecessary, since the missing function can be implemented in terms of its counterpart.
In the RS-485 example, it is then possible to write a "burst" function that raises DE
, transmits all the packet in its entirety, then lowers DE
before returning, doing so in a manner that is transparent to the calling application.
Alternatives
Right now, trying to get interrupt and async interfaces to work, but it's taking quite a bit to wrap my head around how these interfaces work from a driver-writing perspective.
A prior product I've written code for uses a hybrid approach: interrupt-driven receive and polling transmit, and this has served us well in production for over 5 years. "KISS" principle is often "good enough" for many applications.
Additional Context
No response