(转载)RS485---串口编程

最近被安排实现 linux rs485 串口通信。期间遇到各种问题,现在加以分析总结。

一、硬件相关 

1.1 单工、半双工、全双工

首先,我使用的是芯片为 SP3485E 为半双工通信。那么先要明确什么是单工、半双工、全双工。
单工数据传输只支持数据在一个方向上传输;
半双工数据传输允许数据在两个方向上传输
,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;
全双工数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。 
网卡的全双工(Full Duplex)是指网卡在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的网卡一般都支持全双工。


提到全双工,就不能不提与之密切对应的另一个概念,那就是“半双工(Half Duplex)”,
所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆车通过,
当目前有两量车对开,这种情况下就只能一辆先过,等到头儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台。

1.2 关于RS485通信

RS232 标准是诞生于 RS485 之前的,但是 RS232 有几处不足的地方:
接口的信号电平值较高,
达到十几 V,使用不当容易损坏接口芯片,电平标准也与TTL 电平不兼容。
传输速率有局限,不可以过高,一般到一两百千比特每秒(Kb/s)就到极限了。
接口使用信号线和 GND 与其它设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。
传输距离有限,最多只能通信几十米。
通信的时候只能两点之间进行通信,不能够实现多机联网通信。

针对 RS232 接口的不足,就不断出现了一些新的接口标准,RS485 就是其中之一,它具备以下的特点
采用差分信号。我们在讲 A/D 的时候,讲过差分信号输入的概念,同时也介绍了差分输入的好处,最大的优势是可以抑制共模干扰。
尤其当工业现场环境比较复杂,干扰比较多时,采用差分方式可以有效的提高通信可靠性。RS485 采用两根通信线,通常用 A 和 B 或者 D+和 D-来表示。逻辑“1”以两线之间的电压差为+(0.2~6)V 表示,逻辑“0”以两线间的电压差为-(0.2~6)V 来表示,是一种典型的差分通信。
RS485 通信速率快
,最大传输速度可以达到 10Mb/s 以上。
RS485 内部的物理结构,采用的是平衡驱动器和差分接收器的组合,抗干扰能力也大大增加
传输距离最远可以达到 1200 米左右,但是它的传输速率和传输距离是成反比的,只有在 100Kb/s 以下的传输速度,才能达到最大的通信距离,如果需要传输更远距离可以使用中继。
可以在总线上进行联网实现多机通信,总线上允许挂多个收发器,从现有的 RS485芯片来看,有可以挂 32、64、128、256 等不同个设备的驱动器。
RS485 的接口非常简单,与 RS232 所使用的 MAX232 是类似的,只需要一个 RS485转换器,就可以直接与单片机的 UART 串口连接起来,并且使用完全相同的异步串行通信协议。
但是由于 RS485 是差分通信,因此接收数据和发送数据是不能同时进行的,也就是说它是一种半双工通信。RS485为差分通信:最大的优势是可以抑制共模干扰。

1.3  关于sp3485硬件分析


                                                                上图为SP3485原理图 

(1)引脚说明: 

Pin1 - RO: 接收器输出

Pin2 - RE#:接收器输出使能 (低电平有效)

Pin3 - DE: 驱动器输出使能 (高电平有效)

Pin4 - DI: 驱动器输入

Pin5 - GND: 连接地

Pin6 - A: 驱动器输出/接收器输入 (同相)

Pin7 - B: 驱动器输出/接收器输入 (反相)

Pin8 - Vcc       

注意将AB间120欧姆去掉,如果采用阻抗匹配的电缆,300米以内几乎可以不用加终端电阻。加终端电阻的缺点就是增大了线路的无用功耗,尤其是电池系统供电时,降低了电池的续航能力。

PS: 我一开始没有将它去掉,导致只能发送数据,无法接收数据。

(2)电气特性

RS-232电平电气特性

EIA电平(串口)

逻辑1:-3V~-15V

逻辑0:+3V~+15V


TTL电平(TPAD)

