libcm is a C development framework with an emphasis on audio signal processing applications.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

cmSerialPort.c 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. //| Copyright: (C) 2009-2020 Kevin Larke <contact AT larke DOT org>
  2. //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
  3. #include "cmPrefix.h"
  4. #include "cmGlobal.h"
  5. #include "cmRpt.h"
  6. #include "cmErr.h"
  7. #include "cmCtx.h"
  8. #include "cmMem.h"
  9. #include "cmMallocDebug.h"
  10. #include "cmSerialPort.h"
  11. #include "cmThread.h"
  12. #include <poll.h>
  13. #include <termios.h>
  14. #include <unistd.h> // close()
  15. #include <fcntl.h> // O_RDWR
  16. #include <sys/ioctl.h> // TIOCEXCL
  17. typedef struct cmSerialPort_str
  18. {
  19. cmErr_t _err;
  20. cmThreadH_t _thH;
  21. const char* _deviceStr;
  22. int _deviceH;
  23. unsigned _baudRate;
  24. unsigned _cfgFlags;
  25. cmSeCallbackFunc_t _cbFunc;
  26. void* _cbArg;
  27. struct termios _ttyAttrs;
  28. struct pollfd _pollfd;
  29. unsigned _pollPeriodMs;
  30. } cmSerialPort_t;
  31. cmSerialPort_t* _cmSePtrFromHandle( cmSeH_t h )
  32. {
  33. cmSerialPort_t* p = (cmSerialPort_t*)h.h;
  34. assert(p!=NULL);
  35. return p;
  36. }
  37. void _cmSeSetClosedState( cmSerialPort_t* p )
  38. {
  39. if( p->_deviceStr != NULL )
  40. cmMemFree((char*)(p->_deviceStr));
  41. p->_deviceH = -1;
  42. p->_deviceStr = NULL;
  43. p->_baudRate = 0;
  44. p->_cfgFlags = 0;
  45. p->_cbFunc = NULL;
  46. p->_cbArg = NULL;
  47. }
  48. cmSeRC_t _cmSeGetAttributes( cmSerialPort_t* p, struct termios* attr )
  49. {
  50. if( tcgetattr(p->_deviceH, attr) == -1 )
  51. return cmErrSysMsg(&p->_err,kGetAttrFailSeRC,errno,"Error getting tty attributes from %s.",p->_deviceStr);
  52. return kOkSeRC;
  53. }
  54. cmSeRC_t _cmSePoll( cmSerialPort_t* p, unsigned timeOutMs )
  55. {
  56. cmSeRC_t rc = kOkSeRC;
  57. int sysRC;
  58. if((sysRC = poll(&p->_pollfd,1,timeOutMs)) == 0)
  59. rc = kTimeOutSeRC;
  60. else
  61. {
  62. if( sysRC < 0 )
  63. rc = cmErrSysMsg(&p->_err,kReadFailSeRC,errno,"Poll failed on serial port.");
  64. }
  65. return rc;
  66. }
  67. bool _cmSeThreadFunc(void* param)
  68. {
  69. cmSerialPort_t* p = (cmSerialPort_t*)param;
  70. cmSeH_t h;
  71. h.h = p;
  72. unsigned readN;
  73. if( cmSeIsOpen(h) )
  74. cmSeReceiveCbTimeOut(h,p->_pollPeriodMs,&readN);
  75. return true;
  76. }
  77. cmSeRC_t _cmSeDestroy( cmSerialPort_t* p )
  78. {
  79. cmSeRC_t rc = kOkSeRC;
  80. // stop the thread first
  81. if( cmThreadDestroy(&p->_thH) != kOkThRC )
  82. {
  83. rc = cmErrMsg(&p->_err,kThreadErrSeRC,"Thread destroy failed.");
  84. goto errLabel;
  85. }
  86. // Block until all written output has been sent from the device.
  87. // Note that this call is simply passed on to the serial device driver.
  88. // See tcsendbreak(3) ("man 3 tcsendbreak") for details.
  89. if (tcdrain(p->_deviceH) == -1)
  90. {
  91. rc = cmErrSysMsg(&p->_err,kFlushFailSeRC,errno,"Error waiting for serial device '%s' to drain.", p->_deviceStr );
  92. goto errLabel;
  93. }
  94. // It is good practice to reset a serial port back to the state in
  95. // which you found it. This is why we saved the original termios struct
  96. // The constant TCSANOW (defined in termios.h) indicates that
  97. // the change should take effect immediately.
  98. if (tcsetattr(p->_deviceH, TCSANOW, &p->_ttyAttrs) == -1)
  99. {
  100. rc = cmErrSysMsg(&p->_err,kSetAttrFailSeRC,errno,"Error resetting tty attributes on serial device '%s'.",p->_deviceStr);
  101. goto errLabel;
  102. }
  103. if( p->_deviceH != -1 )
  104. {
  105. if( close(p->_deviceH ) != 0 )
  106. {
  107. rc = cmErrSysMsg(&p->_err,kCloseFailSeRC,errno,"Port close failed on serial dvice '%s'.", p->_deviceStr);
  108. goto errLabel;
  109. }
  110. _cmSeSetClosedState(p);
  111. }
  112. cmMemPtrFree(&p);
  113. errLabel:
  114. return rc;
  115. }
  116. cmSeH_t cmSeCreate( cmCtx_t* ctx, cmSeH_t* hp, const char* deviceStr, unsigned baudRate, unsigned cfgFlags, cmSeCallbackFunc_t cbFunc, void* cbArg, unsigned pollPeriodMs )
  117. {
  118. cmSeRC_t rc = kOkSeRC;
  119. struct termios options;
  120. cmSeH_t h;
  121. // if the port is already open then close it
  122. if((rc = cmSeDestroy(hp)) != kOkSeRC )
  123. return *hp;
  124. cmSerialPort_t* p = cmMemAllocZ(cmSerialPort_t,1);
  125. cmErrSetup(&p->_err,&ctx->rpt,"Serial Port");
  126. p->_deviceH = -1;
  127. // open the port
  128. if( (p->_deviceH = open(deviceStr, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1 )
  129. {
  130. rc = cmErrSysMsg(&p->_err,kOpenFailSeRC,errno,"Error opening serial '%s'",cmStringNullGuard(deviceStr));
  131. goto errLabel;;
  132. }
  133. // Note that open() follows POSIX semantics: multiple open() calls to
  134. // the same file will succeed unless the TIOCEXCL ioctl is issued.
  135. // This will prevent additional opens except by root-owned processes.
  136. // See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details.
  137. if( ioctl(p->_deviceH, TIOCEXCL) == -1 )
  138. {
  139. rc = cmErrSysMsg(&p->_err,kResourceNotAvailableSeRC,errno,"The serial device '%s' is already in use.", cmStringNullGuard(deviceStr));
  140. goto errLabel;
  141. }
  142. // Now that the device is open, clear the O_NONBLOCK flag so
  143. // subsequent I/O will block.
  144. // See fcntl(2) ("man 2 fcntl") for details.
  145. /*
  146. if (fcntl(_deviceH, F_SETFL, 0) == -1)
  147. {
  148. _error("Error clearing O_NONBLOCK %s - %s(%d).", pr.devFilePath.c_str(), strerror(errno), errno);
  149. goto errLabel;
  150. }
  151. */
  152. // Get the current options and save them so we can restore the
  153. // default settings later.
  154. if (tcgetattr(p->_deviceH, &p->_ttyAttrs) == -1)
  155. {
  156. rc = cmErrSysMsg(&p->_err,kGetAttrFailSeRC,errno,"Error getting tty attributes from the device '%s'.",deviceStr);
  157. goto errLabel;
  158. }
  159. // The serial port attributes such as timeouts and baud rate are set by
  160. // modifying the termios structure and then calling tcsetattr to
  161. // cause the changes to take effect. Note that the
  162. // changes will not take effect without the tcsetattr() call.
  163. // See tcsetattr(4) ("man 4 tcsetattr") for details.
  164. options = p->_ttyAttrs;
  165. // Set raw input (non-canonical) mode, with reads blocking until either
  166. // a single character has been received or a 100ms timeout expires.
  167. // See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios")
  168. // for details.
  169. cfmakeraw(&options);
  170. options.c_cc[VMIN] = 1;
  171. options.c_cc[VTIME] = 1;
  172. // The baud rate, word length, and handshake options can be set as follows:
  173. // set baud rate
  174. cfsetspeed(&options, baudRate);
  175. options.c_cflag |= CREAD | CLOCAL; // ignore modem controls
  176. // set data word size
  177. cmClrBits(options.c_cflag, CSIZE); // clear the word size bits
  178. cmEnaBits(options.c_cflag, CS5, cmIsFlag(cfgFlags, kDataBits5SeFl));
  179. cmEnaBits(options.c_cflag, CS6, cmIsFlag(cfgFlags, kDataBits6SeFl));
  180. cmEnaBits(options.c_cflag, CS7, cmIsFlag(cfgFlags, kDataBits7SeFl));
  181. cmEnaBits(options.c_cflag, CS8, cmIsFlag(cfgFlags, kDataBits8SeFl));
  182. cmClrBits(options.c_cflag, PARENB); // assume no-parity
  183. // if the odd or even parity flag is set
  184. if( cmIsFlag( cfgFlags, kEvenParitySeFl) || cmIsFlag( cfgFlags, kOddParitySeFl ) )
  185. {
  186. cmSetBits(options.c_cflag, PARENB);
  187. if( cmIsFlag(cfgFlags, kOddParitySeFl ) )
  188. cmSetBits( options.c_cflag, PARODD);
  189. }
  190. // set two stop bits
  191. cmEnaBits( options.c_cflag, CSTOPB, cmIsFlag(cfgFlags, k2StopBitSeFl));
  192. // set hardware flow control
  193. //cmEnaBits(options.c_cflag, CCTS_OFLOW, cmIsFlag(cfgFlags, kCTS_OutFlowCtlFl));
  194. //cmEnaBits(options.c_cflag, CRTS_IFLOW, cmIsFlag(cfgFlags, kRTS_InFlowCtlFl));
  195. //cmEnaBits(options.c_cflag, CDTR_IFLOW, cmIsFlag(cfgFlags, kDTR_InFlowCtlFl));
  196. //cmEnaBits(options.c_cflag, CDSR_OFLOW, cmIsFlag(cfgFlags, kDSR_OutFlowCtlFl));
  197. //cmEnaBits(options.c_cflag, CCAR_OFLOW, cmIsFlag(cfgFlags, kDCD_OutFlowCtlFl));
  198. cmClrBits(options.c_cflag,CRTSCTS); // turn-off hardware flow control
  199. // 7 bit words, enable even parity, CTS out ctl flow, RTS in ctl flow
  200. // note: set PARODD and PARENB to enable odd parity)
  201. //options.c_cflag |= (CS7 | PARENB | CCTS_OFLOW | CRTS_IFLOW );
  202. // Cause the new options to take effect immediately.
  203. if (tcsetattr(p->_deviceH, TCSANOW, &options) == -1)
  204. {
  205. rc = cmErrSysMsg(&p->_err,kSetAttrFailSeRC,errno,"Error setting tty attributes on serial device %.", deviceStr);
  206. goto errLabel;
  207. }
  208. memset(&p->_pollfd,0,sizeof(p->_pollfd));
  209. p->_pollfd.fd = p->_deviceH;
  210. p->_pollfd.events = POLLIN;
  211. p->_deviceStr = cmMemAllocStr( deviceStr );
  212. p->_baudRate = baudRate;
  213. p->_cfgFlags = cfgFlags;
  214. p->_cbFunc = cbFunc;
  215. p->_cbArg = cbArg;
  216. p->_pollPeriodMs = pollPeriodMs;
  217. // create the listening thread
  218. if( cmThreadCreate( &p->_thH, _cmSeThreadFunc, p, &ctx->rpt) != kOkThRC )
  219. {
  220. rc = cmErrMsg(&p->_err,kThreadErrSeRC,"Thread initialization failed.");
  221. goto errLabel;
  222. }
  223. if( hp != NULL )
  224. hp->h = p;
  225. else
  226. h.h = p;
  227. errLabel:
  228. if( rc != kOkSeRC )
  229. {
  230. _cmSeDestroy(p);
  231. h.h = NULL;
  232. }
  233. return hp != NULL ? *hp : h;
  234. }
  235. cmSeRC_t cmSeDestroy(cmSeH_t* hp )
  236. {
  237. cmSeRC_t rc = kOkSeRC;
  238. if( hp==NULL || !cmSeIsOpen(*hp) )
  239. return kOkSeRC;
  240. cmSerialPort_t* p = _cmSePtrFromHandle(*hp);
  241. if((rc = _cmSeDestroy(p)) != kOkSeRC )
  242. return rc;
  243. hp->h = NULL;
  244. return rc;
  245. }
  246. cmSeRC_t cmSeSetCallback( cmSeH_t h, cmSeCallbackFunc_t cbFunc, void* cbArg )
  247. {
  248. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  249. p->_cbFunc = cbFunc;
  250. p->_cbArg = cbArg;
  251. return kOkSeRC;
  252. }
  253. cmSeRC_t cmSeStart( cmSeH_t h )
  254. {
  255. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  256. if( cmThreadPause(p->_thH,0) != kOkThRC )
  257. return cmErrMsg(&p->_err,kThreadErrSeRC,0,"Thread start failed.");
  258. return kOkSeRC;
  259. }
  260. bool cmSeIsOpen( cmSeH_t h)
  261. {
  262. if( h.h == NULL )
  263. return false;
  264. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  265. return p->_deviceH != -1;
  266. }
  267. cmSeRC_t cmSeSend( cmSeH_t h, const void* byteA, unsigned byteN )
  268. {
  269. cmSeRC_t rc = kOkSeRC;
  270. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  271. if( !cmSeIsOpen(h) )
  272. return cmErrWarnMsg( &p->_err, kResourceNotAvailableSeRC, "An attempt was made to transmit from a closed serial port.");
  273. if( byteN == 0 )
  274. return rc;
  275. // implement a non blocking write - if less than all the bytes were written then iterate
  276. unsigned i = 0;
  277. do
  278. {
  279. int n = 0;
  280. if((n = write( p->_deviceH, ((char*)byteA)+i, byteN-i )) == -1 )
  281. {
  282. rc = cmErrSysMsg(&p->_err,kWriteFailSeRC,errno,"Write failed on serial port '%s'.", p->_deviceStr );
  283. break;
  284. }
  285. i += n;
  286. }while( i<byteN );
  287. return rc;
  288. }
  289. cmSeRC_t cmSeReceiveCbNb( cmSeH_t h, unsigned* readN_Ref)
  290. {
  291. cmSeRC_t rc = kOkSeRC;
  292. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  293. const unsigned bufN = 512;
  294. char buf[ bufN ];
  295. if( readN_Ref != NULL)
  296. *readN_Ref = 0;
  297. if((rc = cmSeReceiveNb(h,buf,bufN,readN_Ref)) == kOkSeRC )
  298. if( readN_Ref > 0 && p->_cbFunc != NULL )
  299. p->_cbFunc( p->_cbArg, buf, *readN_Ref );
  300. return rc;
  301. }
  302. cmSeRC_t cmSeReceiveCbTimeOut( cmSeH_t h, unsigned timeOutMs, unsigned* readN_Ref)
  303. {
  304. cmSeRC_t rc;
  305. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  306. if((rc = _cmSePoll(p,timeOutMs)) == kOkSeRC )
  307. rc = cmSeReceiveCbNb(h,readN_Ref);
  308. return rc;
  309. }
  310. cmSeRC_t cmSeReceiveNb( cmSeH_t h, void* buf, unsigned bufN, unsigned* readN_Ref)
  311. {
  312. cmSeRC_t rc = kOkSeRC;
  313. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  314. if( readN_Ref != NULL )
  315. *readN_Ref = 0;
  316. if( !cmSeIsOpen(h) )
  317. return cmErrWarnMsg(&p->_err, kResourceNotAvailableSeRC, "An attempt was made to read from a closed serial port.");
  318. int n = 0;
  319. // if attempt to read the port succeeded ...
  320. if((n =read( p->_deviceH, buf, bufN )) != -1 )
  321. *readN_Ref = n;
  322. else
  323. {
  324. // ... or failed and it wasn't because the port was empty
  325. if( errno != EAGAIN)
  326. rc = cmErrSysMsg(&p->_err,kReadFailSeRC,errno,"An attempt to read the serial port '%s' failed.", p->_deviceStr );
  327. }
  328. return rc;
  329. }
  330. cmSeRC_t cmSeReceive( cmSeH_t h, void* buf, unsigned bufByteN, unsigned timeOutMs, unsigned* readN_Ref )
  331. {
  332. cmSeRC_t rc = kOkSeRC;
  333. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  334. if((rc = _cmSePoll(p,timeOutMs)) == kOkSeRC )
  335. rc = cmSeReceiveNb(h,buf,bufByteN,readN_Ref);
  336. return rc;
  337. }
  338. const char* cmSeDevice( cmSeH_t h)
  339. {
  340. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  341. return p->_deviceStr;
  342. }
  343. unsigned cmSeBaudRate( cmSeH_t h)
  344. {
  345. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  346. return p->_baudRate;
  347. }
  348. unsigned cmSeCfgFlags( cmSeH_t h)
  349. {
  350. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  351. return p->_cfgFlags;
  352. }
  353. unsigned cmSeReadInBaudRate( cmSeH_t h )
  354. {
  355. struct termios attr;
  356. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  357. if((_cmSeGetAttributes(p,&attr)) != kOkSeRC )
  358. return 0;
  359. return cfgetispeed(&attr);
  360. }
  361. unsigned cmSeReadOutBaudRate( cmSeH_t h)
  362. {
  363. struct termios attr;
  364. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  365. if((_cmSeGetAttributes(p,&attr)) != kOkSeRC )
  366. return 0;
  367. return cfgetospeed(&attr);
  368. }
  369. unsigned cmSeReadCfgFlags( cmSeH_t h)
  370. {
  371. struct termios attr;
  372. unsigned result = 0;
  373. cmSerialPort_t* p = _cmSePtrFromHandle(h);
  374. if((_cmSeGetAttributes(p,&attr)) == false )
  375. return 0;
  376. switch( attr.c_cflag & CSIZE )
  377. {
  378. case CS5:
  379. cmSetBits( result, kDataBits5SeFl);
  380. break;
  381. case CS6:
  382. cmSetBits( result, kDataBits6SeFl );
  383. break;
  384. case CS7:
  385. cmSetBits( result, kDataBits7SeFl);
  386. break;
  387. case CS8:
  388. cmSetBits( result, kDataBits8SeFl);
  389. break;
  390. }
  391. cmEnaBits( result, k2StopBitSeFl, cmIsFlag( attr.c_cflag, CSTOPB ));
  392. cmEnaBits( result, k1StopBitSeFl, !cmIsFlag( attr.c_cflag, CSTOPB ));
  393. if( cmIsFlag( attr.c_cflag, PARENB ) )
  394. {
  395. cmEnaBits( result, kOddParitySeFl, cmIsFlag( attr.c_cflag, PARODD ));
  396. cmEnaBits( result, kEvenParitySeFl, !cmIsFlag( attr.c_cflag, PARODD ));
  397. }
  398. return result;
  399. }
  400. //====================================================================================================
  401. //
  402. //
  403. void _cmSePortTestCb( void* arg, const void* byteA, unsigned byteN )
  404. {
  405. const char* text = (const char*)byteA;
  406. for(unsigned i=0; i<byteN; ++i)
  407. printf("%c:%i ",text[i],(int)text[i]);
  408. if( byteN )
  409. fflush(stdout);
  410. }
  411. cmSeRC_t cmSePortTest(cmCtx_t* ctx)
  412. {
  413. // Use this test an Arduino running study/serial/arduino_xmt_rcv/main.c
  414. cmSeRC_t rc = kOkSeRC;
  415. const char* device = "/dev/ttyACM0";
  416. unsigned baud = 38400;
  417. unsigned serialCfgFlags = kDefaultCfgSeFlags;
  418. unsigned pollPeriodMs = 50;
  419. cmSeH_t h;
  420. h.h = NULL;
  421. h = cmSeCreate(ctx,&h,device,baud,serialCfgFlags,_cmSePortTestCb,NULL,pollPeriodMs);
  422. if( cmSeIsOpen(h) )
  423. cmSeStart(h);
  424. bool quitFl = false;
  425. printf("q=quit\n");
  426. while(!quitFl)
  427. {
  428. char c = getchar();
  429. if( c == 'q')
  430. quitFl = true;
  431. else
  432. if( '0' <= c && c <= 'z' )
  433. cmSeSend(h,&c,1);
  434. }
  435. cmSeDestroy(&h);
  436. return rc;
  437. }