Skip to content

kinetis/serial: Enable RTS as GPIO for RS485CONTROL#18417

Merged
xiaoxiang781216 merged 4 commits intoapache:masterfrom
ghnotgood:master
Mar 27, 2026
Merged

kinetis/serial: Enable RTS as GPIO for RS485CONTROL#18417
xiaoxiang781216 merged 4 commits intoapache:masterfrom
ghnotgood:master

Conversation

@ghnotgood
Copy link
Contributor

@ghnotgood ghnotgood commented Feb 20, 2026

When using CONFIG_UART?_RS485CONTROL, RTS pin is used as a transmit enable pin, i.e., it is set high when sending data and low otherwise. PIN_UART?_RTS is defined in the board.h file as the appropriate ALT functionality of the chip's port.

However, it may happen that PIN_UART?_RTS is wired to another pin of the chip that does not support the RTS as ALT functionality of the UART? in question. This commit addresses such a situation.

When UART?_RS485CONTROL_RTSISGPIO is set in menuconfig for the given UART?, it is expected that the PIN_UART?_RTS is defined as GPIO_OUTPUT, and the PIN_UART?_RTS is set high when sending data and low otherwise.

Summary

When working on NuttX port for our board, I found out that RTS of our UARTs 1, 2, and 3 are wired to Kinetis K60's pins that do not support RTS as an alternate functionality of the given UARTs. It looks like that in the original system of our board, RTSs were wired to GPIOs that were set to high when sending data and low otherwise. This is not considered in NuttX -- it is expected that only pins that support ALT functionality are used.

The commit in this PR allows any chip's pin that is defined as PIN_UART?_RTS and configured as GPIO_OUTPUT to work as RTS for the RS485CONTROL.

Impact

  • There is new UART configuration option UART?_RS485CONTROL_RTSISGPIO for each ? in 0 to 5. Each option depends on the appropriate UART?_RS485CONTROL.

  • The up_send procedure of the kinetis_serial.c is changed. If the configuration option is enabled for the given UART, the appropriate GPIO is set high before up_serialout and set low after 150 us delay when up_serialout returned.

Testing

On our board I work on, I configured CONFIG_UART1_RS485CONTROL_RTSISGPIO=y and CONFIG_UART2_RS485CONTROL_RTSISGPIO=y. UART0 is mapped to /dev/ttyS1 and UART1 is mapped to /dev/ttyS2. Then, I run the following test code:

static const char *devusart1 = "/dev/ttyS1";
static const char *devusart2 = "/dev/ttyS2";

int main(int argc, char **argv)
{
        _info("test from %s to %s", devusart1, devusart2);

        int from = open(devusart1, O_RDWR);
        if (0 > from) {
                _err("ERROR: failed to open %s: %s", devusart1, strerror(errno));
                return 1;
        } else {
                _info("from is %d", from);
        }   

        int to = open(devusart2, O_RDWR);
        if (0 > to) {
                _err("ERROR: failed to open %s: %s", devusart2, strerror(errno));
                return 1;
        } else {
                _info("to is %d", to);
        }   

        int i, r;
        uint8_t sb, rb; 

        for (i = 0; i < 260; i++) {
                sb = i;

                r = write(from, &sb, 1); 

                _info("(%d.) wrote %d byte(s): %d", i, r, sb);
                _info("now, try to read it");

                r = read(to, &rb, 1); 

                _info("read %d byte(s): %d", r, rb);

                rb++;

                _info("incremented what was read, sending back");

                r = write(to, &rb, 1); 

                _info("back %d byte(s): %d", r, rb);
                _info("now, try to back-read it");

                r = read(from, &sb, 1); 

                _info("back-read %d byte(s): %d", r, sb);

                if (1 + i == sb && 1 + i == rb) {
                        _info("GOOD %d -> %d == %d", i, sb, rb);
                } else {
                        _info("BAAD %d -> %d == %d", i, sb, rb);
                }   
        }   

        close(from);
        close(to);
        return 0;
}

and checked that the program finishes and the printed output makes sense.

@github-actions github-actions bot added Arch: arm Issues related to ARM (32-bit) architecture Size: M The size of the change in this PR is medium labels Feb 20, 2026
acassis
acassis previously approved these changes Feb 20, 2026
@cederom
Copy link
Contributor

cederom commented Feb 21, 2026

Thank you @ghnotgood :-)

  • Can you please provide runtime build and test logs? Build log can be short build invocation and summary to show that firmware builds fine. Runtime log should contain commands and their results from boot, uname -a, and your testing application run and execution.
  • So you are manually asserting the RTS pin before and deasserting after sending data. Are you sure that receive mode is available right after boot? Do you deassert RTS pin (low) by the custom board init or this is done by up_setup()?

@ghnotgood
Copy link
Contributor Author

So we discussed with @michallenc and collegues offline. (Thanks for the discussion!) The problem with up_udelay(150) is that 150 us works for a single character on 115200 and that a delay in an interrupt handler is not good.

