6

Linux 驱动像单片机一样读取一帧dmx512串口数据 - 大龄小凡

 1 year ago
source link: https://www.cnblogs.com/hylife/p/17057273.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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;
}

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

2870493-20230117104223576-387102387.png

2870493-20230117104824386-1288949797.png

DMX512协议解析

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

dmx512协议串口波特率为250000

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

2870493-20230117101145339-1001449429.png

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

2870493-20230117101242499-447038268.png

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK