Linux 串口编程记录

参考:https://blog.csdn.net/wangzhen209/article/details/52620906

执笔时间:2019.1.18

一、安装串口驱动

在 /dev/目录中 tty* ttys* 的文件为串口设备的驱动文件,现在的笔记本和台式机很多已经不在自带串口,我用了USB-串口的数据线,此时驱动为ttyUSB*,我用的Ubuntu 18.04 内核中已经自带USB-串口的驱动文件,不过没有编译。如果直接使用串口线,而没有用到USB转串口设备,就不需要安装驱动。 

1、如果使用了USB转串口,一般情况下也不需要安装驱动了,目前linux系统已经包含了该驱动,可以自动识别,亦可通过以下命令查看以便确认是否支持。 

lsmod | grep usbserial

如果输出内容中包含 usbserial 说明能正确识别该设备,否则安装该设备的驱动。 

参考:https://blog.csdn.net/hhhlizhao/article/details/53790822

输入 dmesg | tail -f

若出现类似

[ 8697.367818] usb 1-2: Detected FT232RL
[ 8697.368307] usb 1-2: FTDI USB Serial Device converter now attached to ttyUSB0

的信息,说明USB转串口的驱动已经加载,可以直接使用。

注意:默认情况下ubuntu已经安装了USB转串口驱动(pl2303)。

2、插上USB转串口,在终端输入命令#dmesg | grep ttyUSB0,如果出现连接成功信息,则说明ubuntu系统已经识别该设备了。

注意:虚拟机环境下的ubuntu默认情况下是不能自动识别的,需要在虚拟机窗口右下角点击"Prolific USB-Serial Controller",然后选择"Connect (Disconnect from Host)",这样才能被ubuntu系统识别。

3、在上面minicom的配置中设置Serial Device: /dev/ttyUSB0,重启开发板,这样基本上就可以正常使用minicom来打印串口信息了。

4、问题
如果经过上面的步骤minicom还是不能正常工作,出现如下错误提示:
# sudo minicom
minicom: cannot open /dev/ttyUSB0: 没有该文件或目录

这时可以尝试换一个USB口,然后再次操作以上流程。如果还是提示这个错误,那么可以使用下面的方法来解决。

这 种方法是在硬件里添加串口设备,将window下的设备添加到虚拟机里。也就是说,要在window获得焦点的时候加入usb转串口,然后再到虚拟机下将 这个设备添加进去,这时就可以在ubuntu下查看添加的这个设备的设备文件,一般是/dev/tty0或者 /dev/ttyS0。

这种方法其实是将window的usb转串口作为虚拟机的串口,所以就是tty0或者ttyS0了,而不是真正在ubuntu下加载的。

具体步骤如下:
(1)打开虚拟机环境,然后选择"VM-->Settings(Ctrl+D)"。
(2)点"Add",进入添加硬件向导,选择"Serial Port",点"Next"。
(3)选择第一项"Use physical port on the host",点"Next"。
(4)选择"Physical serial port"方式为"Auto detect",勾选"Connect at power on",点"Finish"完成。
 

二、串口测试

现在已经有很多linux串口调试工具,https://blog.csdn.net/wilylcyu/article/details/51766283

其中cutecom简单实用,界面友好,适合类似我这种Linux入门的同志使用。

安装 cutecom :sudo apt-get install cutecom

打开程序:sudo cutecom

点开右上角settings,设置合适自己的参数,重点是设备选取一定要正确

我的是 /dev/ttyUSB0

点击Open打开串口,在input栏输入内容按Enter即可令串口发送数据

将串口的第二、第三脚短接,再次发送数据,即可在接受栏中看到发送的数据

证明串口设备正常发、收数据。

三、串口编程

参考:https://blog.csdn.net/u012010054/article/details/81092579

#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> 
#include <linux/kernel.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
 
char rev_buf[256];
char databuf[] = {1,2,0x33,0x34,0x35,6,7,11,12,89};
int uart_fd;
 
#define DEV_NAME "/dev/ttyUSB0"
 