However, an oscilloscope shows that UART_S1_TC does not trigger deterministically and I was not able to find out how to generate interrupt after the sending of the bits is done.

The idea behind this PR is to set an GPIO to 1 when sending (up_send procedure) and set the GPIO to 0 when finished. I have explored the following potential solutions for setting GPIO to 0:

  • up_udelay in the handler is not good
  • spawn LPWORK thread with up_udelay could work, but some HPWORK may delay it
  • spawn HPWORK thread with up_udelay sets GPIO to 0 before the bits are actually sent
  • spawn HPWORK thread with up_udelay after 1 tick delay sets GPIO to 0 after 12 ms because it depends on USEC_PER_TICK
  • the same as above for HPWORK with nxsched_usleep

The current idea is that I will try to use Kinetis' timers (FTM or PIT or maybe LPTMR), but I have no experience with that and I am afraid that the lower-half drivers are not implemented. If anyone has any idea, I am happy for suggestions.

@cederom I will include the required logs next time, sorry.

So you are manually asserting the RTS pin before and deasserting after sending data. Are you sure that receive mode is available right after boot?

This PR applies only for CONFIG_UART?_RS485CONTROL, where RTS is used as RS-485 transmit enable. It should not affect receiving.

Do you deassert RTS pin (low) by the custom board init or this is done by up_setup()?

I expect something like the following in the include/board.h:

#define PIN_UART0_RTS (GPIO_OUTPUT | PIN_PORTC | PIN18)

which is implicilty the same as:

#define PIN_UART0_RTS (GPIO_OUTPUT | GPIO_OUTPUT_ZERO | PIN_PORTC | PIN18)

but that is a responsibility of a developer, I think.

When using CONFIG_UART?_RS485CONTROL, RTS pin is set high when sending
data and low otherwise. PIN_UART?_RTS is defined in the board.h file as
the appropriate ALT functionality of the chip's port.

However, it may happen that PIN_UART?_RTS is wired to another pin of the
chip that does not support the RTS as ALT functionality of the UART?  in
question. This commit addresses such a situation.

When UART?_RS485CONTROL_RTSISGPIO is set in menuconfig for the given
UART?, it is expected that the PIN_UART?_RTS is defined as GPIO_OUTPUT,
and the PIN_UART?_RTS is set high when sending data and low otherwise.

Signed-off-by: Jiri Vlasak <jvlasak@elektroline.cz>
@ghnotgood
Copy link
Contributor Author

I have reworked this MR to avoid delays, I am sorry for delay.

The build log is:

$ make
CC:  iob/iob_update_pktlen.c iob/iob_update_pktlen.c: In function 'iob_update_pktlen':
iob/iob_update_pktlen.c:128:27: warning: 'penultimate' may be used uninitialized [-Wmaybe-uninitialized]
  128 |       while (next != NULL && nrequire > ninqueue)
      |              ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
iob/iob_update_pktlen.c:52:21: note: 'penultimate' was declared here
   52 |   FAR struct iob_s *penultimate;
      |                     ^~~~~~~~~~~
CC:  chip/kinetis_serial.c chip/kinetis_serial.c: In function 'up_rxint':
chip/kinetis_serial.c:1832:4: warning: #warning "Revisit:  How are errors enabled?" [-Wcpp]
 1832 | #  warning "Revisit:  How are errors enabled?"
      |    ^~~~~~~
