Linux 驱动像单片机一样读取一帧dmx512串口数据

硬件全志R528

目标:实现Linux 读取一帧dmx512串口数据。

问题分析:因为串口数据量太大,帧与帧之间的间隔太小。通过Linux自带的读取函数方法无法获取到

帧头和帧尾,读取到的数据都是缓存区中的,数据量又大。导致缓冲区中一直有很多数据,

又由于dmx512数据协议中并没有帧头帧尾字段只有普通数据,无法通过特定的帧头帧尾截取到一完整帧的数据。

所以只能像单片机一样通过串口寄存器对LSR 的UART_LSR_FE位 (接收到错误帧)认为是一帧结束和开始。

通过对Linux驱动读取串口数据的过程分析,

tty_read() ----> ld->ops->read() ----> n_tty_read()
n_tty_read()中add_wait_queue(&tty->read_wait, &wait)没有数据的时候上层的read进程阻塞在此
而在串口有数据来的时候n_tty_receive_buf()--->wake_up_interruptible(&tty->read_wait),唤醒上面的read进程n_tty_read()中会继续运行,将数据拷到用户空间
从整个分析来看,uart驱动会把从硬件接受到的数据暂时存放在tty_buffer里面,然后调用线路规程的receive_buf()把数据存放到tty->read_buf里面,

而系统调用的read()函数直接从tty->read_buf里面读取数据。

所以最终判断在uart的串口中断接收处理函数中增加接收代码比较合适。

 

 Linux 设置非标准波特率参考上次的博客。

方法:

1、写一个简单字符驱动dmx512_uart.c,放在sunxi-uart.c同文件夹中。

在驱动读函数中设置全局变量标识,等待读取数据,后copy_to_user上传到用户空间.

修改同目录下的Makefile 和Kconfig 后添加到内核,编译到内核中。

 

