【imx6ul】linux下rs485的使用

0、说明

        对于linux下的485使用,其实就是linux下的串口使用。但是485有一个控制信号,在485等待接收的时候,控制信号需要时低电平,在发送的时候需要为高电平。所以对于linux下485驱动,最主要的任务就是完的成对控制信号电平的操作

        该控制信号就是485芯片的2/3号引脚。

 1、485测试程序及改进过程

       如下demo程序,其实大部分代码是一个串口的程序。主要关注一下如下的rs485_enable函数。该函数通过ioctl的方式配置了使能参数。

#include <stdio.h>
#include <termios.h>
#include <linux/ioctl.h>
#include <linux/serial.h>
#include <asm-generic/ioctls.h> /* TIOCGRS485 + TIOCSRS485 ioctl definitions */
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>

/**
 * @brief: set the properties of serial port
 * @Param: fd: file descriptor
 * @Param: nSpeed: Baud Rate
 * @Param: nBits: character size
 * @Param: nEvent: parity of serial port
 * @Param: nStop: stop bits
 */

typedef enum {DISABLE = 0, ENABLE} RS485_ENABLE_t;

int set_port(int fd, int nSpeed, int nBits, char nEvent, int nStop)
{
	struct termios newtio, oldtio;

	memset(&oldtio, 0, sizeof(oldtio));
	/* save the old serial port configuration */
	if(tcgetattr(fd, &oldtio) != 0) {
		perror("set_port/tcgetattr");
		return -1;
	}

	memset(&newtio, 0, sizeof(newtio));
	/* ignore modem control lines and enable receiver */
	newtio.c_cflag = newtio.c_cflag |= CLOCAL | CREAD;
	newtio.c_cflag &= ~CSIZE;
	/* set character size */
	switch (nBits) {
		case 8:
			newtio.c_cflag |= CS8;
			break;
		case 7:
			newtio.c_cflag |= CS7;
			break;
		case 6:
			newtio.c_cflag |= CS6;
			break;
		case 5:
			newtio.c_cflag |= CS5;
			break;
		default:
			newtio.c_cflag |= CS8;
			break;
	}
	/* set the parity */
	switch (nEvent) {
		case 'o':
		case 'O':
			newtio.c_cflag |= PARENB;
			newtio.c_cflag |= PARODD;
			newtio.c_iflag |= (INPCK | ISTRIP);
			break;
		case 'e':
		case 'E':
			newtio.c_cflag |= PARENB;
			newtio.c_cflag &= ~PARODD;
			newtio.c_iflag |= (INPCK | ISTRIP);
			break;
		case 'n':
		case 'N':
			newtio.c_cflag &= ~PARENB;
			break;
		default:
			newtio.c_cflag &= ~PARENB;
			break;
	}
	/* set the stop bits */
	switch (nStop) {
		case 1:
			newtio.c_cflag &= ~CSTOPB;
			break;
		case 2:
			newtio.c_cflag |= CSTOPB;
			break;
		default:
			newtio.c_cflag &= ~CSTOPB;
			break;
	}
	/* set output and input baud rate */
	switch (nSpeed) {
		case 0:
			cfsetospeed(&newtio, B0);
			cfsetispeed(&newtio, B0);
			break;
		case 50:
			cfsetospeed(&newtio, B50);
			cfsetispeed(&newtio, B50);
			break;
		case 75:
			cfsetospeed(&newtio, B75);
			cfsetispeed(&newtio, B75);
			break;
		case 110:
			cfsetospeed(&newtio, B110);
			cfsetispeed(&newtio, B110);
			break;
		case 134:
			cfsetospeed(&newtio, B134);
			cfsetispeed(&newtio, B134);
			break;
		case 150:
			cfsetospeed(&newtio, B150);
			cfsetispeed(&newtio, B150);
			break;
		case 200:
			cfsetospeed(&newtio, B200);
			cfsetispeed(&newtio, B200);
			break;
		case 300:
			cfsetospeed(&newtio, B300);
			cfsetispeed(&newtio, B300);
			break;
		case 600:
			cfsetospeed(&newtio, B600);
			cfsetispeed(&newtio, B600);
			break;
		case 1200:
			cfsetospeed(&newtio, B1200);
			cfsetispeed(&newtio, B1200);
			break;
		case 1800:
			cfsetospeed(&newtio, B1800);
			cfsetispeed(&newtio, B1800);
			break;
		case 2400:
			cfsetospeed(&newtio, B2400);
			cfsetispeed(&newtio, B2400);
			break;
		case 4800:
			cfsetospeed(&newtio, B4800);
			cfsetispeed(&newtio, B4800);
			break;
		case 9600:
			cfsetospeed(&newtio, B9600);
			cfsetispeed(&newtio, B9600);
			break;
		case 19200:
			cfsetospeed(&newtio, B19200);
			cfsetispeed(&newtio, B19200);
			break;
		case 38400:
			cfsetospeed(&newtio, B38400);
			cfsetispeed(&newtio, B38400);
			break;
		case 57600:
			cfsetospeed(&newtio, B57600);
			cfsetispeed(&newtio, B57600);
			break;
		case 115200:
			cfsetospeed(&newtio, B115200);
			cfsetispeed(&newtio, B115200);
			break;
		case 230400:
			cfsetospeed(&newtio, B230400);
			cfsetispeed(&newtio, B230400);
			break;
		default:
			cfsetospeed(&newtio, B115200);
			cfsetispeed(&newtio, B115200);
			break;
	}
	/* set timeout in deciseconds for non-canonical read */
	newtio.c_cc[VTIME] = 0;
	/* set minimum number of characters for non-canonical read */
	newtio.c_cc[VMIN] = 0;
	/* flushes data received but not read */
	tcflush(fd, TCIFLUSH);
	/* set the parameters associated with the terminal from
		the termios structure and the change occurs immediately */
	if((tcsetattr(fd, TCSANOW, &newtio))!=0)
	{
		perror("set_port/tcsetattr");
		return -1;
	}

	return 0;
}

