写在前边
本文是B站up主韦东山的4_10-6_Linux串口应用编程_回环_哔哩哔哩_bilibili视频的笔记,其中有些部分博主也没有理解,希望各位辩证的看。
面向有连接型
在串口应用实验中,根据视频信息,需要将串口6的发送和接收引脚连起来,在整个原理图中并未找到串口6,而且这里也没有购买扩展口,所以一直未找到串口6的位置,最后在百问网的问答贴中才找到了J5的复用接口:
串口应用编程 1MX6ULL J5原理图
串口应用编程 2MX6ULL J5复用原理图
串口应用编程 3实物连接图
连接完成后进行软件编程,这里软件编程主要就是完成串口的基本设置,最后在while循环中一直循环发送后读取数据。
这里的主要是实现串口的初始化和设置函数,接下来一个一个分析。
初始化函数:
/* com参数的格式为"/dev/ttySAC1" (串口文件描述符)*/
int open_port(char* com)
{
int fd;
//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
// 打开串口,获得文件描述符,O_NOCTTY:如果路径名指向终端设备,不要把这个设备用作控制终端。
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;
}
这个函数主要就是打开串口设备节点,然后获得串口的文件描述符fd。
设置串口参数函数:
/* set_opt(fd,115200,8,'N',1)
* 设置串口参数
*/
int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio, oldtio;
/*
* tcgetattr函数用于获取与终端相关的参数。参数fd为终端的文件描述符,返回的结果保存在termios结构体中
* tcflag_t c_iflag;
* tcflag_t c_oflag;
* tcflag_t c_cflag;
* tcflag_t c_lflag;
* cc_t c_cc[NCCS];
* 在配置终端之前,通常需要先保存当前的终端属性,
* 以便在配置完成后可以恢复到原来的状态。这样可以
* 避免对终端的其他部分产生不必要的影响
*/
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;
}
这个函数中主要实现了对串口的基本参数的设置:波特率、停止位、校验位等,其中,这里还保存了之前串口的设置,可以后期自己在使用完成后恢复之前的串口设置:
串口应用编程 4保存串口之前设置
串口之前的设置保存在oldtio结构体中。
串口应用编程 5设置最少接收到数据返回
这里设置最小接收到1个字节数据才返回,一直等待。
这两个函数实现完成后,在主函数中调用即可,而且后续再次使用时,一般不需要更改这两个函数内部,只需要在外部调用即可。
主函数:
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);
if (c == 'Q')
{
break;
}
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;
}
实现结果是完成串口回环,即向控制台发送数据,串口接受到数据之后,将这个数据返回到控制台处,具体运行结果如下图。
串口应用编程 6运行程序结果