RS485驱动

来源:互联网 发布:linux创建新文件 编辑:程序博客网 时间:2024/05/17 05:17

一、原理
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#endifstatic         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平台使用,希望可以对同样在此平台开发的朋友们有点帮助吧。

0 0