Sunday, October 30, 2016

9-bit serial communication in Linux (Raspbian) 8M1, 8S1 modes (with sources)

9-bit serial communication in Linux (Raspbian) 8M1, 8S1 modes (with sources)

 Problem

As you know, there is a problem when you use 9-bit serial communication under Linux. Here you can find solution which will help you solve this issue.



As I described earlier (see my post 9 bit UART (8M1, 8S1 modes) in Linux (hack) ) in RS-485 networks 8M1 mode sometime used for address byte transfer till device selection.

RS-485 network example

Usually protocols looks like this:
<address>, <other_protocol_data>

Where <other_procol_data> can be payload, CRC, etc. Often, when device address transferred via RS-485 it will be transferred in MARK parity mode and other protocol data will be transferred with SPACE parity.

9 bit MARK when address byte transferred

But there is a problem - you can't use 8M1/8S1 modes in Linux.

Solution

General information

As I described in my previous post all that you need is 8M1/8S1 emulation using ODD/EVEN parity modes, supported by Linux.

I got questions about C++ sources which can demonstrate this solution. So, I decided to write full solution. I found device with this protocol:

<device_addres>, ~<device_addres>, <command_byte>, ~<command_byte>

Where:
  • <device_addres> - device address;
  • <command_byte> - byte encoded command;
  • ~ - not operation (reverted byte)
So, my device address is 0x06 and supported command is 0x54. Full command packet will be:
0x06, 0xF9, 0x54, 0xAB

And 0x06 must transferred with set 9-bit - MARK parity must be set for this byte. Other data must be transferred with SPACE parity.

You can download source here: https://github.com/JFF-Bohdan/ninebituart

Or you can download sources using git: https://github.com/JFF-Bohdan/ninebituart.git

This source is C++ using Qt SDK and uses C++11. You can use this source directly or adapt it to your library/SDK.

Let's start from main.cpp:

in this file port will be opened and configured. Pointer to the port object will be transferred to the processor - class which will help work with 9-bit. Using setMarkingEnabled() method you can enable/disable 9-bit for transferred bytes.

AHardwareNineBitSender class will implement 9-bit support using MARK/SPACE parity and ASoftwareNineBitSender class will emulate MARK/SPACE using ODD/EVEN parities (supported by Linux). This classes inherits same class ANineBitSenderBase and have same signatures, so you can dynamically select class for processor.

Let's look into uartcommunicator.cpp:

as you can see in ASoftwareNineBitSender::writeData() implementation before transferring each byte updateParityMode() will be called. This method calculates bits set to ONE and dynamically changes parity for port between EVEN/ODD to emulate MARK/SPACE parities.

You need to understand that this source is for specific device and implements specific protocol. Protocol packet will be compiled using testPacket() function (in main.cpp)