/*dmx512_uart.c 代码*/ #include <linux/module.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/init.h> #include <linux/cdev.h> #include "dmx512_uart.h"  #define CDEV_NAME  "dmx512_uart_dev" struct dmx512_uart_dev *dmx512_devp;   static ssize_t dmx512drv_read (struct file *filp, char __user *buf, size_t size, loff_t *ppos) {     int len =0;     int num =0;     int ret =0;     int i=0;     //printk("%s startn",__func__);      if(size > DMX512_BUF_LEN)     {         dmx512_devp->r_size = DMX512_BUF_LEN;     }     else     {         dmx512_devp->r_size = size;     }     memset(dmx512_devp->dmx_buff,0,sizeof(dmx512_devp->dmx_buff));     dmx512_devp->end_read_flag = false;     dmx512_devp->recv_len =0;     dmx512_devp->num_break =0;     dmx512_devp->start_read_flag = true;      while(!dmx512_devp->end_read_flag) /*等待获取数据*/     {         msleep(100);         num++;         if(num > 50)         {             printk("timeoutn");             break;         }     }     if(dmx512_devp->recv_len < size)     {         len = dmx512_devp->recv_len;     }     else     {             len = size;         }          if(copy_to_user(buf,dmx512_devp->dmx_buff, len))         ret = -EFAULT;     else{         ret = len;     }     //printk("%s endn",__func__);     return ret;      } static ssize_t dmx512drv_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) {      return 0; } static int dmx512drv_close (struct inode *inodp, struct file *filp) {     //printk("%sn",__func__);     return 0;  } static int dmx512drv_open (struct inode *inodp, struct file *filp) {     //printk("%sn",__func__);     return 0; }  static const struct file_operations dmx512drv_fops = {     .owner = THIS_MODULE,     .open =dmx512drv_open,     .read =dmx512drv_read,     .write =dmx512drv_write,     .release =dmx512drv_close, };  static int __init dmx512_init(void) {     int ret;     dmx512_devp =kzalloc(sizeof(struct dmx512_uart_dev), GFP_KERNEL);     if(!dmx512_devp)     {         ret = -ENOMEM;         return ret;     } #if 0         /*动态申请dev*/     ret = alloc_chrdev_region(&dmx512_devp->dev,0, 1, CDEV_NAME);     if(ret)     {         printk("failed to allocate char device regionn");         return ret;     }      cdev_init(&dmx512_devp->cdev,&dmx512drv_fops);          ret = cdev_add(&dmx512_devp->cdev,dmx512_devp->dev,1);         if(ret)     {         printk("failed to cdev_addn");         goto unregister_chrdev;      }              return 0; unregister_chrdev:     unregister_chrdev_region(dmx512_devp->dev,1);     return ret; #endif      dmx512_devp->dev_major = register_chrdev(0,"dmx512_uart_drv",&dmx512drv_fops);     if(dmx512_devp->dev_major < 0)     {         printk(KERN_ERR"register_chrdev errorn");         ret =- ENODEV;         goto err_0;      }     dmx512_devp->cls = class_create(THIS_MODULE,"dmx512_cls");     if(IS_ERR(dmx512_devp->cls))     {         printk(KERN_ERR"class_create errorn");         ret = PTR_ERR(dmx512_devp->cls);         goto err_1;     }     dmx512_devp->dev = device_create(dmx512_devp->cls, NULL,MKDEV(dmx512_devp->dev_major, 0),NULL,"dmx512_uart");     if(IS_ERR(dmx512_devp->dev))     {         printk(KERN_ERR"device_create errorn");         ret = PTR_ERR(dmx512_devp->dev);         goto err_2;     }     return 0;  err_2:         class_destroy(dmx512_devp->cls); err_1:         unregister_chrdev(dmx512_devp->dev_major,"dmx512_uart_drv");              err_0:     kfree(dmx512_devp);     return ret;  }  static void __exit  dmx512_exit(void) { #if 0     cdev_del(&dmx512_devp->cdev);     unregister_chrdev_region(dmx512_devp->dev,1); #endif     device_destroy(dmx512_devp->cls, MKDEV(dmx512_devp->dev_major, 0));     class_destroy(dmx512_devp->cls);     unregister_chrdev(dmx512_devp->dev_major,"dmx512_uart_drv");     kfree(dmx512_devp);  }   module_init(dmx512_init); module_exit(dmx512_exit); MODULE_LICENSE("GPL");   /*dmx512_uart.h 头文件*/ #ifndef _DMX512_UART_H_ #define _DMX512_UART_H_  #define DMX512_BUF_LEN (4096+1+3) struct dmx512_uart_dev {     unsigned int dev_major;     struct class *cls;     struct device *dev;     int recv_len;     int r_size;     bool start_read_flag;     bool end_read_flag;     unsigned char num_break;     unsigned char dmx_buff[DMX512_BUF_LEN]; };  extern struct dmx512_uart_dev *dmx512_devp;  #endif /*_DMX512_UART_H_*/

 

2、串口接收中断处理函数中根据全局变量标识开始读取数据。

通过对寄存器LSR 的UART_LSR_FE位进行判断,为新的一帧的开始和结束。

通过对内核源码的分析找到uart的串口中断接收处理函数。在

sunxi-uart.c -》static unsigned int sw_uart_handle_rx(struct sw_uart_port *sw_uport, unsigned int lsr)