逻辑1:+2V~+5V

逻辑0:+0V~+0.8V


接收数据:EIA->TTL  232转TTL

发送数据:TTL->EIA  TTL转232

 

串口异步通信的重要参数:

波特率: bps (bit per second)

数据位的个数: 5 6 7 8

校验方式: 奇校验 偶校验 无校验

停止位: 1bit  2bit


RS485电平 和RS422电平 由于两者均采用 差分传输(平衡传输)的方式,所以他们的电平方式,一般有两个引脚 A,B

发送端 AB间的电压差

+2 ~ +6v 1

-2 ~ -6v   0

接收端 AB间的电压差

大于 +200mv   1

小于 -200mv 0


定义逻辑1为B>A的状态

定义逻辑0为A>B的状态

AB之间的电压差不小于200mv


一对一的接头的情况下:

RS232 可做到双向传输,全双工通讯   最高传输速率 20kbps

422    只能做到单向传输,半双工通讯,最高传输速率10Mbps

485    双向传输,半双工通讯, 最高传输速率10Mbps

(3)串行数据的格式

异步串行数据的一般格式是:起始位+数据位+停止位,(8-N-1格式) 其中起始位1 位,数据位可以是5、6、7、8位,停止位可以是1、1.5、2位。

起始位是一个值为0的位,所以对于正逻辑的TTL电平,起始位是一位时间的低电平;停止位是值为1的位,所以对于正逻辑的TTL电平,停止位是高电平。线路路空闲或者数据传输结束,对于正逻辑的TTL电平,线路总是1。对于负逻辑(如RS-232电平)则相反。

例如,对于16进制数据55aaH,当采用8位数据位、1位停止位传输时,它在信号线上的波形如图1(TTL电平)和图2(RS-232电平)所示。 (先传第一个字节55,再传第二个字节aa,每个字节都是从低位向高位逐位传输)


                   图1 TTL电平的串行数据帧格式(55aah)


                    图2 RS-232电平的串行数据帧格式(55aah)

(4)根据波形图计算波特率

如图3是图1在示波器中的显示示意,其中灰色线是示波器的时间分度线,此时假设是200us/格。

                     图3 波特率计算示意图

可以看了,第一个字节的10位(1位起始位,8位数据位和1位停止位)共占约1.05ms,这样可计算出其波特率约为:

10bit / 1.05ms X 1000 ≈ 9600 bit/s

如果上图中的时间轴是100us/格,同样可以计算出波特率应是19200bit/s。

当通讯不正常,又能观察到波形时,就可根据上述方法,从波形图计算一下波特率是否正确。

(5)根据波形图判断RS-485收发数据的正确与否

RS-485是一种半双工的串行通讯方式(RS-422为全双工),485电平芯片所以要正确接收和发送数据,必需保证控制信号和数据的同步,否则要么发送数据丢失,要么接收数据可能丢失。

RS-485发送数据时的正确时序如图4所示。


              图4 RS-485的正确发送数据时序

在图4中,发送控制信号的宽度基本与数据信号的宽度一致,所以能保证发送数据的正确和发送后及时转为接收。

图5 和图6 分别是控制信号太短和控制信号太长的情况。


                图5 RS-485控制信号太短时的时序


                图6 RS-485控制信号太短时的时序

在图5中,由于控制信号关闭过早,则第二个字节的后两位将发送错误;在图6中,由于控制信号关闭过迟,使485芯片在发送数据后,不能及时转到接收状态,此时总线若有数据过来,则本单元将不能正确接收。

总结:只要掌握上述波形分析方法,任何异步串行数据的接收和发送问题,基本都可以得到解决。

二、串口通信

2.1 串口的操作一般都通过四个步骤来完成:

1、打开串口
2、配置串口:对串口的波特率、数据位、停止位、校验码、等进行设置
3、读写串口
4、关闭串口

