Problem
As we know 9 bit UART (8M1, 8S1) mode in Linux (like in any POSIX systems) is not supported. So we can't communicate with devices which uses 9 bit.
Now we can fix it!
Now we can fix it!
Information
In w: RS-485 many devices (up to 255) can share one line (two wires) and communicate with Master Device on demand. Also, RS485 half-duplex, so, only one device (master or slave) can talk into the line in the moment. Usually programmers uses protocols (Modbus or custom) which tells what device must answer to the master device (PC or master controller). For addressing each device must have it own unique address.
Protocol example.
For example, packet can be like that:
<address>, <command>, <data_len>, [data], <crc>
where <crc> is error detection code which is XOR sum off all bytes in packet except crc.
Let's send command with id 0x5d to our imagined device with address 0x05. This command does not require any data bytes, so packet will be:
0x05, 0x5d, 0x00, 0x58
So, all devices in RS485 line must read this packet and check address byte. If device address is the same as address byte of packet device understands that this packet addressed for this device.
UPD: later I added solution with sources which can be found here
Sometime devices (usually based on RS485) uses MARK/SPACE parity for address byte marking.
In this case only address byte (first byte of our imagined protocol) marked with '1' bit of parity bit (mark parity) and all other bytes marked with '0' parity bit (space parity).
When using this technique, devices must listen and read only for bytes with '1' parity bit. Only when this byte have same value as device address device can read all other bytes of packet.
UPD: later I added solution with sources which can be found here
Parity and 9 bit
As we can see in Wikipedia w: parity bit is the simplest way of error detection. This error detection bit can be added to the each byte of packet.Sometime devices (usually based on RS485) uses MARK/SPACE parity for address byte marking.
In this case only address byte (first byte of our imagined protocol) marked with '1' bit of parity bit (mark parity) and all other bytes marked with '0' parity bit (space parity).
When using this technique, devices must listen and read only for bytes with '1' parity bit. Only when this byte have same value as device address device can read all other bytes of packet.
When using this technique the source code of sending function will be like (C++ style pseudo code):
---------------------------------------
bool sendData(
const uint8_t deviceAddress
const uint8_t commandByte,
const uint8_t dataLength,
const uint8_t *data
) {
const uint8_t crcByte = 0;
//calculating CRC for this packet
crcByte = updateXorCrc(crcByte, 1, &deviceAddress);
crcByte = updateXorCrc(crcByte, 1, &commandByte);
crcByte = updateXorCrc(crcByte, 1, &dataLength);
if(dataLength)
crcByte = updateXorCrc(crcByte, dataLength, data);
//configuring UART parity mode for address byte
if(!uartConfigureMode(PARITY_MARK))
return false;
if(!uartSendData(1, &deviceAddress))
return false;
//configuring UART parity mode for all other bytes
if(!uartConfigureMode(PARITY_SPACE))
return false;
if(!uartSendData(1, &commandByte))
return false;
if(!uartSendData(1, &dataLength))
return false;
if(dataLength) {
if(!uartSendData(dataLength, data))
return false;
}
return false;
}
---------------------------------------
As we can see port must be reconfigured twice:
---------------------------------------
bool sendData(
const uint8_t deviceAddress
const uint8_t commandByte,
const uint8_t dataLength,
const uint8_t *data
) {
const uint8_t crcByte = 0;
//calculating CRC for this packet
crcByte = updateXorCrc(crcByte, 1, &deviceAddress);
crcByte = updateXorCrc(crcByte, 1, &commandByte);
crcByte = updateXorCrc(crcByte, 1, &dataLength);
if(dataLength)
crcByte = updateXorCrc(crcByte, dataLength, data);
//configuring UART parity mode for address byte
if(!uartConfigureMode(PARITY_MARK))
return false;
if(!uartSendData(1, &deviceAddress))
return false;
//configuring UART parity mode for all other bytes
if(!uartConfigureMode(PARITY_SPACE))
return false;
if(!uartSendData(1, &commandByte))
return false;
if(!uartSendData(1, &dataLength))
return false;
if(dataLength) {
if(!uartSendData(dataLength, data))
return false;
}
return false;
}
---------------------------------------
As we can see port must be reconfigured twice:
- for PARITY_MARK when sending address byte;
- for PARITY_SPACE when sending all other bytes.
Solution
But we still have devices which uses 9 bit mode and we want work with them! Also, we want work with them from our Linux devices (for example Raspberry Pi and other nice things).As we know, 9 bit UART (8M1, 8S1) mode in Linux (like in any POSIX systems) is not supported. So we can't communicate with devices which which uses 9 bit.
Solution is using EVEN_PARITY and ODD_PARITY except MARK/SPACE parity using. We can make little analysis for each byte for sending and configure UART parity mode (on-the-fly) for each packet byte (including our first address byte). We must count bits in each byte and select between EVEN/ODD parity understanding our final result bit ('1' or '0').
---------------------------------------
int countBits(uint8_t value)
{
int ret = 0;
while(value > 0) {
ret++;
value >>= 1;
}
return ret;
}
bool sendByte
(
const uint8_t dataByte,
ComPortParity *uartParity,
const bool markSpaceNeeded = false
)
{
int bitsCount = countBits(dataByte);
ComPortParity newParity = PARITY_NONE;
//retrieving new parity type
if(markSpaceNeeded) {
newParity = ((bitsCount % 2) == 0) ? PARITY_ODD : PARITY_EVEN;
} else {
newParity = ((bitsCount % 2) == 0) ? PARITY_EVEN : PARITY_ODD;
}
//updating parity
if(newParity != oldParity) {
if(!uartConfigurePort(newParity)
return false;
*oldParity = newParity
}
return uartSendData(1, &dataByte);
}
bool sendData(
const uint8_t deviceAddress
const uint8_t commandByte,
const uint8_t dataLength,
const uint8_t *data
) {
const uint8_t crcByte = 0;
ComPortParity uartParity = uartGetParity();
//calculating CRC for this packet
crcByte = updateXorCrc(crcByte, 1, &deviceAddress);
crcByte = updateXorCrc(crcByte, 1, &commandByte);
crcByte = updateXorCrc(crcByte, 1, &dataLength);
if(dataLength)
crcByte = updateXorCrc(crcByte, dataLength, data);
if(!sendByte(deviceAddress, uartParity, true))
return false;
if(!sendByte(commandByte, uartParity, false))
return false;
if(!sendByte(dataLength, uartParity, false))
return false;
if(dataLength) {
for(register int i=0; i<dataLength; ++i) {
if(!sendByte(data[i], uartParity, false))
return false;
}
}
return true;
}
---------------------------------------
As you can see we tries to find solution between EVEN and ODD parity and configures port on-the-fly for known result - '1' or '0'. So we emulates MARK/SPACE parity.
As a result we can communicate with old-style devices which uses 9 bit. Tested in Raspbian OS (on Raspberry Pi with FT485 USB-RS485) convertor.
Congratulations!
UPD.: later I added complete solution with demo source code. You can find it here.
Hello, can you also post the code of the "uartSendData" function. Or can you post a demo project for the Raspberry pi on your blog or github ?
ReplyDeletethanks
Hello, it's just pseudo-code. It's not C++. Also, I don't have any devices that supports protocols with 9 bit. If you want I can help you, I can order device with protocol based on 9-bit (just like in text above) and write very simple program (just ping-pong with device) which will show you how to use this trick. Do you really need it? I can write some code using Python or Qt C++. Which language do you prefer?
ReplyDeleteLater I added complete solution with demo source code. You can find it here:
ReplyDeletehttp://bohdan-danishevsky.blogspot.com/2016/10/9-bit-serial-communication-in-linux.html