RS485驱动
2015-12-18 16:10
381 查看
一、原理
RS232用两根线实现全双工,两根线各做各的,互不影响,可以同时进行;RS485虽然可以用四根线实现全双工,但是实际应用中比较少见,更常见的是只用两根线实现半双工,这样一来,就涉及到“收状态”和“发状态”的切换,这一切换又涉及两种情况:
1、驱动程序中已经含有对半双工情况下的接受切换,驱动程序会根据你读或写的动作,自动进行切换。这种情况下,RS485的编程就与RS232完全没有区别。
2、驱动程序不带自动切换,此时,为了完成切换,必须使用额外的GPIO连接RS485收发模块的接受使能端,在接受、发送数据之前,首先对使能端置位,使之处于正确的“接收”或“发送”状态。
二、支持平台
此文是基于TI芯片AM1808平台的RS485驱动,由于他们的软串口不支持485串口数据传输,所以自己写了一个简单的485驱动,此驱动只用来简单的控制485的使能管脚,拉高或拉低电平,满足读写。
1.rs485驱动
注意:在这个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.测试程序
三。小结
以上驱动程序和测试程序比较简单,只适合在AM1808平台使用,希望可以对同样在此平台开发的朋友们有点帮助吧。
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平台使用,希望可以对同样在此平台开发的朋友们有点帮助吧。
相关文章推荐
- Sim Module Profile
- eclipse删除svn下载的文件后如何恢复
- 马尔科夫及隐含马尔科夫模型
- Android 异常 -1 column '_id' does not exist
- Xcode 之自己编译静态库
- 【开发日记】计算精度
- Microsoft Virtual PC虚拟机安装Centos局域网无法ping通问题
- phpize的深入理解
- rman模拟故障恢复实验
- play框架中模块开发过程中的体会
- nested exception is javax.validation.ValidationException错误解决思路
- OC(objective-c)_runtime运行时官方文档翻译 -- api 速查表
- 八种程序员境界,你将要做哪一种
- NGUI UILabel 代码换行
- rman命令学习-tina(下)
- App跳转至AppStore评价
- building workspace问题
- Net4.0的网站在IE10、IE11出现“__doPostBack未定义”的解决办法。
- Flask安装过程中“配置虚拟环境”步骤报错,找不到activate.bat
- Elasticsearch dynamic mapping 动态模板 Default mapping 默认列