Documented the new simpler transport layer
This commit is contained in:
parent
cf9c945fb6
commit
0be8c0258c
122
README.md
122
README.md
|
@ -6,7 +6,7 @@ SSS7 is the **S**eidenstrasse **S**ignaling **S**ystem 7.
|
|||
|
||||
This repository contains the schematics its the hardware,
|
||||
along with a description of the physical layer and the transport layer.
|
||||
The actual application layer will be developed independent of this specification.
|
||||
The actual application layer is developed independently of this project.
|
||||
|
||||
|
||||
Physical Layer
|
||||
|
@ -15,9 +15,9 @@ The physical layer is based on a RS485-like two wire bus
|
|||
(normal RS485 has different levels when idle),
|
||||
using the *MCP2551* is used as transceiver chip.
|
||||
Since this chip is actually a CAN-transceiver,
|
||||
it has a well defined behaviour in case of collisions (no short circuits possible)
|
||||
it has a well defined behaviour in case of collisions
|
||||
and it allows to read back the bits which were actually on the bus while sending.
|
||||
Additionally for most regular RS485 transceivers a collision on the bus results
|
||||
For regular RS485 transceivers a collision on the bus results
|
||||
in one transceiver pulling the bus while the other one pulls it high,
|
||||
effectively creating a temporary short circuit.
|
||||
Therefore using a CAN-transceiver is preferable for our bus configuration.
|
||||
|
@ -25,22 +25,21 @@ Therefore using a CAN-transceiver is preferable for our bus configuration.
|
|||
The MCP2551 requires that the levels of the differential data lines are within +-7V
|
||||
of its ground potential.
|
||||
This a bit of a problem, since it is not really practical to run an additional wire
|
||||
along the bus to provide a common.
|
||||
Additionally routers may use power supplies which do not have a floating potential
|
||||
along the bus to provide a common ground.
|
||||
Additionally routers may use power supplies which do not have a floating ground potential
|
||||
(e.g. a computer power supply).
|
||||
Connecting the ground rails of two non-floating power supplies can lead to a nasty
|
||||
short circuit in the worst case.
|
||||
Connecting the ground rails of two non-floating power supplies will lead to a problems.
|
||||
Therefore an isolated DC/DC converter is used to provide a floating supply voltage
|
||||
for the transceiver chip and the data lines are isolated using optocouplers.
|
||||
Still a common ground potential for all modes is required for the transceiver chips to work.
|
||||
This is solved by adding diodes in inverse direction between the floating 5v rail and both data lines
|
||||
and between the chips ground and bot datalines.
|
||||
Still a common ground potential for all nodes is required for the transceiver chips to work.
|
||||
This is solved by adding diodes in blocking direction between the floating 5v rail and both data lines
|
||||
and between the chips ground and both data lines.
|
||||
If the potential of the high data line is more than 0.7v above the floating 5v rail,
|
||||
on of diodes will become conducting effectively shifting the floating grounds potential up.
|
||||
Likewise if the potential of the low data line is more than 0.7v below the floating ground rail,
|
||||
the ground rail will be shifted down.
|
||||
Hence the floating grounds rails of all modems connected to bus will be within +-0.7v relative to each other
|
||||
(not considering the voltage drop over the long wires).
|
||||
Hence the floating grounds rails of all modems connected to bus will be within +-0.7v
|
||||
relative to each other(not considering the voltage drop over the long wires).
|
||||
|
||||
Additionally these diodes suppress voltages spikes on the bus,
|
||||
protecting the transceiver.
|
||||
|
@ -50,45 +49,77 @@ can communicate over long distances (1000m+).
|
|||
The data is send at 9600 Baud (aka. 9,6kbit/s) with a maximum baudrate error of +-5%,
|
||||
to ensure that even the slowest microcontroller can participate in the communication.
|
||||
A slow bitrate also necessary to keep communication and collision detection reliable over longer
|
||||
distances.
|
||||
|
||||
distances and allows to use slower (and cheaper) optocouplers.
|
||||
|
||||
Transport Layer
|
||||
---------------
|
||||
A device is allowed to send data after the bus has been idle for at least *48 bit times* (aka. 5ms).
|
||||
After this period the sender is allowed to send a single frame.
|
||||
The frame format is simple: The first two bytes are a always 0xAAFE.
|
||||
This preamble allows the uart to synchronise to the incoming data.
|
||||
The bit pattern also increases the chances,
|
||||
that a collision of two preambles results in frame error.
|
||||
The next byte is the length of the payload in bytes,
|
||||
followed by the actual payload.
|
||||
The remaining 2 bytes are a 16bit CRC checksum over the frames payload.
|
||||
If a received frame has timed out or if its CRC sum does not match its payload,
|
||||
any receiving devices should assume a collision and drop the frame.
|
||||
A frame can be assumed as timed out if it was not fully received yet and no
|
||||
additional data has been received for at least *24 bit times* (aka. 2.5ms).
|
||||
Furthermore each frame is associated to a priority between 0 (highest) and 5 (lowest),
|
||||
which is not part of the frame send over the bus.
|
||||
Instead the priority is used to calculate the necessary back off in case of a collision.
|
||||
|
||||
The sender has to detect collisions by reading back each byte written to the bus immediately after writing it.
|
||||
If the read byte does not match the written byte, a collision occurred.
|
||||
In this case the sender has to wait for random back off interval until attempting a retransmit.
|
||||
The back off interval is calculated as `(timeout * bit_time) * (priority + rand(1,5))`,
|
||||
where `bit_time = 1 / 9600 s` and priority is the frames priority.
|
||||
After the backoff interval the bus has to be idle for *48 bit times* before a retransmit.
|
||||
|
||||
Devices receiving on the bus are not necessarily able to detect collisions,
|
||||
unless they show up as timing error at their UART.
|
||||
Therefore receiving devices should rely only on the length and the CRC sum of frame to detect collisions.
|
||||
### Frame format:
|
||||
``` 0xAA 0xFE <payload 16 bytes> <payload crc 1byte> ```
|
||||
|
||||
16 bytes total
|
||||
|
||||
**Header: 0xAA 0xFE**
|
||||
- Choosen emperically to help the Uarts to synchronize
|
||||
- Improves detection of two headers colliding
|
||||
|
||||
**CRC-Type: Maxim iButton 8-bit**
|
||||
Polynomial: x^8 + x^5 + x^4 + 1 (0x8C)
|
||||
Initial value: 0x0
|
||||
|
||||
### Communication Protocol
|
||||
|
||||
- **Not final, use with caution**
|
||||
- Frames are sent using standard RS232 Uarts connected to sss7 modems
|
||||
- Uarts should be configured to: 9600baud, 8bits, 1 stop bit, no flow-control, no parity
|
||||
- Every node can send a frame at an arbitrary point in time,
|
||||
unless it is currently receiving a frame.
|
||||
- Collision detection on the sender side is done by reading back each sent byte.
|
||||
- Collision checking on receiver side, is done by checking the CRC and frame header.
|
||||
- If a frame has been started and there are no new bytes received for 20ms,
|
||||
the frame is considered timed out and all received data can be dropped.
|
||||
- Incoming messages are stored in a fifo until the application retrieves them.
|
||||
- The receive fifo has a size of at least 2 messages.
|
||||
- If the fifo is full new messages will be dropped, until the application retrieves a message.
|
||||
- Even if sending was successful, there is still a chance that the receiver could not
|
||||
receive the frame due to missing buffer space or not enough processing time to react. **Important messages should utilize a ack-mechanism.**
|
||||
- It is up to the application to resend messages in case of a collision/missing ack.
|
||||
|
||||
### Planned API
|
||||
- **Not final, use with caution**
|
||||
- All calls are non-blocking,
|
||||
any real work should be done by ISRs or an eventloop, which is called peridically.
|
||||
|
||||
- **sss7_can_send()**
|
||||
|
||||
Checks wether we see the bus as idle.
|
||||
Return true iff. we neither are currently receiving a frame
|
||||
nor are sending a frame of our own.
|
||||
|
||||
- **sss7_send_failed()**
|
||||
|
||||
Checks the send error flag.
|
||||
Returns true if the last attempt at sending a frame failed.
|
||||
An attempt is considered failed if the Uart reads back a different byte than the byte send out.
|
||||
This indicates either a collision or a hardware failure.
|
||||
|
||||
- **sss7_send(payload)**
|
||||
|
||||
Constructs a frame around the payload and prepares for sending.
|
||||
The actual sending is either done in the ISR or by the eventloop, depending on the platform.
|
||||
The frame is completely sent iff. ```sss7_can_send() && !sss7_send_failed()``` holds.
|
||||
|
||||
- **sss7_has_received()**
|
||||
|
||||
Returns True iff. the receive fifo contains at least one message.
|
||||
|
||||
- **sss7_get_received(payload)**
|
||||
|
||||
Retrieves the oldest message from the receive fifo.
|
||||
It is up the application to provide sufficient space in payload.
|
||||
Does nothing sss7_has_received() is false.
|
||||
|
||||
Even though the collision detection theoretically ensures that there are no damaged frames due to collisions,
|
||||
there might still be frames with a broken crc sum or wrong length due to electrical interference on the bus.
|
||||
For autorouters this should not be a problem since they will need mechanism for acknowledging the successful
|
||||
execution of actions anyway.
|
||||
Therefore a lost frame will either result in a command not being executed or in command being executed but not acked.
|
||||
Both cases can be handled efficiently by the resending the command of no ack was received.
|
||||
|
||||
Licenses
|
||||
--------
|
||||
|
@ -103,4 +134,3 @@ Therefore you should ask for permission before using the seidenstrasse logo in d
|
|||
Also please remove my personal logo if I am not involved in your project.
|
||||
|
||||
The schematics and pcb layout files in hardware are released under the *CERN Open Hardware Licence v1.2*.
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
#!/usr/bin/env python2
|
||||
|
||||
import random
|
||||
import time
|
||||
|
||||
from sss7bus import SSS7Bus
|
||||
|
||||
|
||||
bus = SSS7Bus('/dev/ttyUSB0')
|
||||
|
||||
framecount = 0
|
||||
lost = 0
|
||||
|
||||
while True:
|
||||
bus.send_message("Hello World!")
|
||||
|
||||
more = True
|
||||
while more:
|
||||
msg = bus.read_message()
|
||||
print msg
|
||||
print framecount
|
||||
frame = int(msg.split(':')[1])
|
||||
delta = frame - framecount
|
||||
if delta > 1 and framecount <> 0:
|
||||
lost += delta
|
||||
|
||||
framecount = frame
|
||||
|
||||
if frame % 100 == 0 and frame > 0:
|
||||
print "Lost Frames: %d %d %f" % (frame, lost, 100.0 * lost / frame)
|
||||
|
||||
more = bus.has_message()
|
||||
|
||||
time.sleep((500.0 + random.randint(0,500)) / 1000.0)
|
|
@ -1,20 +0,0 @@
|
|||
#!/usr/bin/env python2
|
||||
|
||||
import random
|
||||
import time
|
||||
|
||||
from sss7bus import SSS7Bus
|
||||
|
||||
|
||||
bus = SSS7Bus('/dev/ttyUSB1')
|
||||
|
||||
counter = 0
|
||||
|
||||
while True:
|
||||
bus.send_message("Timestamp:%d:%d" % (counter, time.time()))
|
||||
while bus.has_message():
|
||||
bus.read_message()
|
||||
|
||||
counter += 1
|
||||
|
||||
time.sleep((500.0 + random.randint(0,500)) / 1000.0)
|
|
@ -1,3 +0,0 @@
|
|||
crcmod==1.7
|
||||
pyserial==2.7
|
||||
wheel==0.24.0
|
|
@ -1,160 +0,0 @@
|
|||
#!/usr/bin/env python2
|
||||
|
||||
import serial
|
||||
import random
|
||||
import time
|
||||
from crcmod.predefined import PredefinedCrc
|
||||
|
||||
|
||||
# Timings in bit times
|
||||
TIMEOUT = 48
|
||||
CLEARCHANNEL = 2 * TIMEOUT
|
||||
|
||||
|
||||
class SSS7Bus(object):
|
||||
|
||||
def __init__(self, port, baudrate=9600):
|
||||
random.seed()
|
||||
|
||||
self._bit_time = 1.0 / baudrate
|
||||
|
||||
self._serial = serial.Serial(port, baudrate, timeout=self._bit_time * TIMEOUT)
|
||||
|
||||
self._buffer = []
|
||||
|
||||
|
||||
def _debug(self, msg):
|
||||
print "[SSS7Bus] %s" % msg
|
||||
|
||||
|
||||
def _flush_input_buffer(self):
|
||||
while self._serial.inWaiting() > 0:
|
||||
self._debug("Flushing input buffer")
|
||||
self._read_frame()
|
||||
|
||||
|
||||
def _can_send(self):
|
||||
self._flush_input_buffer()
|
||||
|
||||
self._serial.timeout = self._bit_time * CLEARCHANNEL
|
||||
self._debug("Checking if bus is idle for %f seconds" % (self._serial.timeout))
|
||||
|
||||
first_byte = self._serial.read(1)
|
||||
if len(first_byte) == 0:
|
||||
self._debug("Bus seems idle")
|
||||
return True
|
||||
|
||||
self._debug("Bus is not idle, reading frames")
|
||||
self._read_frame_rest(first_byte)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _read_frame(self):
|
||||
self._debug("Trying to read a frame from the bus")
|
||||
self._serial.timeout = timeout=self._bit_time * TIMEOUT
|
||||
|
||||
first_byte = self._serial.read(1)
|
||||
if len(first_byte) == 0:
|
||||
self._debug("Timeout reading first byte")
|
||||
return False
|
||||
|
||||
return self._read_frame_rest(first_byte)
|
||||
|
||||
|
||||
def _read_frame_rest(self, first_byte):
|
||||
if ord(first_byte) <> 0xAA:
|
||||
self._debug("Wrong first byte: %s" % hex(ord(first_byte)))
|
||||
return False
|
||||
|
||||
second_byte = self._serial.read(1)
|
||||
if len(second_byte) == 0:
|
||||
self._debug("Timeout reading second byte")
|
||||
return False
|
||||
|
||||
if ord(second_byte) <> 0xFE:
|
||||
self._debug("Wrong second byte")
|
||||
return False
|
||||
|
||||
|
||||
length_byte = self._serial.read(1)
|
||||
if len(length_byte) == 0:
|
||||
self._debug("Timeout reading length byte")
|
||||
return False
|
||||
|
||||
length = ord(length_byte) + 2 # Payload length + 2 byte CRC16
|
||||
|
||||
self._serial.timeout = timeout=self._bit_time * TIMEOUT
|
||||
|
||||
self._debug("Trying to read remaing %d bytes of frame" % length)
|
||||
data = self._serial.read(length)
|
||||
|
||||
# if read returns less then length, no new byte has been received for timeout seconds
|
||||
if len(data) != length:
|
||||
self._debug("Timeout reading frame payload")
|
||||
return False
|
||||
|
||||
crc = data[-2:]
|
||||
payload = data[:-2]
|
||||
|
||||
crc16 = PredefinedCrc('crc16')
|
||||
crc16.update(payload)
|
||||
real_crc = crc16.digest()
|
||||
|
||||
if real_crc != crc:
|
||||
self._debug("Wrong crc for frame payload")
|
||||
return True
|
||||
|
||||
self._debug("Sucessfully read rest of frame")
|
||||
self._buffer.insert(0,payload)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def _send_byte(self, byte):
|
||||
self._serial.write(byte)
|
||||
return byte == self._serial.read(1)
|
||||
|
||||
|
||||
def _send_frame(self, frame):
|
||||
while not self._can_send():
|
||||
pass
|
||||
|
||||
for byte in frame:
|
||||
result = self._send_byte(byte)
|
||||
if not result:
|
||||
self._debug("Sending frame failed with collision")
|
||||
return False
|
||||
|
||||
self._debug("Successfully send frame")
|
||||
return True
|
||||
|
||||
|
||||
def send_message(self, msg, priority=5):
|
||||
if len(msg) > 253:
|
||||
raise ValueError("Message length can not exceed 253 byte")
|
||||
|
||||
crc16 = PredefinedCrc('crc16')
|
||||
crc16.update(msg)
|
||||
msg_crc = crc16.digest()
|
||||
|
||||
frame = chr(0xAA) + chr(0xFE) + chr(len(msg)) + msg + msg_crc
|
||||
|
||||
result = self._send_frame(frame)
|
||||
|
||||
while not result:
|
||||
backoff = TIMEOUT * self._bit_time * (4 + priority + random.randint(1,5))
|
||||
self._debug("Collision occured backing off %f" % backoff)
|
||||
time.sleep(backoff)
|
||||
result = self._send_frame(frame)
|
||||
|
||||
|
||||
def has_message(self):
|
||||
return len(self._buffer) > 0
|
||||
|
||||
def read_message(self):
|
||||
while not self.has_message():
|
||||
self._read_frame()
|
||||
|
||||
return self._buffer.pop()
|
Loading…
Reference in New Issue