Python PySerial with Auto RTS through Half-Duplex RS-485 breakout board using Beaglebone Black Angstrom

Timothy Vann picture Timothy Vann · Jan 15, 2014 · Viewed 10k times · Source

I'm trying to use a Beaglebone Black running Angstrom (3.8 kernel) to communicate with devices on a half-duplex RS-485 network at 9600-N-8-1.

I'm trying to use an RS-485 Breakout board similar to this one: https://www.sparkfun.com/products/10124, except the chip is a MAX3485 http://www.maximintegrated.com/datasheet/index.mvp/id/1079. I bought the board pre-assembled with pins and a terminal strip. A friend of mine tested it with an oscilloscope and declared that the RS-485 board does work. The board has five pins that connect to the BBB. 3-5V (Power), RX-I, TX-O, RTS, and GND.

I've disabled HDMI support on the BBB so that the UART4_RTSn and UART4_CTSn pins will be available.

    mkdir /mnt/boot
    mount /dev/mmcblk0p1 /mnt/boot
    nano /mnt/boot/uEnv.txt
    #change contents of uEnv.txt to the following:
    optargs=quiet capemgr.disable_partno=BB-BONELT-HDMI,BB-BONELT-HDMIN 

Then I've found an overlay to enable UART-4 with RTS/CTS control:

            /*
     * Modified version of /lib/firmware/BB-UART4-00A0.dtbo to add RTS so we can reset Arduinos
     */
    /dts-v1/;
    /plugin/;

    / {
      compatible = "ti,beaglebone", "ti,beaglebone-black";
      part-number = "BB-UART4-RTS";
      version = "00A0";
      exclusive-use = "P9.13", "P9.11", "P9.15", "P8.33", "P8.35", "uart4";

      fragment@0 {
        target = <0xdeadbeef>;

        __overlay__ {


          pinmux_bb_uart4_pins {
            pinctrl-single,pins = <
              0x070 0x26   /* P9_11 = UART4_RXD = GPIO0_30,    MODE6 */
              0x074 0x06   /* P9_13 = UART4_TXD = GPIO0_31,    MODE6 */
              /* need to enable both RTS and CTS, if we only turn on RTS then driver gets confused */
              0x0D0 0x26   /* P8_35 = UART4_CTSN = lcd_data12, MODE6 */
              0x0D4 0x06   /* P8_33 = UART4_RTSN = lcd_data13, MODE6 */
              /* 0x040 0x0F   /* P9_15 = GPIO1_16 = GPIO48,       MODE7  failed attempt to put DTR on gpio */
            >;
            linux,phandle = <0x1>;
            phandle = <0x1>;
          };
        };
      };

      fragment@1 {
        target = <0xdeadbeef>;

        __overlay__ {
          status = "okay";
          pinctrl-names = "default";
          pinctrl-0 = <0x1>;
        };
      };

      __symbols__ {
        bb_uart4_pins = "/fragment@0/__overlay__/pinmux_bb_uart4_pins";
      };

      __fixups__ {
        am33xx_pinmux = "/fragment@0:target:0";
        uart5 = "/fragment@1:target:0"; /* Not a mistake: UART4 is named uart5 */
      };

      __local_fixups__ {
        fixup = "/fragment@1/__overlay__:pinctrl-0:0";
      };
    };

Compiled and Enabled the overlay:

    cd /lib/firmware
    dtc -O dtb -o BB-UART4-RTS-00A0.dtbo -b 0 -@ BB-UART4-RTS-00A0.dts 
    echo BB-UART4-RTS:00A0  > /sys/devices/bone_capemgr.*/slots

Hooked up the 485 board to the BB like this

    3-5V to P9_05 (VDD_5V)
    RX-I to P9_13 (UART4_TXD) 
    TX-O to P9_11 (UART4_RXD)
    RTS  to P8_33 (UART4_RTSn)
    GND  to P9_01 (DGND)

In python I'm trying to use the serial port like this:

    import serial
    ser = serial.Serial('/dev/ttyO4', baudrate=9600, rtscts=True)
    ser.write(list_of_byte_dat)

I know the program works because when I use a USB to RS-485 converter on /dev/ttyUSB0 and set rtscts=False the communication works in both directions just fine. But I can't get communication to work correctly using the RS-485 board.

I have two issues with the RS-485 board, both deal with RTS.

  1. The RTS on the board works backwards from the way I expect it to. When I apply voltage on the RTS pin of the rs485 board the RTS led on the board goes off and the board will not transmit. When I remove voltage from the RTS pin the RTS led turns on and the board will transmit. How do I reverse the polarity of the UART_RTSn pin on the BBB?

    Temporary solution: I've made a small bone script program that uses UART4_RTSn pin as input. It turns on a different GPIO when the UART4_RTSn pin is off and turns off that same GPIO pin when the UART4_RTSn pin is on. Then hooked up the RTS pin on the rs485 board to the GPIO pin instead of the UART4_RTSn pin.

    This seems to be a poor solution, but it does make the RTS on the RS485 board come on at the correct time when echoing to the /dev/ttyO4 from the command line.

    How can I change the polarity of the UART4_RTSn pin either by adjusting the hardware configuration or by changing the configuration in pyserial?

    This brings me to the second issue

  2. As I stated in problem 1 the UART4_RTSn pin will work automatically (but backwards) for me when echoing a value to the tty port like this:

    echo -en '\x02\xFD\xCD......' > /dev/ttyO4
    

    This will make the UART4_RTSn led blink while the data is being transmitted. If I have it setup without the bonescript mentioned above, then it will be on normally and blink off while transmitting. If I use my bonescript hack then it will be off normally and blink on while transmitting (which is what I want). However this only works when using echo from the command line. When I use python and setup the serial port the UART4_RTSn pin becomes inactive. It will not blink while transmitting. As soon as I make the statement in python:

    ser = serial.Serial('/dev/ttyO4', baudrate=9600, rtscts=True)
    

    The UART4_RTSn pin shuts off and stays off. It does not blink when sending information using ser.write(stuff). As a result the rs485 board is not enabled for transmission. How do I get the UART4_RTSn pin to work automatically in pyserial? I've tried setting rtscts=False and it did not work.

    I am able to use ser.setRTS(True) or ser.setRTS(False) to manually toggle the pin value so I know I'm using the correct pin and that it is being recognized. But I don't want to toggle the UART4_RTSn pin directly. I want it to work automatically when the serial port is transmitting data and it does when using echo, but not in Python.

Any help would be greatly appreciated.

Answer

Peter Gibson picture Peter Gibson · Jan 17, 2014

RTS is normally an active low signal, I suspect the reason that you see data being transmitted with echo is that it is not using RTS/CTS (leaving it high) and so is only able to transmit data.

According to a post on http://www.raspberrypi.org/phpBB3/viewtopic.php?f=26&t=29408

If you enable hardware flow control (CRTSCTS in "man termios", or "stty crtscts -F /dev/ttyAMA0", or pySerial rtscts=True), then sending will take place only when CTS is asserted. RTS will be asserted except when the kernel input buffer is full. The kernel input buffer is about one page or 4KB, so your application has to get well behind with its reads before RTS actually changes.

So check that CTS is being asserted (pulled to ground) outside of your board. However I don't think this will give you the right control over RTS that you need.

So, for your application you should disable hardware flow control (rtscts=False) and manually control RTS with setRTS(1) before a write and setRTS(0) afterwards.

If you are still not seeing data get through to the device, try swapping the A & B wires - A/B labelling is (frustratingly) not consistent across RS485 devices. It is better to use D+/D- labelling if possible in your own applications.