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,或者先清掉历史标志,再次设置。