Skip to content

Polling UART transmission of a buffer #96187

@sjlongland

Description

@sjlongland

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:

  1. drop nRE / raise DE
  2. wait for assertion/deassertion period of RS-485 transceiver
  3. transmit the character
  4. wait a bit to ensure we don't chop the character off
  5. drop DE / raise nRE
  6. 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

Metadata

Metadata

Assignees

Labels

EnhancementChanges/Updates/Additions to existing featuresarea: UARTUniversal Asynchronous Receiver-Transmitter

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions