iop_serial.cpp
Go to the documentation of this file.
1/** @file iop_serial.h Process image via serial line */
2
3/*
4 FAU Discrete Event Systems Library (libfaudes)
5
6 Copyright (C) 2011, Thomas Moor.
7
8*/
9
10
11// include header
12#include "iop_serial.h"
13
14// only compile for use with spi configured
15#ifdef FAUDES_IODEVICE_SERIAL
16
17namespace faudes {
18
19/*
20 **********************************************
21 **********************************************
22 **********************************************
23
24 implementation: serial helpers
25
26 **********************************************
27 **********************************************
28 **********************************************
29 */
30
31
32// hardcoded block size (64bit)
33#define PSIZE 8
34
35// hardcoded timeout (usec) for serial transmission
36#define PUSECPB ((int) 1000000.0* (8.0+1.0+1.0) / 115200.0)
37#define PUSECXX PUSECPB
38
39
40// open serial port, return fd or -1 on error
41int serialOpen(const std::string& devname) {
42 // open device / det filedescriptor
43 int fd=-1;
44 fd = open(devname.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
45 if(fd == -1) return -1;
46 // set termio options (raw 115200 8N1, no flow control)
47 // see also "Serial Programming Guide for POSIX Operating Systems" by Michael R. Sweet
48 struct termios options;
49 tcgetattr(fd, &options);
50 cfsetispeed(&options, B115200);
51 cfsetospeed(&options, B115200);
52 options.c_cflag |= (CLOCAL | CREAD);
53 options.c_cflag &= ~CSIZE; // 8 data bit
54 options.c_cflag |= CS8;
55 options.c_cflag &= ~PARENB; // no parity
56 options.c_cflag &= ~CSTOPB; // one stop bit
57 options.c_cflag &= ~CRTSCTS; // no harware flow control
58 options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw input (no line editing, no signals)
59 options.c_iflag &= ~(IXON | IXOFF | IXANY); // no software flow control
60 options.c_iflag &= ~(INLCR | IGNCR | ICRNL | IUCLC | IMAXBEL); // no CR/LF mapping and thelike
61 options.c_oflag &= ~OPOST; // raw output (no delays after CR etc)
62 tcsetattr(fd, TCSANOW, &options);
63 // configure via fcntl
64 fcntl(fd, F_SETFL, FNDELAY); // nonblocking read (should be by open option anyway)
65 // done
66 return fd;
67}
68
69// close serial port
70void serialClose(int fd) {
71 close(fd);
72}
73
74// write block (-1 on error, else 0)
75int serialWriteBlock(int fd, char* data, int len) {
76 // debug
77 /*
78 static char mdata[1024];
79 if(len>1024) len=1024;
80 for(int i=0; i<len; i++) mdata[i]= data[i]+'A';
81 data=mdata;
82 */
83 while(len>0) {
84 int n=write(fd, data, len);
85 if(n<0) break;
86 len-=n;
87 }
88 if(len!=0)
89 FD_DH("spiDevice()::serialWriteBlock(): cannot transmit");
90 return len == 0 ? 0 : -1;
91}
92
93// flush input buffer
94void serialFlush(int fd) {
95 char d;
96 int cnt=0;
97 while(read(fd, &d,1)==1) cnt++;
98 if(cnt!=0) FD_DH("spiDevice()::serialFlush(): rec/drop #" << cnt);
99}
100
101// read block (return -1 on error, else 0)
102// Note: this fnct reads the data if the amount of characters available
103// exactly matches the specified block length; in any other case,
104// the read buffer is flushed and -1 is returned to indicate an
105// error.
106int serialReadBlock(int fd, char* data, int len) {
107 int n1= read(fd, data,len);
108 // error / no data at all
109 if(n1<=0) return -1;
110 // bytes missing, allow for transmission to complete
111 if(n1<len){
112 usleep( (len-n1)* PUSECPB + PUSECXX );
113 int n2= read(fd, data+n1,len-n1);
114 if(n2<=0) {
115 FD_DH("spiDevice()::serialReadBlock(): rec/drop #" << n1)
116 return -1;
117 }
118 n1+=n2;
119 }
120 // test for empty buffer
121 int n3= read(fd, data,1);
122 if(n3==1) {
123 FD_DH("spiDevice()::serialReadBlock(): rec/drop #" << n1);
124 serialFlush(fd);
125 return -1;
126 }
127 // success
128 return 0;
129}
130
131
132
133/*
134 **********************************************
135 **********************************************
136 **********************************************
137
138 implementation: spiDevice
139
140 **********************************************
141 **********************************************
142 **********************************************
143 */
144
145
146// std faudes, incl dummy
147FAUDES_TYPE_IMPLEMENTATION(SpiDevice,spiDevice,sDevice)
148
149// autoregister (not functional, see xdevice constructor)
150AutoRegisterType<spiDevice> gRtiRegisterSpiDevice("SpiDevice");
151
152//constructor
153spiDevice::spiDevice(void) : sDevice() {
154 FD_DHV("spiDevice(" << mName << ")::spiDevice()");
155 // have appropriate default label for token io
156 mDefaultLabel = "SpiDevice";
157 // pointer to internal I/O-image
158 mpImage=0;
159 pOutputImage=0;
160 pInputImage=0;
161 mpOutputMask=0;
162 // behavioural defaults
163 mMaster=false;
164 mSyncWrite=true;
165}
166
167//deconstructor
168spiDevice::~spiDevice(void) {
169 FD_DHV("spiDevice(" << mName << ")::~spiDevice()");
170 Stop();
171}
172
173// Clear
174void spiDevice::Clear(void) {
175 // clear base
176 sDevice::Clear();
177 // my configuration
178 mMaster=false;
179 mDeviceFiles.clear();
180 mPorts.clear();
181 mSyncWrite=true;
182}
183
184
185//Compile(void)
186void spiDevice::Compile(void){
187 //setup up internal data structure
188 FD_DHV("spiDevice(" << mName << ")::Compile()");
189 // call base
190 sDevice::Compile();
191 // test for illegal address range
192 if(mMaxBitAddress+1 > PSIZE*8) {
193 std::stringstream errstr;
194 errstr << "Invalid address range (must not exceed " << PSIZE*8 << ")";
195 throw Exception("spiDevice:Compile", errstr.str(), 52);
196 }
197 // slave must have exactly one serial device
198 if((!mMaster) && (mDeviceFiles.size()!=1)) {
199 std::stringstream errstr;
200 errstr << "Slave must have exactly one device file specified";
201 throw Exception("spiDevice:Compile()", errstr.str(), 52);
202 }
203 // master must have at least one serial device
204 if((mMaster) && (mDeviceFiles.size()==0)) {
205 std::stringstream errstr;
206 errstr << "Master must have at least one device file specified";
207 throw Exception("spiDevice:Compile()", errstr.str(), 52);
208 }
209}
210
211
212//DoWrite(rTr,rLabel,pContext)
213void spiDevice::DoWritePreface(TokenWriter& rTw, const std::string& rLabel, const Type* pContext) const {
214 FD_DHV("spiDevice("<<mName<<")::DoWritePreface()");
215 //call base
216 sDevice::DoWritePreface(rTw,"",pContext);
217 // role
218 Token ftoken;
219 ftoken.SetEmpty("Role");
220 if(mMaster) {
221 ftoken.InsAttributeString("value","master");
222 } else {
223 ftoken.InsAttributeString("value","slave");
224 }
225 rTw << ftoken;
226 // devicefiles
227 for(unsigned int i=0; i<mDeviceFiles.size(); i++) {
228 Token dtoken;
229 ftoken.SetEmpty("DeviceFile");
230 ftoken.InsAttributeString("value",mDeviceFiles.at(i));
231 rTw << ftoken;
232 }
233}
234
235
236//DoReadPreface(rTr,rLabel,pContext)
237void spiDevice::DoReadPreface(TokenReader& rTr,const std::string& rLabel, const Type* pContext){
238 //dummy for token-input
239 FD_DHV("spiDevice("<<mName<<")::DoReadPreface()");
240 //call base
241 sDevice::DoReadPreface(rTr,"",pContext);
242 // my global configuration
243 Token token;
244 while(rTr.Peek(token)) {
245 // role
246 if(token.IsBegin("Role")) {
247 rTr.ReadBegin("Role");
248 mMaster=false;
249 if(!token.ExistsAttributeString("value")) {
250 std::stringstream errstr;
251 errstr << "Invalid role tag" << rTr.FileLine();
252 throw Exception("spiDevice:Read", errstr.str(), 52);
253 }
254 std::string val=token.AttributeStringValue("value");
255 std::transform(val.begin(), val.end(), val.begin(), tolower);
256 if(val=="master") mMaster =true;
257 else if(val=="slave") mMaster =false;
258 else {
259 std::stringstream errstr;
260 errstr << "Invalid role tag" << rTr.FileLine();
261 throw Exception("spiDevice:Read", errstr.str(), 52);
262 }
263 rTr.ReadEnd("Role");
264 continue;
265 }
266 // device file
267 if(token.IsBegin("DeviceFile")) {
268 rTr.ReadBegin("DeviceFile");
269 if(!token.ExistsAttributeString("value")) {
270 std::stringstream errstr;
271 errstr << "Invalid device tag" << rTr.FileLine();
272 throw Exception("spiDevice:Read", errstr.str(), 52);
273 }
274 mDeviceFiles.push_back(token.AttributeStringValue("value"));
275 rTr.ReadEnd("DeviceFile");
276 continue;
277 }
278 // unknown: break
279 break;
280 }
281}
282
283
284// Start(void)
285void spiDevice::Start(void) {
286 //open wago-device
287 if(mState!=Down) return;
288 FD_DH("spiDevice(" << mName << ")::Start(): open devices #" << mDeviceFiles.size());
289 // initialize serial line(s)
290 for(unsigned int i=0; i<mDeviceFiles.size(); i++) {
291 int fd=serialOpen(mDeviceFiles.at(i));
292 if(fd<0) {
293 std::stringstream errstr;
294 errstr << "cannot open serial line " << mDeviceFiles.at(i);
295 throw Exception("spiDevice()::Start()", errstr.str(), 552);
296 }
297 mPorts.push_back(fd);
298 }
299 // initialize images
300 mpImage = new char[PSIZE];
301 memset(mpImage,0,PSIZE);
302 pOutputImage=mpImage;
303 pInputImage=mpImage;
304 // initialize output mask
305 mpOutputMask = new char[PSIZE];
306 memset(mpOutputMask,0,PSIZE);
307 for(int bit=0; bit<=mMaxBitAddress; bit++)
308 if(!mOutputLevelIndexMap[bit].Empty())
309 mpOutputMask[bit/8] |= (0x01 << (bit %8));
310 // call base (incl. reset)
311 sDevice::Start();
312 // pessimistic: let background thread figure presence of other nodes
313 mState=StartUp;
314}
315
316// Stop()
317void spiDevice::Stop(void) {
318 //close serial interface
319 if(mState != Up && mState != StartUp) return;
320 FD_DHV("spiDevice(" << mName << ")::Stop()");
321 // call base
322 sDevice::Stop();
323 // close lines
324 for(unsigned int i=0; i< mPorts.size(); i++)
325 serialClose(mPorts.at(i));
326 mPorts.clear();
327 // invalidate images
328 if(mpImage) delete mpImage;
329 mpImage=0;
330 pOutputImage=0;
331 pInputImage=0;
332 if(mpOutputMask) delete mpOutputMask;
333 mpOutputMask=0;
334 // down
335 mState=Down;
336}
337
338
339// loopcall-back for serial comminucation
340void spiDevice::DoLoopCallback(void) {
341 // bail out
342 if(mState!=Up && mState!=StartUp) return;
343 //FD_DHV("spiDevice(" << mName << ")::DoLoopCallBack()");
344 // master: send image block to each client and await reply
345 if(mMaster) {
346 FD_DHV("spiDevice()::DoLoopCallBack(): master send images #" << mPorts.size());
347 for(unsigned int i=0; i< mPorts.size(); i++) {
348 // discard input buffer
349 serialFlush(mPorts.at(i));
350 // write
351 serialWriteBlock(mPorts.at(i),mpImage,PSIZE);
352 // await reply
353 usleep(3000); // hardcoded, conservative ... here the cheap design shows :-(
354 char buffer[PSIZE];
355 int err=serialReadBlock(mPorts.at(i),buffer,PSIZE);
356 if(err!=0) continue;
357 FD_DHV("spiDevice()::DoLoopCallBack(): master received image");
358 // copy to my image, except for my outputs
359 for(int i=0; i<PSIZE; i++)
360 mpImage[i]= (buffer[i] & ~mpOutputMask[i]) | (mpImage[i] & mpOutputMask[i]);
361 }
362 }
363 // slave: receive image block if available, send reply
364 if(!mMaster) {
365 //FD_DHV("spiDevice()::DoLoopCallBack(): slave await images #" << mPorts.size());
366 // test for image
367 char buffer[PSIZE];
368 int err=serialReadBlock(mPorts.at(0),buffer,PSIZE);
369 if(err==0) {
370 FD_DHV("spiDevice()::DoLoopCallBack(): slave received image");
371 // copy to my image, except for my outputs
372 for(int i=0; i<PSIZE; i++)
373 mpImage[i]= (buffer[i] & ~mpOutputMask[i]) | (mpImage[i] & mpOutputMask[i]);
374 // write
375 serialWriteBlock(mPorts.at(0),mpImage,PSIZE);
376 }
377 } // end: slave
378}
379
380
381// DoReadSignalsPre(void)
382bool spiDevice::DoReadSignalsPre(void) {
383 return pInputImage!=0;
384}
385
386
387// DoReadSignalsPost(void)
388void spiDevice::DoReadSignalsPost(void) {
389}
390
391
392//ReadSignal(int)
393bool spiDevice::DoReadSignal(int bit){
394 // Read one input value, addressed by bit number (0 to 63);
395
396 // Determine byte and bit address
397 int byte = bit / 8;
398 bit = bit % 8;
399
400 // Read bit
401 return ( pInputImage[byte] & (0x01 << (bit)) ) != 0x00;
402}
403
404
405// DoWriteSignalsPre(void)
406bool spiDevice::DoWriteSignalsPre(void) {
407 return pOutputImage!=0;
408}
409
410// DoWrtieSignalsPost(void)
411void spiDevice::DoWriteSignalsPost(void) {
412}
413
414
415//DoWriteSignal(int,int)
416void spiDevice::DoWriteSignal(int bit, bool value){
417
418 // Write one actor value, adressed by bit number (0 to 63);
419 FD_DHV("spiDevice("<<mName<<")::DoWriteSignal(" << bit << ", " << value <<")");
420
421 // Determine byte and bit addresse.
422 int byte = (bit) / 8;
423 bit = (bit) % 8;
424
425 // Write value to output-image using bit-operations
426 if(value) pOutputImage[byte] |= (0x01 << (bit));
427 else pOutputImage[byte] &= ~(0x01 << (bit));
428
429}
430
431
432} // namespace
433
434
435
436#endif // end serial support
#define FAUDES_TYPE_IMPLEMENTATION(ftype, ctype, cbase)
Definition cfl_types.h:958
#define FD_DHV(message)
Definition iop_vdevice.h:37
#define FD_DH(message)
Definition iop_vdevice.h:27
AutoRegisterType< mbDevice > gRtiRegisterSpiDevice("ModbusDevice")

libFAUDES 2.33k --- 2025.09.16 --- c++ api documentaion by doxygen