CC:  chip/kinetis_sdhc.c In file included from chip/kinetis_sdhc.c:34:
chip/kinetis_sdhc.c: In function 'kinetis_blocksetup':
chip/kinetis_sdhc.c:1906:10: warning: format '%ld' expects argument of type 'long int', but argument 3 has type 'unsigned int' [-Wformat=]
 1906 |   mcinfo("blocksize=%ld, total transfer=%ld (%ld blocks)\n", blocksize,
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ~~~~~~~~~
      |                                                              |
      |                                                              unsigned int
chip/kinetis_sdhc.c:1906:23: note: format string is defined here
 1906 |   mcinfo("blocksize=%ld, total transfer=%ld (%ld blocks)\n", blocksize,
      |                     ~~^
      |                       |
      |                       long int
      |                     %d
chip/kinetis_sdhc.c:1906:10: warning: format '%ld' expects argument of type 'long int', but argument 4 has type 'unsigned int' [-Wformat=]
 1906 |   mcinfo("blocksize=%ld, total transfer=%ld (%ld blocks)\n", blocksize,
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1907 |          blocksize * nblocks, nblocks);
      |          ~~~~~~~~~~~~~~~~~~~
      |                    |
      |                    unsigned int
chip/kinetis_sdhc.c:1906:43: note: format string is defined here
 1906 |   mcinfo("blocksize=%ld, total transfer=%ld (%ld blocks)\n", blocksize,
      |                                         ~~^
      |                                           |
      |                                           long int
      |                                         %d
chip/kinetis_sdhc.c:1906:10: warning: format '%ld' expects argument of type 'long int', but argument 5 has type 'unsigned int' [-Wformat=]
 1906 |   mcinfo("blocksize=%ld, total transfer=%ld (%ld blocks)\n", blocksize,
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1907 |          blocksize * nblocks, nblocks);
      |                               ~~~~~~~
      |                               |
      |                               unsigned int
chip/kinetis_sdhc.c:1906:48: note: format string is defined here
 1906 |   mcinfo("blocksize=%ld, total transfer=%ld (%ld blocks)\n", blocksize,
      |                                              ~~^
      |                                                |
      |                                                long int
      |                                              %d
chip/kinetis_sdhc.c: In function 'kinetis_recvshortcrc':
chip/kinetis_sdhc.c:2235:13: warning: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'uint32_t' {aka 'long unsigned int'} [-Wformat=]
 2235 |       mcerr("ERROR: Wrong response CMD=%08x\n", cmd);
      |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ~~~
      |                                                 |
      |                                                 uint32_t {aka long unsigned int}
chip/kinetis_sdhc.c:2235:43: note: format string is defined here
 2235 |       mcerr("ERROR: Wrong response CMD=%08x\n", cmd);
      |                                        ~~~^
      |                                           |
      |                                           unsigned int
      |                                        %08lx
CC:  main.c main.c: In function 'test_rs485_main':
main.c:212:16: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
  212 |         while (r = read(tty, &ch, 1)) {
      |                ^
main.c: In function 'main5':
main.c:242:13: warning: variable 'r' set but not used [-Wunused-but-set-variable]
  242 |         int r;
      |             ^
CC:  userleds.c In file included from userleds.c:31:
userleds.c: In function 'board_userled_all':
userleds.c:105:11: warning: format '%X' expects argument of type 'unsigned int', but argument 3 has type 'uint32_t' {aka 'long unsigned int'} [-Wformat=]
  105 |   ledinfo("setting all leds with 0x%08X", ledset);
      |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ~~~~~~
      |                                           |
      |                                           uint32_t {aka long unsigned int}
userleds.c:105:39: note: format string is defined here
  105 |   ledinfo("setting all leds with 0x%08X", ledset);
      |                                    ~~~^
      |                                       |
      |                                       unsigned int
      |                                    %08lX
LD: nuttx
Memory region         Used Size  Region Size  %age Used
       vectflash:         444 B         1 KB     43.36%
      cfmprotect:          16 B         16 B    100.00%
       progflash:      217016 B      1022 KB     20.74%
        datasram:       26136 B       128 KB     19.94%
CP: nuttx.hex

I also did another round of testing from computer to the board via RS485 (USB converter) with the following program:

int
main(int argc, char **argv)
{
        _info("test write to %s", argv[1]);

        int tty = open(argv[1], O_RDWR);
        if (0 > tty) {
                _err("ERROR: failed to open %s: %s", argv[1], strerror(errno));
                return 1;
        } else {
                _info("tty is %d", tty);
        }

        int r;
        uint8_t ch; 

        while (r = read(tty, &ch, 1)) {
                _info("received %d: %c", r, ch);
                ch += 1;
                write(tty, &ch, 1); 
        }

        close(tty);
        return 0;
}

when writing 5 into the tio /dev/ttyUSB1, the 6 is returned.

Let me know if I should add something more?

When there are no data in the TX buffer, we can avoid call to the
uart_xmitchars.

Signed-off-by: Jiri Vlasak <jvlasak@elektroline.cz>
Because it is not needed anymore.

The original code here is from the initial implementation in the commit
66b873e.

Signed-off-by: Jiri Vlasak <jvlasak@elektroline.cz>
When a developer working on BSP wants UART1 with RTS, the following
needs to be added in the BSP's include/board.h file:

	#define PIN_UART1_RTS PIN_UART1_RTS_1

which says that PIN_UART1_RTS_1 -- the first alternative pin with UART1
RTS function -- should be used for the UART1's RTS.

There are no alternative pins for PIN_UART2_RTS, therefore a similar
definition is not used for PIN_UART2_RTS.

However, that is a complication when we want PIN_UART2_RTS to be defined
as GPIO, for example:

	#define PIN_UART2_RTS (GPIO_OUTPUT | PIN_PORTB | PIN2)

In such a case, PIN_UART2_RTS is later redefined to the only alternative
function from hardware/kinetis_???pinmux.h file.

This patch avoids the redefinition of already defined names.

We considered renaming PIN_UART2_RTS to PIN_UART2_RTS_1 in the
hardware/kinetis_???pinmux.h file, but that is breaking change. We try
to avoid breaking change.

Signed-off-by: Jiri Vlasak <jvlasak@elektroline.cz>
@michallenc michallenc requested a review from acassis March 26, 2026 14:36
Copy link
Contributor

@cederom cederom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @ghnotgood :-)

@xiaoxiang781216 xiaoxiang781216 merged commit 7f5e028 into apache:master Mar 27, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: arm Issues related to ARM (32-bit) architecture Size: M The size of the change in this PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants