硬件全志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串口数据的更多相关文章

  1. 单片机知识是Linux驱动开发的基础之一

    这是arm裸机1期加强版第1课第2节课程的wiki文字版. 为什么没前途也要学习单片机? 因为它是个很好的入口. 学习单片机可以让我们抛开复杂的软件结构,先掌握硬件操作,如:看原理图.芯片手册.写程序 ...

  2. linux驱动程序设计的硬件基础,王明学learn

    linux驱动程序设计的硬件基础(一) 本章讲总结学习linux设备程序设计的硬件基础. 一.处理器 1.1通用处理器 通用处理器(GPP)并不针对特定的应用领域进行体系结构和指令集的优化,它们具有一 ...

  3. Linux驱动之LCD驱动编写

    在Linux驱动之内核自带的S3C2440的LCD驱动分析这篇博客中已经分析了编写LCD驱动的步骤,接下来就按照这个步骤来字尝试字节编写LCD驱动.用的LCD屏幕为tft屏,每个像素点为16bit.对 ...

  4. Linux 驱动开发

    linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...

  5. linux驱动工程面试必问知识点

    linux内核原理面试必问(由易到难) 简单型 1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些? 2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化, ...

  6. linux驱动面试题整理

    1.字符型驱动设备你是怎么创建设备文件的,就是/dev/下面的设备文件,供上层应用程序打开使用的文件? 答:mknod命令结合设备的主设备号和次设备号,可创建一个设备文件. 评:这只是其中一种方式,也 ...

  7. 嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】

    转自:https://blog.csdn.net/Guet_Kite/article/details/78574781 权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 h ...

  8. linux 驱动之LCD驱动(有framebuffer)

    <简介> LCD驱动里有个很重要的概念叫帧缓冲(framebuffer),它是Linux系统为显示设备提供的一个接口,应用程序在图形模式允许对显示缓冲区进行读写操作.用户根本不用关心物理显 ...

  9. linux驱动开发之块设备学习笔记

    我的博客主要用来存放我的学习笔记,如有侵权,请与我练习,我会立刻删除.学习参考:http://www.cnblogs.com/yuanfang/archive/2010/12/24/1916231.h ...

  10. 在Linux下的中断方式读取按键驱动程序

    // 在Linux下的中断方式读取按键驱动程序 //包含外部中断 休眠 加入poll机制 // 采用异步通知的方式 // 驱动程序发 ---> app接收 (通过kill_fasync()发送) ...

随机推荐

  1. Vue中router路由的使用、router-link的使用(在项目中的实际运用方式)

    文章目录 1.先看router中的index.js文件 2.router-link的使用 3.实现的效果 前提:router已经安装 1.先看router中的index.js文件 import Vue ...

  2. 你应该知道的数仓安全:都是同名Schema惹的祸

    摘要:我是管理员账号,怎么还没有权限?当小伙伴询问的时候,我第一时间就会想到都是用户同名Schema惹的祸 本文分享自华为云社区<你应该知道的数仓安全--都是同名Schema惹的祸>,作者 ...

  3. IDEA& Android Studio 配置

    1.配置环境 首先要安装好JDK,但不需要单独下载SDK,只需在IDEA或AS的"设置->外观与行为->->系统设置->Android SDK"中下载相应版 ...

  4. 28.解析器Parser

    什么是解析器 因为前后端分离,可能有json.xml.html等各种不同格式的内容 后端也必须要有一个解析器来解析前端发送过来的数据 不然后端无法处理前端数据 后端有一个渲染器Render,和解析器是 ...

  5. java代码整洁之道

    package Day01;import org.junit.Test;import java.text.NumberFormat;import java.util.Scanner;public cl ...

  6. .Net Core 3.0 对 MongoDB 的多条件(两种)查询操作

    前言   在日常开发中,偶尔会用到 MongoDB 的数据操作,也花费了一些时间调试,因此在此处记录一下,共同进步. 废话少说,出招吧! 正文 2.1 准备工作 首先需要引入 .Net 平台链接 Mo ...

  7. ThreadLocal的介绍与运用

    ThreadLocal全面解析 学习目标 了解ThreadLocal的介绍 掌握ThreadLocal的运用场景 了解ThreadLocal的内部结构 了解ThreadLocal的核心方法源码 了解T ...

  8. Vue2基础知识学习

    Vue2基础知识学习 01.初识 new Vue({ el: '#root', //用于指定当前Vue实例为哪个容器服务,值通常为css选择器符 data () { return { } } }); ...

  9. 思维分析逻辑 1 DAY

    数据分析原则:坚决不做提数机器. 数据分析工作模块 日报 了解业务现状 提升数据敏感性 数据波动解释 周报 了解数据的短期趋势 版本迭代分析 为结论型报告背书 月报 梳理业务的流程 为决策提供部分建议 ...

  10. MLP(SGD or Adam) Perceptron Neural Network Working by Pytorch(including data preprocessing)

    通过MLP多层感知机神经网络训练模型,使之能够根据sonar的六十个特征成功预测物体是金属还是石头.由于是简单的linearr线性仿射层,所以网络模型的匹配度并不高. 这是我的第一篇随笔,就拿这个来练 ...