Warning! If you want to test this source with your device, you need:
  1. change testPacket() implementation (in main.cpp) - this function compiles command packet (from host-device to slave-device in RS-485;
  2. change processor->readData() call (in main.cpp) - you need set first parameter - needful bytes count and second parameter - wait time (in msecs) for response wait;
  3. change TEST_PORT_NAME definition - this is port name;
  4. connect your your device to the RS-485;
  5. connect your USB-RS485 to the RPi and USB-RS485 to RS-485 network*
If you from Ukraine, you can use USB-RS485 convertors from http://vkmodule.com.ua:
http://vkmodule.com.ua/Converter/ConverterUSB485.html I'm using this devices for long time and they are amazing.

Compiling and testing

First of all you need install Qt SDK and gcc compiler:

$ sudo apt-get install build-essential
$ sudo apt-get install qt5-default
$ sudo apt-get install libqt5serialport5-dev
$ sudo apt-get install libqt5serialport5

Installing git client:

$ sudo apt-get install git 

If you have problems with Qt5 SDK downloading, you may need update your system. You can find solution here.

Go home (or to any folder you prefer for tests):

$ cd ~

Download sources:

$ git clone https://github.com/JFF-Bohdan/ninebituart.git

If this operation succeeded you will got a ninebituart directory in working  directory. Let's compile source code, first of all we need go to the folder with sources:

$ cd ninebituart

Before next step, please be sure that you have correct date & time in your system. You can use command date for current date & time retrieve. Also, you can find solution complete solution for packages update by using system-update.sh. After checking date & time you can continue compiling:

$ qmake ./ninebituart.pro

$ make

When compile finish you will go executable in bin directory.

If you want you can run it by this command (you must be in bin folder):

$ cd bin

Now we can execute executable and test

$ ./ninebituart 

Example output (on my RPi when communicating with my device):
avail ports:
"ttyAMA0"
"ttyUSB0"

opening port:  /dev/ttyUSB0
updating parity
TX: "06"
updating parity
TX: "f954ab"
RX:  "067e40a2ea"
COMPLETE


As you can see, i have two UARTs: "ttyAMA0", "ttyUSB0". Then program opened UART "/dev/ttyUSB0" and sent device address "06" (with 9 bit set), after that was sent data "f954ab" And finally, received device response: "067e40a2ea". Everything works!

Warning! Example output correct just for my device.

Hope you like it.

Enjoy!

10 comments:

  1. Hi I tried your code over the 9 bit protocol for wafer RS232 to MDB, but i am getting random code in linux, rasbin rasbery pi, compared to same code in windows. is there any encoding changes needed ?

    ReplyDelete
    Replies
    1. Could you be so kind to explain your question more detailed? Thank you.

      Delete
  2. Hi, I got the logic for sending M/S. But what would be the logic for receiving Mark/Space Parity byte on a device which doesn't support Mark/Space modes?

    ReplyDelete
    Replies
    1. Sorry man, I don't know any practical solutions.

      But if you using 9-bit just for first byte, you can try to set PARITY/MARK (according to your device address) and then, after first byte received you can switch to 8 bit mode.

      I think this is possible.

      Delete
  3. What would you recommend as a linux distrubution / version that could work with ninebituart? I know most of the usb/uart adapters have either ftdi or prolific chipset but not sure about what linux and driver is known to work with the hardware and with 9-bit?

    ReplyDelete
    Replies
    1. As I know, there is no Linux distributes which supports 9-bit. Bu you can emulate it as described.

      Delete
  4. For example, I tried on a Debian 3.16.7 / QT 5.3.2, but this usually reports 'Error opening port: "Input/output error"'.. however occasionally it does open /dev/ttyUSB0 and does transmit. Unfortunately, custom baud rates also don't work.. from what I've read it was an issue with Qt < 5.3, yet for me I can only get it to output standard baud rates.

    BTW, doing an lsusb -vv, I see:
    Bus 001 Device 006: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter
    Device Descriptor:
    bLength 18
    bDescriptorType 1
    bcdUSB 1.10
    bDeviceClass 255 Vendor Specific Class
    bDeviceSubClass 0
    bDeviceProtocol 0
    bMaxPacketSize0 8
    idVendor 0x1a86 QinHeng Electronics
    idProduct 0x7523 HL-340 USB-Serial adapter
    bcdDevice 2.54
    iManufacturer 0
    iProduct 2 (error)
    iSerial 0
    bNumConfigurations 1
    Configuration Descriptor:
    bLength 9
    bDescriptorType 2
    wTotalLength 39
    bNumInterfaces 1
    bConfigurationValue 1
    iConfiguration 0
    bmAttributes 0x80
    (Bus Powered)
    MaxPower 96mA
    Interface Descriptor:
    bLength 9
    bDescriptorType 4
    bInterfaceNumber 0
    bAlternateSetting 0
    bNumEndpoints 3
    bInterfaceClass 255 Vendor Specific Class
    bInterfaceSubClass 1
    bInterfaceProtocol 2
    iInterface 0
    Endpoint Descriptor:
    bLength 7
    bDescriptorType 5
    bEndpointAddress 0x82 EP 2 IN
    bmAttributes 2
    Transfer Type Bulk
    Synch Type None
    Usage Type Data
    wMaxPacketSize 0x0020 1x 32 bytes
    bInterval 0
    Endpoint Descriptor:
    bLength 7
    bDescriptorType 5
    bEndpointAddress 0x02 EP 2 OUT
    bmAttributes 2
    Transfer Type Bulk
    Synch Type None
    Usage Type Data
    wMaxPacketSize 0x0020 1x 32 bytes
    bInterval 0
    Endpoint Descriptor:
    bLength 7
    bDescriptorType 5
    bEndpointAddress 0x02 EP 2 OUT
    bmAttributes 2
    Transfer Type Bulk
    Synch Type None
    Usage Type Data
    wMaxPacketSize 0x0020 1x 32 bytes
    bInterval 0
    Endpoint Descriptor:
    bLength 7
    bDescriptorType 5
    bEndpointAddress 0x81 EP 1 IN
    bmAttributes 3
    Transfer Type Interrupt
    Synch Type None
    Usage Type Data
    wMaxPacketSize 0x0008 1x 8 bytes
    bInterval 1
    Device Status: 0x0000
    (Bus Powered)

    Using the device under windows, the usb/serial works fine with any baud rate (RealTerm).

    ReplyDelete
    Replies
    1. Sorry, bro, I have no such devices right now. But I used this trick from Python and Qt, so it's not depends on SDK

      Delete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Hi!

    Thank for this tutorial. This is very useful for me.I have some questions. Must i use your data structure or can I send just a address then one or more data byte? If i can how, because when i set ret array to two elements after the address sent, the program not updated parity. The other question is, when i print the Tx data not in hex, i get this "\u0002" for the address(0x02). Is this normal?

    Thank you in advance for your reply

    ReplyDelete