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 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,每一个数据间隔的空闲时间,是高电平,可以不要。
一帧数据包括 start + Slotx: + stop + MTBP = 4+32+8+0=44us
参考文档
(19条消息) DMX512协议解析_春风得意吃火锅的博客-CSDN博客_dmx512协议标准
(19条消息) tty驱动 read 过程梳理_0x460的博客-CSDN博客
Linux 驱动像单片机一样读取一帧dmx512串口数据的更多相关文章
- 单片机知识是Linux驱动开发的基础之一
这是arm裸机1期加强版第1课第2节课程的wiki文字版. 为什么没前途也要学习单片机? 因为它是个很好的入口. 学习单片机可以让我们抛开复杂的软件结构,先掌握硬件操作,如:看原理图.芯片手册.写程序 ...
- linux驱动程序设计的硬件基础,王明学learn
linux驱动程序设计的硬件基础(一) 本章讲总结学习linux设备程序设计的硬件基础. 一.处理器 1.1通用处理器 通用处理器(GPP)并不针对特定的应用领域进行体系结构和指令集的优化,它们具有一 ...
- Linux驱动之LCD驱动编写
在Linux驱动之内核自带的S3C2440的LCD驱动分析这篇博客中已经分析了编写LCD驱动的步骤,接下来就按照这个步骤来字尝试字节编写LCD驱动.用的LCD屏幕为tft屏,每个像素点为16bit.对 ...
- Linux 驱动开发
linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...
- linux驱动工程面试必问知识点
linux内核原理面试必问(由易到难) 简单型 1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些? 2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化, ...
- linux驱动面试题整理
1.字符型驱动设备你是怎么创建设备文件的,就是/dev/下面的设备文件,供上层应用程序打开使用的文件? 答:mknod命令结合设备的主设备号和次设备号,可创建一个设备文件. 评:这只是其中一种方式,也 ...
- 嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】
转自:https://blog.csdn.net/Guet_Kite/article/details/78574781 权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 h ...
- linux 驱动之LCD驱动(有framebuffer)
<简介> LCD驱动里有个很重要的概念叫帧缓冲(framebuffer),它是Linux系统为显示设备提供的一个接口,应用程序在图形模式允许对显示缓冲区进行读写操作.用户根本不用关心物理显 ...
- linux驱动开发之块设备学习笔记
我的博客主要用来存放我的学习笔记,如有侵权,请与我练习,我会立刻删除.学习参考:http://www.cnblogs.com/yuanfang/archive/2010/12/24/1916231.h ...
- 在Linux下的中断方式读取按键驱动程序
// 在Linux下的中断方式读取按键驱动程序 //包含外部中断 休眠 加入poll机制 // 采用异步通知的方式 // 驱动程序发 ---> app接收 (通过kill_fasync()发送) ...
随机推荐
- 动词时态=>4.将来时态和过去将来时态构成详解
将来时态构成详解 使用助动词will构成的将来时态 一般将来时态 与一般过去时态相反(时间上) 如果说 一般过去,我们将其当做一张照片 从这张照片当中,我们无法得知 动作什么时候开始 什么时候结束 只 ...
- MySQL 索引失效-模糊查询,最左匹配原则,OR条件等。
索引失效 介绍 索引失效就是我们明明在查询时的条件为索引列(包括自己新建的索引),但是索引不能起效,走的是全表扫描.explain 后可查看type=ALL. 这是为什么呢? 首先介绍有以下几种情况索 ...
- WPF 鼠标移动到图片变大,移开还原,单击触发事件效果
<Grid> <Canvas x:Name="LayoutRoot"> <Image Cursor=" ...
- Asp.Net Core MVC传值 Asp.Net Core API 前台写法
$("#Add_User").click(function () { var obj = { //"属性名": 传递的值, "User_Name&qu ...
- .NET中的拦截器filter的使用
拦截器的使用 使用场景分析 我们先想像一个场景,就是程序员开发软件,他是怎么工作的呢?我们都知道,普通的程序员只需要根据需求文档开发相应的功能即可,他不用和客户谈论软件需求,不用理会软件卖多少钱,他要 ...
- java File类与文件输入/输出流:FileInputStream与FileOutputStream
java File类与文件输入/输出流 File类 File类是java.io包中唯一代表磁盘文件本身的类,该类主要用于文件和目录的创建.文件的查找和文件的删除等. 文件的创建与删除 1.File(S ...
- Hadoop如何保证自己的江湖地位?Yarn功不可没
前言 任何计算任务的运行都离不开计算资源,比如CPU.内存等,那么如何对于计算资源的管理调度就成为了一个重点.大数据领域中的Hadoop之所以一家独大,深受市场的欢迎,和他们设计了一个通用的资源管理调 ...
- elasticsearch倒排索引(全面了解)
SimpleAI推荐语: 前年转过这篇文章,最近在看检索相关论文,发现又有点忘记倒排索引(inverted index)的具体内容,遂翻出来再看看,不得不说,这个漫画画的太好了,娓娓道来,一看就懂,再 ...
- Javascript | 模拟mvc实现点餐程序
MVC模式是一个比较成熟的开发模式.M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式.其中,View的定义比较清晰,就是用 ...
- python之实现文件的读写
很早之前做自动化测试,并没有将测试数据与数据库关联,而是直接通过json.ymal.excel等文件管理的.那么怎么用python读写文件呢? 在操作文件前,都需要打开文件,打开文件用内置函数open ...