Lydia - Printhead
ModbusMaster.h
Go to the documentation of this file.
1 #pragma once
2 #include <mbed.h>
3 
4 template <size_t rxBufSize>
5 struct TransactionSerial : SerialBase, NonCopyable<TransactionSerial<rxBufSize>> {
6  enum struct Result : uint8_t {
7  success = 0x00,
8  timeout = 0xE0,
9  busy = 0xE1,
10  };
11 
12  EventQueue* queue;
13  Semaphore txnLock; // Mutex doesn't work for some reason...
14  Timeout frameTimeout;
15 
16  std::chrono::milliseconds rxTimeout;
17  std::chrono::microseconds frameDelim;
18  size_t rxIdx = 0;
19  uint8_t rxBuf[rxBufSize] = {};
20  int timeoutEvent = 0;
21 
22  Callback<void()> preTransmit = NULL;
23  Callback<void()> postTransmit = NULL;
24  Callback<void(Result)> complete = NULL;
25 
27  EventQueue* queue,
28  PinName txPin,
29  PinName rxPin,
30  int baud,
31  std::chrono::microseconds frameDelim,
32  std::chrono::milliseconds rxTimeout = 50ms)
33  : SerialBase(txPin, rxPin, baud),
34  queue(queue),
35  txnLock(1, 1),
38  }
39 
40  inline void setTimeout(std::chrono::milliseconds t) { rxTimeout = t; };
41  inline void attachPreTransmit(Callback<void()> f) { preTransmit = f; };
42  inline void attachPostTransmit(Callback<void()> f) { postTransmit = f; };
43 
44  void rxHandler() {
45  if(!readable()) return;
46  if(rxIdx >= rxBufSize) rxIdx = 0;
47  lock();
48  rxBuf[rxIdx++] = _base_getc();
49  unlock();
50  frameTimeout.attach([this]() {
51  queue->call([this]() {
52  if(txnLock.try_acquire()) { // Guard rx outside txn
53  txnLock.release();
54  return;
55  }
57  });
58  }, frameDelim);
59  }
60 
62  attach(NULL);
63  queue->cancel(timeoutEvent);
64  txnLock.release();
66  }
67 
68  void timeoutHandler() {
69  attach(NULL);
70  frameTimeout.detach();
71  txnLock.release();
73  }
74 
75  void transact(uint8_t* txBuf, int txLen, Callback<void(Result)> cb) {
76  if (!txnLock.try_acquire()) return cb(Result::busy);
77  rxIdx = 0;
78  complete = cb;
79  timeoutEvent = queue->call_in(rxTimeout, [this]() { timeoutHandler(); });
80  if (preTransmit) preTransmit();
81  write(txBuf, txLen, [this](int e){
82  attach([this](){ rxHandler(); });
83  if(postTransmit) queue->call(postTransmit);
84  }, SERIAL_EVENT_TX_COMPLETE);
85  }
86 };
87 
88 template <size_t txBufSize, size_t rxBufSize>
89 struct ModbusMaster : NonCopyable<ModbusMaster<txBufSize, rxBufSize>> {
90  enum struct Result : uint8_t {
91  success = 0x00,
92  // modbus exceptions
93  illegalFunction = 0x01,
94  illegalDataAddress = 0x02,
95  illegalDataValue = 0x03,
96  slaveDeviceFailure = 0x04,
97  acknowledge = 0x05,
98  slaveDeviceBusy = 0x06,
99  negativeAcknowledge = 0x07,
100  memoryParityError = 0x08,
101  // response errors
102  timeout = 0xE0,
103  busy = 0xE1,
104  incompleteResponse = 0xE2,
105  invalidSlaveId = 0xE3,
106  invalidFunction = 0xE4,
107  invalidCRC = 0xE5,
108  };
109 
110  enum struct FC : uint8_t { // Modbus Function Codes
111  none = 0x00,
112  // bit access
113  readCoils = 0x01,
114  readDiscreteInputs = 0x02,
115  writeSingleCoil = 0x05,
116  writeMultipleCoils = 0x0F,
117  // word access
118  readHoldingRegisters = 0x03,
119  readInputRegisters = 0x04,
120  writeSingleRegister = 0x06,
121  writeMultipleRegisters = 0x10,
122  };
123 
125  typedef Callback<void(Result)> CB;
126 
128  uint8_t slaveId;
129  bool checkCrc = true;
130  CB complete = NULL;
131  Callback<uint16_t(uint8_t* adu, uint16_t len)> postReceive = NULL;
132 
133  size_t txIdx = 0;
134  uint8_t adu[txBufSize];
136 
137  inline ModbusMaster(
138  EventQueue* queue,
139  PinName txPin,
140  PinName rxPin,
141  int baud,
142  uint8_t slaveId,
143  std::chrono::milliseconds rxTimeout = 50ms)
144  : slaveId(slaveId),
145  stx(queue, txPin, rxPin, baud, std::chrono::microseconds((35 * MODBUS_MAX_LIFESPAN00) / baud), rxTimeout) {
146  }
147 
148  inline void setSlaveId(uint8_t id) { slaveId = id; };
149  inline void setTimeout(std::chrono::milliseconds t) { stx.setTimeout(t); };
150  inline void setCrcCheck(bool c) { checkCrc = c; };
151  inline void attachPreTransmit(Callback<void()> f) { stx.attachPreTransmit(f); };
152  inline void attachPostTransmit(Callback<void()> f) { stx.attachPostTransmit(f); };
153  inline void attachPostReceive(Callback<uint16_t(uint8_t* adu, uint16_t len)> f) { postReceive = f; };
154 
155  inline uint8_t* getCoils() { return stx.rxBuf + 3; };
156  inline uint16_t* getRegisters() { return reinterpret_cast<uint16_t*>(stx.rxBuf + 3); }
157 
158  void writeUInt16(uint16_t val) {
159  adu[txIdx++] = val >> 8;
160  adu[txIdx++] = val;
161  }
162 
163  uint32_t crc16(uint8_t* buf, int len) {
164  uint32_t crc = 0xFFFF;
165  for (int pos = 0; pos < len; pos++) {
166  crc ^= (uint32_t) buf[pos]; // XOR byte into least sig. byte of crc
167  for (int i = 8; i != 0; i--) { // Loop over each bit
168  if ((crc & 0x0001) != 0) { // If the LSB is set
169  crc >>= 1; // Shift right and XOR 0xA001
170  crc ^= 0xA001;
171  } else { // Else LSB is not set
172  crc >>= 1; // Just shift right
173  }
174  }
175  }
176  return crc;
177  }
178 
179  void transaction(FC fc, uint16_t addr, uint16_t num, uint8_t* val, CB cb) {
180  txIdx = 0;
181  reqFc = fc;
182  complete = cb;
183 
184  // Build Request
185  // ==============================================
186  adu[txIdx++] = slaveId;
187  adu[txIdx++] = static_cast<uint8_t>(fc);
188  writeUInt16(addr);
189  switch(fc) {
190  case FC::readCoils:
196  writeUInt16(num);
197  default:
198  break;
199  }
200  uint8_t payloadLen = 0;
201  switch(fc) {
202  case FC::writeSingleCoil:
203  payloadLen = 2;
204  break;
206  payloadLen = (num / 8) + 1;
207  adu[txIdx++] = payloadLen;
208  break;
210  payloadLen = 2;
211  break;
213  payloadLen = num * 2;
214  adu[txIdx++] = payloadLen;
215  break;
216  default:
217  break;
218  }
219  for(int i = 0; i < payloadLen; i++) adu[txIdx++] = val[i];
220  uint32_t crc = crc16(adu, txIdx);
221  adu[txIdx++] = crc;
222  adu[txIdx++] = crc >> 8;
223 
224  // Serial Transaction
225  // ============================================
226  stx.transact(adu, txIdx, [this](auto result) {
227  if(result != STX::Result::success) {
228  complete((Result) result);
229  return;
230  }
231 
232  uint32_t rxLen = stx.rxIdx;
233  if(postReceive) rxLen = postReceive(stx.rxBuf, rxLen);
234 
235  if(rxLen < 4) {
237  return;
238  }
239 
240  if(checkCrc) {
241  uint32_t compCRC = crc16(stx.rxBuf, rxLen - 2);
242  uint16_t recvCRC = stx.rxBuf[rxLen - 2] | (stx.rxBuf[rxLen - 1] << 8);
243  if(recvCRC != compCRC) {
245  return;
246  }
247  }
248 
249  if(stx.rxBuf[0] != slaveId) {
251  return;
252  }
253 
254  if(static_cast<FC>(stx.rxBuf[1] & 0x7F) != reqFc) {
256  return;
257  }
258 
259  if(stx.rxBuf[1] & 0x80) { // Exception
260  complete(static_cast<Result>(stx.rxBuf[2]));
261  return;
262  }
263 
264  switch(reqFc) {
266  case FC::readInputRegisters: {
267  uint16_t* reg = getRegisters();
268  uint8_t regCount = (rxLen - 5) / 2;
269  for(int i = 0; i < regCount; i++) {
270  reg[i] = __builtin_bswap16(reg[i]);
271  }
272  }
273  default:
274  break;
275  }
276 
278  });
279  }
280 
281  void readCoils(uint16_t addr, uint16_t num, CB cb) {
282  transaction(FC::readCoils, addr, num, NULL, cb);
283  }
284  void readDiscreteInputs(uint16_t addr, uint16_t num, CB cb) {
285  transaction(FC::readDiscreteInputs, addr, num, NULL, cb);
286  }
287  void writeSingleCoil(uint16_t addr, bool val, CB cb) {
288  uint16_t payload = __builtin_bswap16(val ? 0xFF00 : 0x0000);
289  transaction(FC::writeSingleCoil, addr, 1, reinterpret_cast<uint8_t*>(&payload), cb);
290  }
291  void writeMultipleCoils(uint16_t addr, uint16_t num, uint8_t* val, CB cb) {
292  transaction(FC::writeMultipleCoils, addr, num, val, cb);
293  }
294 
295  void readHoldingRegisters(uint16_t addr, uint16_t num, CB cb) {
296  transaction(FC::readHoldingRegisters, addr, num, NULL, cb);
297  }
298  void readInputRegisters(uint16_t addr, uint16_t num, CB cb) {
299  transaction(FC::readInputRegisters, addr, num, NULL, cb);
300  }
301  void writeSingleRegister(uint16_t addr, uint16_t val, CB cb) {
302  val = __builtin_bswap16(val);
303  uint8_t* payload = reinterpret_cast<uint8_t*>(&val);
304  transaction(FC::writeSingleRegister, addr, 1, payload, cb);
305  }
306  void writeMultipleRegisters(uint16_t addr, uint16_t num, uint16_t* val, CB cb) {
307  for(int i = 0; i < num; i++) val[i] = __builtin_bswap16(val[i]);
308  uint8_t* payload = reinterpret_cast<uint8_t*>(val);
309  transaction(FC::writeMultipleRegisters, addr, num, payload, cb);
310  }
311 
312 };
ModbusMaster::readCoils
void readCoils(uint16_t addr, uint16_t num, CB cb)
Definition: ModbusMaster.h:281
ModbusMaster::writeMultipleRegisters
void writeMultipleRegisters(uint16_t addr, uint16_t num, uint16_t *val, CB cb)
Definition: ModbusMaster.h:306
ModbusMaster::setCrcCheck
void setCrcCheck(bool c)
Definition: ModbusMaster.h:150
ModbusMaster::getCoils
uint8_t * getCoils()
Definition: ModbusMaster.h:155
ModbusMaster::Result::illegalDataValue
@ illegalDataValue
ModbusMaster::FC::readInputRegisters
@ readInputRegisters
ModbusMaster::attachPostReceive
void attachPostReceive(Callback< uint16_t(uint8_t *adu, uint16_t len)> f)
Definition: ModbusMaster.h:153
ModbusMaster::Result::success
@ success
TransactionSerial::Result::busy
@ busy
ModbusMaster::Result::slaveDeviceFailure
@ slaveDeviceFailure
ModbusMaster::Result
Result
Definition: ModbusMaster.h:90
ModbusMaster::attachPreTransmit
void attachPreTransmit(Callback< void()> f)
Definition: ModbusMaster.h:151
ModbusMaster::FC::writeSingleCoil
@ writeSingleCoil
ModbusMaster::stx
STX stx
Definition: ModbusMaster.h:127
ModbusMaster::transaction
void transaction(FC fc, uint16_t addr, uint16_t num, uint8_t *val, CB cb)
Definition: ModbusMaster.h:179
ModbusMaster::writeMultipleCoils
void writeMultipleCoils(uint16_t addr, uint16_t num, uint8_t *val, CB cb)
Definition: ModbusMaster.h:291
ModbusMaster::Result::slaveDeviceBusy
@ slaveDeviceBusy
TransactionSerial::rxBuf
uint8_t rxBuf[rxBufSize]
Definition: ModbusMaster.h:19
ModbusMaster::FC::writeMultipleRegisters
@ writeMultipleRegisters
TransactionSerial::Result::timeout
@ timeout
ModbusMaster::Result::invalidCRC
@ invalidCRC
ModbusMaster::FC::readDiscreteInputs
@ readDiscreteInputs
TransactionSerial::txnLock
Semaphore txnLock
Definition: ModbusMaster.h:13
ModbusMaster::postReceive
Callback< uint16_t(uint8_t *adu, uint16_t len)> postReceive
Definition: ModbusMaster.h:131
ModbusMaster::FC::writeMultipleCoils
@ writeMultipleCoils
TransactionSerial::attachPreTransmit
void attachPreTransmit(Callback< void()> f)
Definition: ModbusMaster.h:41
ModbusMaster::Result::busy
@ busy
ModbusMaster::Result::incompleteResponse
@ incompleteResponse
ModbusMaster::Result::timeout
@ timeout
TransactionSerial::queue
EventQueue * queue
Definition: ModbusMaster.h:12
ModbusMaster::FC::readHoldingRegisters
@ readHoldingRegisters
TransactionSerial::frameDelim
std::chrono::microseconds frameDelim
Definition: ModbusMaster.h:17
ModbusMaster::Result::invalidSlaveId
@ invalidSlaveId
ModbusMaster::getRegisters
uint16_t * getRegisters()
Definition: ModbusMaster.h:156
ModbusMaster::ModbusMaster
ModbusMaster(EventQueue *queue, PinName txPin, PinName rxPin, int baud, uint8_t slaveId, std::chrono::milliseconds rxTimeout=50ms)
Definition: ModbusMaster.h:137
TransactionSerial::rxHandler
void rxHandler()
Definition: ModbusMaster.h:44
TransactionSerial::rxIdx
size_t rxIdx
Definition: ModbusMaster.h:18
TransactionSerial::frameTimeout
Timeout frameTimeout
Definition: ModbusMaster.h:14
ModbusMaster::Result::illegalFunction
@ illegalFunction
ModbusMaster::Result::memoryParityError
@ memoryParityError
TransactionSerial::timeoutEvent
int timeoutEvent
Definition: ModbusMaster.h:20
ModbusMaster::slaveId
uint8_t slaveId
Definition: ModbusMaster.h:128
ModbusMaster::FC::writeSingleRegister
@ writeSingleRegister
ModbusMaster::FC
FC
Definition: ModbusMaster.h:110
TransactionSerial::attachPostTransmit
void attachPostTransmit(Callback< void()> f)
Definition: ModbusMaster.h:42
ModbusMaster::writeSingleRegister
void writeSingleRegister(uint16_t addr, uint16_t val, CB cb)
Definition: ModbusMaster.h:301
ModbusMaster::FC::readCoils
@ readCoils
TransactionSerial::transact
void transact(uint8_t *txBuf, int txLen, Callback< void(Result)> cb)
Definition: ModbusMaster.h:75
ModbusMaster::Result::acknowledge
@ acknowledge
ModbusMaster::reqFc
FC reqFc
Definition: ModbusMaster.h:135
TransactionSerial::setTimeout
void setTimeout(std::chrono::milliseconds t)
Definition: ModbusMaster.h:40
TransactionSerial::preTransmit
Callback< void()> preTransmit
Definition: ModbusMaster.h:22
ModbusMaster::FC::none
@ none
ModbusMaster::setTimeout
void setTimeout(std::chrono::milliseconds t)
Definition: ModbusMaster.h:149
ModbusMaster::complete
CB complete
Definition: ModbusMaster.h:130
TransactionSerial::Result
Result
Definition: ModbusMaster.h:6
ModbusMaster::checkCrc
bool checkCrc
Definition: ModbusMaster.h:129
ModbusMaster::readInputRegisters
void readInputRegisters(uint16_t addr, uint16_t num, CB cb)
Definition: ModbusMaster.h:298
ModbusMaster::STX
TransactionSerial< rxBufSize > STX
Definition: ModbusMaster.h:124
ModbusMaster
Definition: ModbusMaster.h:89
ModbusMaster::attachPostTransmit
void attachPostTransmit(Callback< void()> f)
Definition: ModbusMaster.h:152
TransactionSerial::rxTimeout
std::chrono::milliseconds rxTimeout
Definition: ModbusMaster.h:16
TransactionSerial::timeoutHandler
void timeoutHandler()
Definition: ModbusMaster.h:68
ModbusMaster::CB
Callback< void(Result)> CB
Definition: ModbusMaster.h:125
TransactionSerial::rxCompleteHandler
void rxCompleteHandler()
Definition: ModbusMaster.h:61
TransactionSerial::Result::success
@ success
ModbusMaster::adu
uint8_t adu[txBufSize]
Definition: ModbusMaster.h:134
ModbusMaster::readHoldingRegisters
void readHoldingRegisters(uint16_t addr, uint16_t num, CB cb)
Definition: ModbusMaster.h:295
ModbusMaster::Result::negativeAcknowledge
@ negativeAcknowledge
ModbusMaster::writeSingleCoil
void writeSingleCoil(uint16_t addr, bool val, CB cb)
Definition: ModbusMaster.h:287
ModbusMaster::crc16
uint32_t crc16(uint8_t *buf, int len)
Definition: ModbusMaster.h:163
ModbusMaster::Result::illegalDataAddress
@ illegalDataAddress
TransactionSerial::TransactionSerial
TransactionSerial(EventQueue *queue, PinName txPin, PinName rxPin, int baud, std::chrono::microseconds frameDelim, std::chrono::milliseconds rxTimeout=50ms)
Definition: ModbusMaster.h:26
TransactionSerial::postTransmit
Callback< void()> postTransmit
Definition: ModbusMaster.h:23
TransactionSerial::complete
Callback< void(Result)> complete
Definition: ModbusMaster.h:24
ModbusMaster::readDiscreteInputs
void readDiscreteInputs(uint16_t addr, uint16_t num, CB cb)
Definition: ModbusMaster.h:284
ModbusMaster::writeUInt16
void writeUInt16(uint16_t val)
Definition: ModbusMaster.h:158
TransactionSerial
Definition: ModbusMaster.h:5
ModbusMaster::Result::invalidFunction
@ invalidFunction
ModbusMaster::setSlaveId
void setSlaveId(uint8_t id)
Definition: ModbusMaster.h:148
ModbusMaster::txIdx
size_t txIdx
Definition: ModbusMaster.h:133