kstmApp/src/drvAsynI2C.cpp
changeset 3 ec018606c207
equal deleted inserted replaced
2:3b3af1ff2783 3:ec018606c207
       
     1 //******************************************************************************
       
     2 // Copyright (C) 2015 Florian Feldbauer <florian@ep1.ruhr-uni-bochum.de>
       
     3 //                    - University Mainz, Institute foer nuc
       
     4 //
       
     5 // This file is part of drvAsynCan
       
     6 //
       
     7 // drvAsynCan is free software; you can redistribute it and/or modify
       
     8 // it under the terms of the GNU General Public License as published by
       
     9 // the Free Software Foundation; either version 3 of the License, or
       
    10 // (at your option) any later version.
       
    11 //
       
    12 // drvAsynCan is distributed in the hope that it will be useful,
       
    13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    15 // GNU General Public License for more details.
       
    16 //
       
    17 // You should have received a copy of the GNU General Public License
       
    18 // along with this program; if not, write to the Free Software
       
    19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
       
    20 //
       
    21 // brief   AsynPortDriver for PANDA Raspberry Pi CAN interface
       
    22 //
       
    23 // version 3.0.0; Jul. 29, 2014
       
    24 //******************************************************************************
       
    25 
       
    26 //_____ I N C L U D E S _______________________________________________________
       
    27 
       
    28 // ANSI C includes
       
    29 #include <cstdio>
       
    30 #include <cstdlib>
       
    31 #include <cstring>
       
    32 #include <cerrno>
       
    33 #include <fcntl.h>
       
    34 #include <linux/i2c-dev.h>
       
    35 //#include <linux/i2c.h>
       
    36 #include <sys/ioctl.h>
       
    37 #include <sys/types.h>
       
    38 //#include <sys/select.h>
       
    39 #include <sys/stat.h>
       
    40 #include <unistd.h>
       
    41 
       
    42 // EPICS includes
       
    43 #include <epicsEvent.h>
       
    44 #include <epicsExport.h>
       
    45 #include <epicsMutex.h>
       
    46 #include <epicsString.h>
       
    47 #include <epicsThread.h>
       
    48 #include <epicsTime.h>
       
    49 #include <epicsTimer.h>
       
    50 #include <epicsTypes.h>
       
    51 #include <iocsh.h>
       
    52 
       
    53 // local includes
       
    54 #include "drvAsynI2C.h"
       
    55 
       
    56 //_____ D E F I N I T I O N S __________________________________________________
       
    57 
       
    58 //_____ G L O B A L S __________________________________________________________
       
    59 
       
    60 //_____ L O C A L S ____________________________________________________________
       
    61 static epicsTimerId       i2c_timer;
       
    62 static epicsTimerQueueId  i2c_timerQueue;
       
    63 
       
    64 //_____ F U N C T I O N S ______________________________________________________
       
    65 
       
    66 //------------------------------------------------------------------------------
       
    67 //! @brief       Timeout handler for I2C communication 
       
    68 //------------------------------------------------------------------------------
       
    69 static void timeoutHandler( void *ptr ) {
       
    70   drvAsynI2C *pi2c = static_cast<drvAsynI2C*>( ptr );
       
    71   pi2c->timeout();
       
    72 }
       
    73 
       
    74 //------------------------------------------------------------------------------
       
    75 //! @brief       Called when asyn clients call pasynOctet->read().
       
    76 //! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
       
    77 //! @param [out] value      Address of the string to read.
       
    78 //! @param [in]  maxChars   Maximum number of characters to read.
       
    79 //! @param [out] nActual    Number of characters actually read.
       
    80 //! @param [out] eomReason  Reason that read terminated.
       
    81 //! @return      in case of no error occured asynSuccess is returned. Otherwise
       
    82 //!              asynError or asynTimeout is returned. A error message is stored
       
    83 //!              in pasynUser->errorMessage.
       
    84 //! @sa          writeOctet
       
    85 //!
       
    86 //! Read a byte stream from the I2C bus.
       
    87 //! To set the correct slave address the pasynOctet->write() call has to be used.
       
    88 //! If the slave has multiple registers, use the write call to setup slave
       
    89 //! address and register address, followed by this read call.
       
    90 ///------------------------------------------------------------------------------
       
    91 asynStatus drvAsynI2C::readOctet( asynUser *pasynUser, char *value, size_t maxChars,
       
    92                                   size_t *nActual, int *eomReason ) {
       
    93   if( _fd < 0 ) {
       
    94     epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
       
    95                    "%s: %s disconnected:", portName, _deviceName );
       
    96     return asynError;
       
    97   }
       
    98   if( maxChars <= 0 ) {
       
    99     epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
       
   100                    "%s: %s maxchars %d Why <=0?", portName, _deviceName, (int)maxChars );
       
   101     return asynError;
       
   102   }
       
   103 
       
   104   int thisRead = 0;
       
   105   int nRead = 0;
       
   106   bool timerStarted = false;
       
   107   asynStatus status = asynSuccess;
       
   108 
       
   109   _timeout = false;
       
   110 
       
   111   if( eomReason ) *eomReason = 0;
       
   112 
       
   113   for(;;) {
       
   114     if( !timerStarted && pasynUser->timeout > 0 ) {
       
   115       epicsTimerStartDelay( i2c_timer, pasynUser->timeout );
       
   116       timerStarted = true;
       
   117     }
       
   118     thisRead = read( _fd, value, maxChars );
       
   119     if( thisRead > 0 ) {
       
   120       nRead = thisRead;
       
   121       break;
       
   122     } else {
       
   123       if( thisRead < 0 && ( errno != EWOULDBLOCK )
       
   124                        && ( errno != EINTR )
       
   125                        && ( errno != EAGAIN ) ) {
       
   126         epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
       
   127                        "%s: %s read error: %s",
       
   128                        portName, _deviceName, strerror( errno ) );
       
   129         status = asynError;
       
   130         break;
       
   131       }
       
   132     }
       
   133     if( _timeout ) break;
       
   134   }
       
   135   if( timerStarted ) epicsTimerCancel( i2c_timer );
       
   136   if( _timeout && asynSuccess == status ) status = asynTimeout;
       
   137 
       
   138 /*
       
   139   int mytimeout = (int)( pasynUser->timeout * 1.e6 );
       
   140   if ( 0 > mytimeout ) {
       
   141 
       
   142     nbytes = read( _fd, value, maxChars );
       
   143     if ( 0 > nbytes ) {
       
   144       epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize, 
       
   145                      "Error receiving message from device '%s': %d %s", 
       
   146                      _deviceName, errno, strerror( errno ) );
       
   147       return asynError;
       
   148     }
       
   149 
       
   150   } else {
       
   151 
       
   152     fd_set fdRead;
       
   153     struct timeval t;
       
   154     
       
   155     // calculate timeout values
       
   156     t.tv_sec  = mytimeout / 1000000L;
       
   157     t.tv_usec = mytimeout % 1000000L;
       
   158     
       
   159     FD_ZERO( &fdRead );
       
   160     FD_SET( _fd, &fdRead );
       
   161     
       
   162     // wait until timeout or a message is ready to get read
       
   163     int err = select( _fd + 1, &fdRead, NULL, NULL, &t );
       
   164     
       
   165     // the only one file descriptor is ready for read
       
   166     if ( 0 < err ) {
       
   167       nbytes = read( _fd, value, maxChars );
       
   168       if ( 0 > nbytes ) {
       
   169         epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize, 
       
   170                        "Error receiving message from device '%s': %d %s", 
       
   171                        _deviceName, errno, strerror( errno ) );
       
   172         return asynError;
       
   173       }
       
   174     }
       
   175     
       
   176     // nothing is ready, timeout occured
       
   177     if ( 0 == err ) return asynTimeout;
       
   178     if ( 0 > err )  {
       
   179       epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize, 
       
   180                      "Error receiving message from device '%s': %d %s", 
       
   181                      _deviceName, errno, strerror( errno ) );
       
   182       return asynError;
       
   183     }
       
   184   }
       
   185 */
       
   186 
       
   187   *nActual = nRead;
       
   188   if( eomReason && *nActual >= maxChars ) {
       
   189     *eomReason = ASYN_EOM_CNT;
       
   190   }
       
   191 
       
   192   asynPrint( pasynUser, ASYN_TRACEIO_DRIVER, 
       
   193              "%s: read %lu bytes from %s, return %s\n",
       
   194              portName, (unsigned long)*nActual, _deviceName,
       
   195              pasynManager->strStatus( status ) );
       
   196   
       
   197   return status; 
       
   198 }
       
   199 
       
   200 //------------------------------------------------------------------------------
       
   201 //! @brief       Called when asyn clients call pasynOctet->write().
       
   202 //! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
       
   203 //! @param [in]  value      Address of the string to write.
       
   204 //! @param [in]  nChars     Number of characters to write.
       
   205 //! @param [out] nActual    Number of characters actually written.
       
   206 //! @return      in case of no error occured asynSuccess is returned. Otherwise
       
   207 //!              asynError or asynTimeout is returned. A error message is stored
       
   208 //!              in pasynUser->errorMessage.
       
   209 //!
       
   210 //! Write a byte stream to the i2c bus. First byte holds the slave address, followed
       
   211 //! by the acutal data to send.
       
   212 //! Example: Setting the configuration register of the AD7998 to convert all
       
   213 //! 8 channels and use the BUSY output:
       
   214 //! value = 0x20 0x02 0x0ffa
       
   215 //! Depending on the slave device, multiple write commands can be concatenated
       
   216 ///------------------------------------------------------------------------------
       
   217 asynStatus drvAsynI2C::writeOctet( asynUser *pasynUser, char const *value, size_t maxChars,
       
   218                                    size_t *nActual ){
       
   219 
       
   220   int thisWrite = 0;
       
   221   bool timerStarted = false;
       
   222   asynStatus status = asynSuccess;
       
   223 
       
   224   if( _fd < 0 ) {
       
   225     epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
       
   226                    "%s: %s disconnected:", portName, _deviceName );
       
   227     return asynError;
       
   228   }
       
   229   asynPrint( pasynUser, ASYN_TRACEIO_DRIVER, 
       
   230              "drvAsynI2C::writeOctet: trying to send %u bytes\n",
       
   231              maxChars );
       
   232 
       
   233   if( 0 == maxChars ) {
       
   234     *nActual = 0;
       
   235     return asynSuccess;
       
   236   }
       
   237 
       
   238   _timeout = false;
       
   239 
       
   240   printf( "drvAsynI2C::writeOctet: sending: ");
       
   241   for( size_t viech = 0; viech < maxChars; ++viech ) {
       
   242     printf( "0x%02x", value[viech] );
       
   243   }
       
   244   printf( "\n" );
       
   245 
       
   246   int addr = value[0];
       
   247   if( addr != _slaveAddress ) {
       
   248     // set slave address
       
   249    if( ioctl( _fd, I2C_SLAVE, addr ) < 0 ) {
       
   250      epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
       
   251                     "%s: %s Can't set slave address: %s",
       
   252                     portName, _deviceName, strerror( errno ) );
       
   253      return asynError;
       
   254 
       
   255     _slaveAddress = addr;
       
   256    }
       
   257   }
       
   258   ++value;
       
   259   int nleft = maxChars - 1;
       
   260   if( 0 < nleft ) {
       
   261 
       
   262     if( pasynUser->timeout > 0 ) {
       
   263       epicsTimerStartDelay( i2c_timer, pasynUser->timeout );
       
   264       timerStarted = true;
       
   265     }
       
   266 
       
   267     for(;;) {
       
   268       thisWrite = write( _fd, value, nleft );
       
   269       if ( 0 < thisWrite ) {
       
   270         nleft -= thisWrite;
       
   271         if( 0 == nleft ) break;
       
   272         value += thisWrite;
       
   273       }
       
   274       if( _timeout || 0 == pasynUser->timeout ) {
       
   275         status = asynTimeout;
       
   276         break;
       
   277       }
       
   278       if( 0 > thisWrite && ( errno != EWOULDBLOCK )
       
   279                         && ( errno != EINTR )
       
   280                         && ( errno != EAGAIN ) ) {
       
   281         epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
       
   282                        "%s: %s write error: %s",
       
   283                        portName, _deviceName, strerror( errno ) );
       
   284         status = asynError;
       
   285         break;
       
   286       }
       
   287     }
       
   288     if( timerStarted ) epicsTimerCancel( i2c_timer );
       
   289 
       
   290   }
       
   291 
       
   292   *nActual = maxChars - nleft;
       
   293 
       
   294   asynPrint( pasynUser, ASYN_TRACEIO_DRIVER, 
       
   295              "%s: wrote %lu bytes to %s, return %s\n",
       
   296              portName, (unsigned long)*nActual, _deviceName,
       
   297              pasynManager->strStatus( status ) );
       
   298   
       
   299   return status; 
       
   300 }
       
   301 
       
   302 //------------------------------------------------------------------------------
       
   303 //! @brief       Connect driver to device
       
   304 //! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
       
   305 //! @return      in case of no error occured asynSuccess is returned. Otherwise
       
   306 //!              asynError or asynTimeout is returned. A error message is stored
       
   307 //!              in pasynUser->errorMessage.
       
   308 ///------------------------------------------------------------------------------
       
   309 asynStatus drvAsynI2C::connect( asynUser *pasynUser ) {
       
   310   if( _fd >= 0 ) {
       
   311     epicsSnprintf( pasynUser->errorMessage,pasynUser->errorMessageSize,
       
   312                    "%s: Link to %s already open!", portName, _deviceName );
       
   313     return asynError;
       
   314   }
       
   315   asynPrint( pasynUser, ASYN_TRACEIO_DRIVER,
       
   316              "%s: Open connection to %s\n", portName, _deviceName );
       
   317 
       
   318   if( ( _fd = open( _deviceName, O_RDWR ) ) < 0 ) {
       
   319     epicsSnprintf( pasynUser->errorMessage,pasynUser->errorMessageSize,
       
   320                    "%s: Can't open %s: %s", portName, _deviceName, strerror( errno ) );
       
   321     return asynError;
       
   322   }
       
   323   if( ioctl( _fd, I2C_FUNCS, &_i2cfuncs ) < 0 ) {
       
   324     epicsSnprintf( pasynUser->errorMessage,pasynUser->errorMessageSize,
       
   325                    "%s: Can't get functionality of %s: %s", portName, _deviceName, strerror( errno ) );
       
   326     return asynError;
       
   327   }
       
   328   pasynManager->exceptionConnect( pasynUser );
       
   329   return asynSuccess;
       
   330 }
       
   331 
       
   332 //------------------------------------------------------------------------------
       
   333 //! @brief       Disconnect driver from device
       
   334 //! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
       
   335 //! @return      in case of no error occured asynSuccess is returned. Otherwise
       
   336 //!              asynError or asynTimeout is returned. A error message is stored
       
   337 //!              in pasynUser->errorMessage.
       
   338 ///------------------------------------------------------------------------------
       
   339 asynStatus drvAsynI2C::disconnect( asynUser *pasynUser ) {
       
   340   asynPrint( pasynUser, ASYN_TRACEIO_DRIVER,
       
   341              "%s: disconnect %s\n", portName, _deviceName );
       
   342   epicsTimerCancel( i2c_timer );
       
   343   if( _fd >= 0 ) {
       
   344     close( _fd );
       
   345     _fd = -1;
       
   346     pasynManager->exceptionDisconnect(pasynUser);
       
   347   } 
       
   348   return asynSuccess;
       
   349 }
       
   350 
       
   351 //------------------------------------------------------------------------------
       
   352 //! @brief       Standard C'tor.
       
   353 //! @param [in]  portName The name of the asynPortDriver to be created.
       
   354 //! @param [in]  ttyName  The name of the device
       
   355 //------------------------------------------------------------------------------
       
   356 drvAsynI2C::drvAsynI2C( const char *portName, const char *ttyName ) 
       
   357   : asynPortDriver( portName,
       
   358                     0, // maxAddr
       
   359                     0, // paramTableSize
       
   360                     asynCommonMask | asynOctetMask | asynDrvUserMask, // Interface mask
       
   361                     asynCommonMask | asynOctetMask,  // Interrupt mask
       
   362                     ASYN_CANBLOCK, // asynFlags
       
   363                     1,  // Autoconnect
       
   364                     0,  // Default priority
       
   365                     0 ) // Default stack size
       
   366 {
       
   367   _deviceName = epicsStrDup( ttyName );
       
   368   _fd = -1;
       
   369   _slaveAddress = 0;
       
   370 //  if( ( _fd = open( _deviceName, O_RDWR ) ) < 0 ) {
       
   371 //    std::cerr << "Cannot open port" << std::endl;
       
   372 //    return;
       
   373 //  }
       
   374 //  if( ioctl( _fd, I2C_FUNCS, &_i2cfuncs ) < 0 ) {
       
   375 //    std::cerr << "Cannont get I2C_FUNCS" << std::endl;
       
   376 //    return;
       
   377 //  }
       
   378 }
       
   379 
       
   380 // Configuration routines.  Called directly, or from the iocsh function below 
       
   381 extern "C" {
       
   382   //----------------------------------------------------------------------------
       
   383   //! @brief       EPICS iocsh callable function to call constructor
       
   384   //!              for the drvAsynI2C class.
       
   385   //! @param [in]  portName The name of the asyn port driver to be created.
       
   386   //! @param [in]  ttyName  The name of the interface 
       
   387   //----------------------------------------------------------------------------
       
   388   int drvAsynI2CConfigure( const char *portName, const char *ttyName ) {
       
   389     if( !portName ) {
       
   390       printf( "Port name missing.\n" );
       
   391       return -1;
       
   392     }
       
   393     if( !ttyName ) {
       
   394       printf( "TTY name missing.\n" );
       
   395       return -1;
       
   396     }
       
   397     drvAsynI2C* pi2c = new drvAsynI2C( portName, ttyName );
       
   398     i2c_timerQueue = epicsTimerQueueAllocate( 1, epicsThreadPriorityScanLow );
       
   399     i2c_timer = epicsTimerQueueCreateTimer( i2c_timerQueue, timeoutHandler, pi2c );
       
   400     if( !i2c_timer ) {
       
   401       printf( "drvAsynI2C: Can't create timer.\n");
       
   402       return -1;
       
   403     }
       
   404     return( asynSuccess );
       
   405   }
       
   406   static const iocshArg initI2CArg0 = { "portName", iocshArgString };
       
   407   static const iocshArg initI2CArg1 = { "ttyName",  iocshArgString };
       
   408   static const iocshArg * const initI2CArgs[] = { &initI2CArg0, &initI2CArg1 };
       
   409   static const iocshFuncDef initI2CFuncDef = { "drvAsynI2CConfigure", 2, initI2CArgs };
       
   410   static void initI2CCallFunc( const iocshArgBuf *args ) {
       
   411     drvAsynI2CConfigure( args[0].sval, args[1].sval );
       
   412   }
       
   413 
       
   414   //----------------------------------------------------------------------------
       
   415   //! @brief   Register functions to EPICS
       
   416   //----------------------------------------------------------------------------
       
   417   void drvAsynI2CRegister( void ) {
       
   418     static int firstTime = 1;
       
   419     if ( firstTime ) {
       
   420       iocshRegister( &initI2CFuncDef, initI2CCallFunc );
       
   421       firstTime = 0;
       
   422     }
       
   423   }
       
   424   
       
   425   epicsExportRegistrar( drvAsynI2CRegister );
       
   426 }
       
   427