2.2 完整代码:


  
  
  1. #include <fcntl.h> //文件控制定义
  2. #include <stdio.h> //标准输入输出定义
  3. #include <stdlib.h> //标准函数库定义
  4. #include <unistd.h> //Unix标准函数定义
  5. #include <errno.h> //错误好定义
  6. #include <termios.h> //POSIX终端控制定义
  7. #include <sys/ioctl.h> //ioctl函数定义
  8. #include <string.h> //字符操作
  9. #include <sys/types.h>
  10. #include <sys/stat.h>
  11. #include <pthread.h>
  12. #include <sys/timeb.h>
  13. //时间戳
  14. long long getSystemTime() {
  15. struct timeb t;
  16. ftime(&t);
  17. return 1000 * t.time + t.millitm;
  18. }
  19. long long start;
  20. long long end;
  21. //定义互斥量
  22. pthread_mutex_t mutex;
  23. int fd_gpio;
  24. struct termios newtio, oldtio;
  25. typedef struct {
  26. int pin_idx;
  27. int pin_dir;
  28. int pin_sta;
  29. } davinci_gio_arg;
  30. typedef enum {
  31. AT91PIO_DIR_OUT = 0,
  32. AT91PIO_DIR_INP
  33. } davinci_gio_dir;
  34. //驱动判断输入输出模式
  35. davinci_gio_arg arg;
  36. #define DEV_PIO_LED "/dev/pio"
  37. // 需要手动添加设备号 mknod /dev/pio c 203 0
  38. #define PIO_NUM 47
  39. // 47pin 为控制输入输出方向引脚
  40. #define DEV_UART "/dev/ttyS1"
  41. // /dev/ttyS1 为串口设备
  42. #define IOCTL_PIO_SETDIR 1 //set gpio direct
  43. #define IOCTL_PIO_GETDIR 2 //get gpio direct
  44. #define IOCTL_PIO_SETSTA 3 //set gpio status
  45. #define IOCTL_PIO_GETSTA 4 //get gpio status
  46. //保存信息
  47. int log_init( const char *strFileName )
  48. {
  49. int fdLog = -1;
  50. if( -1 == (fdLog = open( strFileName, O_CREAT|O_TRUNC ) ) )
  51. {
  52. }
  53. close( fdLog );
  54. }
  55. int log_out( const char *strFileName, const char * szLog )
  56. {
  57. int fdLog = -1;
  58. if( -1 == ( fdLog = open( strFileName, O_CREAT|O_WRONLY|O_APPEND ) ) )
  59. {
  60. printf( "LOG (%s) open error!\n", strFileName );
  61. return -1;
  62. }
  63. write( fdLog, szLog, strlen( szLog ) );
  64. close( fdLog );
  65. return 0;
  66. }
  67. //配置串口
  68. /* 参数说明:fd 设备文件描述符,nspeed 波特率,nbits 数据位数(7位或8位),
  69. parity 奇偶校验位('n'或'N'为无校验位,'o'或'O'为偶校验,'e'或'E'奇校验),
  70. nstop 停止位(1位或2位)
  71. 成功返回1,失败返回-1。
  72. */
  73. int set_com_opt( int fd, int nspeed, int nbits, char parity, int nstop )
  74. {
  75. char szTmp[ 128];
  76. //打印配置信息
  77. sprintf( szTmp, "set_com_opt - speed:%d,bits:%d,parity:%c,stop:%d\n",
  78. nspeed, nbits, parity, nstop );
  79. log_out( "./485.log", szTmp );
  80. //保存并测试现在有串口参数设置,在这里如果串口号等出错,会有相关的出错信息
  81. if( tcgetattr( fd, &oldtio ) != 0 )
  82. {
  83. sprintf( szTmp, "SetupSerial 1" );
  84. log_out( "./485.log", szTmp );
  85. perror( "SetupSerial 1" );
  86. return -1;
  87. }
  88. //修改输出模式,原始数据输出
  89. bzero( &newtio, sizeof( newtio ));
  90. newtio.c_cflag &=~(OPOST);
  91. //屏蔽其他标志位
  92. newtio.c_cflag |= (CLOCAL | CREAD );
  93. newtio.c_cflag &= ~CSIZE;
  94. //设置数据位
  95. switch( nbits )
  96. {
  97. case 7:
  98. newtio.c_cflag |= CS7;
  99. break;
  100. case 8:
  101. newtio.c_cflag |= CS8;
  102. break;
  103. default:
  104. perror( "Unsupported date bit!\n");
  105. return -1;
  106. }
  107. //设置校验位
  108. switch( parity )
  109. {
  110. case 'n':
  111. case 'N': //无奇偶校验位
  112. newtio.c_cflag &= ~PARENB;
  113. newtio.c_iflag &= ~INPCK;
  114. break;
  115. case 'o':
  116. case 'O': //设置为奇校验
  117. newtio.c_cflag |= ( PARODD | PARENB );
  118. newtio.c_iflag |= ( INPCK | ISTRIP );
  119. break;
  120. case 'e':
  121. case 'E': //设置为偶校验
  122. newtio.c_iflag |= ( INPCK |ISTRIP );
  123. newtio.c_cflag |= PARENB;
  124. newtio.c_cflag &= ~PARODD;
  125. break;
  126. default:
  127. perror( "unsupported parity\n");
  128. return -1;
  129. }
  130. //设置停止位
  131. switch( nstop )
  132. {
  133. case 1:
  134. newtio.c_cflag &= ~CSTOPB;
  135. break;
  136. case 2:
  137. newtio.c_cflag |= CSTOPB;
  138. break;
  139. default :
  140. perror( "Unsupported stop bit\n");
  141. return -1;
  142. }
  143. //设置波特率
  144. switch( nspeed )
  145. {
  146. case 2400:
  147. cfsetispeed( &newtio, B2400 );
  148. cfsetospeed( &newtio, B2400 );
  149. break;
  150. case 4800:
  151. cfsetispeed( &newtio, B4800 );
  152. cfsetospeed( &newtio, B4800 );
  153. break;
  154. case 9600:
  155. cfsetispeed( &newtio, B9600 );
  156. cfsetospeed( &newtio, B9600 );
  157. break;
  158. case 115200:
  159. cfsetispeed( &newtio, B115200 );
  160. cfsetospeed( &newtio, B115200 );
  161. break;
  162. case 460800:
  163. cfsetispeed( &newtio, B460800 );
  164. cfsetospeed( &newtio, B460800 );
  165. break;
  166. default:
  167. cfsetispeed( &newtio, B9600 );
  168. cfsetospeed( &newtio, B9600 );
  169. break;
  170. }
  171. //设置等待时间和最小接收字符
  172. newtio.c_cc[VTIME] = 0;
  173. newtio.c_cc[VMIN] = 0;
  174. //VTIME=0,VMIN=0,不管能否读取到数据,read都会立即返回。
  175. //输入模式
  176. newtio.c_lflag &= ~(ICANON|ECHO|ECHOE|ISIG);
  177. //设置数据流控制
  178. newtio.c_iflag &= ~(IXON|IXOFF|IXANY); //使用软件流控制
  179. //如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
  180. tcflush( fd, TCIFLUSH );
  181. //激活配置 (将修改后的termios数据设置到串口中)
  182. if( tcsetattr( fd, TCSANOW, &newtio ) != 0 )
  183. {
  184. sprintf( szTmp, "serial set error!\n" );
  185. log_out( "./485.log", szTmp );
  186. perror( "serial set error!" );
  187. return -1;
  188. }
  189. log_out( "./485.log", "serial set ok!\n" );
  190. return 1;
  191. }
  192. //打开串口并返回串口设备文件描述
  193. int open_com_dev( char *dev_name )
  194. {
  195. int fd;
  196. char szTmp[ 128];
  197. log_init( "./485.log" );
  198. if(( fd = open( dev_name, O_RDWR|O_NOCTTY|O_NDELAY)) == -1 )
  199. {
  200. perror( "open\n");
  201. //printf("Can't open Serial %s Port!\n", dev_name );
  202. sprintf( szTmp, "Can't open Serial %s Port!\n", dev_name );
  203. log_out( "./485.log", szTmp );
  204. return -1;
  205. }
  206. sprintf( szTmp, "open %s ok!\n", dev_name );
  207. log_out( "./485.log", szTmp );
  208. if(fcntl(fd,F_SETFL, 0)< 0)
  209. {
  210. printf( "fcntl failed!\n");
  211. }
  212. //printf("Open %s ok\n",dev_name );
  213. return fd;
  214. }
  215. //发送云台数据
  216. void* task(void* p)
  217. {
  218. char ch;
  219. int j = 0, nread = 0;
  220. while( scanf ( "%s", &ch) == 1)
  221. {
  222. pthread_mutex_lock (&mutex);
  223. arg.pin_sta = 1; //设为高电平 发送态
  224. ioctl(fd_gpio, IOCTL_PIO_SETSTA, &arg);
  225. int fd = open_com_dev( DEV_UART );
  226. if( fd < 0 )
  227. {
  228. printf( "open UART device error! %s\n", DEV_UART );
  229. }
  230. else
  231. set_com_opt(fd, 2400, 8, 'n', 1);
  232. //set_com_opt(fd, 9600,8,'n',1);
  233. char buff[] = { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11};
  234. int len = write(fd,buff, sizeof (buff));
  235. if (len < 0)
  236. {
  237. perror ( "write err");
  238. exit ( -1);
  239. }
  240. //打印发送数据
  241. printf ( "sead: ");
  242. for (j = 0; j < sizeof(buff); j++)
  243. {
  244. printf ( "%02X ", buff[j]);
  245. }
  246. printf ( "\n");
  247. //清除scanf缓冲区
  248. scanf ( "%*[^\n]");
  249. scanf ( "%*c");
  250. close (fd);
  251. pthread_mutex_unlock (&mutex);
  252. }
  253. }
  254. //单片机数据收发
  255. void* task1(void* p)
  256. {
  257. char buf[ 255];
  258. int j = 0, res = 0, nread = 0, i = 0;
  259. while ( 1) {
  260. pthread_mutex_lock (&mutex);
  261. arg.pin_sta = 1; //设为高电平 发送态
  262. ioctl(fd_gpio, IOCTL_PIO_SETDIR, &arg);
  263. //打开/dev/pio
  264. int fd_s = open_com_dev( DEV_UART );
  265. if( fd_s < 0 )
  266. {
  267. printf( "open UART device error! %s\n", DEV_UART );
  268. }
  269. else
  270. set_com_opt(fd_s, 2400, 8, 'n', 1);
  271. //set_com_opt(fd_s, 9600,8,'n',1);
  272. //发送数据
  273. char buff[] = { 0xaa, 0x55, 0x05, 0x00, 0x33, 0x44, 0x14, 0x90, 0x00};
  274. int len = write(fd_s,buff, sizeof (buff));
  275. if (len < 0)
  276. {
  277. perror ( "write err");
  278. exit ( -1);
  279. }
  280. printf ( "sead: ");
  281. for (j = 0; j < sizeof (buff); j++)
  282. {
  283. printf ( "%02X ", buff[j]);
  284. }
  285. printf ( "\n");
  286. close (fd_s);
  287. start=getSystemTime();
  288. arg.pin_sta = 0; //设为低电平 接收态
  289. ioctl(fd_gpio, IOCTL_PIO_SETSTA, &arg);
  290. int fd_r=open_com_dev( DEV_UART );
  291. if( fd_r < 0 )
  292. {
  293. printf( "open UART device error! %s\n", DEV_UART );
  294. }
  295. else
  296. set_com_opt(fd_r, 2400, 8, 'n', 1);
  297. //set_com_opt(fd_r, 9600,8,'n',1);
  298. //执行select
  299. fd_set rd;
  300. FD_ZERO(&rd);
  301. FD_SET(fd_r, &rd);
  302. if ((res = select (fd_r+ 1,&rd, NULL, NULL, NULL) )< 0)
  303. {
  304. perror ( "read err");
  305. exit ( -1);
  306. }
  307. memset (buf, 0, sizeof (buf));
  308. if (FD_ISSET (fd_r, &rd))
  309. {
  310. //接收数据 8 8 2
  311. int res1 = 0;
  312. while ((nread = read(fd_r, buf, 8)) > 0)
  313. {
  314. //打印接收数据
  315. for (i = 0; i < nread; i++)
  316. {
  317. printf ( "%02X ", buf[i]);
  318. }
  319. //退出循环, 这里有点疑问
  320. res1 += nread;
  321. if (res1 == 18)
  322. {
  323. memset (buf, 0, sizeof (buf));
  324. printf ( "\n");
  325. break;
  326. }
  327. }
  328. }
  329. close (fd_r);
  330. pthread_mutex_unlock (&mutex);
  331. end=getSystemTime();
  332. printf( "time: %lld ms\n", end-start);
  333. usleep ( 200000);
  334. }
  335. }
  336. int main (void)
  337. {
  338. int error = 0, error1 = 0;
  339. arg.pin_idx = PIO_NUM;
  340. arg.pin_dir = AT91PIO_DIR_OUT;
  341. //打开/dev/pio设备
  342. fd_gpio = open(DEV_PIO_LED, O_RDWR);
  343. if(fd_gpio < 0)
  344. {
  345. perror( "fd_gpio open err");
  346. exit ( -1);
  347. }
  348. //初始化互斥量
  349. pthread_mutex_init (&mutex, 0);
  350. pthread_t tid, tid1;
  351. //创建线程
  352. error = pthread_create (&tid, NULL, task, NULL);
  353. error1 = pthread_create (&tid1, NULL, task1, NULL);
  354. //等待线程结束
  355. pthread_join (tid, NULL);
  356. pthread_join (tid1, NULL);
  357. //销毁互斥量
  358. pthread_mutex_destroy(&mutex);
  359. //关闭设备
  360. close (fd_gpio);
  361. return 0;
  362. }
