所学均来自百问网
目录
1. 串口硬件介绍
UART(Universal Asynchronous Receiver and Transmitter):异步发送和接收
格式:
起始位(1bit)+ 数据位(8~9bit)+ 奇偶校验位(1bit)+ 停止位(1~1.5bit)
起始位:当未有数据发送时,数据线处于逻辑“1”状态;先发出一个逻辑“0”信号,表示开始传输字符。
数据位:紧随起始位之后,数据位表示真正要发送或接收的信息,位数一般有8位或9位
奇偶校验位: 数据位末尾可以选择是否添加奇偶校验位,用于检测数据传输是否正确
停止位:代表信息传输结束的标志位,可以是1位,1.5位或2位。停止位的位数越多,数据传输的速率也越慢
波特率:每秒钟传输的数据位数,一般设置为9600或115200
2. TTY体系中设备节点的差别
图解:硬件通过驱动串口与RAM串口发送和接收数据,当PC数据发送过去后会先到达行规程,行规程判断数据,若是普通字符则立刻返回给串口回显到虚拟机TTY,若遇到回车,则会发送到app后执行命令并将结果返回给行规程再给串口回显到虚拟机TTY
举例:键盘输入lsa回退格,虚拟机TTY显示ls,但行规格收到ls a 回退格
/dev/ttyN(N=1,2,3,…)
/dev/tty3、/dev/tty4:表示某个程序使用的虚拟终端
// 在tty3、tty4终端来回切换,执行命令 echo hello > /dev/tty3 echo hi > /dev/tty4
/dev/tty0
/dev/tty0:表示前台程序的虚拟终端
-
你正在操作的界面,就是前台程序
-
其他后台程序访问/dev/tty0的话,就是访问前台程序的终端,切换前台程序时,/dev/tty0是变化的
// 1. 在tty3终端执行如下命令 // 2. 然后在tty3、tty4来回切换 while [ 1 ]; do echo msg_from_tty3 > /dev/tty0; sleep 5; done
/dev/tty
/dev/tty:表示当前程序所使用的终端,可能是虚拟终端,也可能是真实的中断。
程序A在前台、后台间切换,它自己的/dev/tty都不会变。
// 1. 在tty3终端执行如下命令 // 2. 然后在tty3、tty4来回切换 while [ 1 ]; do echo msg_from_tty3 > /dev/tty; sleep 5; done
/dev/console 选哪个?内核的打印信息从哪个设备上显示出来? 可以通过内核的cmdline来指定, 比如: console=ttyS0 console=tty 我不想去分辨这个设备是串口还是虚拟终端, 有没有办法得到这个设备? 有!通过/dev/console! console=ttyS0时:/dev/console就是ttyS0 console=tty时:/dev/console就是前台程序的虚拟终端 console=tty0时:/dev/console就是前台程序的虚拟终端 console=ttyN时:/dev/console就是/dev/ttyN console有多个取值时,使用最后一个取值来判断
3. Linux串口编程相关函数
-
编程套路:
-
open
-
设置行规程,比如波特率、数据位、停止位、校验位、RAW模式、一有数据就返回
-
read/write
-
termios用于获取和设置串口参数,它包含了波特率、字符大小、停止位、奇偶校验位等。
这些函数在名称上有一些惯例:
*tc:terminal contorl
*cf: control flag
struct termios {
tcflag_t c_iflag; // 输入模式标志
tcflag_t c_oflag; // 输出模式标志
tcflag_t c_cflag; // 控制模式标志
tcflag_t c_lflag; // 本地模式标志
cc_t c_cc[NCCS]; // 控制字符数组
speed_t c_ispeed; // 输入波特率(非POSIX)
speed_t c_ospeed; // 输出波特率
};
-
获取与终端(或串口)相关的参数设置,并将这些设置存储在
termios
结构体中。这个结构体随后可以被修改,并通过tcsetattr
函数应用回终端。-
int tcgetattr(int fd, struct termios *termios_p);
-
fd
:打开的终端或串口设备文件的文件描述符。 -
termios_p
:指向termios
结构体的指针,用于存储获取的参数设置。 -
返回值:成功时返回0,失败时返回-1并设置errno。
-
-
根据
termios
结构体中的参数设置来配置终端(或串口)-
int tcsetattr(int fd, int actions, const struct termios *termios_p);
-
fd
:打开的终端或串口设备文件的文件描述符。 -
actions
:指定何时更改设置,可以是TCSANOW
(立即更改)、TCSADRAIN
(等待所有写入的数据都传输出去后再更改)或TCSAFLUSH
(等待所有写入的数据都传输出去,并且丢弃所有未读取的数据后再更改)。 -
termios_p
:指向包含新设置的termios
结构体的指针。 -
返回值:成功时返回0,失败时返回-1并设置errno。
-
-
丢弃未传输或未读取的数据。
-
int tcflush(int fd, int queue_selector);
-
fd
:打开的终端或串口设备文件的文件描述符。 -
queue_selector
:指定要丢弃的数据类型,可以是TCIFLUSH
(丢弃收到的数据,但不读取)、TCOFLUSH
(丢弃写入的数据,但不传送)或TCIOFLUSH
(同时丢弃收到的数据和写入的数据)。 -
返回值:成功时返回0,失败时返回-1并设置errno。
-
-
设置终端(或串口)的输入波特率。
-
speed_t cfsetispeed(struct termios *termios_p, speed_t speed);
-
termios_p
:指向termios
结构体的指针,该结构体的c_ispeed
字段将被设置为新的输入波特率。 -
speed
:新的输入波特率,通常是B0
到B57600
(或其他更高值,取决于系统支持)之间的一个常量。 -
返回值:返回之前的输入波特率。
-
-
设置终端(或串口)的输出波特率。
-
speed_t cfsetospeed(struct termios *termios_p, speed_t speed);
-
termios_p
:指向termios
结构体的指针,该结构体的c_ospeed
字段将被设置为新的输出波特率。 -
speed
:新的输出波特率,通常是B0
到B57600
(或其他更高值,取决于系统支持)之间的一个常量。 -
返回值:返回之前的输入波特率。
-
-
内存区域清空
-
void bzero(void *s, size_t n);
-
void *s:指向要清理的内存指针
-
size_t n:清理的字节数
-
4. 示例代码(回环)
// 此代码只需要一个串口,串口的自己发送消息自己接收消息
// 操作:将串口的RX和TX连接成一个回环
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;
// 获取串口相关参数设置
if ( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
// 清空内存
bzero( &newtio, sizeof( newtio ) );
// 对串口的控制模式和本地模式进行设置
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
newtio.c_oflag &= ~OPOST; /*Output*/
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent )
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch( nSpeed )
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VMIN] = 1; /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间:
* 比如VMIN设为10表示至少读到10个数据才返回,
* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
* 假设VTIME=1,表示:
* 10秒内一个数据都没有的话就返回
* 如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
*/
// 清理缓存
tcflush(fd,TCIFLUSH);
// 配置串口参数
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
//printf("set done!\n");
return 0;
}
int open_port(char *com)
{
int fd;
//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
fd = open(com, O_RDWR|O_NOCTTY);
if (-1 == fd){
return(-1);
}
if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
{
printf("fcntl failed!\n");
return -1;
}
return fd;
}
/*
* ./serial_send_recv <dev>
*/
int main(int argc, char **argv)
{
int fd;
int iRet;
char c;
/* 1. open */
/* 2. setup
* 115200,8N1
* RAW mode
* return data immediately
*/
/* 3. write and read */
if (argc != 2)
{
printf("Usage: \n");
printf("%s </dev/ttySAC1 or other>\n", argv[0]);
return -1;
}
fd = open_port(argv[1]);
if (fd < 0)
{
printf("open %s err!\n", argv[1]);
return -1;
}
iRet = set_opt(fd, 115200, 8, 'N', 1);
if (iRet)
{
printf("set port err!\n");
return -1;
}
printf("Enter a char: ");
while (1)
{
scanf("%c", &c);
iRet = write(fd, &c, 1);
iRet = read(fd, &c, 1);
if (iRet == 1)
printf("get: %02x %c\n", c, c);
else
printf("can not get data\n");
}
return 0;
}