UART Decode Algorithm And Noise Immunity

Background

Most serial protocols in BAS domain are based on UART (Universal Asynchronous Receiver/Transmitter), e.g. Modbus, MS/TP.

The data stream consists of bytes. Each byte is a UART frame with 4 elements: start bit, 5 to 9 data bits, parity bit if has,  1 or 2 stop bits. Wikipedia for reference.  

The basic algorithm inside UART decoder includes bit sampling and start bit detection, they all affect performance of noise immunity.

Bit Sampling

There are three most common sampling algorithms: single sampling, majority vote, debounce.

Single Sampling

It is the most easy method that samples at the middle of bit. It is very sensitive to noise.

Majority Vote

A optimized sampling method is to take several samples near the middle of bit, then determine the value by majority vote.

In practice, a common design is using 16 times over sampling, 3 samples will be taken on 8th, 9th, 10th sample clock. The value is determined by at least 2 samples with identical value.  But if the width of noise signal is wider than 1/16 bit, 2 false values may be sampled.

Debounce

It’s another over sampling method. If the debounce time is 2 samples, only 2 consecutive samples have same value, the value will be confirmed, otherwise old value will be kept, so single sample noise will be filtered. But if the width of noise signal is wider than 1 sample, 2 consecutive false values may be sampled.

Start Bit Detection

Detection of start bit is critical for decoder to synchronize with sender. That is where “Asynchronous” coming from. There are 3 common detection algorithms on market: falling edge, falling edge with start bit validation, complicated others.

Detect Start Bit By Falling Edge

This method recognizes falling edge as start bit, no more validation will be taken. It is unguarded to noise. For a RS485 bus in idle state, electromagnetic interference is easy to cause noise signal leading to false start bit.

Detect Start Bit By Falling Edge With Start Bit Validation

This method is mainstream on the market. It does not only detect falling edge, it also samples the start bit (uses algorithm mentioned above), if value 0 is sampled, then the start bit is validated. if not, it regards the falling edge as noise then searches another falling edge.

By validation the start bit, it has a much better noise immunity, but for RS485 bus in idle state, this method still has some drawbacks:

  • If the noise width is near half of bit, it may be regarded as start of bit.
  • If there are 2 noise signals, the second one occurs near 1/2 bit after first one. then both falling edge and start bit validation are satisfied.
  • If the noise signal occurs less than 1/2 bit ahead of falling edge of real start bit. The middle start bit sample will read real start bit and pass validation. So the detected start bit will be ahead of real start bit to 1/2 bit most, that may cause error on later data bit sampling.

Detect Start Bit With Other Complicated Algorithms

MCUs from STMicroelectronics and Freescale (now NXP) have a complicate Algorithms to detect start bit, all three criteria below should be satisfied:

  • A: Falling edge should has 3 leading 1.
  • B: At least 2 samples of 0 should be found on 3th, 5th, 7th of clock.
  • C: At least 2 samples of 0 should be found on 8th, 9th, 10th of clock.

This algorithms works very well in noisy environment, but complicated algorithms usually lead to bug, For example:

When logic level switches from 1 to 0, a noise wider than 1/16 bits occurs at specific position. The falling edge at 1th clock will not meet rule C, so the decoder will skip it and search other falling edge, because there is no other falling edge meeting rule A, no start bit will be detected. (It had been verified on STM32F072RB)

Algorithm Of BACRouter From v6.00+

We implement a 32 times oversampling filter, for each slide window with 32 samples, if the number of 1 is greater than 21, value 1 is outputted, if the number of 1 is less than 11, value 0 is outputted, otherwise the value same as previous slide window is outputted.

Then the bit sampling and detection of start bit will be very simple.  Every bit is sampled at the middle of bit. The start bit detection need to search the falling edge only.
This algorithm has excellent noise immunity:

  • Noise width less than 2/3 bit will not lead to false start bit on a idle RS485 bus.
  • Noise width less than 1/3 bit will not affect bit sampling
  • Noise ahead of real start bit will only advance decoded start bit.

The only situation that it performs worse than traditional decoder is that the noise width or signal distortion is more than 1/3 bit and signal at the middle of bit is still valid.