执行结果:

三、串口通信总结

虽然以上代码只有三百多行,但是其包含的内容确是很多的,下面就一一的来总结。

一般招聘信息上 都会有这样一项要求。了解Modbus基于RS485,RS232,以太网等总线的通讯协议,熟练操作Modbus相关软件。

上面我们对RS485,RS232硬件做了分析,接下来我们看一下软件上面该如何处理。
主要分为下面部分来讲:

(1)串口编程详解

参看:Linux串口编程详解 

前面已经提到过Linux下皆为文件,这当然也包括我们今天的主角 UART0 串口。因此对他的一切操作都和文件的操作一样(涉及到了open,read,write,close等文件的基本操作)。

(一)Linux下的串口编程又那几部分组成

 

 

1.    打开串口

2.    串口初始化

3.    读串口或写串口

4.    关闭串口

(二)串口的打开

既然串口在linux中被看作了文件,那么在对文件进行操作前先要对其进行打开操作。

1.在Linxu中,串口设备是通过串口终端设备文件来访问的

即通过访问/dev/ttyS0,/dev/ttyS1,/dev/ttyS2这些设备文件实现对串口的访问。

==============================

这里有个问题:

你怎么知道访问的是哪个串口?

可以进行一下测试,echo hello > /dev/ttyS0  

