linux下串口编程
上一篇 / 下一篇 2012-08-17 00:40:02 / 个人分类:转载
今天心血来潮,学习了一下linux下的串口编程。参考网上的一些学习资料,做了一下学习笔记。
串口简介串行口是计算机一种常用的接口,主要用于算机主机与外设之间以及主机系统与主机系统之间数据的串行传送,具有连接线少,通讯简单,得到广泛的使用。
常用的串口是 RS-232-C接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、 调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个 25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4%的情况下,传输电缆长度应为 50 英尺。
计算机串口的引脚说明
序号 | 信号名称 | 符号 | 流向 | 功能 |
2 | 发送数据 | TXD | DTE->DCE | DTE发送串行数据 |
3 | 接收数据 | RXD | DTE<-CE | DTE 接收串行数据 |
4 | 请求发送 | RTS | DTE->DCE | DTE请求DCE将线路切换到发送方式 |
5 | 允许发送 | CTS | DTE<-CE | DCE告诉DTE线路已接通可以发送数据 |
6 | 数据设备准备好 | DSR | DTE<-CE | DCE准备好 |
7 | 信号地 | 信号公共地 | ||
8 | 载波检测 | DCD | DTE<-CE | 表示DCE接收到远程载波 |
20 | 数据终端准备好 | DTR | DTE->DCE | DTE准备好 |
22 | 振铃指示 | RI | DTE<-CE | 表示DCE与线路接通,出现振铃 |
串口访问函数
1)打开串口
int fd;
fd = open("/dev/ttyS0",O_RDWR|NOCTTY|O_NDELAY);
ttyS0是串口1,ttyS1是串口2,O_RDWR|NOCTTY|O_NDELAY分别表示可读写,非控制终端(防止任意的中断信号(如键盘)影响程序的执行),不关注DCD信号线的状态.详细设置如下:
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以读写方式打开文件
O_APPEND 写入数据时添加到文件末尾
O_CREATE 如果文件不存在则产生该文件,使用该标志需要设置访问权限位mode_t
O_EXCL 指定该标志,并且指定了O_CREATE标志,如果打开的文件存在则会产生一个错误
O_TRUNC 如果文件存在并且成功以写或者只写方式打开,则清除文件所有内容,使得文件长度变为0
O_NOCTTY 如果打开的是一个终端设备,这个程序不会成为对应这个端口的控制终端,如果没有该标志,任何一个输入,例如键盘中止信号等,都将影响进程。
O_NONBLOCK 该标志与早期使用的O_NDELAY标志作用差不多。程序不关心DCD信号线的状态,如果指定该标志,进程将一直在休眠状态,直到DCD信号线为0。
O_SYNC 对I/O进行写等待
返回值:成功返回文件描述符,如果失败返回-1
2)关闭串口
close(fd); //返回值:成功返回0,否则返回-1
3)向串口写数据
int n;
n = write(fd,buff,bufLen);
buff是数据的缓冲区地址,bufLen是其长度,n 是实际发送的长度
4)从串口读数据
n = read(fd,buff,bufLen);
注意,在对串口进行读取操作的时候,如果是使用的RAW模式,每个read系统调用将返回当前串行输入缓冲区中存在的字节数。如果没有数据,将会一致阻塞 到有字符达到或者间隔时钟到期,或者发生错误。如果想使read函数在没有数据的时候立即返回则可以使用fcntl函数来设置文件访问属性。例如:
fcntl(fd, F_SETFL, FNDELAY);
这样设置后,当没有可读取的数据时,read函数立即返回0。
通过fcntl(fd, F_SETFL, 0)可以设置回一般状态。
5)设置串口属性
控制结构为:POSIX终端接口
大多数系统都支持POSIX终端接口,POSIX终端通过一个termios结构来进行控制,该结构定义在termios.h文件中。
termios结构
struct termios
{
tcflag_t c_iflag; /* 输入选项标志 */
tcflag_t c_oflag; /* 输出选项标志 */
tcflag_t c_cflag; /* 控制选项标志 */
tcflag_t c_lflag; /* 本地选项标志 */
cc_t c_cc[NCCS]; /* 控制特性 */
};
(1)设置波特率
struct termios Opt;
tcgetattr(fd, &Opt); /*得到当前的串口属性*/
cfsetispeed(&Opt,B19200); /*设置为19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt); /*设置新的串口属性*/
(2)设置控制模式->流控
不使用:Opt.c_cflag &= ~CRTSCTS
硬件: Opt.c_cflag |= CRTSCTS
软件: Opt.c_cflag = IXON|IXOFF|IXANY
->设置字符的大小
Opt.c_cflag &= ~ CSIZE//先屏蔽字符大小位
Opt.c_cflag |= CS8 //选择8位数据位
Opt.c_cflag |= CS7 //选择7位数据位
Opt.c_cflag |= CS6 //选择6位数据位
Opt.c_cflag |= CS5 //选择5位数据位
->设置奇偶校验
无校验位: Opt.c_cflag &=~PARENB
奇校验: Opt.c_cflag |= PARENB;
Opt.c_cflag &=PARODD;
偶校验: Opt.c_cflag |=PARENB;
Opt.c_cflag &=~PARODD;
串口开发实例
#include <stdio.h> /*标准输入输出定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <unistd.h> /*Unix标准函数定义*/
#include <sys/types.h> /**/
#include <sys/stat.h> /**/
#include <fcntl.h> /*文件控制定义*/
#include <termios.h> /*PPSIX终端控制定义*/
#include <errno.h> /*错误号定义*/
#include <string.h> /**/
/**
*@brief 设置串口通信速率
*@param fd 类型 int 打开串口的文件句柄
*@param speed 类型 int 串口速度
*@return void
*/
int speed_arr[] = {B38400 ,B19200, B9600, B4800, B2400, B1200, B300, B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300, };
void set_speed(int fd, int speed)
{
int i;
int status;
struct termios Opt;
tcgetattr(fd, &Opt);
for(i = 0; i < sizeof(speed_arr)/sizeof(int); i++)
{
if(speed == name_arr[i])
{
tcflush(fd, TCIOFLUSH);
cfsetispeed(&Opt, speed_arr[i]);
cfsetospeed(&Opt, speed_arr[i]);
status = tcsetattr(fd, TCSANOW, &Opt);
if(status != 0)
perror( "tcsetattr fd1 ");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
/**
*@brief 设置串口数据位,停止位和效验位
*@param fd 类型 int 打开的串口文件句柄
*@param databits 类型 int 数据位 取值为7或者8
*@param stopbits 类型 int 停止位 取值为1或者2
*@param parity 类型 int 效验类型 取值为N,E,O,,S
*/
int set_Parity(int fd, int databits, int stopbits,int parity)
{
struct termios options;
if(tcgetattr(fd,&options) != 0)
{
perror( "SetupSerial 1 ");
return -1 ;
}
options.c_cflag &= ~CSIZE;
switch(databits) /*设置数据位数*/
{
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
fprintf(stderr, "Unsupported data size\n ");
return -1;
}
switch (parity)
{
case 'n':
case 'N':
options.c_cflag &= ~PARENB; /* Clear parity enable */
options.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case 'o':
case 'O':
options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E':
options.c_cflag |= PARENB; /* Enable parity */
options.c_cflag &= ~PARODD; /* 转换为偶效验*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'S':
case 's': /*as no parity*/
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
fprintf(stderr, "Unsupported parity\n ");
return -1;
}
/* 设置停止位*/
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr, "Unsupported stop bits\n ");
return -1;
}
/* Set input parity option */
if(parity != 'n')
options.c_iflag |= INPCK;
options.c_cc[VTIME] = 150; // 15 seconds
options.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH); /* Update the options and do it NOW */
if(tcsetattr(fd,TCSANOW,&options) != 0)
{
perror( "SetupSerial 3 ");
return -1;
}
return 0;
}
/**
*@breif 打开串口
*/
int OpenDev(char *Dev)
{
int fd = open(Dev, O_RDWR | O_NOCTTY | O_NDELAY); //| O_NOCTTY | O_NDELAY
if (-1 == fd)
{ /*设置数据位数*/
perror( "Can 't Open Serial Port ");
return -1;
}
else
return fd;
}
/**
*@breif main()
*/
int main(int argc, char **argv)
{
int fd;
int nread;
char buff[512] = "12345";
char *dev = "/dev/pts/1";
fd = OpenDev(dev);
if(fd > 0)
set_speed(fd,19200);
else
{
printf( "Can 't Open Serial Port!\n ");
exit(0);
}
if(set_Parity(fd,8,1,'N') == -1)
{
printf( "Set Parity Error\n ");
exit(1);
}
/*receive datas*/
/*while(1)
{
while((nread = read(fd,buff,512))> 0)
{
printf( "\nLen %d\n ",nread);
buff[nread+1]= '\0';
printf( "\n%s ",buff);
}
}*/
/*send datas*/
if (write(fd, buff, strlen(buff))>= 0)
{
printf("\nsend string: %s \n", buff);
}
//close(fd);
//exit(0);
}
测试
由于不方便进行主机与主机或者主机与外设之间进行串口通信的测试在上面例子中,我利用了linux主机上的伪终端设备来做个测试。首先获取当前伪终端的名称,可在终端输入“ps”命令,如:
[shuaigexin@shuaigexin dev]$ psPID TTY TIME CMD
3736 pts/1 00:00:00 bash
4120 pts/1 00:00:00 ps
[shuaigexin@shuaigexin dev]$
或者运行代码获取当前伪终端的名称:
#include <stdio.h>
#include <unistd.h>
int main()
{
char* _ttyname;
if((_ttyname = ttyname(STDIN_FILENO)) == NULL)
{
printf("Get ttyname error\n");
return -1;
}
printf("ttyname is %s\n", _ttyname);
if((_ttyname = ttyname(STDOUT_FILENO)) == NULL)
{
printf("Get ttyname error\n");
return -1;
}
printf("ttyname is %s\n", _ttyname);
return 0;
}
运行结果:
[shuaigexin@shuaigexin ttyname]$ ./ttyname
ttyname is /dev/pts/1
ttyname is /dev/pts/1
[shuaigexin@shuaigexin ttyname]$
从上面的结果可知当前伪终端的名称为“pts/1”。这样就可以向该终端发送串行数据“12345”,在主机在编译并运行程序,得出的结果如下:
[shuaigexin@shuaigexin com]$ ./com1
12345
send string: 12345
为以测试伪终端串行读数据功能,修改了一下源代码,只是将
/*while(1)
{
while((nread = read(fd,buff,512))> 0)
{
printf( "\nLen %d\n ",nread);
buff[nread+1]= '\0';
printf( "\n%s ",buff);
}
}*/
这段代码的注释符号去掉。重新编译并运行代码,结果如下:
[shuaigexin@shuaigexin com]$ ./com1
hello shuaigexin
Len 17
hello shuaigexin
hello world
Len 12
hello world
[shuaigexin@shuaigexin com]$
从测试的结果看出,达到串行通信的效果。
有机会的话,测试一下主机与主机或者主机与外设之间进行串口通信。
参考:
http://blog.chinaunix.net/u1/45323/showart_445312.html
http://junyi620.blog.163.com/blog/static/3051088020079150552264/
左锦,《Linux 下串口编程入门》
http://topic.csdn.net/t/20060927/14/5051783.html
http://blog.csdn.net/liqinghua1653/archive/2009/02/18/3904084.aspx
转载:http://hi.baidu.com/hnewfriend/blog/item/2e98cd05823e0e171d95832a.html
TAG: linux下串口编程
希望自己天天进步
标题搜索
日历
|
|||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
1 | 2 | 3 | 4 | 5 | 6 | ||||
7 | 8 | 9 | 10 | 11 | 12 | 13 | |||
14 | 15 | 16 | 17 | 18 | 19 | 20 | |||
21 | 22 | 23 | 24 | 25 | 26 | 27 | |||
28 | 29 | 30 |
我的存档
数据统计
- 访问量: 66294
- 日志数: 79
- 书签数: 1
- 建立时间: 2010-08-29
- 更新时间: 2013-04-06