|
1 | 1 | # Modbus
|
2 | 2 | C++ Modbus parsing library.
|
3 | 3 |
|
4 |
| -# Setting Up a Slave Device |
5 |
| -The focus of this library is to allow for a serializable data types to be exposed over |
6 |
| -modbus in a clearer manner. Modbus registers are unsigned 16 bit values. Other data types |
7 |
| -need to be translated to these 16 bit values and back. The idea of this library is to use |
8 |
| -a user defined data store type that controls access to the underlying data types this is especially |
9 |
| -useful for preventing unaligned data accesses. |
10 |
| - |
11 |
| -A slave device is composed of controlled and data stores using C++ templates. |
12 |
| -Each slave needs a CoilController, HoldingRegisterController, DiscreteInputController, and an InputRegisterController. |
13 |
| -Each of these controls access to a data store. |
14 |
| -A simple data store for registers would just be an array of uint16_ts. For a coil or discrete input it would be a bit field. |
15 |
| -These basic data stores are available but are less powerful than creating a data store for the data types being used. |
16 |
| -The basic controller types are: |
17 |
| - |
18 |
| - using CoilController = |
19 |
| - Modbus::CoilController<Modbus::BitFieldDataStore<kCoilCount>>; |
20 |
| - using HoldingRegisterController = |
| 4 | +## Setting Up a Slave Device |
| 5 | +This library facilitates exposing serializable data types over Modbus in a more transparent manner. Modbus registers are 16-bit unsigned integers, and other data types must be translated into these 16-bit values and vice versa. The core concept of the library is to use a user-defined data store type that controls access to the underlying data types. This approach is especially useful for preventing unaligned data access. |
| 6 | + |
| 7 | +A slave device in this library consists of controlled data stores, managed via C++ templates. Each slave device requires a `CoilController`, `HoldingRegisterController`, `DiscreteInputController`, and an `InputRegisterController`, with each controller managing access to a specific data store. |
| 8 | + |
| 9 | +A basic data store for registers might simply be an array of `uint16_t` values, while coils or discrete inputs are typically stored as bit fields. Basic data stores are available, but more complex, user-defined data stores can provide additional flexibility and power. |
| 10 | + |
| 11 | +Example of basic controller types: |
| 12 | + |
| 13 | +```cpp |
| 14 | +using CoilController = |
| 15 | + Modbus::CoilController<Modbus::BitFieldDataStore<kCoilCount>>; |
| 16 | +using HoldingRegisterController = |
21 | 17 | Modbus::HoldingRegisterController<Modbus::RegisterDataStore<kHoldingRegisterCount>>;
|
22 |
| - using DiscreteInputController = |
23 |
| - Modbus::DiscreteInputController<Modbus::BitFieldDataStore<kDiscreteInputCount>>; |
24 |
| - using InputRegisterController = |
25 |
| - Modbus::InputRegisterController<Modbus::RegisterDataStore<kInputRegisterCount>>; |
| 18 | +using DiscreteInputController = |
| 19 | + Modbus::DiscreteInputController<Modbus::BitFieldDataStore<kDiscreteInputCount>>; |
| 20 | +using InputRegisterController = |
| 21 | + Modbus::InputRegisterController<Modbus::RegisterDataStore<kInputRegisterCount>>; |
| 22 | +```` |
26 | 23 |
|
27 |
| -## Specialized Data Stores |
28 |
| -Using mapped data stores allows the user to expose the actual data types being used with |
29 |
| -the serialization encapsulated. |
| 24 | +### Specialized Data Stores |
30 | 25 |
|
| 26 | +To expose actual data types with the serialization encapsulated, you can use mapped data stores. For instance: |
| 27 | + |
| 28 | +```cpp |
31 | 29 | using HoldingRegisterController =
|
32 | 30 | Modbus::HoldingRegisterController<Modbus::MappedRegisterDataStore<HoldingRegisters::MemoryMapController>>;
|
33 | 31 | using InputRegisterController =
|
34 | 32 | Modbus::InputRegisterController<Modbus::MappedRegisterDataStore<InputRegisters::MemoryMapController>>;
|
| 33 | +``` |
| 34 | + |
| 35 | +## Protocol Layer |
35 | 36 |
|
| 37 | +A slave device is managed by a subclass of `ProtocolRtuSlave`. The protocol layer handles: |
36 | 38 |
|
37 |
| -1. Subclass of ProtocolRtuSlave. |
38 |
| - - Timeouts |
39 |
| - - When to process packets |
40 |
| - - IO device control, this layer puts the recieved characters |
41 |
| - into the protocol and transmitting the generated responses |
42 |
| -2. Data Stores |
43 |
| - - The holding registers, coils, discrete inputs, and input registers are |
44 |
| - generalized as data stores. The default store mechanism can be used where |
45 |
| - the data store is literal, i.e. all bits and registers are stored |
46 |
| - and accessed directly. Other data stores might define a memory map |
47 |
| - that accesses system variables. This will lower memory use and remove the |
48 |
| - need to update the store or poll on changes. |
| 39 | +1. **Timeouts**: Managing the timeout between the master and slave. |
| 40 | +2. **Packet Processing**: Determining when to process incoming packets. |
| 41 | +3. **IO Device Control**: This layer receives characters and inputs them into the protocol, while also handling the transmission of generated responses. |
| 42 | + |
| 43 | +## Data Stores |
| 44 | + |
| 45 | +The Modbus data types—holding registers, coils, discrete inputs, and input registers—are treated as data stores. By default, the data store mechanism directly stores and accesses all bits and registers. However, it is possible to use specialized data stores that define a memory map to access system variables, which reduces memory usage and eliminates the need for periodic updates or polling. |
49 | 46 |
|
50 | 47 | ## Testing With Other Libraries
|
| 48 | + |
51 | 49 | ### Using socat
|
52 |
| -[socat][1] is an easy way to spoof a serial |
53 |
| -connection. A bidirectional pipe is setup allowing two processes |
54 |
| -to see the pipe as if it was an attached serial device. |
55 | 50 |
|
56 |
| -**Use** : |
| 51 | +[socat](http://www.dest-unreach.org/socat/) can simulate a serial connection between two processes. It creates a bidirectional pipe, allowing both processes to treat the pipe as if it were a physical serial device. |
| 52 | + |
| 53 | +**Example**: |
| 54 | + |
| 55 | +```bash |
57 | 56 | socat -d -d -d -x PTY,link=/tmp/ptyp0,raw,echo=0,ispeed=b9600 PTY,link=/tmp/ttyp0,raw,echo=0,ospeed=b9600
|
| 57 | +``` |
58 | 58 |
|
59 |
| -In this example the pipes are setup at /tmp/ptyp0 and /tmp/ttyp0 running at 9600 baud with some debug info (level set by the number of '-d'). |
60 |
| -The master is started on one pipe and the slave on another and can be treated as connected hardware. |
| 59 | +This command sets up two pipes at `/tmp/ptyp0` and `/tmp/ttyp0` running at 9600 baud with debug information. The master can be started on one pipe, and the slave on another, simulating a connected hardware setup. |
61 | 60 |
|
62 | 61 | ### Libmodbus
|
63 | 62 |
|
| 63 | +For more information on the Modbus protocol and a C library implementation, check out [Libmodbus](https://libmodbus.org/). |
| 64 | + |
64 | 65 | ### PyModbus
|
65 | 66 |
|
66 |
| -[1]: http://www.dest-unreach.org/socat/ |
67 |
| -[2]: https://libmodbus.org/ |
68 |
| -[3]: https://github.com/Jacajack/liblightmodbus |
69 |
| -[4]: https://github.com/riptideio/pymodbus |
| 67 | +PyModbus is a Python library that implements the Modbus protocol. It can be used to test and interface with the C++ slave. More information can be found on [GitHub](https://github.com/riptideio/pymodbus). |
| 68 | + |
| 69 | +## References |
| 70 | + |
| 71 | ++ [socat](http://www.dest-unreach.org/socat/) |
| 72 | ++ [Libmodbus](https://libmodbus.org/) |
| 73 | ++ [PyModbus GitHub](https://github.com/riptideio/pymodbus) |
| 74 | ++ [liblightmodbus](https://github.com/Jacajack/liblightmodbus) |
0 commit comments