看看是否有hello输出。

如果串口使用不对,会出现错误:

setup serial:bad file descriptor
set parity Error

==============================

2.调用open()函数来代开串口设备,对于串口的打开操作,必须使用O_NOCTTY参数。

l  O_NOCTTY:表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务一个输入(eg:键盘中止信号等)都将影响进程。

l  O_NDELAY:表示不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。

3.打开串口模块有那及部分组成

1> 调用open()函数打开串口,获取串口设备文件描述符

2> 获取串口状态,判断是否阻塞

3> 测试打开的文件描述符是否为终端设备

 

4程序:

[cpp]  view plain  copy
  1. /***************************************************************** 
  2. * 名称:                    UART0_Open 
  3. * 功能:                    打开串口并返回串口设备文件描述 
  4. * 入口参数:            fd    :文件描述符     port :串口号(ttyS0,ttyS1,ttyS2) 
  5. * 出口参数:            正确返回为1,错误返回为0 
  6. *****************************************************************/  
  7. int UART0_Open(int fd,char* port)  
  8. {  
  9.    
  10.       fd = open( port, O_RDWR|O_NOCTTY|O_NDELAY);  
  11.       if (FALSE == fd)  
  12.              {  
  13.                     perror("Can't Open Serial Port");  
  14.                     return(FASLE);  
  15.              }  
  16.   //判断串口的状态是否为阻塞状态                              
  17.   if(fcntl(fd, F_SETFL, 0) < 0)  
  18.       {  
  19.              printf("fcntl failed!/n");  
  20.            return(FALSE);  
  21.       }       
  22.   else  
  23.   {  
  24.        printf("fcntl=%d/n",fcntl(fd, F_SETFL,0));  
  25.   }  
  26.   //测试是否为终端设备      
  27.   if(0 == isatty(STDIN_FILENO))  
  28.       {  
  29.              printf("standard input is not a terminal device/n");  
  30.         return(FALSE);  
  31.       }  
  32.   else  
  33.       {  
  34.            printf("isatty success!/n");  
  35.       }         
  36.   printf("fd->open=%d/n",fd);  
  37.   return fd;  
  38. }  

