|
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 |