RS485驱动

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

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

lincuiting06

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值