嵌入式Linux应用开发基础-串口+RS485

前言

最近做嵌入式Linux项目,有用到RS485。就顺带复习了一下串口、RS485的相关知识。
此篇文章主要是记录一下嵌入式Linux的RS485开发的基础知识和注意事项,与君共勉!

一、Linux串口基础

1.串口初始化
(1)在Linux一切皆为文件,串口也不例外,初始化串口之前,需要先打开设备,调用标准函数open:

int fd = open(RS485_ONE_UART_DEV, O_RDWR | O_NOCTTY)

O_RDWR 代表可读写,O_NOCTTY代表不成为端口的控制终端(否则任何输入都会中断程序的执行)
(2)获取串口参数:串口本身有参数,我们可以通过tcgetattr函数获得,并存入struct termios结构体

#include<termios.h>
struct termios options; 
tcgetattr(fd, &options);

其中,struct termios结构体如下:

struct termios
{unsigned short c_iflag; /* 输入模式标志*/
unsigned short c_oflag; /* 输出模式标志*/
unsigned short c_cflag; /* 控制模式标志*/
unsigned short c_lflag; /*区域模式标志或本地模式标志或局部模式*/
unsigned char c_line; /*行控制line discipline */
unsigned char c_cc[NCC]; /* 控制字符特性*/
};

(3)修改串口参数并保存:修改参数后,可调用tcsetattr函数保存

if((tcsetattr(fd,TCSANOW,&options))!=0)
{
	perror("tcsetattr error");
	return -2;
}

(4)串口参数介绍:
struct termios的c_iflag,表示输入模式,可用以下宏(支持或运算):
BRKINT:当在输入行中检测到一个终止状态时,产生一个中断。
TGNBRK:忽略输入行中的终止状态。
TCRNL:将接受到的回车符转换为新行符。
TGNCR:忽略接受到的新行符。
INLCR:将接受到的新行符转换为回车符。
IGNPAR:忽略奇偶校检错误的字符。
INPCK:对接收到的字符执行奇偶校检。
PARMRK:对奇偶校检错误作出标记。
ISTRIP:将所有接收的字符裁减为7比特。
IXOFF:对输入启用软件流控。
IXON:对输出启用软件流控。
如果BRKINT和TGNBRK标志都未被设置,则输入行中的终止状态就被读取为NULL(0X00)字符。

struct termios的c_oflag,表示输入模式,可用以下宏(支持或运算):
OPSOT:打开输出处理功能
ONLCR:将输出中的换行符转换为回车符
OCRNL:将回车符转换为换行符
ONOCR:第0行不输出回车符
ONLRET:不输出回车符
NLDLY:换行符延时选择
CRDLY:回车符延时
TABDLY:制表符延时

struct termios的c_cflag,表示控制模式,可用以下宏(支持或运算):
CLOCAL:忽略所有调制解调器的状态行
CREAD:启用字符接收器
CS5/CS6/CS7/CS8:发送或接收字符时使用5/6/7/8比特
CSTOPB:每个字符使用两停止位
HUPCL:关闭时挂断调制解调器
PARENB:启用奇偶校验码的生成和检测功能
PARODD:只使用奇检验而不用偶校验

struct termios的c_cflag,表示特殊控制字符:
c_cc[VMIN]:代表读数据的最小字节
c_cc[VTIME]:等待第一个字节的最大时间,单位100ms
其中:
MIN = 0, TIME = 0时:read立即返回,如果有待处理的字符,它们就会被返回,如果没有,read调用返回0,且不读取任何字符
MIN = 0, TIME > 0时:有字符处理或经过TIME个0.1秒后返回
MIN > 0, TIME = 0时:read一直等待,直到有MIN个字符可以读取,返回值是字符的数量.到达文件尾时返回0
MIN > 0, TIME > 0时:read调用时,它会等待接收一个字符.在接收到第一个字符及其后续的每个字符后,启用一个字符间隔定时器.当有MIN个字符可读或两字符间的时间间隔超进TIME个0.1秒时,read返回

