当前位置:   article > 正文

Ubuntu18.04 实现串口通信_ubuntu串口发送数据

ubuntu串口发送数据

 最近由于项目需要, 研究了关于在ubuntu下串口通信的功能实现。期间遇到一些问题,跟大家分享下。

 1. 代码 

comm_service.h

  1. #ifndef comm_service_h
  2. #define comm_service_h
  3. //串口相关的头文件
  4. #include <stdio.h> /*标准输入输出定义*/
  5. #include <stdlib.h> /*标准函数库定义*/
  6. #include <unistd.h> /*Unix 标准函数定义*/
  7. #include <sys/types.h>
  8. #include <sys/stat.h>
  9. #include <sys/ioctl.h>
  10. #include <linux/serial.h>
  11. #include <fcntl.h> /*文件控制定义*/
  12. #include <termios.h> /*PPSIX 终端控制定义*/
  13. #include <errno.h> /*错误号定义*/
  14. #include <string.h>
  15. //宏定义
  16. #define FALSE -1
  17. #define TRUE 0
  18. namespace dst_ccms_api
  19. {
  20. class comm_service
  21. {
  22. public:
  23. comm_service();
  24. ~comm_service();
  25. /*******************************************************************
  26. *名称: CommOpen
  27. *功能: 打开串口并返回串口设备文件描述
  28. *param: port 串口号(ttyS0,ttyS1,ttyS2)
  29. *return: void
  30. *******************************************************************/
  31. int CommOpen(char *port);
  32. /*******************************************************************
  33. *名称: CommClose
  34. *功能: 关闭串口并返回串口设备文件描述
  35. *param: fd 文件描述符
  36. *return: void
  37. *******************************************************************/
  38. void CommClose();
  39. /*******************************************************************
  40. *名称: CommInit
  41. *功能: 串口初始化
  42. *param: fd 串口文件描述符
  43. * speed 串口速度
  44. * flow_ctrl 数据流控制 (0:不使用 1:硬件流控制 2:软件流控制)
  45. * databits 数据位 取值为 7 或者8
  46. * stopbits 停止位 取值为 1 或者2
  47. * parity 效验类型 取值为N(无校验位),E(偶检验),O(奇校验)
  48. *return: 正确返回为1,错误返回为-1
  49. *******************************************************************/
  50. int CommInit(int baudrate, int flow_ctrl, int databits, int stopbits, int parity);
  51. /*******************************************************************
  52. *名称: CommRecv
  53. *功能: 接收串口数据
  54. *param: rcv_buf 接收串口中数据存入rcv_buf缓冲区中
  55. * data_len 一帧数据的长度
  56. *return: 正确返回为1,错误返回为-1
  57. *******************************************************************/
  58. int CommRecv(char *rcv_buf, int data_len);
  59. /*******************************************************************
  60. *名称: CommSend
  61. *功能: 发送数据
  62. *param: send_buf 存放串口发送数据
  63. * data_len 一帧数据的长度
  64. *return: 正确返回为1,错误返回为-1
  65. *******************************************************************/
  66. int CommSend(char *send_buf, int data_len);
  67. /*******************************************************************
  68. *名称: getCommFD
  69. *功能: 获取文件描述
  70. *return: 文件描述句柄
  71. *******************************************************************/
  72. int CommGetFD() const;
  73. /*******************************************************************
  74. *名称: serialFlush
  75. *功能: 清除缓冲区 TCIOFLUSH(刷清输入、输出队列) TCIFLUSH(刷清输入队列) TCIFLUSH (刷清输出队列)
  76. *return: true:开启 flase:关闭
  77. *******************************************************************/
  78. void CommFlush(int flush);
  79. private:
  80. /*******************************************************************
  81. *名称: commSet
  82. *功能: 设置串口数据位,停止位和效验位
  83. *param: fd 串口文件描述符
  84. * baudrate 波特率
  85. * flow_ctrl 数据流控制
  86. * databits 数据位 取值为 7 或者8
  87. * stopbits 停止位 取值为 1 或者2
  88. * parity 效验类型 取值为N,E,O,,S
  89. *return: void
  90. *******************************************************************/
  91. int commSet(int baudrate, int flow_ctrl, int databits, int stopbits, int parity);
  92. /*******************************************************************
  93. *名称: commIsOpen
  94. *功能: 串口是否开启
  95. *return: true:开启 flase:关闭
  96. *******************************************************************/
  97. bool commIsOpen() const;
  98. /*******************************************************************
  99. *名称: serial_set_speci_baud
  100. *功能: 非特定波特率设置
  101. *param: options 配置
  102. baud 非标准波特率(如28800..)
  103. *return: 正确返回为0,错误返回为-1
  104. *******************************************************************/
  105. int serial_set_speci_baud(struct termios &options,int baud);
  106. int m_fd; // 文件描述句柄
  107. };
  108. } // namespace qh_dst_ccms_api
  109. #endif

