一、原理
RS232用两根线实现全双工,两根线各做各的,互不影响,可以同时进行;RS485虽然可以用四根线实现全双工,但是实际应用中比较少见,更常见的是只用两根线实现半双工,这样一来,就涉及到“收状态”和“发状态”的切换,这一切换又涉及两种情况:
1、驱动程序中已经含有对半双工情况下的接受切换,驱动程序会根据你读或写的动作,自动进行切换。这种情况下,RS485的编程就与RS232完全没有区别。
2、驱动程序不带自动切换,此时,为了完成切换,必须使用额外的GPIO连接RS485收发模块的接受使能端,在接受、发送数据之前,首先对使能端置位,使之处于正确的“接收”或“发送”状态。
二、支持平台
此文是基于TI芯片AM1808平台的RS485驱动,由于他们的软串口不支持485串口数据传输,所以自己写了一个简单的485驱动,此驱动只用来简单的控制485的使能管脚,拉高或拉低电平,满足读写。
1.rs485驱动
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* for in_interrupt */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define RS485DE GPIO_TO_PIN(2, 15)//lct add 2015/11/19
#define nREV 0
#define nSENT 1
#define LAST_BYTES_FIFO_TO_SHIFTER 1
//#define RS485SENTBEGIN 0
//#define RS485SENTOVER 1
//ioctl函数传参的时候,命令字cmd最好不要自己定义宏,内核会过滤掉不合法的cmd,因为cmd分为4个部分,type,number,direction,size。自己定义的cmd没有这四个部分,内核直接过滤掉你的ioctl请求,所以ioctl根本不会到驱动,在应用层调用的时候就被返回错误了。
#define DRIVERNAME "rs485driver"
static int rs485_open(struct inode*, struct file *);
static int rs485_close(struct inode*, struct file *);
static long rs485_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
#ifdef DEBUG_WG
#define PRIN_DEBUG printk
#else
#define PRIN_DEBUG
#endif
static dev_t dev;
static struct cdev cdev;
static struct class *rs485_class = NULL;
static struct file_operations rs485_ctl_fops =
{
.owner = THIS_MODULE,
.open = rs485_open,
.release = rs485_close,
.unlocked_ioctl = rs485_ioctl,
};
//struct rs485de_ctl
//{
// long BaudRate; //19200
// int parity; // 0
// int startBits; // 1
// int dataBits; // 8
// int stopBits; // 1
// long count; // 15字节
//};
//struct rs485de_ctl *dep;
static int __init rs485_init(void)
{
int result;
result = alloc_chrdev_region(&dev, 0, 1, DRIVERNAME);
if(result < 0){
printk("Error registering rs485 character device\n");
return -ENODEV;
}
printk(KERN_INFO "rs485 major#: %d, minor#: %d\n", MAJOR(dev), MINOR(dev));
cdev_init(&cdev, &rs485_ctl_fops);
cdev.owner = THIS_MODULE;
cdev.ops = &rs485_ctl_fops;
result = cdev_add(&cdev, dev, 1);
if(result){
unregister_chrdev_region(dev, 1);
printk("Error adding rs485.. error no:%d\n",result);
return -EINVAL;
}
rs485_class = class_create(THIS_MODULE, DRIVERNAME);
device_create(rs485_class, NULL, dev, NULL, DRIVERNAME);
printk(DRIVERNAME " initialized\n");
return 0;
}
static int rs485_open(struct inode*inode, struct file *filp)
{
int status;
printk("=======%s\n", __func__);
status = gpio_request(RS485DE, "rs485 enable\n");
printk("====status=%d\n", status);
if (status < 0) {
gpio_free(RS485DE);
return status;
}
//set 485_DE/RE as output and set it to be low level as receive
gpio_direction_output(RS485DE, 0);
return 0;
}
static int rs485_close(struct inode*inode, struct file *filp)
{
printk("=======%s\n", __func__);
gpio_free(RS485DE);
return 0;
}
void set_rs485de_sent(void)
{
printk("=======%s\n", __func__);
//set 485_DE/RE as output and set it to be high level as send
gpio_direction_output(RS485DE, 0);
gpio_set_value(RS485DE, 1);
}
void set_rs485de_receive(void)
{
//set 485_DE/RE as output and set it to be low level as receive
gpio_direction_output(RS485DE, 0);
gpio_set_value(RS485DE, 0);
}
//计算切换电平的延时时间,主要跟起始位,校验位,数据位,停止位
//void read_delay_and_clr_de(void)
//{
// unsigned int i;
// unsigned int delay_time_us;
// delay_time_us=(1000000/dep->BaudRate)+1;
// delay_time_us=delay_time_us*(dep->parity+dep->stopBits+dep->dataBits+dep->startBits)*LAST_BYTES_FIFO_TO_SHIFTER;
// printk("delay time is %dus\n",delay_time_us);
//
// for(i=0;i
// udelay(1);
// set_rs485de_receive();
//}
static long rs485_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
switch(cmd)
{
case 0:
set_rs485de_sent();
break;
case 1:
set_rs485de_receive();
break;
default:
ret=-1;
break;
}
return ret;
}
static void __exit rs485_exit(void)
{
//printk("=======%s\n", __func__);
printk("rs485 chrdev exit!\n");
cdev_del(&cdev);
unregister_chrdev_region(dev, 1);
device_destroy(rs485_class, dev);
class_destroy(rs485_class);
}
MODULE_LICENSE("GPL");
module_init(rs485_init);
module_exit(rs485_exit);
注意:在这个rs485驱动调试过程中,遇到的最大的问题就是电平切换后的延时时间的计算,
delay_time_us=(1000000/dep->BaudRate)+1;
delay_time_us=delay_time_us*(dep->parity+dep->stopBits+dep->dataBits+dep->startBits)*LAST_BYTES_FIFO_TO_SHIFTER;
为了不增加驱动负担,可以把延时时间计算好之后,直接在用户空间调用,比如usleep(8500);
2.测试程序
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int openport(char *strDev)
{
int fd = open( strDev, O_RDWR|O_NONBLOCK);//O_NDELAY |O_NONBLOCK|O_NOCTTY
if(fd==-1)
{
printf("Open Serial Port Device %s error no %d\n",
strDev , fd );
return 0;
}
if(fcntl(fd, F_SETFL, 0)<0)
printf("fcntl failed!\n");
else
printf("fcntl=%d\n",fcntl(fd, F_SETFL,0));
//测试是否为终端设备
if(isatty(STDIN_FILENO) == 0)
printf("standard input is not a terminal device\n");
else
printf("isatty success!\n");
printf("fd-open=%d\n",fd);
return fd;
}
int setport(int fd, int baud, int databits, int stopbits, int parity)
{
int baudrate;
struct termios newtio;
switch(baud)
{
case 300:
baudrate=B300;
break;
case 600:
baudrate=B600;
break;
case 1200:
baudrate=B1200;
break;
case 2400:
baudrate=B2400;
break;
case 4800:
baudrate=B4800;
break;
case 9600:
baudrate=B9600;
break;
case 19200:
baudrate=B19200;
break;
case 38400:
baudrate=B38400;
case 57600:
baudrate=B57600;
break;
case 115200:
baudrate=B115200;
break;
default :
baudrate=B9600;
break;
}
tcgetattr(fd,&newtio);
bzero(&newtio,sizeof(newtio));
newtio.c_cflag &= ~CSIZE;
switch (databits) //设置数据位数
{
case 7:
newtio.c_cflag |= CS7; //7位数据位
break;
case 8:
newtio.c_cflag |= CS8; //8位数据位
break;
default:
newtio.c_cflag |= CS8;
break;
}
switch (parity) //设置校验
{
case 'n':
case 'N':
newtio.c_cflag &= ~PARENB; /* Clear parity enable */
newtio.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case 'o':
case 'O':
newtio.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
newtio.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E':
newtio.c_cflag |= PARENB; /* Enable parity */
newtio.c_cflag &= ~PARODD; /* 转换为偶效验*/
newtio.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'S':
case 's': /*as no parity*/
newtio.c_cflag &= ~PARENB;
newtio.c_cflag &= ~CSTOPB;break;
default:
newtio.c_iflag &= ~INPCK; /* Enable parity checking */
break;
}
switch (stopbits)//设置停止位
{
case 1:
newtio.c_cflag &= ~CSTOPB; //1
break; //请到HTTp://www.timihome.net访问
case 2:
newtio.c_cflag |= CSTOPB; //2
break;
default:
newtio.c_cflag &= ~CSTOPB;
break;
}
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 13;
newtio.c_cflag |= (CLOCAL|CREAD);
newtio.c_oflag|=OPOST;
newtio.c_iflag &=~CRTSCTS;
newtio.c_iflag &=~(IXON|IXOFF|IXANY);
cfsetispeed(&newtio,baudrate);
cfsetospeed(&newtio,baudrate);
tcflush(fd, TCIFLUSH);
if (tcsetattr(fd,TCSANOW,&newtio) != 0)
{
return -1;
}
return 0;
}
int readport(int fd, char *buf, int maxLen)//读数据,参数为串口,BUF,长度
{
char szBuf[15] = {0};
int nLen = 0;
nLen = read(fd, buf, maxLen);
if(nLen > 0)
printf("\n nLen = %d\n", nLen);
else if(nLen < 0)
{
printf("Read uart data failed \r\n");
return -1;
}
return nLen;
}
int writeport(int fd, char *buf, int len) //发送数据
{
int wrnum = 0;
wrnum = write(fd, buf, len);
if(wrnum == len)
{
printf("write uart data success \r\n");
return wrnum;
}
else
{
printf("write uart data failed \r\n");
return -1;
}
}
// 关闭串口
void closeport(int fd)
{
close(fd);
}
int main(int argc, unsigned long *argv[])
{
int ret, i;
int fd_485, fd_uart;
int write_buf_size, read_buf_size;
char write_buf[15] = {0X69, 0XA1, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XC8};
char read_buf[15];
fd_uart = openport("/dev/ttySU4");
if(fd_uart==-1)
{
perror("The /dev/ttySU4 open error.");
exit(1);
}
if((fd_485=open("/dev/rs485driver",O_RDONLY | O_NONBLOCK))<0)
{
perror("can not open device rs485driver\n");
exit(1);
}
ret = setport(fd_uart, 19200, 8, 1, 'N');
if(ret < 0)
{
printf("Set Serial Port failed .\r\n");
return 0;
}
while(1)
{
ret=ioctl(fd_485,0,NULL);//send
write_buf_size=writeport(fd_uart,write_buf,15);
if(write_buf_size<0)
return;
usleep(8320);
ioctl(fd_485,1,NULL);
read_buf_size=read(fd_uart,read_buf,15);
if(read_buf_size>0)
{
printf("\n");
printf("read_buf_size len:%d",read_buf_size);
printf(" ");
for(i=0;iprintf("0x%02x",read_buf[i]);
}
printf("\n");
}else
printf("Timeout\n");
printf("/dev/ttySU4 has received %d chars!\n",read_buf_size);
fflush(stdout);
}
closeport(fd_uart);
return 0;
}
三。小结
以上驱动程序和测试程序比较简单,只适合在AM1808平台使用,希望可以对同样在此平台开发的朋友们有点帮助吧。