/**
 * @brief: open serial port
 * @Param: dir: serial device path
 */
int open_port(char *dir)
{
	int fd ;
	fd = open(dir, O_RDWR);
	if(fd < 0) {
		perror("open_port");
	}
	return fd;
}

/**
 * @brief: print usage message
 * @Param: stream: output device
 * @Param: exit_code: error code which want to exit
 */
void print_usage (FILE *stream, int exit_code)
{
    fprintf(stream, "Usage: option [ dev... ] \n");
    fprintf(stream,
            "\t-h  --help     Display this usage information.\n"
            "\t-d  --device   The device ttyS[0-3] or ttySCMA[0-1]\n"
	    "\t-b  --baudrate Set the baud rate you can select\n"
	    "\t               [230400, 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300]\n"
            "\t-s  --string   Write the device data\n"
	    "\t-e  --1 or 0 , Write 1 to enable rs485_mode(only at atmel)\n");
    exit(exit_code);
}

/**
 * @brief: main function
 * @Param: argc: number of parameters
 * @Param: argv: parameters list
 */

int rs485_enable(const int fd, const RS485_ENABLE_t enable)
{
        struct serial_rs485 rs485conf;
        int res;

        /* Get configure from device */
        res = ioctl(fd, TIOCGRS485, &rs485conf);
        if (res < 0) {
                perror("Ioctl error on getting 485 configure:");
                close(fd);
                return res;
        }

        /* Set enable/disable to configure */
        if (enable) {   // Enable rs485 mode
                rs485conf.flags |= SER_RS485_ENABLED;
        } else {        // Disable rs485 mode
                rs485conf.flags &= ~(SER_RS485_ENABLED);
        }

        rs485conf.delay_rts_before_send = 0x00000004;

        /* Set configure to device */
        res = ioctl(fd, TIOCSRS485, &rs485conf);
        if (res < 0) {
                perror("Ioctl error on setting 485 configure:");
                close(fd);
        }

        return res;
}