The filter also works for ARC156.

ARC156 and Multiple Frame Ratio

One of ARCNET’s greatest feature is determinacy. It has several design decisions to help it being deterministic.

  • Only 1 packet can be sent for each token pass.
  • The max payload of a packet is limited to 507 bytes.
  • Every delay is strictly limited.

So for ARC156, the time of the token passing a node is about 40.1 milliseconds in worst case that the node has a non-broadcast packet with 507 bytes payload to send and the propagation delay is extreme.

If a node has no packets to send, neglecting propagation delay, it only take 448 microseconds for the token passing. It’s very fast comparing to ms/tp.

In BACnet network, most packets have less than 50 bytes. For example, a ReadProperty request relayed from BACnet IP to ARC156 has only 25 bytes payload, a typical respond has only 32 bytes payload.

If there is a ARC156 bus with 30 nodes, one of nodes is router which relays packets from/to BACnet IP network. For each token passing round, the router sends one ReadProperty request asking for Present_Value property of Analog object in one of other nodes which replies a Complex ACK.  The time for the a passing round is about  21.4 milliseconds, so the throughout is 1/21.4 = 0.0467 transaction per milliseconds.

If we allow the router to send multiple request when it holds a token but passes token out before it has used token for 40.1 milliseconds,  the throughout will be improved while the determinacy is not degraded.

For example, if the router send 10 requests to different nodes before it passes the token out, it only takes 36.7 milliseconds. Nodes with request received will send reply when token is passing them. The total time for a token passing round is about 90.8 milliseconds.  The throughout is 10/90.8 = 0.11 transaction per milliseconds, it is about 2.36 times as standard implementation.

“Multiple Frame Ratio” parameter for BACRouter’s ARC156 port defines how long time BACRouter can hold the token to send multiple packets. For example, If “Multiple Frame Ratio” is 0.5,  then BACRouter can hold the token for 0.5 * 40.1 milliseconds = 20 milliseconds. 40.1 milliseconds here is the longest time a node can hold token we discussed at the begin.

CCN Gateway Changelog

Download&Extract the zip file, there are 2 firmware files inside it, one is for BACnet version, another is for Modbus version.

For CCN Gateway with firmware version prior to v3.00, please go to the bottom of page.

3.08  2025.10.04  Download

CCN communication performance is improved.

BACnet Mapping of String point:  the unit should be NO_UNITS, not SQUARE_METERS.

3.07  2025.06.06  Download

Carrier has a localized air cooled heat pump serial in Chinese market.  30RQVHS is one of the model. We found its A1/B1 terminals talks CCN and Modbus RTU slave at the same time. Its CCN timing is a bit different to other chillers. The central control panel writes to its SERVICE table to control it.

This version modifies CCN timing to be compatible with it, and supports CONFIG and SERVICE tables.

3.06  2025.04.10  Download

Fix bug that “IP Router” setting will be lost after reboot.

3.05  2025.02.26  Download

Fix bug that may crash the daemon when there is collision on the CCN bus.

Updates BACnet ms/tp driver from BACRouter 6.09

2.26  Download

Most Versatile Modbus Gateway

BACRouter has a powerful Modbus master module, but there are still some scenarios that it can not handle well, for examples:

  1. Point’s value is read from register 3×00001, but has to write to register 4×00001.
  2. Value 1 read from Modbus mapping to BACnet 1.0, but writing BACnet value 1.0 to Modbus has to write 10. (Lua script for Modbus master module can do asymmetrical conversion for customized Analog object)
  3. Analog/Multistate object mapping to several Modbus coils or discrete inputs.
  4. Write only Modbus data address, read from it will report timeout or exception.

Fortunately,  we have free client module which can implement a most versatile Modbus gateway by Lua script.  We host the modbus.lua scripts on github.com

For modbus.lua, all devices under a bus share same baudrate/parity/timeout property. For Modbus master module, each device under a bus has independent timing property.

User should try Modbus master module at first, because of the test feature of Modbus master module, user can rapidly verify the mapping setting.

After all normal points have been setup, there may leave some weird points,  you should test read for those points.