(三)串口的初始化

1.    在linux中的串口初始化和前面的串口初始化一样。

需要设置串口波特率,数据流控制,帧的格式(即数据位个数,停止位,校验位,数据流控制)

2.    串口初始化模块有那几部分组成:

1> 设置波特率

2> 设置数据流控制

3> 设置帧的格式(即数据位个数,停止位,校验位)


说明:

1> 设置串口参数时要用到termios结构体,因此先要通过函数 tcgettattr(fd,&options)获得串口指向termios结构的指针。termios.h头文件中定义的termios结构,如下:



[cpp]  view plain  copy
  1. struct termios  
  2. {  
  3.    tcflag_t  c_iflag;   //输入模式标志  
  4.    tcflag_t  c_oflag;  //输出模式标志  
  5.    tcflag_t  c_cflag;  //控制模式标志  
  6.    tcflag_t  c_lflag;   //本地模式标志  
  7.    cc_t   c_line;              //line discipline  
  8.    cc_t   c_cc[NCC];    //control characters  
  9. }  

2> 通过cfsetispeed函数和cfsetospeed函数用来设置串口的输入/输出波特率。一般情况下,输入和输出波特率相等的。

3> 设置数据位可以通过修改termios机构体中c_flag来实现。其中CS5,CS6,CS7,CS8对应数据位的5,6,7,8。在设置数据位时,必须要用CSIZE做位屏蔽。