int main(int argc, char *argv[])
{
	char *write_buf = "0123456789";
	char read_buf[100];
	int fd, i, len, nread,r;
	pid_t pid;
	int next_option;
	extern struct termios oldtio;
	int speed ;
	char *device;
	int spee_flag = 0, device_flag = 0;
	const char *const short_options = "hd:s:b:e:";
	const struct option long_options[] = {
		{ "help",   0, NULL, 'h'},
		{ "device", 1, NULL, 'd'},
		{ "string", 1, NULL, 's'},
		{ "baudrate", 1, NULL, 'b'},
		{ NULL,     0, NULL, 0  }
	};

	if (argc < 2) {
		print_usage (stdout, 0);
		exit(0);
	}

	while (1) {
		next_option = getopt_long (argc, argv, short_options, long_options, NULL);
		if (next_option < 0)
			break;
		switch (next_option) {
			case 'h':
				print_usage (stdout, 0);
				break;
			case 'd':
				device = optarg;
				device_flag = 1;
				break;
			case 'b':
				speed = atoi(optarg);
				spee_flag = 1;
				break;
			case 's':
				write_buf = optarg;
				break;
			case 'e':
				r = atoi(optarg);
				break;
			case '?':
				print_usage (stderr, 1);
				break;
			default:
				abort ();
		}
	}

	if ((!device_flag)||(!spee_flag)) {
		print_usage (stderr, 1);
		exit(0);
	}

	/* open serial port */
	fd = open_port(device);
	if (fd < 0) {
		perror("open failed");
		return -1;
	}
	if(r)
	{
	    rs485_enable(fd,ENABLE);
	}
	/* set serial port */
	i = set_port(fd, speed, 8, 'N', 1);
	if (i < 0) {
		perror("set_port failed");
		return -1;
	}

    while (1) {
        /* if new data is available on the serial port, read and print it out */
        nread = read(fd ,read_buf ,sizeof(read_buf));
        if (nread > 0) {
            printf("RECV[%3d]: ", nread);
            for(i = 0; i < nread; i++)
                printf("0x%02x ", read_buf[i]);
            printf("\n");
			write(fd, read_buf, nread);//自己添加
        }
    }
	/* restore the old configuration */
	tcsetattr(fd, TCSANOW, &oldtio);
	close(fd);
	return 0;
}

        执行以上app,会发现,app接收到第一帧数据后,可以发送回去,之后外接再来数据的时候已经接收不上来了。测试DE控制信号,发现是高,也就是发送完成没有将DE拉低。

        当然这个程序本是开发板厂家提供用于硬件测试的。厂家485测试提供了两个app一个专门用于收的测试,一个专门用于发的测试,或许就是因为提供的APP过于简单吧。

        那么为什么呢。第一个就是找出为什么发送后控制信号被拉高后没有再次拉低,导致无法接收。

        查看内核485帮助文档。似乎以上程序是确少了一些配置。如内核提示了SER_RS485_RTS等相关的config设置,这些设置应该就是会影响控制信号的控制。

   From user-level, RS485 configuration can be get/set using the previous
   ioctls. For instance, to set RS485 you can use the following code:

	#include <linux/serial.h>

	/* Driver-specific ioctls: */
	#define TIOCGRS485      0x542E
	#define TIOCSRS485      0x542F

	/* Open your specific device (e.g., /dev/mydevice): */
	int fd = open ("/dev/mydevice", O_RDWR);
	if (fd < 0) {
		/* Error handling. See errno. */
	}

	struct serial_rs485 rs485conf;

	/* Enable RS485 mode: */
	rs485conf.flags |= SER_RS485_ENABLED;

	/* Set logical level for RTS pin equal to 1 when sending: */
	rs485conf.flags |= SER_RS485_RTS_ON_SEND;
	/* or, set logical level for RTS pin equal to 0 when sending: */
	rs485conf.flags &= ~(SER_RS485_RTS_ON_SEND);

	/* Set logical level for RTS pin equal to 1 after sending: */
	rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;
	/* or, set logical level for RTS pin equal to 0 after sending: */
	rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);

	/* Set rts delay before send, if needed: */
	rs485conf.delay_rts_before_send = ...;

	/* Set rts delay after send, if needed: */
	rs485conf.delay_rts_after_send = ...;

	/* Set this flag if you want to receive data even whilst sending data */
	rs485conf.flags |= SER_RS485_RX_DURING_TX;

	if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {
		/* Error handling. See errno. */
	}

	/* Use read() and write() syscalls here... */

	/* Close the device when finished: */
	if (close (fd) < 0) {
		/* Error handling. See errno. */
	}

 优化1:发送完成后,控制信号始终为高

        增加了如下标志后,发现发送完成后,控制信号被拉低,可以再次接收数据了。

        rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;

/*
 * interrupts disabled on entry
 */
static void imx_stop_tx(struct uart_port *port)
{
    struct imx_port *sport = (struct imx_port *)port;
    unsigned long temp;

    /*
     * We are maybe in the SMP context, so if the DMA TX thread is running
     * on other cpu, we have to wait for it to finish.
     */
    if (sport->dma_is_enabled && sport->dma_is_txing)
        return;

    temp = readl(port->membase + UCR1);
    writel(temp & ~UCR1_TXMPTYEN, port->membase + UCR1);

    /* in rs485 mode disable transmitter if shifter is empty */
    if (port->rs485.flags & SER_RS485_ENABLED &&
        readl(port->membase + USR2) & USR2_TXDC) {

        if (sport->txen_gpio != -1) {
            if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND){
                gpio_set_value(sport->txen_gpio, 0);
            }
        } 

可以定位到,在发送完成后,即imx_stop_tx,根据SER_RS485_RTS_AFTER_SEND设置情况,将控制信号拉低了。

优化2:上电后第二次运行APP的时候出现控制信号常态为高

        定位到原因是第一次执行app时,已经配置了SER_RS485_RTS_AFTER_SEND标志,当再次配置时,出现拉高控制信号。具体在驱动如下:

static int imx_rs485_config(struct uart_port *port,                    
                struct serial_rs485 *rs485conf)                        
{                                                                      
    struct imx_port *sport = (struct imx_port *)port;                  
                                                                       
    /* unimplemented */                                                
    rs485conf->delay_rts_before_send = 0;                              
    rs485conf->delay_rts_after_send = 0;                               
    rs485conf->flags |= SER_RS485_RX_DURING_TX;                        
                                                                       
    /* RTS is required to control the transmitter */                   
    if (sport->txen_gpio == -1 && !sport->have_rtscts)                 
        rs485conf->flags &= ~SER_RS485_ENABLED;                        
                                                                       
    if (rs485conf->flags & SER_RS485_ENABLED) {                        
                                                                       
        if (sport->txen_gpio != -1) {                                  
            if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND)          
                gpio_set_value(sport->txen_gpio, 1);                   
            else                                                       
                gpio_set_value(sport->txen_gpio, 0);                   
        }

可见,当应用层执行ioctl(fd, TIOCSRS485, &rs485conf);的时候,会根据历史flag设置控制信号。因此在APP执行的时候,判断是否SER_RS485_RTS_AFTER_SEND已经被置位,则不再调用ioctl,或者先清掉历史标志,再次设置。

  • 11
    点赞
  • 92
    收藏
    觉得还不错? 一键收藏
  • 23
    评论
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值