硬體全志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
參考文件