Then the “Read Group” will verify the grouping.

Then you should export the device’s setting.

There is a fcmbconv.py python program on github repository, install python and run it, it will promote you to choose modbus.lua, then the device config file you had exported, then it will create a free client bus config file for the device.

Then import the created config into BACRouter under “Free Client Module”.

At last you have to modify the special settings for those weird points.

Example:

We assume there is a device, has several normal points, and

1. Analog output objects “lamp1” and “lamp2” get on/off status from bit 0 and bit 1 of Register 4×00032,  value 1 on the corresponding bit means on, value 0 means off.  To turn on/off of lamps, register 4×00031 should be written, but the value 1 on the corresponding bit means turning off, value 0 means turning on.

2. 8 bits from 1×00008 map to analog input object “temperature3”.

3. Write-only register 4×00100(Read will report timeout), low byte maps analog output object “load1”, high byte maps to analog output object “load2”.

Setup a Modbus device: ExampleModbusDevice,  verify the grouping by “Read Group”
Using fcmbconv.py to convert it to:  FCModbus

Import it into free client module:

Modify setting for weird points.

For lamp1:

For temperature3:

For load1 and load2:

Finally, verify its behavior by Yabe. If the script fails, please check the log.

The final config file for the free client bus is:  FCModbusFinal

BACRouter Changlog v6.00+

BACRouter switched to ARM soc from v6.00.  Now the firmware file format is xxxx_xxxx_6.00_arm_xxxx.tar.gz

For firmware version prior to v6.00, please visit: old firmware

6.18  2025.10.04  Download

Modbus bug on MultipleStateInput object introdued by v4.34: when a MSI object is mapped to holding register, its present value will not be updated.

Modbus WebUI bug on MultipleState object’s state string editing starting from v6.00 is fixed.

6.17  2025.09.27

Improvement over v6.16: Global broadcast now has a independent block rule.

6.16  2025.09.13

New feature: Relay block function allows user defined expression to control which packet is allowed to be relayed. Please access it on the “Network Settings” page.

6.15  2025.09.02  Download

A bug was introduced by v6.13. “Read coil from discrete” works reversed in the daemon, that is said, the daemon read coil from discrete when the flags is unset.  The WebUI still takes right value when performing “Test read”.

When MSTP runtime info page on WebUI calcuates “Token pass rate”, there may be a “Divide by 0” except which will restart the daemon. It is usually triggered when token just start to pass.

6.14  2025.08.22

WebUI bug on Modbus TCP: Test will fail when slave address is out of range of 1~247 (The valid range of slave address for Modbus TCP is 0~255).

6.13  2025.06.02  Download

Modbus module now support device that its holding register or coil is write only, but the value of holding register and coil could be read from input register or discrete input at the same address offset. Please check “Read redirect” in “Modbus parameter”.

ARC156 revert change in last version: Give up recovery from noise after passing token.

6.12  2025.04.29  Download

ARC156:  If next station had been verified, when passing token failed, retry it once.

ARC156: If a frame lasts less than 5 bits,  regards it as noise.

6.11  2025.04.02  Download

ARCnet standard and Datasheet of COM20019i state that the accuracy of clock should be 0.01% or better, so our previous implementation utilize a conservative clock error correction logic to tolerate clock error to 0.05%. After testing ZN551 and AAR module, we found they actually tolerate clock error up to 4.5% when decoding packets(The reconfiguration process still require clock error less than 0.2%), they are supposed to synchronize receiver clock to pulse preceding every byte. In this version, we implement a aggressive logic to achieve similar or better performance.

6.10  2025.03.28

ARC156:  The “Nodes on the bus” runtime info may detect non-existed node when two segments of bus with active nodes join together.  The workaround is double detecting to avoid false signal.

ARC156:  If BACRouter is the sole node on the bus, it will keep sending ITT(Invite To Transmit) to look for other nodes, none data packet can be sent. any NPDU sent to this port will be timed-out on the queue, then it will be annoying in the log. In this version, if BACRouter find itself is sole node on the bus, it will delete packet on the queue at a reasonable rate, and record it on the packet capture buffer to pretend sending the packet out. So when investigating packet captured, BACRouter are sending packets but get no response, this behavior is consistent to other data-link type.

