您的位置:首页 > 其它

RS485驱动

2015-12-18 16:10 381 查看
一、原理

RS232用两根线实现全双工,两根线各做各的,互不影响,可以同时进行;RS485虽然可以用四根线实现全双工,但是实际应用中比较少见,更常见的是只用两根线实现半双工,这样一来,就涉及到“收状态”和“发状态”的切换,这一切换又涉及两种情况:

1、驱动程序中已经含有对半双工情况下的接受切换,驱动程序会根据你读或写的动作,自动进行切换。这种情况下,RS485的编程就与RS232完全没有区别。

2、驱动程序不带自动切换,此时,为了完成切换,必须使用额外的GPIO连接RS485收发模块的接受使能端,在接受、发送数据之前,首先对使能端置位,使之处于正确的“接收”或“发送”状态。

二、支持平台

此文是基于TI芯片AM1808平台的RS485驱动,由于他们的软串口不支持485串口数据传输,所以自己写了一个简单的485驱动,此驱动只用来简单的控制485的使能管脚,拉高或拉低电平,满足读写。

1.rs485驱动

#include <linux/fs.h>
#include <linux/major.h>
#include <linux/blkdev.h>
#include <linux/capability.h>
#include <linux/smp_lock.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>     /* for in_interrupt */
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/version.h>
#include <asm/irq.h>
#include <linux/gpio.h>
#include <linux/moduleparam.h>
#include <linux/list.h>
#include <linux/cdev.h>
#include <linux/proc_fs.h>
#include <linux/mm.h>
#include <linux/seq_file.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/ioctl.h>

#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<delay_time_us;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 <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>

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;i<read_buf_size;i++)
{
printf("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平台使用,希望可以对同样在此平台开发的朋友们有点帮助吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: