本帖最后由 weiyaoxing 于 2016-12-15 12:27 编辑
一、串口知识 串行接口 (SerialInterface) 是指数据一位一位地顺序传送,其特点是 通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。 1、 波特率 表示每秒传输的比特数,串口通信的双方必须保持一致才能通信数据位,若波特率为115200,它表示什么呢? 对于发送断,即每秒钟发送115200bit。 对于接收端,115200波特率意味着串口通信在数据线上的采样率为115200Hz. 2、 数据位 这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、6、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。7位或8位数据中不仅仅是数据,还包括开始/停止位,数据位以及奇偶校验位等 3、 停止位 用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。 4、 奇偶校验 一般奇偶校验位的应用是由硬件完成的,软件只需要在初始化的时候对MCU的串口外设设置一下 奇偶校验的形式一般是在数据位后面跟一个奇偶校验位,如果数据位是8位的,那么加上校验位就是9位数据;如果数据位是7位的,那么加上校验位就是8位数据,校验位和数据之间没有任何意义上的关联,它不是数据的一部分,它只是关心数据中的“1”的个数是否为奇数(奇校验),或者偶数(偶校验)。奇校验,奇校验就是控制器在发送的一个字节或者一个数据帧里面含有的“1”的个数进行奇数个的修正调整,这里采用8位数据位+1位奇校验位的形式举个简单的例子,A发送数据0x35到B,后面紧跟一个奇校验位X, 0x35的二进制 = 0011 0101,可以看出8个数据位中一共有4个‘1’,那此时硬件会根据“1”的个数来设置X,这里会将奇校验位X调整为“1”,为什么呢? 因为数据位的“1”的个数是偶数,而我们用的是奇校验,所以为了达到有奇数个“1”的目的,调整奇偶校验位X为“1”,那么这9个位发出去就有奇数个“1”啦,当B接收的时候,B的硬件同样会对接收到“数据+X”中“1”的个数进行计数判断,如果是奇数个“1”,则认为数据接收正确,否则认为数据错误。而当A发送数据0x25( 0010 0101)到B的时候,则X应该就是“0”了,因为要保证发送出去的数据位+X位一共有奇数个“1”。 二、串口编程 串口编程的步骤:
1、 打开串口 这里是串口操作需要的一些头文件
- #include /*标准输入输出定义*/
- #include /*标准函数库定义*/
- #include /*Unix 标准函数定义*/
- #include
- #include
- #include /*文件控制定义*/
- #include /*PPSIX 终端控制定义*/
- #include /*错误号定义*/
复制代码
Linux下一切皆文件,所以串口操作也是对文件进行操作的 Linux的串口文件位于 /dev下的
- /dev/ttyS0 /* 串口0 */
- /dev/ttyS1 /* 串口1 */
复制代码
这里我们通过标准的文件打开操作尝试打开串口 1 在这之前先建立一个 .c 文件 这里是 uatr2.c
- #include /*标准输入输出定义*/
- #include /*标准函数库定义*/
- #include /*Unix 标准函数定义*/
- #include
- #include
- #include /*文件控制定义*/
- #include /*PPSIX 终端控制定义*/
- #include /*错误号定义*/
- int main()
- {
- int fd;
- /*以读写方式打开串口*/
- fd = open( "/dev/ttyS1", O_RDWR);
- if (-1 == fd)
- /* 不能打开串口一*/
- perror(" 提示错误!");
- else
- printf("successn");
- close(fd);
- }
复制代码
编译 .c 文件
运行
调用open()函数来代开串口设备,对于串口的打开操作,必须使用O_NOCTTY参数。 O_NOCTTY:表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务一个输入(eg:键盘中止信号等)都将影响进程。 O_NDELAY:表示不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。
1、 串口波特率设置 串口的设置主要是设置 struct termios 结构体的各成员值。
- struct termio
- { unsigned short c_iflag; /* 输入模式标志 */
- unsigned short c_oflag; /* 输出模式标志 */
- unsigned short c_cflag; /* 控制模式标志*/
- unsigned short c_lflag; /* local mode flags */
- unsigned char c_line; /* line discipline */
- unsigned char c_cc[NCC]; /* control characters */
- };
复制代码
设置这个结构体很复杂,这里就只说说常见的一些设置:
波特率设置
下面是修改波特率的代码:
- struct termios Opt;
- tcgetattr(fd, &Opt);
- cfsetispeed(&Opt,B19200); /*设置为19200Bps*/
- cfsetospeed(&Opt,B19200);
- tcsetattr(fd,TCANOW,&Opt);
复制代码
设置波特率的例子函数:
- /**
- *@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);
- }
- }
- }
复制代码
3、校验位和停止位的设置
设置效验的函数:
- /**
- *@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(FALSE);
- }
- 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 sizen"); return (FALSE);
- }
- 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 parityn");
- return (FALSE);
- }
- /* 设置停止位*/
- switch (stopbits)
- {
- case 1:
- options.c_cflag &= ~CSTOPB;
- break;
- case 2:
- options.c_cflag |= CSTOPB;
- break;
- default:
- fprintf(stderr,"Unsupported stop bitsn");
- return (FALSE);
- }
- /* Set input parity option */
- if (parity != 'n')
- options.c_iflag |= INPCK;
- tcflush(fd,TCIFLUSH);
- options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/
- options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
- if (tcsetattr(fd,TCSANOW,&options) != 0)
- {
- perror("SetupSerial 3");
- return (FALSE);
- }
- return (TRUE);
- }
复制代码
4、 读写串口
设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。
发送数据
- char buffer[1024];
- int Length;
- int nByte;nByte = write(fd, buffer ,Length);
复制代码
读取串口数据
使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。
可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。
- char buff[1024];
- int Len;
- int readByte = read(fd,buff,Len);
复制代码
5、 关闭串口
关闭串口就是关闭文件。
close(fd);
下面是串口1的一个简单的读取与发送的例子(复制上面的两个函数)
- #include /*标准输入输出定义*/
- #include /*标准函数库定义*/
- #include /*Unix标准函数定义*/
- #include /**/
- #include /**/
- #include /**/
- #include /*文件控制定义*/
- #include /*PPSIX终端控制定义*/
- #include /*错误号定义*/
- #define FALSE 0
- #define TRUE 1
- int OpenDev(char *Dev)
- {
- int fd = open( Dev, O_RDWR ); //| 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 , n;
- int nread;
- char buff[512];
- char *dev ="/dev/ttyS1";
- 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')== FALSE) {
- printf("Set Parity Errorn");
- exit(1);
- }
- while(1) {
- while((nread = read(fd,buff,512))>0) {
- printf("nLen %dn",nread);
- buff[nread+1]='\0';
- printf("n%s",buff);
- n = write(fd, "I getr", 5); //如果收到数据则向串口发送I Get
- if (n < 0)
- fputs("write() of 4 bytes failed!n", stderr);
- }
- }
- close(fd);
- exit(0);
- }
复制代码
这里我用的是putty,经测试,使用的PL2302 u***转串口会出现不可预知错误的乱码。使用原生的串口则收发正常。
也可以使用命令
- echo hello world >/dev/ttyS1
复制代码
向串口一发送数据 hello world
最后附上nanopi的图
本文参考:https://www.ibm.com/developerworks/cn/linux/l-serials/
|