kstmApp/src/drvAsynI2C.cpp
changeset 3 ec018606c207
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kstmApp/src/drvAsynI2C.cpp	Mon Jun 27 18:08:05 2016 +0200
@@ -0,0 +1,427 @@
+//******************************************************************************
+// Copyright (C) 2015 Florian Feldbauer <florian@ep1.ruhr-uni-bochum.de>
+//                    - University Mainz, Institute foer nuc
+//
+// This file is part of drvAsynCan
+//
+// drvAsynCan is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// drvAsynCan is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+// brief   AsynPortDriver for PANDA Raspberry Pi CAN interface
+//
+// version 3.0.0; Jul. 29, 2014
+//******************************************************************************
+
+//_____ I N C L U D E S _______________________________________________________
+
+// ANSI C includes
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+#include <fcntl.h>
+#include <linux/i2c-dev.h>
+//#include <linux/i2c.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+//#include <sys/select.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+// EPICS includes
+#include <epicsEvent.h>
+#include <epicsExport.h>
+#include <epicsMutex.h>
+#include <epicsString.h>
+#include <epicsThread.h>
+#include <epicsTime.h>
+#include <epicsTimer.h>
+#include <epicsTypes.h>
+#include <iocsh.h>
+
+// local includes
+#include "drvAsynI2C.h"
+
+//_____ D E F I N I T I O N S __________________________________________________
+
+//_____ G L O B A L S __________________________________________________________
+
+//_____ L O C A L S ____________________________________________________________
+static epicsTimerId       i2c_timer;
+static epicsTimerQueueId  i2c_timerQueue;
+
+//_____ F U N C T I O N S ______________________________________________________
+
+//------------------------------------------------------------------------------
+//! @brief       Timeout handler for I2C communication 
+//------------------------------------------------------------------------------
+static void timeoutHandler( void *ptr ) {
+  drvAsynI2C *pi2c = static_cast<drvAsynI2C*>( ptr );
+  pi2c->timeout();
+}
+
+//------------------------------------------------------------------------------
+//! @brief       Called when asyn clients call pasynOctet->read().
+//! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
+//! @param [out] value      Address of the string to read.
+//! @param [in]  maxChars   Maximum number of characters to read.
+//! @param [out] nActual    Number of characters actually read.
+//! @param [out] eomReason  Reason that read terminated.
+//! @return      in case of no error occured asynSuccess is returned. Otherwise
+//!              asynError or asynTimeout is returned. A error message is stored
+//!              in pasynUser->errorMessage.
+//! @sa          writeOctet
+//!
+//! Read a byte stream from the I2C bus.
+//! To set the correct slave address the pasynOctet->write() call has to be used.
+//! If the slave has multiple registers, use the write call to setup slave
+//! address and register address, followed by this read call.
+///------------------------------------------------------------------------------
+asynStatus drvAsynI2C::readOctet( asynUser *pasynUser, char *value, size_t maxChars,
+                                  size_t *nActual, int *eomReason ) {
+  if( _fd < 0 ) {
+    epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
+                   "%s: %s disconnected:", portName, _deviceName );
+    return asynError;
+  }
+  if( maxChars <= 0 ) {
+    epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
+                   "%s: %s maxchars %d Why <=0?", portName, _deviceName, (int)maxChars );
+    return asynError;
+  }
+
+  int thisRead = 0;
+  int nRead = 0;
+  bool timerStarted = false;
+  asynStatus status = asynSuccess;
+
+  _timeout = false;
+
+  if( eomReason ) *eomReason = 0;
+
+  for(;;) {
+    if( !timerStarted && pasynUser->timeout > 0 ) {
+      epicsTimerStartDelay( i2c_timer, pasynUser->timeout );
+      timerStarted = true;
+    }
+    thisRead = read( _fd, value, maxChars );
+    if( thisRead > 0 ) {
+      nRead = thisRead;
+      break;
+    } else {
+      if( thisRead < 0 && ( errno != EWOULDBLOCK )
+                       && ( errno != EINTR )
+                       && ( errno != EAGAIN ) ) {
+        epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
+                       "%s: %s read error: %s",
+                       portName, _deviceName, strerror( errno ) );
+        status = asynError;
+        break;
+      }
+    }
+    if( _timeout ) break;
+  }
+  if( timerStarted ) epicsTimerCancel( i2c_timer );
+  if( _timeout && asynSuccess == status ) status = asynTimeout;
+
+/*
+  int mytimeout = (int)( pasynUser->timeout * 1.e6 );
+  if ( 0 > mytimeout ) {
+
+    nbytes = read( _fd, value, maxChars );
+    if ( 0 > nbytes ) {
+      epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize, 
+                     "Error receiving message from device '%s': %d %s", 
+                     _deviceName, errno, strerror( errno ) );
+      return asynError;
+    }
+
+  } else {
+
+    fd_set fdRead;
+    struct timeval t;
+    
+    // calculate timeout values
+    t.tv_sec  = mytimeout / 1000000L;
+    t.tv_usec = mytimeout % 1000000L;
+    
+    FD_ZERO( &fdRead );
+    FD_SET( _fd, &fdRead );
+    
+    // wait until timeout or a message is ready to get read
+    int err = select( _fd + 1, &fdRead, NULL, NULL, &t );
+    
+    // the only one file descriptor is ready for read
+    if ( 0 < err ) {
+      nbytes = read( _fd, value, maxChars );
+      if ( 0 > nbytes ) {
+        epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize, 
+                       "Error receiving message from device '%s': %d %s", 
+                       _deviceName, errno, strerror( errno ) );
+        return asynError;
+      }
+    }
+    
+    // nothing is ready, timeout occured
+    if ( 0 == err ) return asynTimeout;
+    if ( 0 > err )  {
+      epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize, 
+                     "Error receiving message from device '%s': %d %s", 
+                     _deviceName, errno, strerror( errno ) );
+      return asynError;
+    }
+  }
+*/
+
+  *nActual = nRead;
+  if( eomReason && *nActual >= maxChars ) {
+    *eomReason = ASYN_EOM_CNT;
+  }
+
+  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER, 
+             "%s: read %lu bytes from %s, return %s\n",
+             portName, (unsigned long)*nActual, _deviceName,
+             pasynManager->strStatus( status ) );
+  
+  return status; 
+}
+
+//------------------------------------------------------------------------------
+//! @brief       Called when asyn clients call pasynOctet->write().
+//! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
+//! @param [in]  value      Address of the string to write.
+//! @param [in]  nChars     Number of characters to write.
+//! @param [out] nActual    Number of characters actually written.
+//! @return      in case of no error occured asynSuccess is returned. Otherwise
+//!              asynError or asynTimeout is returned. A error message is stored
+//!              in pasynUser->errorMessage.
+//!
+//! Write a byte stream to the i2c bus. First byte holds the slave address, followed
+//! by the acutal data to send.
+//! Example: Setting the configuration register of the AD7998 to convert all
+//! 8 channels and use the BUSY output:
+//! value = 0x20 0x02 0x0ffa
+//! Depending on the slave device, multiple write commands can be concatenated
+///------------------------------------------------------------------------------
+asynStatus drvAsynI2C::writeOctet( asynUser *pasynUser, char const *value, size_t maxChars,
+                                   size_t *nActual ){
+
+  int thisWrite = 0;
+  bool timerStarted = false;
+  asynStatus status = asynSuccess;
+
+  if( _fd < 0 ) {
+    epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
+                   "%s: %s disconnected:", portName, _deviceName );
+    return asynError;
+  }
+  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER, 
+             "drvAsynI2C::writeOctet: trying to send %u bytes\n",
+             maxChars );
+
+  if( 0 == maxChars ) {
+    *nActual = 0;
+    return asynSuccess;
+  }
+
+  _timeout = false;
+
+  printf( "drvAsynI2C::writeOctet: sending: ");
+  for( size_t viech = 0; viech < maxChars; ++viech ) {
+    printf( "0x%02x", value[viech] );
+  }
+  printf( "\n" );
+
+  int addr = value[0];
+  if( addr != _slaveAddress ) {
+    // set slave address
+   if( ioctl( _fd, I2C_SLAVE, addr ) < 0 ) {
+     epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
+                    "%s: %s Can't set slave address: %s",
+                    portName, _deviceName, strerror( errno ) );
+     return asynError;
+
+    _slaveAddress = addr;
+   }
+  }
+  ++value;
+  int nleft = maxChars - 1;
+  if( 0 < nleft ) {
+
+    if( pasynUser->timeout > 0 ) {
+      epicsTimerStartDelay( i2c_timer, pasynUser->timeout );
+      timerStarted = true;
+    }
+
+    for(;;) {
+      thisWrite = write( _fd, value, nleft );
+      if ( 0 < thisWrite ) {
+        nleft -= thisWrite;
+        if( 0 == nleft ) break;
+        value += thisWrite;
+      }
+      if( _timeout || 0 == pasynUser->timeout ) {
+        status = asynTimeout;
+        break;
+      }
+      if( 0 > thisWrite && ( errno != EWOULDBLOCK )
+                        && ( errno != EINTR )
+                        && ( errno != EAGAIN ) ) {
+        epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
+                       "%s: %s write error: %s",
+                       portName, _deviceName, strerror( errno ) );
+        status = asynError;
+        break;
+      }
+    }
+    if( timerStarted ) epicsTimerCancel( i2c_timer );
+
+  }
+
+  *nActual = maxChars - nleft;
+
+  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER, 
+             "%s: wrote %lu bytes to %s, return %s\n",
+             portName, (unsigned long)*nActual, _deviceName,
+             pasynManager->strStatus( status ) );
+  
+  return status; 
+}
+
+//------------------------------------------------------------------------------
+//! @brief       Connect driver to device
+//! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
+//! @return      in case of no error occured asynSuccess is returned. Otherwise
+//!              asynError or asynTimeout is returned. A error message is stored
+//!              in pasynUser->errorMessage.
+///------------------------------------------------------------------------------
+asynStatus drvAsynI2C::connect( asynUser *pasynUser ) {
+  if( _fd >= 0 ) {
+    epicsSnprintf( pasynUser->errorMessage,pasynUser->errorMessageSize,
+                   "%s: Link to %s already open!", portName, _deviceName );
+    return asynError;
+  }
+  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER,
+             "%s: Open connection to %s\n", portName, _deviceName );
+
+  if( ( _fd = open( _deviceName, O_RDWR ) ) < 0 ) {
+    epicsSnprintf( pasynUser->errorMessage,pasynUser->errorMessageSize,
+                   "%s: Can't open %s: %s", portName, _deviceName, strerror( errno ) );
+    return asynError;
+  }
+  if( ioctl( _fd, I2C_FUNCS, &_i2cfuncs ) < 0 ) {
+    epicsSnprintf( pasynUser->errorMessage,pasynUser->errorMessageSize,
+                   "%s: Can't get functionality of %s: %s", portName, _deviceName, strerror( errno ) );
+    return asynError;
+  }
+  pasynManager->exceptionConnect( pasynUser );
+  return asynSuccess;
+}
+
+//------------------------------------------------------------------------------
+//! @brief       Disconnect driver from device
+//! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
+//! @return      in case of no error occured asynSuccess is returned. Otherwise
+//!              asynError or asynTimeout is returned. A error message is stored
+//!              in pasynUser->errorMessage.
+///------------------------------------------------------------------------------
+asynStatus drvAsynI2C::disconnect( asynUser *pasynUser ) {
+  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER,
+             "%s: disconnect %s\n", portName, _deviceName );
+  epicsTimerCancel( i2c_timer );
+  if( _fd >= 0 ) {
+    close( _fd );
+    _fd = -1;
+    pasynManager->exceptionDisconnect(pasynUser);
+  } 
+  return asynSuccess;
+}
+
+//------------------------------------------------------------------------------
+//! @brief       Standard C'tor.
+//! @param [in]  portName The name of the asynPortDriver to be created.
+//! @param [in]  ttyName  The name of the device
+//------------------------------------------------------------------------------
+drvAsynI2C::drvAsynI2C( const char *portName, const char *ttyName ) 
+  : asynPortDriver( portName,
+                    0, // maxAddr
+                    0, // paramTableSize
+                    asynCommonMask | asynOctetMask | asynDrvUserMask, // Interface mask
+                    asynCommonMask | asynOctetMask,  // Interrupt mask
+                    ASYN_CANBLOCK, // asynFlags
+                    1,  // Autoconnect
+                    0,  // Default priority
+                    0 ) // Default stack size
+{
+  _deviceName = epicsStrDup( ttyName );
+  _fd = -1;
+  _slaveAddress = 0;
+//  if( ( _fd = open( _deviceName, O_RDWR ) ) < 0 ) {
+//    std::cerr << "Cannot open port" << std::endl;
+//    return;
+//  }
+//  if( ioctl( _fd, I2C_FUNCS, &_i2cfuncs ) < 0 ) {
+//    std::cerr << "Cannont get I2C_FUNCS" << std::endl;
+//    return;
+//  }
+}
+
+// Configuration routines.  Called directly, or from the iocsh function below 
+extern "C" {
+  //----------------------------------------------------------------------------
+  //! @brief       EPICS iocsh callable function to call constructor
+  //!              for the drvAsynI2C class.
+  //! @param [in]  portName The name of the asyn port driver to be created.
+  //! @param [in]  ttyName  The name of the interface 
+  //----------------------------------------------------------------------------
+  int drvAsynI2CConfigure( const char *portName, const char *ttyName ) {
+    if( !portName ) {
+      printf( "Port name missing.\n" );
+      return -1;
+    }
+    if( !ttyName ) {
+      printf( "TTY name missing.\n" );
+      return -1;
+    }
+    drvAsynI2C* pi2c = new drvAsynI2C( portName, ttyName );
+    i2c_timerQueue = epicsTimerQueueAllocate( 1, epicsThreadPriorityScanLow );
+    i2c_timer = epicsTimerQueueCreateTimer( i2c_timerQueue, timeoutHandler, pi2c );
+    if( !i2c_timer ) {
+      printf( "drvAsynI2C: Can't create timer.\n");
+      return -1;
+    }
+    return( asynSuccess );
+  }
+  static const iocshArg initI2CArg0 = { "portName", iocshArgString };
+  static const iocshArg initI2CArg1 = { "ttyName",  iocshArgString };
+  static const iocshArg * const initI2CArgs[] = { &initI2CArg0, &initI2CArg1 };
+  static const iocshFuncDef initI2CFuncDef = { "drvAsynI2CConfigure", 2, initI2CArgs };
+  static void initI2CCallFunc( const iocshArgBuf *args ) {
+    drvAsynI2CConfigure( args[0].sval, args[1].sval );
+  }
+
+  //----------------------------------------------------------------------------
+  //! @brief   Register functions to EPICS
+  //----------------------------------------------------------------------------
+  void drvAsynI2CRegister( void ) {
+    static int firstTime = 1;
+    if ( firstTime ) {
+      iocshRegister( &initI2CFuncDef, initI2CCallFunc );
+      firstTime = 0;
+    }
+  }
+  
+  epicsExportRegistrar( drvAsynI2CRegister );
+}
+