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,或者先清掉历史标志,再次设置。
(1652条消息) 【imx6ul】linux下rs485的使用_linux rs485_【星星之火】的博客-优快云博客
文章介绍了Linux环境下485通信的实现,重点在于控制信号RTS的管理。原始程序在发送数据后未能正确拉低RTS,导致无法接收后续数据。通过分析内核文档并修改程序,添加了SER_RS485_RTS_AFTER_SEND标志,解决了发送后控制信号保持高电平的问题。此外,还讨论了程序在二次运行时因历史标志导致的问题,并提出了相应的解决策略。
3389

被折叠的 条评论
为什么被折叠?



