- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
硬件全志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 start\n",__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( " timeout\n " ); 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 end\n",__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("%s\n",__func__); return 0 ; } static int dmx512drv_open ( struct inode *inodp, struct file * filp) { // printk("%s\n",__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 region\n " ); 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_add\n " ); 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 error\n " ); 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 error\n " ); 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 error\n " ); 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%x\n " , 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 =%s\n",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 = %d\n",ret); // ret = cfsetospeed(&newttys1, 0020001); // printf("reto = %d\n",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_rate\n " ); } if (argc == 2 ) baud = atoi(argv[ 1 ]); printf( " baud =%d\n " ,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 error\n " ); return - 1 ; } while ( 1 ) { fd_dmx512 = open(DMX512_DEV_NAME,O_RDONLY); if (fd_dmx512 < 0 ) { printf( " open dmx512 device error\n " ); return - 1 ; } memset(buff, 0 , sizeof (buff)); printf( " Read start\n " ); n = read(fd_dmx512,buff, 600 ); printf( " Read end\n " ); 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 error\n " ); sleep( 5 ); } return 0 ; }
。
通过测试后正常读取到串口数据 。
。
。
。
。
DMX512协议解析 。
(1)采用RS-485总线收发器,差分电压进行传输的,抗干扰能力强,信号可以进行长距离传输; (2)不论调光数据是否需要改变,主机都必须发送控制信号。 (3)由于数据帧之间的时间小于1s,所以在1s内没有收到新的数据帧,说明信号已经丢失; (4)因为是数据是调光用的,使用环境是不做安全要求的设备, 并且是不间断传输的,所以不需要复杂的校验.
dmx512协议串口波特率为250000 。
一个bit位 4us 8个位(Slot:x) 4*8=32us,x是从1到512 。
。
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,每一个数据间隔的空闲时间,是高电平,可以不要.
。
参考文档 。
(19条消息) DMX512协议解析_春风得意吃火锅的博客-CSDN博客_dmx512协议标准 。
(19条消息) tty驱动 read 过程梳理_0x460的博客-CSDN博客 。
最后此篇关于Linux驱动像单片机一样读取一帧dmx512串口数据的文章就讲到这里了,如果你想了解更多关于Linux驱动像单片机一样读取一帧dmx512串口数据的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
有时,当我调用 ipdb 时,我知道我想要成为跟踪设置上方的框架。我认为这就是 API 公开 frame 参数的原因(如 the documentation 中所述)。 所以这是函数: import
我是一名优秀的程序员,十分优秀!