(5)串口初始化例程:

/**************************  struct define **************************/
/** uart param **/
typedef struct
{
	int fd;                //串口fd
	int uart_id;           //串口索引
	
    int baudrate;          //波特率
    int databits;          //数据位
    char parity;           //校验位
    int stopbit;           //停止位
}uart_param_t;

/************************************************************************************************************

/* brief: uart初始化函数,打开串口,并创建串口接收线程  
 * param:  uart为串口参数
        uart->baudrate波特率,选填2400/4800/9600/115200
        uart->databits数据位,选填7/8
        uart->parity校验位,选填'O'/'E'/'N'
        uart->stopbit停止位,选填1/2
 * return:  返回值为串口fd,小于0代表初始化串口设备失败
 */
static int app_uart_init(uart_param_t *uart)
{
	int fd=-1; 
	int32_t ret = 0;
	struct termios options;    
	printf("app_uart_init, uart_id:%d, baudrate:%d, databits:%d, parity:%d, stopbit:%d\n", uart->uart_id, uart->baudrate, uart->databits, uart->parity, uart->stopbit);

	// 打开串口设备  
	if(uart->uart_id==UART_ONE_INDEX)
	{
		fd = open(RS485_ONE_UART_DEV, O_RDWR | O_NOCTTY);
	}
	else if(uart->uart_id==UART_TWO_INDEX)
	{
		fd = open(RS485_TWO_UART_DEV, O_RDWR | O_NOCTTY); 
	}
	if (fd < 0) 
	{        
		perror("uart open failed\n");        
		return -1; 
	}   
	
	// 配置串口参数 
	tcgetattr(fd, &options);    
	options.c_iflag = IGNPAR;
	options.c_oflag = 0;	
	options.c_lflag = 0;
	options.c_cflag = (CLOCAL | CREAD); //本地连接、打开接收
	switch( uart->baudrate )
	{
		case 2400:
			options.c_cflag |= B2400;
		break;
		case 4800:
			options.c_cflag |= B4800;
		break;
		case 9600:
			options.c_cflag |= B9600;
		break;
		case 115200:
			options.c_cflag |= B115200;
		break;
		default:
			options.c_cflag |= B9600;
		break;
	}

	switch( uart->databits )
	{
		case 7:
			options.c_cflag |= CS7;
		break;
		case 8:
			options.c_cflag |= CS8;
		break;
		default:
			options.c_cflag |= CS8;
		break;
	}

	switch( uart->parity )
	{
		case 'O':
			options.c_cflag |= PARENB;           //允许输出产生奇偶信息以及输入的奇偶校验(启用同位产生与侦测)
			options.c_cflag |= PARODD;           //输入和输出是奇校验(使用奇同位而非偶同位)
			options.c_iflag |= INPCK;           //启用输入奇偶检测
		break;
		case 'E': 
			options.c_cflag |= PARENB;           //允许输出产生奇偶信息以及输入的奇偶校验(启用同位产生与侦测)
			options.c_cflag &= ~PARODD;          //输入和输出不是奇校验(使用偶同位而非奇同位)	
			options.c_iflag |= INPCK;            //启用输入奇偶检测	
		break;
		case 'N':

		break;
	}

	if( uart->stopbit == 1 )
		options.c_cflag &= ~CSTOPB;              //设置一个停止位
	else if ( uart->stopbit == 2 )
		options.c_cflag |= CSTOPB;	             //设置两个停止位

	options.c_cc[VMIN] = 0;   /* 读数据时的最小字节数 */ 
	options.c_cc[VTIME] = 50; /* 等待第1个数据的时间,单位0.1S: 
	                           *  如果VTIME*0.1秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
	                           */
	tcflush(fd, TCIFLUSH);      
	if((tcsetattr(fd,TCSANOW,&options))!=0)
	{
		perror("tcsetattr error");
		return -2;
	}	
	
   	return fd;
}

2.数据接收
串口的数据发送,调用标准的文件IO就可以了