comm_service.cpp

  1. #include "comm_service.h"
  2. #include "common_logger.h"
  3. using namespace dst_ccms_api;
  4. using namespace J_COMMON_UTIL;
  5. comm_service::comm_service() : m_fd(-1) {}
  6. comm_service::~comm_service() {}
  7. int comm_service::CommOpen(char *port)
  8. {
  9. if (commIsOpen())
  10. return 1;
  11. m_fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
  12. if (m_fd < 0)
  13. {
  14. LError("Can't Open Serial Port.");
  15. return -1;
  16. }
  17. //恢复串口为阻塞状态
  18. if (fcntl(m_fd, F_SETFL, 0) < 0)
  19. {
  20. LError("fcntl failed.");
  21. return -1;
  22. }
  23. //测试是否为终端设备
  24. if (0 == isatty(STDIN_FILENO))
  25. {
  26. LError("standard input is not a terminal device.");
  27. return -1;
  28. }
  29. return 1;
  30. }
  31. void comm_service::CommClose()
  32. {
  33. if (!commIsOpen())
  34. return;
  35. close(m_fd);
  36. }
  37. int comm_service::commSet(int baudrate, int flow_ctrl, int databits, int stopbits, int parity)
  38. {
  39. if (!commIsOpen())
  40. return -1;
  41. int i;
  42. int status;
  43. int speed_arr[] = {B115200, B19200, B9600, B4800, B2400, B1200, B300};
  44. int name_arr[] = {115200, 19200, 9600, 4800, 2400, 1200, 300};
  45. struct termios options;
  46. /* tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数还可以测试配置是否正确,
  47. 该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1. */
  48. if (tcgetattr(m_fd, &options) != 0)
  49. {
  50. LError("commSet tcgetattr error.");
  51. return (FALSE);
  52. }
  53. bool isStandard = false;
  54. //设置串口输入波特率和输出波特率
  55. for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++)
  56. {
  57. if (baudrate == name_arr[i])
  58. {
  59. cfsetispeed(&options, speed_arr[i]);
  60. cfsetospeed(&options, speed_arr[i]);
  61. isStandard =true;
  62. break;
  63. }
  64. }
  65. if(!isStandard){
  66. // 非标准波特率
  67. int res = serial_set_speci_baud(options, baudrate);
  68. if (res != 0)
  69. {
  70. LError("commSet serial_set_speci_baud error.");
  71. return (FALSE);
  72. }
  73. }
  74. //修改控制模式,保证程序不会占用串口
  75. options.c_cflag |= CLOCAL;
  76. //修改控制模式,使得能够从串口中读取输入数据
  77. options.c_cflag |= CREAD;
  78. //设置数据流控制
  79. switch (flow_ctrl)
  80. {
  81. case 0: //不使用流控制
  82. options.c_cflag &= ~CRTSCTS;
  83. break;
  84. case 1: //使用硬件流控制
  85. options.c_cflag |= CRTSCTS;
  86. break;
  87. case 2: //使用软件流控制
  88. options.c_cflag |= IXON | IXOFF | IXANY;
  89. break;
  90. }
  91. //设置数据位,屏蔽其他标志位
  92. options.c_cflag &= ~CSIZE;
  93. switch (databits)
  94. {
  95. case 5:
  96. options.c_cflag |= CS5;
  97. break;
  98. case 6:
  99. options.c_cflag |= CS6;
  100. break;
  101. case 7:
  102. options.c_cflag |= CS7;
  103. break;
  104. case 8:
  105. options.c_cflag |= CS8;
  106. break;
  107. default:
  108. LError("Unsupported data size.");
  109. return (FALSE);
  110. }
  111. //设置校验位
  112. switch (parity)
  113. {
  114. case 'n':
  115. case 'N': //无奇偶校验位。
  116. options.c_cflag &= ~PARENB;
  117. options.c_iflag &= ~INPCK;
  118. //options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  119. break;
  120. case 'o':
  121. case 'O': //设置为奇校验
  122. options.c_cflag |= (PARODD | PARENB);
  123. options.c_iflag |= INPCK;
  124. break;
  125. case 'e':
  126. case 'E': //设置为偶校验
  127. options.c_cflag |= PARENB;
  128. options.c_cflag &= ~PARODD;
  129. options.c_iflag |= INPCK;
  130. break;
  131. case 's':
  132. case 'S': //设置为空格
  133. options.c_cflag &= ~PARENB;
  134. options.c_cflag &= ~CSTOPB;
  135. break;
  136. default:
  137. LError("Unsupported parity.");
  138. return (FALSE);
  139. }
  140. // 解决二进制传输时,数据遇到0x0d , 0x11,0x13 会被丢掉的问题 J. 1027
  141. options.c_iflag &= ~(BRKINT | ICRNL | ISTRIP | IXON);
  142. // 设置停止位
  143. switch (stopbits)
  144. {
  145. case 1:
  146. options.c_cflag &= ~CSTOPB;
  147. break;
  148. case 2:
  149. options.c_cflag |= CSTOPB;
  150. break;
  151. default:
  152. LError("Unsupported stop bits.");
  153. return (FALSE);
  154. }
  155. //修改输出模式,原始数据输出
  156. options.c_oflag &= ~OPOST;
  157. options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
  158. //options.c_lflag &= ~(ISIG | ICANON);
  159. //设置等待时间和最小接收字符
  160. options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
  161. options.c_cc[VMIN] = 1; /* 读取字符的最少个数为1 */
  162. //如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
  163. tcflush(m_fd, TCIFLUSH);
  164. //激活配置 (将修改后的termios数据设置到串口中)
  165. if (tcsetattr(m_fd, TCSANOW, &options) != 0)
  166. {
  167. LError("com set error!");
  168. return (FALSE);
  169. }
  170. return (TRUE);
  171. }
  172. int comm_service::CommInit(int speed, int flow_ctrl, int databits, int stopbits, int parity)
  173. {
  174. if (!commIsOpen())
  175. return -1;
  176. //设置串口数据帧格式
  177. return commSet(speed, flow_ctrl, databits, stopbits, parity) ;
  178. }
  179. int comm_service::CommRecv(char *rcv_buf, int data_len)
  180. {
  181. if (!commIsOpen())
  182. return -1;
  183. int len, fs_sel;
  184. fd_set fs_read;
  185. FD_ZERO(&fs_read);
  186. FD_SET(m_fd, &fs_read);
  187. struct timeval time;
  188. time.tv_sec = 10;
  189. time.tv_usec = 0;
  190. //使用select实现串口的多路通信
  191. fs_sel = select(m_fd + 1, &fs_read, NULL, NULL, &time);
  192. if (fs_sel)
  193. {
  194. len = read(m_fd, rcv_buf, data_len);
  195. return len;
  196. }
  197. else
  198. return FALSE;
  199. }
  200. int comm_service::CommSend(char *send_buf, int data_len)
  201. {
  202. if (!commIsOpen())
  203. return -1;
  204. CommFlush(TCIFLUSH);
  205. int len = 0;
  206. len = write(m_fd, send_buf, data_len);
  207. if (len == data_len)
  208. return len;
  209. else
  210. {
  211. tcflush(m_fd, TCOFLUSH);
  212. return FALSE;
  213. }
  214. }
  215. void comm_service::CommFlush(int flush)
  216. {
  217. if (!commIsOpen())
  218. return;
  219. tcflush(m_fd, flush);
  220. }
  221. int comm_service::CommGetFD() const
  222. {
  223. return m_fd;
  224. }
  225. bool comm_service::commIsOpen() const
  226. {
  227. return m_fd < 0 ? false : true;
  228. }
  229. int comm_service::serial_set_speci_baud(struct termios &options,int baud)
  230. {
  231. struct serial_struct ss,ss_set;
  232. cfsetispeed(&options,B38400);
  233. cfsetospeed(&options,B38400);
  234. if((ioctl(m_fd,TIOCGSERIAL,&ss))<0){
  235. LInfo("1.serial_set_speci_baud ioctl.");
  236. return -1;
  237. }
  238. ss.flags = ASYNC_SPD_CUST;
  239. ss.custom_divisor = ss.baud_base / baud;
  240. if((ioctl(m_fd,TIOCSSERIAL,&ss))<0){
  241. return -1;
  242. }
  243. ioctl(m_fd,TIOCGSERIAL,&ss_set);
  244. return 0;
  245. }