static unsigned int sw_uart_handle_rx(struct sw_uart_port *sw_uport, unsigned int lsr) {     unsigned char ch = 0;     int max_count = 256;     char flag;  #if IS_ENABLED(CONFIG_SERIAL_SUNXI_DMA)     if ((sw_uport->dma->use_dma & RX_DMA)) {         if (lsr & SUNXI_UART_LSR_RXFIFOE) {             dev_info(sw_uport->port.dev, "error:lsr=0x%xn", lsr);             lsr = serial_in(&sw_uport->port, SUNXI_UART_LSR);             return lsr;         }     } #endif      if(lsr & SUNXI_UART_LSR_FE)     {         if((dmx512_devp->start_read_flag) && (strncmp(sw_uport->name,"uart1",5) ==0))  /*现在用的是uart1 不同的端口需要调整,也可以通过驱动直接传过来*/         {             dmx512_devp->num_break++;             if(dmx512_devp->num_break ==1)                 dmx512_devp->recv_len =0;         }     }     do {          if((dmx512_devp->start_read_flag) && (strncmp(sw_uport->name,"uart1",5) ==0))         {                 if((lsr & SUNXI_UART_LSR_FE) &&(max_count !=256))                         dmx512_devp->num_break++;             }                  if (likely(lsr & SUNXI_UART_LSR_DR)) {             ch = serial_in(&sw_uport->port, SUNXI_UART_RBR); #if IS_ENABLED(CONFIG_SW_UART_DUMP_DATA)             sw_uport->dump_buff[sw_uport->dump_len++] = ch; #endif         } else             ch = 0;          flag = TTY_NORMAL;         sw_uport->port.icount.rx++;         if (unlikely(lsr & SUNXI_UART_LSR_BRK_ERROR_BITS)) {             /*              * For statistics only              */             if (lsr & SUNXI_UART_LSR_BI) {                 lsr &= ~(SUNXI_UART_LSR_FE | SUNXI_UART_LSR_PE);                 sw_uport->port.icount.brk++;                  /*                  * We do the SysRQ and SAK checking                  * here because otherwise the break                  * may get masked by ignore_status_mask                  * or read_status_mask.                  */                 if (!ch && uart_handle_break(&sw_uport->port))                     goto ignore_char;             } else if (lsr & SUNXI_UART_LSR_PE)                 sw_uport->port.icount.parity++;             else if (lsr & SUNXI_UART_LSR_FE)                 sw_uport->port.icount.frame++;             if (lsr & SUNXI_UART_LSR_OE)                 sw_uport->port.icount.overrun++;              /*              * Mask off conditions which should be ignored.              */             lsr &= sw_uport->port.read_status_mask; #if IS_ENABLED(CONFIG_SERIAL_SUNXI_CONSOLE)             if (sw_is_console_port(&sw_uport->port)) {                 /* Recover the break flag from console xmit */                 lsr |= sw_uport->lsr_break_flag;             } #endif             if (lsr & SUNXI_UART_LSR_BI)                 flag = TTY_BREAK;             else if (lsr & SUNXI_UART_LSR_PE)                 flag = TTY_PARITY;             else if (lsr & SUNXI_UART_LSR_FE)                 flag = TTY_FRAME;         }         if (uart_handle_sysrq_char(&sw_uport->port, ch))             goto ignore_char;                  //printk("sw_uport->name =%sn",sw_uport->name);         /*增加对break的判断*/            if((dmx512_devp->start_read_flag) && (strncmp(sw_uport->name,"uart1",5) ==0))         {                 if(dmx512_devp->num_break ==1)             {                 dmx512_devp->dmx_buff[dmx512_devp->recv_len] =ch;                 dmx512_devp->recv_len++;                 if(dmx512_devp->recv_len >= dmx512_devp->r_size)                 {                     dmx512_devp->start_read_flag = false;                     dmx512_devp->end_read_flag = true;                       }             }             else if(dmx512_devp->num_break > 1)             {                     dmx512_devp->start_read_flag = false;                     dmx512_devp->end_read_flag = true;                         }         }                  uart_insert_char(&sw_uport->port, lsr, SUNXI_UART_LSR_OE, ch, flag); ignore_char:         lsr = serial_in(&sw_uport->port, SUNXI_UART_LSR);     } while ((lsr & (SUNXI_UART_LSR_DR | SUNXI_UART_LSR_BI)) && (max_count-- > 0));      SERIAL_DUMP(sw_uport, "Rx");     spin_unlock(&sw_uport->port.lock);     tty_flip_buffer_push(&sw_uport->port.state->port);     spin_lock(&sw_uport->port.lock);      return lsr; }

 

3、写应用程序进行验证。

打开设置串口uart1 波特率250000 8 N 2

 

#include<stdio.h> #include<stdlib.h> #include<string.h>  #include <sys/time.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>  #include <termios.h> #include <errno.h> #include <signal.h>  #include <stdbool.h>   #define UART1_DEV_NAME  "/dev/ttyS1"  /*需根据实际端口修改*/ #define DMX512_DEV_NAME "/dev/dmx512_uart" #define BUF_LEN 100 #define MAX_BUF 2048   int oflags =0; int fd =-1; char buff[MAX_BUF] ={0};    /** *@brief  配置串口 *@param  fd:串口文件描述符.           nSpeed:波特率,          nBits:数据位 7 or 8,           nEvent:奇偶校验位,          nStop:停止位 *@return 失败返回-1;成功返回0; */  int set_serial(int fd, int nSpeed, int nBits, char nEvent, int nStop) {     struct termios newttys1, oldttys1;      /*保存原有串口配置*/     if(tcgetattr(fd, &oldttys1) != 0)     {         perror("Setupserial 1");         return - 1;     }     memset(&newttys1, 0, sizeof(newttys1));     //memcpy(&newttys1, &oldttys1, sizeof(newttys1));     /*CREAD 开启串行数据接收,CLOCAL并打开本地连接模式*/     newttys1.c_cflag |= (CLOCAL | CREAD);      newttys1.c_cflag &=~CSIZE; /*设置数据位*/     switch(nBits)    /*数据位选择*/     {         case 7:             newttys1.c_cflag |= CS7;             break;         case 8:             newttys1.c_cflag |= CS8;             break;         default:break;     }          switch(nEvent)  /*奇偶校验位*/     {         case '0':             newttys1.c_cflag |= PARENB; /*开启奇偶校验*/             newttys1.c_iflag |= (INPCK | ISTRIP); /*INPCK打开输入奇偶校验,ISTRIP 去除字符的第八个比特*/             newttys1.c_cflag |= PARODD; /*启动奇校验(默认为偶校验)*/             break;         case 'E':             newttys1.c_cflag |= PARENB; /*开启奇偶校验*/             newttys1.c_iflag |= (INPCK | ISTRIP); /*INPCK打开输入奇偶校验,ISTRIP 去除字符的第八个比特*/             newttys1.c_cflag &= ~PARODD; /*启动偶校验*/             break;         case 'N':             newttys1.c_cflag &= ~PARENB; /*无奇偶校验*/             break;         default:break;     }          switch(nSpeed) /*设置波特率*/     {         case 2400:             cfsetispeed(&newttys1, B2400);             cfsetospeed(&newttys1, B2400);             break;         case 4800:             cfsetispeed(&newttys1, B4800);             cfsetospeed(&newttys1, B4800);             break;         case 9600:             cfsetispeed(&newttys1, B9600);             cfsetospeed(&newttys1, B9600);             break;         case 115200:             cfsetispeed(&newttys1, B115200);             cfsetospeed(&newttys1, B115200);             break;         case 250000:             //ret = cfsetispeed(&newttys1, 0020001);             //printf("reti = %dn",ret);             //ret = cfsetospeed(&newttys1, 0020001);                     //printf("reto = %dn",ret);             newttys1.c_cflag |= 0020001;             break;         default :             cfsetispeed(&newttys1, B9600);             cfsetospeed(&newttys1, B9600);             break;     }          /*设置停止位*/     /*停止位为1,则清除CSTOPB,如停止位为2,则激活CSTOPB*/     if(nStop == 1)     {         newttys1.c_cflag &= ~CSTOPB;  /*默认为停止位1*/     }     else if(nStop == 2)     {         newttys1.c_cflag |= CSTOPB;     }      newttys1.c_iflag &=~(PARMRK); /*不设置的*/      newttys1.c_iflag |= IGNBRK ; /*设置的*/     printf("newttys1.c_iflag= 0x%n",newttys1.c_iflag);       /*设置最少字符和等待时间,对于接收字符和等待时间没有特别的要求时*/     newttys1.c_cc[VTIME] = 0; /*非规范模式读取时的超时时间*/     newttys1.c_cc[VMIN] = 0; /*非规范模式读取时的最小字符数*/          /*tcflush 清空终端未完成的输入、输出请求及数据     TCIFLUSH表示清空正接收到的数据,且不读取出来*/     tcflush(fd, TCIFLUSH);      /*激活配置使其生效*/     if((tcsetattr(fd, TCSANOW, &newttys1)) != 0)     {         perror("usart set error");         return - 1;     }      return 0; }  int main(int argc,char const * argv[]) {      int ret =-1;     int i =0;     int n =0;     int len = BUF_LEN;     int baud = 250000;     int fd_dmx512 =-1;      struct sigaction saio;          if(argc !=2)     {         printf("arg is not 2,arg is app baud_raten");     }     if(argc == 2)         baud = atoi(argv[1]);     printf("baud =%dn",baud);     fd = open(UART1_DEV_NAME, O_RDWR | O_NOCTTY | O_NDELAY);     if(fd < 0)     {         perror("Can't open uart1 port");         return(void *)"uart1 dev error";     }     ret = set_serial(fd,baud, 8, 'N', 2); /*可能需要根据情况调整*/     if(ret < 0)     {         printf("set_serial errorn");         return -1;         }      while(1)     {         fd_dmx512 =open(DMX512_DEV_NAME,O_RDONLY);         if(fd_dmx512 < 0)         {             printf("open dmx512 device errorn");             return -1;         }        memset(buff,0,sizeof(buff));         printf("Read startn");         n = read(fd_dmx512,buff,600);         printf("Read endn");         printf("num=%d :",n);         for(i=0;i<n;i++)             printf("%02x ",buff[i]);         printf("n");              ret = close(fd_dmx512);         if(ret < 0)             printf("close errorn");              sleep(5);     }      return 0; }

 

通过测试后正常读取到串口数据

Linux 驱动像单片机一样读取一帧dmx512串口数据

 

 

Linux 驱动像单片机一样读取一帧dmx512串口数据

 

 

DMX512协议解析

(1)采用RS-485总线收发器,差分电压进行传输的,抗干扰能力强,信号可以进行长距离传输;
(2)不论调光数据是否需要改变,主机都必须发送控制信号。
(3)由于数据帧之间的时间小于1s,所以在1s内没有收到新的数据帧,说明信号已经丢失;
(4)因为是数据是调光用的,使用环境是不做安全要求的设备, 并且是不间断传输的,所以不需要复杂的校验。

dmx512协议串口波特率为250000

一个bit位 4us
8个位(Slot:x) 4*8=32us,x是从1到512

Linux 驱动像单片机一样读取一帧dmx512串口数据

 

break 88us(范围是88μs——1ms)
MAB(Mark After Break) 8us 两个bit位的时间,高电平
start bit 4us 是低电平
Start Code(SC) 32us,8个位,是一段低电平,必须要有,串口表现中数据是0,接收时作头的一部分
stop 8us 两位结束,是高电平
MTBP 0-1s(MARK Time aftet slot,每一个数据间隔的空闲时间,是高电平,可以不要。

一帧数据包括 start + Slotx: + stop + MTBP = 4+32+8+0=44us

Linux 驱动像单片机一样读取一帧dmx512串口数据

 

参考文档

(19条消息) DMX512协议解析_春风得意吃火锅的博客-CSDN博客_dmx512协议标准

(19条消息) tty驱动 read 过程梳理_0x460的博客-CSDN博客

发表评论

相关文章