int rLen = read(fd, rBuffer, len);

3.数据发送
串口的数据发送,也是调用标准的文件IO

int writed_len=write(fd, wbuffer, len);

(特别注意!write函数返回,只是代表数据写入到了串口"文件",并不意味着数据已经通过硬件发送出去了!
记住这一点,后面RS485也会涉及!
)

二、RS485基础

1.RS485总线:
(1)RS485是半双工通讯,物理上由A、B两根总线组成。通过A、B线的电压差来表达0和1,从而传输串行数据
(2)由于数据的发送,需要同时调动A、B两根线的电压,因此总线上,同一时间,有且仅能有一个设备可以发送消息
(3)由于此特性,一般总线上只会有一个主机,负责主动发送消息;其他设备都为从机,只有收到属于自己的消息,才会发送消息(回复消息)

2.如何通过串口使用RS485:
(1)芯片的串口发送的是TTL电平数据,是全双工通讯,而RS485总线是半双工通讯;
(2)由于电平和通讯模式不同,要把TTL电平信号转换为RS485总线信号,需要使用RS485芯片
(3)RS485芯片有两种工作模式,分别是发送模式和接收模式,由一个使能IO控制,一般是高电平使能发送,低电平使能接收
(4)因此串口发送数据前,要通过使能IO,先使能发送;发送完后,使能接收

3.注意事项:
(1)RS485芯片,在接收和发送切换的时候,需要时间。因此切到换发送使能后,需要延时一小会,再调用write函数。具体的切换时间,根据芯片各有不同,拿到实物后实测一下比较稳妥。
(2)前面有提到,串口调用write函数返回,只是把数据写入到串口"文件";如果write函数返回,立马使能接收,会导致发送的数据大部分、甚至全部丢失!
(3)串口的波特率,指的是每秒能发送的bit数。而发送一个字节需要的bit数=起始位(1bit)+数据位(5~8bit)+校验位(1bit)+停止位(1/2bit)

(4)硬件发送串口数据的时间(单位ms) time_ms=数据字节数*(数据位+停止位+2)*1000/波特率
(5)因此write函数返回后,至少延时time_ms毫秒的时间,才能保证数据从硬件发送出去

三、嵌入式Linux的RS485收发例程

1.RS485发送

/* brief:  uart发送数据  
 * param:  fd:文件描述符
        uart为串口参数
        buffer包含待写入数据的缓冲区
        len缓冲区字节数
 * return:  大于等于0代表发送的字节数,小于0代表发送失败
 */
int app_uart_send(int fd, uart_param_t *uart, unsigned char *buffer, int32_t len)
{
	printf("app_uart_send...\n");
	if(fd<0)
	{
		perror("fd<0!\n");        
		return -1; 
	}
	if((uart==NULL)||(buffer==NULL)||(len==0))
	{
		perror("send data err!\n");
		return -2;
	}

	//使能脚置高电平
	app_RS485_en_io_set_cmd(uart->uart_id,1);
	
	//延时20ms(经验值),等待芯片进入发送模式
	usleep(20*1000);
	
	//发送数据到串口
	int writed_len=write(fd, buffer, len);
	
	//延时(理论时间+10)ms,等数据从硬件发送完成
	int sec=len*(uart->databits+uart->stopbit+2)*1000/uart->baudrate+1; 	  //硬件发送数据所需时间(向上取整),单位ms(适用于波特率2400~115200)
	usleep((sec+10)*1000);                   //延时(理论时间+10)ms
	
	//使能脚置低电平,使芯片进入接收模式
	app_RS485_en_io_set_cmd(uart->uart_id,0);
	if(writed_len != len)
	{
		perror("uart send data failed!\n");
	}

	return writed_len;
}

2.RS485接收

/* brief:  uart接收数据
 * param:  fd:文件描述符
        buffer用于存储读取数据的缓冲区
        len缓冲区字节数
 * return:  小于0代表参数错误,0代表没收到数据,大于0代表接收到的字节数
 */