Modbus RTU:  Some devices do not respect T15 between bytes, we remove the verify to improve compatibility.

Application Settings:  “Client Mode” is removed to comply to BTL requirement.

Modbus/FreeClient: When debugging the mapping or script, Writing from BACnet side usually need to be perform, user has to launch a BACnet client for example Yabe. There is often a BACnet server already running on the computer, so user has to start a VM to run Yabe. It wastes a lot time, so we integrate write function on the runtime info page.

6.09  2025.02.26  Download

MSTP bug to manipulate hardware will cause certain NPDU body discarded.

6.08  2025.01.23  Download

WebUI bug on Modbus Master Module:

When “Write Test” analog object mapping to single register, the byte order should respect to “Byte order for single register”, not “Byte order for big integer”.

When “Write Test” multi-state object mapping to 2 registers, the byte order should respect to “Byte order for big integer”, not “Byte order for single register”

The real value of binary object of free client device in “Runtime Info” page should respect to polarity setting.

6.07  2025.01.10  Download

Because BACRouter highly rely on high resolution timer, If there is electromagnetic disturbance, the crystal on the board may be disturbed, so timer will drift, lead to program malfunction.  We add correct function in this version.

6.06  2025.01.07  Download

Bugfix for free client module’s binary output object. It was introduced by v6.01 Polarity feature.

Modbus master module: when writing to read only data address, some devices respond timeout instead of report a exception, which will cause BACRouter keeps trying and fails with offline. For this version, BACRouter will only try 3 times then gives up.

6.05  2025.01.04

Refactor low level arc156 and ms/tp drivers to simplify code.

ARC156 driver add logic to drop packet send to node responding excess NAK to Free Buffer Enquiry

WebUI: Remind user when IP or netmask has been changed but DHCP server keeps enabled,

6.04  2024.12.26  Download

Fix bug on ms/tp:  Sending packet to ms/tp port with NPDU length near 1497 bytes may trigger the bug.

6.03  2024.12.25

ARC156 is usable now.

6.01  2024.12.06

Fixed bug on bus name collision detection for Modbus/Free Client module.

Free Client:  Bianry objects add “Polority” attribute. Multistate objects add state quantity limitation of 256.

Add new IP to BACRouter

We are often faced with third-party devices in the field whose IP cannot be modified and whose IP is not in the same subnet as the BACRouter or other BIP devices.
The solution already in place is to add an IP router, which will result in increased cost and complexity.
Usually we only need the BACRouter to communicate with this third party device using Modbus TCP or BACnet IP protocol, so the easiest way is to add an IP to the BACRouter dedicated to communicate with this device

For example, local IP subnet is 192.168.100.0/24,  BACRouter’s IP is 192.168.100.1;  Third-party device has a IP of 172.16.1.20, its netmask is 255.255.255.0

We will add IP of 172.16.1.1 to BACRouter
Original /etc/rc.local:

root@OpenWrt:~# cat /etc/rc.local
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.

cd /root
./webui&
exit 0

Modify /etc/rc.local: (vi is available too)

root@OpenWrt:~# cat>/etc/rc.local
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.

ifconfig br-lan:1 172.16.1.1 netmask 255.255.255.0
cd /root
./webui&
exit 0
CTRL+D

The ifconfig command will create a br-lan:1 interface with IP 172.16.1.1; if we connect to the third-party device by BIP protocol, we have to add new interface to BACRouter configuration.

Original /root/resource.conf:

~#cat /root/resource.conf
{ “eth0”: { “type”: “ETH”, “ifname”: “br-lan” }, “RS485-1”: { “type”: “USB”, “ifname”: “0:0” }, “RS485-2”: { “type”: “USB”, “ifname”: “0:1” } }

Modify /root/resource.conf: (vi is available too)

~#cat >/root/resource.conf
{ “eth0”: { “type”: “ETH”, “ifname”: “br-lan” }, “eth1”:{“type”:”ETH”, “ifname”:”br-lan:1″}, “RS485-1”: { “type”: “USB”, “ifname”: “0:0” }, “RS485-2”: { “type”: “USB”, “ifname”: “0:1” } }
CTRL+D