以下是几个数据位、停止位和校验位的设置方法:(以下均为1位停止位)

[cpp]  view plain  copy
  1. 8位数据位、无校验位:  
  2. Opt.c_cflag &= ~PARENB;  
  3. Opt.c_cflag &= ~CSTOPB;  
  4. Opt.c_cflag &= ~CSIZE;  
  5. Opt.c_cflag |= CS8;  
  6. 7位数据位、奇校验:  
  7. Opt.c_cflag |= PARENB;  
  8. Opt.c_cflag |= PARODD;  
  9. Opt.c_cflag &= ~CSTOPB;  
  10. Opt.c_cflag &= ~CSIZE;  
  11. Opt.c_cflag |= CS7;  
  12. 7位数据位、偶校验:  
  13. Opt.c_cflag |= PARENB;  
  14. Opt.c_cflag &= ~PARODD;  
  15. Opt.c_cflag &= ~CSTOPB;  
  16. Opt.c_cflag &= ~CSIZE;  
  17. Opt.c_cflag |= CS7;  
  18. 7位数据位、Space校验:  
  19. Opt.c_cflag &= ~PARENB;  
  20. Opt.c_cflag &= ~CSTOPB;  
  21. Opt.c_cflag &= ~CSIZE;  
  22. Opt.c_cflag |= CS7;  