int app_uart_receive(int fd, char *buffer, int32_t len)
{
	if(fd<0)
	{
		perror("fd<0!\n");
		return -1; 
	}
	if((buffer==NULL)||(len==0))
	{
		perror("send data err!\n");
		return -2;
	}

	uint8_t rBuffer[UART_RECV_BUF_SIZE]={0};
	printf("app_uart_receive: read start \n");
	int rLen = read(fd, rBuffer, UART_RECV_BUF_SIZE);
	printf("app_uart_receive: end of read, rLen:%d \n", rLen);
	if (rLen > 0)
	{
		printf("uart read total: %d\n", rLen);
		//防止缓冲区过小导致异常
		if(rLen>len)
		{
			memcpy(buffer,rBuffer,len);
		}
		else
		{
			memcpy(buffer,rBuffer,rLen);
		}
	}

	return rLen;
}
### 回答1: Linux RS485是一种在Linux系统中使用的串行通信协议,它可以实现在长距离传输数据时的稳定性和可靠性。RS485协议可以支持多点通信,也可以支持半双工和全双工通信模式。在Linux系统中,可以通过配置串口参数和使用相应的驱动程序来实现RS485通信。 ### 回答2: RS485是一种串行通信协议,被广泛应用在工业自动化领域中。在Linux系统下使用RS485通信可以实现多点通信和长距离传输数据。需要注意的是,在使用RS485通信时,需要使用特定的转换器将RS485信号转换成RS232信号或USB信号,然后通过串口或USB接口与计算机相连。 在Linux系统下使用RS485通信,需要设置一些参数以便正确识别通信硬件和驱动。通常需要通过设置tty设备的属性来实现,如设置波特率、数据位、停止位、校验位等。此外,还需要选择合适的驱动程序。在Linux系统中,常用的RS485驱动程序有两种,一种是自带的“8250_dw”驱动程序,另一种是第三方的“serial_imx”驱动程序。这两种驱动程序均支持RS485通信,但是“8250_dw”驱动程序需要手动设置RS485模式,而“serial_imx”驱动程序在初始化时即可自动识别RS485模式。 总的来说,Linux系统下使用RS485通信需要做到以下几点: 1. 使用RS485转换器将RS485信号转换成RS232信号或USB信号。 2. 设置tty设备的属性,包括波特率、数据位、停止位、校验位等。 3. 选择合适的驱动程序,根据需要手动设置RS485模式或使用自动识别模式。 4. 编写应用程序实现数据的发送和接收,注意要使用RS485通信相关的函数库。 总的来说,Linux系统下使用RS485通信并不复杂,只需要注意相关的设置和驱动程序选择即可。使用RS485通信可以实现多点通信和长距离传输数据,是工业自动化领域中常用的通信方式。 ### 回答3: RS485是一种串行通信标准,适用于在相对较远距离和高噪声环境下进行通信。它被广泛用于工业自动化和控制领域,如PLC通信,数据采集和仪表测量等。 在Linux系统中,RS485的实现可以通过使用标准串口驱动程序和一个RS485转换器(通常包括一个芯片,例如MAX485),以便实现全双工或半双工通信。 使用Linux系统提供的tty设备文件来访问串口并进行RS485通信是比较常见的做法。为了实现RS485通信,需要通过tty设备文件和串行通信库(如libc的termios库)设置一些特定的属性,例如波特率、数据位、停止位、奇偶校验和RS485模式。 在RS485模式下,首先需要把转换器的操作模式从RS232模式切换到RS485模式,这可以使用GPIO或类似的硬件进行控制。之后,在发送数据前需要将端口设置为发送模式,并设置输出数据保持寄存器的控制脚的状态。在接收数据时,需要将端口设置为接收模式,并根据应用程序的需要对数据进行处理。 总之,Linux系统可以通过串口驱动程序和RS485转换器来实现RS485通信。合理地配置tty设备文件和串行通信库可确保数据的可靠传输和处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值