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_【星星之火】的博客-CSDN博客