int Set_Com_Config(int fd, int baud_rate, int data_bits, char parity, int stop_bits)
{
       struct termios new_cfg, old_cfg;
	   
       int speed;
	   
       /*保存并测试现有串口参数设置,在这里如果串口号等出错,会有相关出错信息*/
       if(tcgetattr(fd, &old_cfg) != 0)       /*该函数得到fd指向的终端配置参数,并将它们保存到old_cfg变量中,成功返回0,否则-1*/
	{
	
              perror("tcgetttr");
			  
              return -1;
			  
       }
 
        /*设置字符大小*/
       new_cfg = old_cfg;   
		
       cfmakeraw(&new_cfg); /*配置为原始模式*/ 
	   
       new_cfg.c_cflag &= ~CSIZE; /*用位掩码清空数据位的设置*/  
 
      /*设置波特率*/
       switch(baud_rate)
	   	
       {
		case 2400:
			speed = B2400;			
			break;
 
		case 4800:			
			speed = B4800;			
			break;
 
		case 9600:
			speed = B9600;			
			break;
 
		case 19200:			
			speed = B19200;			
			break;
 
		case 38400:			
			speed = B38400;			
			break;
 
		default:			
		case 115200:			
			speed = B115200;			
			break;
		
       }
 
       cfsetispeed(&new_cfg, speed); //设置输入波特率
	   
       cfsetospeed(&new_cfg, speed); //设置输出波特率
 
       /*设置数据长度*/
       switch(data_bits)
	   	
       {
		case 5:
			new_cfg.c_cflag &= ~CSIZE;//屏蔽其它标志位
			new_cfg.c_cflag |= CS5;
			break;
		
		case 6:
			new_cfg.c_cflag &= ~CSIZE;//屏蔽其它标志位
			new_cfg.c_cflag |= CS6;
	   		break;
		
		case 7:
			new_cfg.c_cflag &= ~CSIZE;//屏蔽其它标志位
			new_cfg.c_cflag |= CS7;
			break;
 
		default:			
		case 8:
			new_cfg.c_cflag &= ~CSIZE;//屏蔽其它标志位
			new_cfg.c_cflag |= CS8;
			break;
		
       }
 
	/*设置奇偶校验位*/
	switch(parity)
	{
		default:
			
		case 'n':
			
		case 'N': //无校验
			
		{
			new_cfg.c_cflag &= ~PARENB;
			
			new_cfg.c_iflag &= ~INPCK;
		}	
		break;
 
		case 'o': //奇校验
			
		case 'O':
			
		{
			new_cfg.c_cflag |= (PARODD | PARENB);
			
			new_cfg.c_iflag |= INPCK;
		}		
		break;
 
		case 'e': //偶校验
 
		case 'E':
 
		{
 
			new_cfg.c_cflag |=  PARENB;
 
			new_cfg.c_cflag &= ~PARODD;
 
			new_cfg.c_iflag |= INPCK;
 
		}
		break;
		
	}
 
       /*设置停止位*/
       switch(stop_bits)
 
       {
 
		default:
		case 1:
			new_cfg.c_cflag &= ~CSTOPB;
			break;
 
		case 2:
			new_cfg.c_cflag |= CSTOPB;
			break;
		
       }
 
 
 
      /*设置等待时间和最小接收字符*/
       new_cfg.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
 
       new_cfg.c_cc[VMIN] = 1; /* 读取字符的最少个数为1 */
 
 
	/*处理未接收字符*/
	tcflush(fd, TCIFLUSH); //溢出数据可以接收,但不读
 
 
 
       /* 激活配置 (将修改后的termios数据设置到串口中)
         * TCSANOW:所有改变立即生效
         */
       if((tcsetattr(fd, TCSANOW, &new_cfg))!= 0)
 
       {
       
              perror("tcsetattr");
 
              return -1;
			  
       }
 
       return 0;
	   
}
 
int Uart_Send(int fd, char *data, int data_len)  
{  
	int len = 0;  
   	len = write(fd, data, data_len);
	
    	if(len == data_len) {  
        	return len;  
    	} else {  
        	tcflush(fd, TCOFLUSH); //TCOFLUSH刷新写入的数据但不传送  
        	return -1;  
    	}  
} 
 