4> 数据流控制是使用何种方法来标志数据传输的开始和结束。

5> 在设置完波特率,数据流控制,数据位,校验位,停止位,停止位后,还要设置最小等待时间和最小接收字符。

6> 在完成配置后要通过tcsetattr()函数来激活配置。

3.程序:

[cpp]  view plain  copy
  1. /******************************************************************* 
  2. * 名称:                UART0_Set 
  3. * 功能:                设置串口数据位,停止位和效验位 
  4. * 入口参数:        fd         串口文件描述符 
  5. *                              speed      串口速度 
  6. *                              flow_ctrl  数据流控制 
  7. *                           databits   数据位   取值为 7 或者8 
  8. *                           stopbits   停止位   取值为 1 或者2 
  9. *                           parity     效验类型 取值为N,E,O,,S 
  10. *出口参数:              正确返回为1,错误返回为0 
  11. *******************************************************************/  
  12. int UART0_Set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity)  
  13. {  
  14.      
  15.       int   i;  
  16.          int   status;  
  17.          int   speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,  
  18.           B38400, B19200, B9600, B4800, B2400, B1200, B300 };  
  19.      int   name_arr[] = {38400,  19200,  9600,  4800,  2400,  1200,  300,      38400, 19200,  9600, 4800, 2400, 1200,  300 };  
  20.            
  21.     struct termios options;  
  22.      
  23.     /*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数,还可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1. 
  24.     */  
  25.     if  ( tcgetattr( fd,&options)  !=  0)  
  26.        {  
  27.           perror("SetupSerial 1");      
  28.           return(FALSE);   
  29.        }  
  30.     
  31.     //设置串口输入波特率和输出波特率  
  32.     for ( i= 0;  i < sizeof(speed_arr) / sizeof(int);  i++)  
  33.                 {  
  34.               if  (speed == name_arr[i])  
  35.               {         
  36.                           cfsetispeed(&Options, speed_arr[i]);   
  37.                           cfsetospeed(&Options, speed_arr[i]);    
  38.               }  
  39.        }       
  40.      
  41.     //修改控制模式,保证程序不会占用串口  
  42.     options.c_cflag |= CLOCAL;  
  43.     //修改控制模式,使得能够从串口中读取输入数据  
  44.     options.c_cflag |= CREAD;  
  45.     
  46.     //设置数据流控制  
  47.     switch(flow_ctrl)  
  48.     {  
  49.         
  50.        case 0 ://不使用流控制  
  51.               options.c_cflag &= ~CRTSCTS;  
  52.               break;     
  53.         
  54.        case 1 ://使用硬件流控制  
  55.               options.c_cflag |= CRTSCTS;  
  56.               break;  
  57.        case 2 ://使用软件流控制  
  58.               options.c_cflag |= IXON | IXOFF | IXANY;  
  59.               break;  
  60.     }  
  61.     //设置数据位  
  62.     options.c_cflag &= ~CSIZE; //屏蔽其他标志位  
  63.     switch (databits)  
  64.     {    
  65.        case 5    :  
  66.                      options.c_cflag |= CS5;  
  67.                      break;  
  68.        case 6    :  
  69.                      options.c_cflag |= CS6;  
  70.                      break;  
  71.        case 7    :      
  72.                  options.c_cflag |= CS7;  
  73.                  break;  
  74.        case 8:      
  75.                  options.c_cflag |= CS8;  
  76.                  break;    
  77.        default:    &n
发布了5 篇原创文章 · 获赞 0 · 访问量 248

猜你喜欢

转载自blog.csdn.net/DIANZI520SUA/article/details/103596978
今日推荐