now restart BACRouter!

Modbus Devices Config & Lua Scripts & Conversion

Modbus Device Config

Siemens_RDF302.jsonSiemens RD302 Thermostat
Schneider_TC500.jsonSchneider TC500 Thermostat
JCI_T8600.jsonJCI T8600 Thermostat
JCI_T7000.jsonJCI T7000 Series Thermostat
ABB_ACS510.jsonABB ACS510 VFD

Lua Script

bcd.luaEach byte is 0-9, for example: 0x05090702 = 5972
Endian is defined by “big integer”
compat_bcd.luaEach half byte is 0-9, for example: 0x78695231 = 78695231
Endian is defined by “big integer”
m10k.luaEach register is -9999~9999, for example: 0x1f740a69 = 0x1f74*10000 + 0x0a69 = 80522665
Endian is defined by “big integer”
low10th.luaOnly 1 register, low byte is one tenth, the range is 0.0 to 255.9 for example: 0x0503 = 5.3
Endian is defined by “single integer”.
This is found in York TMS2100 FCU thermostat.

Conversion

Convert BASgatewayLX CSV Configuration

BACRouter&VPN

Based on OpenWRT platform, BACRouter could support various VPN, including OpenVPN, wireguard, IPSec, and so on.  To reserve maximum storage space for application program, the VPN support is not enabled by default.

Our OpenWRT code is hosted in https://github.com/hvacrcontrol/openwrt, the branch “bacrouter_new” is code for currently production.

The vpn.config in the repository is the configuration with OpenVPN and wireguard enabled.

Precompiled OpenWRT firmware file is: vpn_bacrouter_19.07_8fe8c902.tar.gz

Refer to upgrade-underlayer-firmware-of-bacrouter for how to upgrade the OpenWRT firmware.

Sometime, we need to enable BIP port on the VPN side, the BACRouter firmware from v4.23 intergrated that functionality. Below is a example of how to config it:

Upgrade Openwrt VPN firmware and BACRouter firmware to v4.23

Configure VPN, here is a example of wireguard:

~#uci show network.vpn
network.vpn.proto=’wireguard’
network.vpn.private_key=’####################################’
network.vpn.addresses=’192.168.231.3/24′

~#uci show network.wg0
network.wg0=wireguard_vpn
network.wg0.public_key=’#####################################’
network.wg0.endpoint_host=’###.###.###.###’
network.wg0.endpoint_port=’####’
network.wg0.route_allowed_ips=’1′
network.wg0.persistent_keepalive=’25’
network.wg0.allowed_ips=’192.168.231.0/24′

More info please refer to https://openwrt.org/docs/guide-user/services/vpn/wireguard/start

~#ifconfig vpn
vpn Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
inet addr:192.168.231.3 P-t-P:192.168.231.3 Mask:255.255.255.0
UP POINTOPOINT RUNNING NOARP MTU:1420 Metric:1
RX packets:557 errors:0 dropped:0 overruns:0 frame:0
TX packets:3593 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:50348 (49.1 KiB) TX bytes:176736 (172.5 KiB)

Add vpn interface to BACRouter:
Original /root/resource.conf:

~#cat /root/resource.conf
{ “eth0”: { “type”: “ETH”, “ifname”: “br-lan” }, “RS485-1”: { “type”: “USB”, “ifname”: “0:0” }, “RS485-2”: { “type”: “USB”, “ifname”: “0:1” } }

Modify /root/resource.conf: (vi is available too)

~#cat >/root/resource.conf
{ “vpn0”: { “type”: “PPP”, “ifname”: “vpn”}, “eth0”: { “type”: “ETH”, “ifname”: “br-lan” }, “RS485-1”: { “type”: “USB”, “ifname”: “0:0” }, “RS485-2”: { “type”: “USB”, “ifname”: “0:1” } }
CTRL+D

Because wireguard works on layer 3 (tun mode), so the type for vpn0 is defined as “PPP”. For VPN works on tap mode, “ETH” should be used.

Restart WebUI, the “System Settings” should show as:

