libcm is a C development framework with an emphasis on audio signal processing applications.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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