2. 代码注释写的挺详细,就不说代码了,下面说下遇到的几个问题

1. 非标准波特率的问题。参考: 转帖:linux串口编程中非标准波特率的实现 (amobbs.com 阿莫电子论坛 - 东莞阿莫电子网站)

通常,在linux下面,设置串口使用终端IO的相关函数设置,如tcsetattr等函数,linux内部有一个对常用波特率列表的索引,根据设置的波特率用底层驱动来设置异步通信芯片的寄存器
但是在一些特定应用中需要用到非标准的波特率,比如125000、28800等 。

对于非标准的任意波特率需要用ioctl(fd, TIOCGSERIAL, p)和ioctl(fd, TIOCSSERIAL, p)的配合,ioctl的最后一个参数是struct serial_struct *类型,在linux/serial.h中定义。其中baud_base是基准晶振频率/16(对于SEP4020来说,baud_base=sysclk/16),你需要设的是custom_divisor这个值,最终的波特率计算公式为baud=baud_base/custom_divisor,所以要设置custom_divisor=baud_base/baud,。
具体过程为,先设置波特率设为38400(tcsetattr),然后用TIOCGSERIAL得到当前的设置,将flags设置ASYNC_SPD_CUST位,设置custom_divisor,最后用TIOCSSERIAL设置。

             代码实现:

  1. int comm_service::serial_set_speci_baud(struct termios &options,int baud)
  2. {
  3. struct serial_struct ss,ss_set;
  4. cfsetispeed(&options,B38400);
  5. cfsetospeed(&options,B38400);
  6. if((ioctl(m_fd,TIOCGSERIAL,&ss))<0){
  7. LInfo("1.serial_set_speci_baud ioctl.");
  8. return -1;
  9. }
  10. ss.flags = ASYNC_SPD_CUST;
  11. ss.custom_divisor = ss.baud_base / baud;
  12. if((ioctl(m_fd,TIOCSSERIAL,&ss))<0){
  13. return -1;
  14. }
  15. ioctl(m_fd,TIOCGSERIAL,&ss_set);
  16. return 0;
  17. }

   2. 在测试过程中,发现接受的字符中缺少内容,总是接受不全。

       解决办法:参考 :解决方法:Linux串口接收字节0x11,0x0d,0x13丢失_Leung_ManWah的博客-CSDN博客

options.c_iflag &= ~(BRKINT | ICRNL | ISTRIP | IXON);

最后:

1. 以上内容均来自网上,有一些原文找不到了,就没附原文链接,如作者有意见,please 楼下留言,我会及时修改。

2. 有需要的小伙伴可以参考下,也许可以少绕一些弯路 。

             

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/128560
推荐阅读
相关标签
  

闽ICP备14008679号