Create another BIP port on vpn0. Because it’s a PPP interface, only “Foreign Device” and “BBMD” mode is allowed.

ATTN:When the WebUI and router daemon startup, it will read IP from every interface defined. so only static configured interface works here.  For wireguard, no further setting is needed because the ip is statically configured.

For OpenVPN tun mode, please Refer to: assign-static-ip-addresses-for-openvpn-clients  When OpenVPN client startups, it may need time to establish connection and get ip from the server. Below startup script will statically pre-configures ip of tun0 to prevent webui’s failing.

~#cat /etc/rc.local
ip tuntap add dev tun0 mode tun
ip link set tun0 up
ip addr add 192.168.231.3/24 dev tun0
/etc/init.d/openvpn start
cd /root
./webui&
exit 0

However, if OpenVPN tap mode is available, tap with bridge is most simple, no adjustment is needed for application level.

Great blog about VPN&BMS: Scott’s Technical Writeups

Convert BASgatewayLX CSV Configuration To BACRouter JSON

BASgatewayLX (Hereinafter referred to as LX) supports CSV configuration file. There are decent documents about the format and dozens of configuration samples for vary devices on their website. The convert tool we provided could convert the CSV file to JSON file which could be imported in BACRounter’s WebUI to create a Modbus slave station.

Convert on our website

Download and extract it to disk, open index.html by your web browser

Matters need attention:

Though LX claims the object name should be unique, but the accessory “Profile Checker” does not check duplicated object name. We found a lots duplicated object names in the samples. So we decided to disable object with duplicated name in converting.

Some CSV files have characters out of UTF8 code page, we guess they are ISO-8859-1 encoding. So if we fail to decode CSV by UTF8, we fall back to ISO-8859-1.

LX supports maximum 64 bytes of object name and description (With ISO-8859-1 encoding?). BACRouter supports 64 bytes too, but with UTF8 encoding. There has possibility that 64 bytes in ISO-8859-1 is larger than 64 bytes when encoding as UTF8. We will tail-cut too long name and description and promote to user.

BACRouter doesn’t allow 2 output objects mapped to overlapped Modbus address, but LX seems to allow it. We will disable the conflicted object in converting.

LX defines register order for every 32 bits data, but BACRouter has to set unitary byte order for all objects. If the consistent byte order definitions are found when converting, the previous definition will be accepted, the conflict will be promoted to user.

BACRouter has object instance range of 0~999 for every slave, but LX seems no limitation. If instance larger than 999 is found, the object will be auto assigned a new instance.

Offline configuration and import/export

From firmware v4.x,Offline configuration had been introduced. Now user could configure a BACRouter without device on hand, then export configuration to a file. When commissioning on the field, user just need to import previously exported file, “Save&Reboot” to take effect the configuration.

From v4.04,The object definitions for Modbus gateway could be import/export as CSV format. User could effectively setup object to Modbus address mapping In batches with Excel/WPS/Libreoffice, then import it to WebUI for detailed modify.

Offline configure on our website  (IE will not work now)

Download and extract it to disk, open index.html by your web browser (Because IE/Edge doesn’t support Local Storage for file:// URL, the user library for binary text and most used engineering unit will not be kept after leaving the web page)

There are 3 types of configuration file:

BACRouter Configuration File

This type of configuration file keeps all settings in BACRouter. If the firmware version of exporting one is same to importing one, WebUI should not complain of the configuration file.

Modbus Master Configuration File

This type of configuration file keeps setting under the scope of Modbus master.  When importing it, collision detecting logic based on current mapping mode will check every enabled slave in this master, if collision is found, the slave will be disabled.

If the master is RTU/ASCII type, it may also be disabled if there are contention for RS485 port.

With these problem, WebUI will prompt user to re-check configuration.

Modbus Slave Configuration File

This type of configuration file keeps setting under the scope of Modbus slave.  When importing it, if collision detecting logic based on current mapping mode found problem, the slave will be disabled.

It is allowed to import TCP salve configuration file in RTU/ASCII master, and vice versa, but the slave may be disabled if Modbus parameter setting do not work.

With these problem, WebUI will prompt user to re-check configuration.