int Uart_Recv(int fd, char *rev_buf, int data_len)
{
	int len, fs_sel;  
    	fd_set fs_read;  
     
    	struct timeval tv_timeout;  
     
    	FD_ZERO(&fs_read); //清空集合
    	FD_SET(fd,&fs_read); // 将一个给定的文件描述符加入集合之中 
     
    	tv_timeout.tv_sec = 5;  
    	tv_timeout.tv_usec = 0;  
     
	//使用select实现串口的多路通信  
	fs_sel = select(fd + 1, &fs_read, NULL, NULL, &tv_timeout); //如果select返回值大于0,说明文件描述符发生了变化
	//printf("fs_sel = %d\n",fs_sel);  //如果返回0,代表在描述符状态改变前已超过timeout时间,错误返回-1  
	
	if(fs_sel)  
	{  
		len = read(fd, rev_buf, data_len);  
		printf("len = %d, fs_sel = %d\n", len, fs_sel);  
		return len;  
	}  
    	else  
	{  
		return 0;  
	}       
}  
 
int Uart_Init(int fd, int speed, int databits, int parity, int stopbits)
{
    	//设置串口数据帧格式  
   	 if (Set_Com_Config(fd, speed, databits, parity, stopbits) == 0)  
	{                                                           
		return 0;  
	}  
    	else  
	{  
		return  -1;  
	}  
}
 
int Uart_Receive_thread(void)
{
	int r_count;
	while(1)
	{
		r_count = Uart_Recv(uart_fd, rev_buf, sizeof(rev_buf));	
		if(r_count)
		{
			for(int i = 0; i < r_count; i++)
			{
				printf("rev_buf[%d] = 0x%x\n", i, rev_buf[i]);		
			}
		}
	}
}
 
void Uart_Send_thread(void)
{
	int s_count;
	
	while(1)
	{
		sleep(1);
		printf("This is Uart_Send_thread.\n");
		s_count = Uart_Send(uart_fd, databuf, strlen(databuf));
		if(s_count == -1)	
			printf("Uart_Send Error!\n");
		else
			printf("Send %d data successfully!\n", s_count);
	}	
}
 
int main(int argc, char *argv[]) 
{  
	pthread_t id_Uart_Receive, id_Uart_Send;
	int ret1, ret2;
	
	uart_fd = open(DEV_NAME, O_RDWR | O_NOCTTY);
    	if(uart_fd == -1)
    	{
        	printf("Uart Open Failed!\n");
        	exit(EXIT_FAILURE);
   	 }
		
	if(Uart_Init(uart_fd, 115200, 8, 'E', 1) == -1)
	{
		printf("Uart_Init Failed!\n");
		exit(EXIT_FAILURE);
	}
	
	ret1 = pthread_create(&id_Uart_Receive, NULL, (void *) Uart_Receive_thread, NULL);
	ret2 = pthread_create(&id_Uart_Send, NULL, (void *) Uart_Send_thread, NULL);
	
	if(ret1!=0){
		printf ("Create Uart_Receive_thread error!\n");
		close(uart_fd);
		exit (1);
	}
	if(ret2!=0){
		printf ("Create Uart_Send_thread error!\n");
		close(uart_fd);
		exit (1);
	}
	while(1)
	{
		printf("This is the main process.\n");
		usleep(1000000);
	}
	pthread_join(id_Uart_Receive, NULL);
	pthread_join(id_Uart_Send, NULL);
	
	return 0;
}

存为文件Comtest.c,用gcc编译:

gcc -o ComTest ComTest.c

报错:

ComTest.c:(.text+0x6a8):对‘pthread_create’未定义的引用
ComTest.c:(.text+0x6c8):对‘pthread_create’未定义的引用

参考:https://www.cnblogs.com/leechanx/p/3322611.html

这是因为linux标准库没有pthread.h,需要在运行参数中指定需要动态加载的库

在编译命令末尾加上 -lpthread

gcc -o ComTest ComTest.c -lpthread

编译成功。

运行ComTest程序,此处需要使用超级管理员权限:

sudo ./ComTest

串口不断发送数据:

This is the main process.
This is Uart_Send_thread.
Send 10 data successfully!
This is the main process.
This is Uart_Send_thread.
Send 10 data successfully!


短接串口2、3引脚后:

This is the main process.
This is Uart_Send_thread.
Send 10 data successfully!
len = 10, fs_sel = 1
rev_buf[0] = 0x1
rev_buf[1] = 0x2
rev_buf[2] = 0x33
rev_buf[3] = 0x34
rev_buf[4] = 0x35
rev_buf[5] = 0x6
rev_buf[6] = 0x7
rev_buf[7] = 0xb
rev_buf[8] = 0xc
rev_buf[9] = 0x59

成功实现Linux下串口设备C语言编程调试。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页