iop_serial.cppGo to the documentation of this file.00001 /** @file iop_serial.h Process image via serial line */ 00002 00003 /* 00004 FAU Discrete Event Systems Library (libfaudes) 00005 00006 Copyright (C) 2011, Thomas Moor. 00007 00008 */ 00009 00010 00011 // include header 00012 #include "iop_serial.h" 00013 00014 // only compile for use with spi configured 00015 #ifdef FAUDES_IODEVICE_SERIAL 00016 00017 namespace faudes { 00018 00019 /* 00020 ********************************************** 00021 ********************************************** 00022 ********************************************** 00023 00024 implementation: serial helpers 00025 00026 ********************************************** 00027 ********************************************** 00028 ********************************************** 00029 */ 00030 00031 00032 // hardcoded block size (64bit) 00033 #define PSIZE 8 00034 00035 // hardcoded timeout (usec) for serial transmission 00036 #define PUSECPB ((int) 1000000.0* (8.0+1.0+1.0) / 115200.0) 00037 #define PUSECXX PUSECPB 00038 00039 00040 // open serial port, return fd or -1 on error 00041 int serialOpen(const std::string& devname) { 00042 // open device / det filedescriptor 00043 int fd=-1; 00044 fd = open(devname.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); 00045 if(fd == -1) return -1; 00046 // set termio options (raw 115200 8N1, no flow control) 00047 // see also "Serial Programming Guide for POSIX Operating Systems" by Michael R. Sweet 00048 struct termios options; 00049 tcgetattr(fd, &options); 00050 cfsetispeed(&options, B115200); 00051 cfsetospeed(&options, B115200); 00052 options.c_cflag |= (CLOCAL | CREAD); 00053 options.c_cflag &= ~CSIZE; // 8 data bit 00054 options.c_cflag |= CS8; 00055 options.c_cflag &= ~PARENB; // no parity 00056 options.c_cflag &= ~CSTOPB; // one stop bit 00057 options.c_cflag &= ~CRTSCTS; // no harware flow control 00058 options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw input (no line editing, no signals) 00059 options.c_iflag &= ~(IXON | IXOFF | IXANY); // no software flow control 00060 options.c_iflag &= ~(INLCR | IGNCR | ICRNL | IUCLC | IMAXBEL); // no CR/LF mapping and thelike 00061 options.c_oflag &= ~OPOST; // raw output (no delays after CR etc) 00062 tcsetattr(fd, TCSANOW, &options); 00063 // configure via fcntl 00064 fcntl(fd, F_SETFL, FNDELAY); // nonblocking read (should be by open option anyway) 00065 // done 00066 return fd; 00067 } 00068 00069 // close serial port 00070 void serialClose(int fd) { 00071 close(fd); 00072 } 00073 00074 // write block (-1 on error, else 0) 00075 int serialWriteBlock(int fd, char* data, int len) { 00076 // debug 00077 /* 00078 static char mdata[1024]; 00079 if(len>1024) len=1024; 00080 for(int i=0; i<len; i++) mdata[i]= data[i]+'A'; 00081 data=mdata; 00082 */ 00083 while(len>0) { 00084 int n=write(fd, data, len); 00085 if(n<0) break; 00086 len-=n; 00087 } 00088 if(len!=0) 00089 FD_DH("spiDevice()::serialWriteBlock(): cannot transmit"); 00090 return len == 0 ? 0 : -1; 00091 } 00092 00093 // flush input buffer 00094 void serialFlush(int fd) { 00095 char d; 00096 int cnt=0; 00097 while(read(fd, &d,1)==1) cnt++; 00098 if(cnt!=0) FD_DH("spiDevice()::serialFlush(): rec/drop #" << cnt); 00099 } 00100 00101 // read block (return -1 on error, else 0) 00102 // Note: this fnct reads the data if the amount of characters available 00103 // exactly matches the specified block length; in any other case, 00104 // the read buffer is flushed and -1 is returned to indicate an 00105 // error. 00106 int serialReadBlock(int fd, char* data, int len) { 00107 int n1= read(fd, data,len); 00108 // error / no data at all 00109 if(n1<=0) return -1; 00110 // bytes missing, allow for transmission to complete 00111 if(n1<len){ 00112 usleep( (len-n1)* PUSECPB + PUSECXX ); 00113 int n2= read(fd, data+n1,len-n1); 00114 if(n2<=0) { 00115 FD_DH("spiDevice()::serialReadBlock(): rec/drop #" << n1) 00116 return -1; 00117 } 00118 n1+=n2; 00119 } 00120 // test for empty buffer 00121 int n3= read(fd, data,1); 00122 if(n3==1) { 00123 FD_DH("spiDevice()::serialReadBlock(): rec/drop #" << n1); 00124 serialFlush(fd); 00125 return -1; 00126 } 00127 // success 00128 return 0; 00129 } 00130 00131 00132 00133 /* 00134 ********************************************** 00135 ********************************************** 00136 ********************************************** 00137 00138 implementation: spiDevice 00139 00140 ********************************************** 00141 ********************************************** 00142 ********************************************** 00143 */ 00144 00145 00146 // std faudes, incl dummy 00147 FAUDES_TYPE_IMPLEMENTATION(SpiDevice,spiDevice,sDevice) 00148 00149 // autoregister (not functional, see xdevice constructor) 00150 AutoRegisterType<spiDevice> gRtiRegisterSpiDevice("SpiDevice"); 00151 00152 //constructor 00153 spiDevice::spiDevice(void) : sDevice() { 00154 FD_DHV("spiDevice(" << mName << ")::spiDevice()"); 00155 // have appropriate default label for token io 00156 mDefaultLabel = "SpiDevice"; 00157 // pointer to internal I/O-image 00158 mpImage=0; 00159 pOutputImage=0; 00160 pInputImage=0; 00161 mpOutputMask=0; 00162 // behavioural defaults 00163 mMaster=false; 00164 mSyncWrite=true; 00165 } 00166 00167 //deconstructor 00168 spiDevice::~spiDevice(void) { 00169 FD_DHV("spiDevice(" << mName << ")::~spiDevice()"); 00170 Stop(); 00171 } 00172 00173 // Clear 00174 void spiDevice::Clear(void) { 00175 // clear base 00176 sDevice::Clear(); 00177 // my configuration 00178 mMaster=false; 00179 mDeviceFiles.clear(); 00180 mPorts.clear(); 00181 mSyncWrite=true; 00182 } 00183 00184 00185 //Compile(void) 00186 void spiDevice::Compile(void){ 00187 //setup up internal data structure 00188 FD_DHV("spiDevice(" << mName << ")::Compile()"); 00189 // call base 00190 sDevice::Compile(); 00191 // test for illegal address range 00192 if(mMaxBitAddress+1 > PSIZE*8) { 00193 std::stringstream errstr; 00194 errstr << "Invalid address range (must not exceed " << PSIZE*8 << ")"; 00195 throw Exception("spiDevice:Compile", errstr.str(), 52); 00196 } 00197 // slave must have exactly one serial device 00198 if((!mMaster) && (mDeviceFiles.size()!=1)) { 00199 std::stringstream errstr; 00200 errstr << "Slave must have exactly one device file specified"; 00201 throw Exception("spiDevice:Compile()", errstr.str(), 52); 00202 } 00203 // master must have at least one serial device 00204 if((mMaster) && (mDeviceFiles.size()==0)) { 00205 std::stringstream errstr; 00206 errstr << "Master must have at least one device file specified"; 00207 throw Exception("spiDevice:Compile()", errstr.str(), 52); 00208 } 00209 } 00210 00211 00212 //DoWrite(rTr,rLabel,pContext) 00213 void spiDevice::DoWritePreface(TokenWriter& rTw, const std::string& rLabel, const Type* pContext) const { 00214 FD_DHV("spiDevice("<<mName<<")::DoWritePreface()"); 00215 //call base 00216 sDevice::DoWritePreface(rTw,"",pContext); 00217 // role 00218 Token ftoken; 00219 ftoken.SetEmpty("Role"); 00220 if(mMaster) { 00221 ftoken.InsAttributeString("value","master"); 00222 } else { 00223 ftoken.InsAttributeString("value","slave"); 00224 } 00225 rTw << ftoken; 00226 // devicefiles 00227 for(unsigned int i=0; i<mDeviceFiles.size(); i++) { 00228 Token dtoken; 00229 ftoken.SetEmpty("DeviceFile"); 00230 ftoken.InsAttributeString("value",mDeviceFiles.at(i)); 00231 rTw << ftoken; 00232 } 00233 } 00234 00235 00236 //DoReadPreface(rTr,rLabel,pContext) 00237 void spiDevice::DoReadPreface(TokenReader& rTr,const std::string& rLabel, const Type* pContext){ 00238 //dummy for token-input 00239 FD_DHV("spiDevice("<<mName<<")::DoReadPreface()"); 00240 //call base 00241 sDevice::DoReadPreface(rTr,"",pContext); 00242 // my global configuration 00243 Token token; 00244 while(rTr.Peek(token)) { 00245 // role 00246 if(token.IsBegin("Role")) { 00247 rTr.ReadBegin("Role"); 00248 mMaster=false; 00249 if(!token.ExistsAttributeString("value")) { 00250 std::stringstream errstr; 00251 errstr << "Invalid role tag" << rTr.FileLine(); 00252 throw Exception("spiDevice:Read", errstr.str(), 52); 00253 } 00254 std::string val=token.AttributeStringValue("value"); 00255 std::transform(val.begin(), val.end(), val.begin(), tolower); 00256 if(val=="master") mMaster =true; 00257 else if(val=="slave") mMaster =false; 00258 else { 00259 std::stringstream errstr; 00260 errstr << "Invalid role tag" << rTr.FileLine(); 00261 throw Exception("spiDevice:Read", errstr.str(), 52); 00262 } 00263 rTr.ReadEnd("Role"); 00264 continue; 00265 } 00266 // device file 00267 if(token.IsBegin("DeviceFile")) { 00268 rTr.ReadBegin("DeviceFile"); 00269 if(!token.ExistsAttributeString("value")) { 00270 std::stringstream errstr; 00271 errstr << "Invalid device tag" << rTr.FileLine(); 00272 throw Exception("spiDevice:Read", errstr.str(), 52); 00273 } 00274 mDeviceFiles.push_back(token.AttributeStringValue("value")); 00275 rTr.ReadEnd("DeviceFile"); 00276 continue; 00277 } 00278 // unknown: break 00279 break; 00280 } 00281 } 00282 00283 00284 // Start(void) 00285 void spiDevice::Start(void) { 00286 //open wago-device 00287 if(mState!=Down) return; 00288 FD_DH("spiDevice(" << mName << ")::Start(): open devices #" << mDeviceFiles.size()); 00289 // initialize serial line(s) 00290 for(unsigned int i=0; i<mDeviceFiles.size(); i++) { 00291 int fd=serialOpen(mDeviceFiles.at(i)); 00292 if(fd<0) { 00293 std::stringstream errstr; 00294 errstr << "cannot open serial line " << mDeviceFiles.at(i); 00295 throw Exception("spiDevice()::Start()", errstr.str(), 552); 00296 } 00297 mPorts.push_back(fd); 00298 } 00299 // initialize images 00300 mpImage = new char[PSIZE]; 00301 memset(mpImage,0,PSIZE); 00302 pOutputImage=mpImage; 00303 pInputImage=mpImage; 00304 // initialize output mask 00305 mpOutputMask = new char[PSIZE]; 00306 memset(mpOutputMask,0,PSIZE); 00307 for(int bit=0; bit<=mMaxBitAddress; bit++) 00308 if(!mOutputLevelIndexMap[bit].Empty()) 00309 mpOutputMask[bit/8] |= (0x01 << (bit %8)); 00310 // call base (incl. reset) 00311 sDevice::Start(); 00312 // pessimistic: let background thread figure presence of other nodes 00313 mState=StartUp; 00314 } 00315 00316 // Stop() 00317 void spiDevice::Stop(void) { 00318 //close serial interface 00319 if(mState != Up && mState != StartUp) return; 00320 FD_DHV("spiDevice(" << mName << ")::Stop()"); 00321 // call base 00322 sDevice::Stop(); 00323 // close lines 00324 for(unsigned int i=0; i< mPorts.size(); i++) 00325 serialClose(mPorts.at(i)); 00326 mPorts.clear(); 00327 // invalidate images 00328 if(mpImage) delete mpImage; 00329 mpImage=0; 00330 pOutputImage=0; 00331 pInputImage=0; 00332 if(mpOutputMask) delete mpOutputMask; 00333 mpOutputMask=0; 00334 // down 00335 mState=Down; 00336 } 00337 00338 00339 // loopcall-back for serial comminucation 00340 void spiDevice::DoLoopCallback(void) { 00341 // bail out 00342 if(mState!=Up && mState!=StartUp) return; 00343 //FD_DHV("spiDevice(" << mName << ")::DoLoopCallBack()"); 00344 // master: send image block to each client and await reply 00345 if(mMaster) { 00346 FD_DHV("spiDevice()::DoLoopCallBack(): master send images #" << mPorts.size()); 00347 for(unsigned int i=0; i< mPorts.size(); i++) { 00348 // discard input buffer 00349 serialFlush(mPorts.at(i)); 00350 // write 00351 serialWriteBlock(mPorts.at(i),mpImage,PSIZE); 00352 // await reply 00353 usleep(3000); // hardcoded, conservative ... here the cheap design shows :-( 00354 char buffer[PSIZE]; 00355 int err=serialReadBlock(mPorts.at(i),buffer,PSIZE); 00356 if(err!=0) continue; 00357 FD_DHV("spiDevice()::DoLoopCallBack(): master received image"); 00358 // copy to my image, except for my outputs 00359 for(int i=0; i<PSIZE; i++) 00360 mpImage[i]= (buffer[i] & ~mpOutputMask[i]) | (mpImage[i] & mpOutputMask[i]); 00361 } 00362 } 00363 // slave: receive image block if available, send reply 00364 if(!mMaster) { 00365 //FD_DHV("spiDevice()::DoLoopCallBack(): slave await images #" << mPorts.size()); 00366 // test for image 00367 char buffer[PSIZE]; 00368 int err=serialReadBlock(mPorts.at(0),buffer,PSIZE); 00369 if(err==0) { 00370 FD_DHV("spiDevice()::DoLoopCallBack(): slave received image"); 00371 // copy to my image, except for my outputs 00372 for(int i=0; i<PSIZE; i++) 00373 mpImage[i]= (buffer[i] & ~mpOutputMask[i]) | (mpImage[i] & mpOutputMask[i]); 00374 // write 00375 serialWriteBlock(mPorts.at(0),mpImage,PSIZE); 00376 } 00377 } // end: slave 00378 } 00379 00380 00381 // DoReadSignalsPre(void) 00382 bool spiDevice::DoReadSignalsPre(void) { 00383 return pInputImage!=0; 00384 } 00385 00386 00387 // DoReadSignalsPost(void) 00388 void spiDevice::DoReadSignalsPost(void) { 00389 } 00390 00391 00392 //ReadSignal(int) 00393 bool spiDevice::DoReadSignal(int bit){ 00394 // Read one input value, addressed by bit number (0 to 63); 00395 00396 // Determine byte and bit address 00397 int byte = bit / 8; 00398 bit = bit % 8; 00399 00400 // Read bit 00401 return ( pInputImage[byte] & (0x01 << (bit)) ) != 0x00; 00402 } 00403 00404 00405 // DoWriteSignalsPre(void) 00406 bool spiDevice::DoWriteSignalsPre(void) { 00407 return pOutputImage!=0; 00408 } 00409 00410 // DoWrtieSignalsPost(void) 00411 void spiDevice::DoWriteSignalsPost(void) { 00412 } 00413 00414 00415 //DoWriteSignal(int,int) 00416 void spiDevice::DoWriteSignal(int bit, bool value){ 00417 00418 // Write one actor value, adressed by bit number (0 to 63); 00419 FD_DHV("spiDevice("<<mName<<")::DoWriteSignal(" << bit << ", " << value <<")"); 00420 00421 // Determine byte and bit addresse. 00422 int byte = (bit) / 8; 00423 bit = (bit) % 8; 00424 00425 // Write value to output-image using bit-operations 00426 if(value) pOutputImage[byte] |= (0x01 << (bit)); 00427 else pOutputImage[byte] &= ~(0x01 << (bit)); 00428 00429 } 00430 00431 00432 } // namespace 00433 00434 00435 00436 #endif // end serial support libFAUDES 2.23h --- 2014.04.03 --- c++ api documentaion by doxygen |