• 最近疫情严重,特殊时期,大家还是要少出门,趁这段时间可以总结一下之前工作中的问题和经验

    0.前言

    在今天终于离职了,办完了所有的手续,感觉一身轻松,在上一家公司,作为一名程序员,已经在偏离写代码开发的歪门邪道上越走越远了,现在疫情比较严重,非常时间,大家还是要少出门,注意安全,所以就趁这段时间总结一下之前工作中的问题和经验吧。

    1.tty与串口驱动

    tty设备的名称是从过去的电传打字机缩写而来,最初是指连接到Unix系统上的物理或者虚拟终端。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备。
    有三种类型的tty’驱动程序:控制台、串口和pty。控制台和pty驱动程序已经被编写好饿了,而且可能也不必为这两类tty驱动程序编写其他的驱动程序。这使得任何使用tty核心与用户和系统交互的新驱动程序都可被看成是串口驱动程序。

    2.关于tty

    Linux tty驱动程序的核心紧挨着标准字符设备驱动层之下,并提供了一系列的功能,作为接口被终端类型设备使用。内核负责控制通过tty设备的数据流,并且格式化这些数据。这使得tty驱动程序把重点放在处理流向或者流出硬件的数据上,而不必重点考虑使用常规方法与用户空间的交互。为了控制数据流,有许多不同的线路规程(line discipline)可虚拟地“插入”任何的tty设备上,这由不同的tty线路规程驱动程序实现。
    在这里插入图片描述
    tty核心从用户那里得到将被发往tty设备的数据,然后把数据发送给tty线路规程驱动程序,该驱动程序负责把数据传递给tty驱动程序。tty驱动程序对数据进行格式化,然后才能发送给硬件。从tty硬件那里接收到的数据将回溯到tty驱动程序,然后流入tty线路规程驱动程序,接着是tty核心,最后用户从tty核心哪里得到数据。有时tty驱动程序直接与tty核心通信,tty核心将数据直接发送给tty驱动程序,但通常是tty线路规程驱动程序修改在二者之间流动的数据。

    3.添加Device

    在arch/arm/boot/dts下的.dtsi文件下添加串口相关的device信息
    在这里插入图片描述
    在imx.c下会通过MODULE_DEVICE_TABLE()去匹配一个device
    在这里插入图片描述

    4.添加Driver

    串口设备驱动核心结构体为uart_driver,在文件linux/drivers/serial/imx.c

    static struct uart_driver imx_uart_uart_driver = {
    	.owner          = THIS_MODULE,
    	.driver_name    = DRIVER_NAME,                 //driver name
    	.dev_name       = DEV_NAME,                    //device name
    	.major          = SERIAL_IMX_MAJOR,            //主设备号
    	.minor          = MINOR_START,                 //次设备号
    	.nr             = ARRAY_SIZE(imx_uart_ports),
    	.cons           = IMX_CONSOLE,
    };
    
    

    串口驱动注册

    static int __init imx_uart_init(void)
    {
    	int ret = uart_register_driver(&imx_uart_uart_driver);         //用uart核心层注册一个驱动程序
    
    	if (ret)
    		return ret;
    
    	ret = platform_driver_register(&imx_uart_platform_driver);    //调用platform_driver_register向内核注册
    	if (ret != 0)
    		uart_unregister_driver(&imx_uart_uart_driver);
    
    	return ret;
    }
    

    uart_register_driver()函数,在drivers/tty/serial/serial_core.c中。
    任何tty驱动程序的主要数据结构是结构tty_driver,被用来向tty核心注册和注销驱动程序

    int uart_register_driver(struct uart_driver *drv)
    {
    	struct tty_driver *normal;
    	int i, retval = -ENOMEM;
    
    	...
    	//每个端口对应一个state
    	drv->state = kzalloc(sizeof(struct uart_state)* drv->nr, GFP_KERNEL);
    	...
    	//分配该串口驱动对应的tty_drvier,tty_driver结构将根据tty驱动程序的需求,用正确的信息初始化
    	normal = alloc_tty_driver(drv->nr);
    	...
    	//让drv->tty_driver字段指向这个tty_driver
    	drv->tty_driver = normal;
    	...
    	//使用tty_set_operation函数拷贝在驱动程序定义的一系列操作函数
    	tty_set_operation(normal, &uart_ops);
    	...
    	//注册tty驱动程序
    	retval = tty_register_driver(normal);
    
    }
    

    为了向tty核心注册这个驱动程序,必须将tty_driver结构传递给tty_register_driver函数。
    在注册自身后,驱动程序使用tty_register_device函数注册它所控制的设备,该函数有三个参数:

    • 属于该设备的tty_driver结构指针
    • 设备的次设备号
    • 指向该tty设备所绑定的device结构指针,如果tty设备没有绑定任何device结构,该参数为NULL
     int tty_register_driver(struct tty_driver *driver)
     {
     	...
     	for (i = 0; i < driver->num; i++) {
    			d = tty_register_device(driver, i, NULL);
    			...
    	}
    	...
     }
    

    5.platform机制

    在driver和device匹配成功之后,会调用probe函数
    在这里插入图片描述

    static int imx_uart_probe(struct platform_device *pdev)
    {
    	struct imx_port *sport;
    	void __iomem *base;
    	int ret = 0;
    	u32 ucr1;
    	struct resource *res;
    	int txirq, rxirq, rtsirq;
    
    	...
    	
    	sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
    	
    	...
    	
    	//添加一个端口结构
    	return uart_add_one_port(&imx_uart_uart_driver, &sport->port);
    }
    
    int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
    {
    	struct uart_state *state;
    	struct tty_port *port;
    	int ret = 0;
    	struct device *tty_dev;
    	int num_groups;
    
    	...
    	
    	//注册tty设备
    	tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
    			uport->line, uport->dev, port, uport->tty_groups);
    	if (!IS_ERR(tty_dev)) {
    		device_set_wakeup_capable(tty_dev, 1);
    	} else {
    		dev_err(uport->dev, "Cannot register tty device on line %d\n",
    		       uport->line);
    	}
    
    	...
    
    	return ret;
    }
    

    6.串口数据流

    在这里插入图片描述

    • open函数
    static int tty_open(struct inode *inode, struct file *filp)
    {
    	struct tty_struct *tty;
    	int noctty, retval;
    	dev_t device = inode->i_rdev;              //表示设备文件的结点,这个域实际上包含了设备号
    	unsigned saved_flags = filp->f_flags;
    
    	...
    
    	if (tty->ops->open)
    		retval = tty->ops->open(tty, filp);    //调用uart_open函数
    	else
    		retval = -ENODEV;
    	filp->f_flags = saved_flags;
    
    	...
    	
    	return 0;
    }
    
    static int uart_open(struct tty_struct *tty, struct file *filp)
    {
    	struct uart_state *state = tty->driver_data;
    	int retval;
    
    	retval = tty_port_open(&state->port, tty, filp);
    	if (retval > 0)
    		retval = 0;
    
    	return retval;
    }
    

    当用户使用open打开由驱动程序分配的设备节点时,tty核心将调用open函数。tty核心使用分配给该设备tty_struct结构的指针,以及一个文件描述符作为参数调用该函数。

    • release函数
    int tty_release(struct inode *inode, struct file *filp)
    {
    	struct tty_struct *tty = file_tty(filp);
    	struct tty_struct *o_tty = NULL;
    	int	do_sleep, final;
    	int	idx;
    	long	timeout = 0;
    	int	once = 1;
    
    	...
    	
    	tty_debug_hangup(tty, "releasing (count=%d)\n", tty->count);
    
    	if (tty->ops->close)
    		tty->ops->close(tty, filp);   //调用uart_close函数
    
    	/* If tty is pty master, lock the slave pty (stable lock order) */
    	tty_lock_slave(o_tty);
    	
    	...
    
    	tty_release_struct(tty, idx);
    	return 0;
    }
    
    static void uart_close(struct tty_struct *tty, struct file *filp)
    {
    	struct uart_state *state = tty->driver_data;
    
    	if (!state) {
    		struct uart_driver *drv = tty->driver->driver_state;
    		struct tty_port *port;
    
    		state = drv->state + tty->index;
    		port = &state->port;
    		spin_lock_irq(&port->lock);
    		--port->count;
    		spin_unlock_irq(&port->lock);
    		return;
    	}
    
    	pr_debug("uart_close(%d) called\n", tty->index);
    
    	tty_port_close(tty->port, tty, filp);
    }
    

    当用户使用先前由open函数创建的文件句柄作为参数调用close时,tty核心调用close函数指针,此时该设备将被关闭。

    • write函数
    static ssize_t tty_write(struct file *file, const char __user *buf,
    						size_t count, loff_t *ppos)
    {
    	struct tty_struct *tty = file_tty(file);
     	struct tty_ldisc *ld;
    	ssize_t ret;
    
    	if (tty_paranoia_check(tty, file_inode(file), "tty_write"))
    		return -EIO;
    	if (!tty || !tty->ops->write ||	tty_io_error(tty))
    			return -EIO;
    	/* Short term debug to catch buggy drivers */
    	if (tty->ops->write_room == NULL)
    		tty_err(tty, "missing write_room method\n");
    	ld = tty_ldisc_ref_wait(tty); 
    	if (!ld)
    		return hung_up_tty_write(file, buf, count, ppos);
    	/*调用线路规程操作函数集中的n_tty_write()函数*/
    	if (!ld->ops->write)
    		ret = -EIO;
    	else
    		ret = do_tty_write(ld->ops->write, tty, file, buf, count);
    	tty_ldisc_deref(ld);
    	return ret;
    }
    
    static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
    			   const unsigned char *buf, size_t nr)
    {
    	const unsigned char *b = buf;
    	DEFINE_WAIT_FUNC(wait, woken_wake_function);
    	int c;
    	ssize_t retval = 0;
    
    	/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
    	if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
    		retval = tty_check_change(tty);
    		if (retval)
    			return retval;
    	}
    
    	down_read(&tty->termios_rwsem);
    
    	/* Write out any echoed characters that are still pending */
    	process_echoes(tty);
    
    	add_wait_queue(&tty->write_wait, &wait);
    	while (1) {
    	
    			...
    			/*调用uart_flush_chars向硬件发送数据*/
    			if (tty->ops->flush_chars)
    				tty->ops->flush_chars(tty);
    		} else {
    			struct n_tty_data *ldata = tty->disc_data;
    
    			while (nr > 0) {
    				mutex_lock(&ldata->output_lock);
    				c = tty->ops->write(tty, b, nr);   //或者调用uart_write函数
    				mutex_unlock(&ldata->output_lock);
    				if (c < 0) {
    					retval = c;
    					goto break_out;
    				}
    				if (!c)
    					break;
    				b += c;
    				nr -= c;
    			}
    		}
    		
    		...
    		
    	}
    break_out:
    	remove_wait_queue(&tty->write_wait, &wait);
    	if (nr && tty->fasync)
    		set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
    	up_read(&tty->termios_rwsem);
    	return (b - buf) ? b - buf : retval;
    }
    
    static void uart_flush_chars(struct tty_struct *tty)
    {
    	uart_start(tty);
    }
    
    static int uart_write(struct tty_struct *tty,
    					const unsigned char *buf, int count)
    {
    	struct uart_state *state = tty->driver_data;
    	struct uart_port *port;
    	struct circ_buf *circ;
    	unsigned long flags;
    	int c, ret = 0;
    
    	/*
    	 * This means you called this function _after_ the port was
    	 * closed.  No cookie for you.
    	 */
    	if (!state) {
    		WARN_ON(1);
    		return -EL3HLT;
    	}
    
    	port = uart_port_lock(state, flags);
    	circ = &state->xmit;
    	if (!circ->buf) {
    		uart_port_unlock(port, flags);
    		return 0;
    	}
    
    	while (port) {
    		c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
    		if (count < c)
    			c = count;
    		if (c <= 0)
    			break;
    		memcpy(circ->buf + circ->head, buf, c);
    		circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
    		buf += c;
    		count -= c;
    		ret += c;
    	}
    
    	__uart_start(tty);
    	uart_port_unlock(port, flags);
    	return ret;
    }
    
    static void uart_start(struct tty_struct *tty)
    {
    	struct uart_state *state = tty->driver_data;
    	struct uart_port *port;
    	unsigned long flags;
    
    	port = uart_port_lock(state, flags);
    	__uart_start(tty);
    	uart_port_unlock(port, flags);
    }
    
    static void __uart_start(struct tty_struct *tty)
    {
    	struct uart_state *state = tty->driver_data;
    	struct uart_port *port = state->uart_port;
    
    	if (port && !uart_tx_stopped(port))
    		port->ops->start_tx(port);   //调用imx.c中的imx_uart_start_tx函数发送数据
    }
    

    当数据要被发送给硬件时,用户调用write函数,首先tty核心接收到了该调用,然后内核将数据发送给tty驱动程序的write函数。tty核心同时也告诉tty驱动程序发功数据的大小。

    • read函数
    static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
    			loff_t *ppos)
    {
    	int i;
    	struct inode *inode = file_inode(file);
    	struct tty_struct *tty = file_tty(file);
    	struct tty_ldisc *ld;
    
    	if (tty_paranoia_check(tty, inode, "tty_read"))
    		return -EIO;
    	if (!tty || tty_io_error(tty))
    		return -EIO;
    
    	/* We want to wait for the line discipline to sort out in this
    	   situation */
    	ld = tty_ldisc_ref_wait(tty);
    	if (!ld)
    		return hung_up_tty_read(file, buf, count, ppos);
    	/*调用线路规程操作函数集中的n_tty_read()函数*/
    	if (ld->ops->read)
    		i = ld->ops->read(tty, file, buf, count);
    	else
    		i = -EIO;
    	tty_ldisc_deref(ld);
    
    	if (i > 0)
    		tty_update_time(&inode->i_atime);
    
    	return i;
    }
    
    static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
    			 unsigned char __user *buf, size_t nr)
    {
    	struct n_tty_data *ldata = tty->disc_data;
    	unsigned char __user *b = buf;
    	DEFINE_WAIT_FUNC(wait, woken_wake_function);
    	int c;
    	int minimum, time;
    	ssize_t retval = 0;
    	long timeout;
    	int packet;
    	size_t tail;
    
    	...
    	
    	while (nr) {
    		
    		...
    		/*如果配置了icanon,那么就按canonical模式拷贝读取的数据*/
    		if (ldata->icanon && !L_EXTPROC(tty)) {
    			retval = canon_copy_from_read_buf(tty, &b, &nr);
    			if (retval)
    				break;
    		} else {
    			int uncopied;
    
    			/* Deal with packet mode. */
    			if (packet && b == buf) {
    				if (put_user(TIOCPKT_DATA, b)) {
    					retval = -EFAULT;
    					break;
    				}
    				b++;
    				nr--;
    			}
    
    			uncopied = copy_from_read_buf(tty, &b, &nr);
    			uncopied += copy_from_read_buf(tty, &b, &nr);
    			if (uncopied) {
    				retval = -EFAULT;
    				break;
    			}
    		}
    
    		n_tty_check_unthrottle(tty);
    
    		if (b - buf >= minimum)
    			break;
    		if (time)
    			timeout = time;
    	}
    	
    	...
    
    	return retval;
    }
    
    1. 如果配置了串口中断,串口在接收到数据后,触发中断
    static irqreturn_t imx_uart_rxint(int irq, void *dev_id)
    {
    	struct imx_port *sport = dev_id;
    	unsigned int rx, flg, ignored = 0;
    	struct tty_port *port = &sport->port.state->port;
    
    	spin_lock(&sport->port.lock);
    
    	while (imx_uart_readl(sport, USR2) & USR2_RDR) {
    		
    		...
    
    		if (uart_handle_sysrq_char(&sport->port, (unsigned char)rx))
    			continue;
    
    		if (unlikely(rx & URXD_ERR)) {
    			
    			...
    			/*读取寄存器的数据*/
    			rx &= (sport->port.read_status_mask | 0xFF);
    
    			if (rx & URXD_BRK)
    				flg = TTY_BREAK;
    			else if (rx & URXD_PRERR)
    				flg = TTY_PARITY;
    			else if (rx & URXD_FRMERR)
    				flg = TTY_FRAME;
    			if (rx & URXD_OVRRUN)
    				flg = TTY_OVERRUN;
    
    #ifdef SUPPORT_SYSRQ
    			sport->port.sysrq = 0;
    #endif
    		}
    
    		if (sport->port.ignore_status_mask & URXD_DUMMY_READ)
    			goto out;
    
    		if (tty_insert_flip_char(port, rx, flg) == 0)
    			sport->port.icount.buf_overrun++;
    	}
    
    out:
    	spin_unlock(&sport->port.lock);
    	tty_flip_buffer_push(port);
    	return IRQ_HANDLED;
    }
    

    调用tty_insert_flip_char将把tty驱动程序获得的、准备发给用户的字符添加到交替缓冲区中。第一个参数是保存数据的tty_struct结构,第二个参数是需要保存的数据,第三个参数是为此字符设置的标志位。如果接收到的字符是常规字符,标志位应该被设置为TTY_NORMAL。

    1. 如果配置了DMA模式,从DMA拷贝数据
    static int imx_uart_start_rx_dma(struct imx_port *sport)
    {
    	struct scatterlist *sgl = &sport->rx_sgl;
    	struct dma_chan	*chan = sport->dma_chan_rx;
    	struct device *dev = sport->port.dev;
    	struct dma_async_tx_descriptor *desc;
    	int ret;
    
    	sport->rx_ring.head = 0;
    	sport->rx_ring.tail = 0;
    	sport->rx_periods = RX_DMA_PERIODS;
    
    	sg_init_one(sgl, sport->rx_buf, RX_BUF_SIZE);
    	
    	...
    	
    	desc->callback = imx_uart_dma_rx_callback;
    	desc->callback_param = sport;
    
    	dev_dbg(dev, "RX: prepare for the DMA.\n");
    	sport->dma_is_rxing = 1;
    	sport->rx_cookie = dmaengine_submit(desc);
    	dma_async_issue_pending(chan);
    	return 0;
    }
    

    tty核心和tty_driver结构并未提供read函数,当tty驱动程序接收到数据后,它将负责把从硬件获取到的任何数据传递给tty核心,而不是使用传统的read函数。tty核心将缓冲数据直到接到来自用户的请求。由于tty核心已提供了缓冲逻辑,因此没有必要为每个tty驱动程序实现它们自己的缓冲区逻辑。当用户要求驱动程序开始或者停止传输数据时,tty核心将通知tty驱动程序。

    7.DMA和中断的配置

    static int imx_uart_startup(struct uart_port *port)
    {
    	struct imx_port *sport = (struct imx_port *)port;
    	int retval, i;
    	unsigned long flags;
    	int dma_is_inited = 0;
    	u32 ucr1, ucr2, ucr4;
    
    	...
    	/*如果dma_is_inited为真,则配置DMA模式,否则使能串口中断*/
    	if (dma_is_inited) {
    		imx_uart_enable_dma(sport);
    		imx_uart_start_rx_dma(sport);
    	} else {
    		ucr1 = imx_uart_readl(sport, UCR1);
    		ucr1 |= UCR1_RRDYEN;
    		imx_uart_writel(sport, ucr1, UCR1);
    
    		ucr2 = imx_uart_readl(sport, UCR2);
    		ucr2 |= UCR2_ATEN;
    		imx_uart_writel(sport, ucr2, UCR2);
    	}
    
    	spin_unlock_irqrestore(&sport->port.lock, flags);
    
    	return 0;
    }
    

    参考资料:
    《LINUX设备驱动程序第三版》
    linux-5.4.9

    展开全文
  • Android串口驱动开发

    2019-07-17 16:19:37
    串口底层驱动,如 /dev/ttyS3 JNI层接口需要根据设备CPU架构类型编译,即在linux内核下使用相应的编译工具编译,注意C文件的接口命名规则需要与java程序包名保持一致, 如 ...

    开发流程

    1. 串口底层驱动,如 /dev/ttyS3

    2. JNI层接口需要根据设备CPU架构类型编译,即在linux内核下使用相应的编译工具编译,注意C文件的接口命名规则需要与java程序包名保持一致,
      如 cn.ingenic.glass.serialport.SerialPort.open (java接口)
      Java_cn_ingenic_glass_serialport_SerialPort_open(JNI接口),
      其中JNI的接口,需要固定格式Java_xxx_xxx

    3. JNI生成的.so文件编译进去Andorid镜像,如libserial_port.so

    4. java程序使用时,需要加载xxx.so文件,如System.loadLibrary(“serial_port”);

    5. 使用时,要对驱动文件作权限管理,以便APK能操作,可在init.rc中设置驱动文件权限,
      如chmod 0666 /dev/ttyS3

    注意事项

    1. 在System.loadLibrary这行的时候就会报 java.lang.UnsatisfiedLinkError: Couldn’t load secret: findLibrary returned null

    产生原因1:加载库名错误
    产生原因2:生成的库和运行平台不匹配,
    解决办法:在jni文件夹下新建Application.mk文件其中指定 APP_ABI := all 生成所有支持平台的.so库

    1. so库可以加载,调用JNI接口时候报错

    产生原因1:加载库名错误
    解决办法:注意调用的JNI接口与JNI文件的命名是否一致

    1. so库可以加载,调用JNI接口也有效,但是在执行时候返回错误,如 fd open return null

    产生原因1:APK在调用JNI层读写或执行底层驱动文件时,权限不够
    解决办法:在root权限下,设置底层驱动文件权限为 0666,所有组可读写

    在这里插入图片描述

    展开全文
  • 串口驱动开发

    2020-02-04 20:47:38
    串口驱动开发 stm32f103CBT6 写好STM32F1 的串口3初始化 收发函数 void Initial_UART3(u32 baudrate) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB1...


    实验芯片:stm32f103CBT6

    串口3初始化收发函数

    初始化函数

    void Initial_UART3(u32 baudrate)
    {
     	GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    
      	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
      	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      	GPIO_Init(GPIOB, &GPIO_InitStructure);    
    
      	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
      	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
      	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	  
    	USART_InitStructure.USART_BaudRate = baudrate;
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;
    	USART_InitStructure.USART_Parity = USART_Parity_No ;
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    
    	USART_Init(USART3, &USART_InitStructure); 
      USART_ITConfig(USART3, USART_IT_IDLE, ENABLE); 
    
      	USART_Cmd(USART3, ENABLE);
    	U3NVIC_Configuration();
    }
    

    做初始化函数有点小插曲
    在这里插入图片描述
    RCC_APB2Periph_GPIOB 的时钟 放到了 RCC_APB1PerphClockCmd()的函数中了
    也就是说 RCC_APB2Periph 时钟总线用 RCC_APB1Perph 的使能函数使能了。这是不对的 。

    造成的结果是串口3可以接收到数据,但是不能发送。 这个结果不知道是为什么。

    中断优先级分组

    void U3NVIC_Configuration(void)
    {
       NVIC_InitTypeDef NVIC_InitStructure; 
       /* Enable the USART3 Interrupt */
    
    	 NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
       NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
       NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
       NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
       NVIC_Init(&NVIC_InitStructure);	
    	
       //Enable DMA1 Channel3 Interrupt 
       NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel3_IRQn;
       NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
       NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
       NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
       NVIC_Init(&NVIC_InitStructure);	
    }
    

    串口3DMA配置

    void DMA_UART3Config(void)
    { 
    
    	DMA_InitTypeDef DMA_InitStructure;                    
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);   
    	
    	DMA_DeInit(DMA1_Channel3);
    	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)SRC_USART3_DR;
    	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART3_DMA_Recieve_Buf;
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;			
    
    	DMA_InitStructure.DMA_BufferSize = UART3_RECIEVE_LENDTH;				 
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;		
    
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;		
    
    	DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;	
    	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    
    	DMA_Init(DMA1_Channel3, &DMA_InitStructure);	
    	DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE);	
    	DMA_Cmd(DMA1_Channel3, ENABLE);
    
    	USART_DMACmd(USART3, USART_DMAReq_Rx, ENABLE); 
    }
    

    串口3发送函数

    void UART3_Put_Char(unsigned char DataToSend)
    {
    	USART_SendData(USART3, DataToSend);
    
      	while(USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET){}
    }
    
    void UART3_Send_Buf(unsigned char *buf,unsigned char len)
    {
    	unsigned char i ;
    	for(i=0;i<len;i++,buf++)
    	{
    		UART3_Put_Char(*buf);
    	}
    }
    

    串口3 接收函数

    void DMA1_Channel3_IRQHandler(void)
    {
    		uint16_t length;
       if(DMA_GetITStatus(DMA1_IT_TC3)==SET) 
       { 
    		 DMA_Cmd(DMA1_Channel3, DISABLE); //¹Ø±ÕDMA£¬·ÀÖ¹´¦ÀíÆÚ¼äÓÐÊý¾Ý  
    		 length = UART3_RECIEVE_LENDTH - DMA_GetCurrDataCounter(DMA1_Channel3);  
    		 if(length != 0)
    		 {
    		 }			
    		DMA_SetCurrDataCounter(DMA1_Channel3, UART3_RECIEVE_LENDTH);  
    		DMA_Cmd(DMA1_Channel3, ENABLE);     	
    		DMA_ClearITPendingBit(DMA1_IT_TC3); 
       }
    }
    
    • 测试这个函数需不需要
      可以不要
    
    //------------------------------------------------------
    void USART3_IRQHandler(void)
    {
        uint8_t data;
    	uint16_t length,check;
    	
      if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)//¿ÕÏÐÖжÏ
      {		
    		DMA_Cmd(DMA1_Channel3, DISABLE); 
    		data = data;
    		data = USART3->SR;  
    		data = USART3->DR;  
    		length = UART3_RECIEVE_LENDTH - DMA_GetCurrDataCounter(DMA1_Channel3);  
     
    		if(length != 0)
    		{
    											
    		}		
    		DMA_SetCurrDataCounter(DMA1_Channel3, UART3_RECIEVE_LENDTH);  
    		DMA_Cmd(DMA1_Channel3, ENABLE);     //´ò¿ªDMA,  			
    		DMA_ClearITPendingBit(DMA1_IT_TC3); 		
    	}
    }
    

    加入打印函数 重定向至串口1

    记住就行, 直接就能用

    
    #if 1// 串口1打印
    #pragma import(__use_no_semihosting)                        
    struct __FILE 
    { 
    	int handle; 
    }; 
    
    FILE __stdout;        
    _sys_exit(int x) 
    { 
    	x = x; 
    } 
    int fputc(int ch, FILE *f)
    {      
    	while((USART1->SR&0X40)==0);//Ñ­»··¢ËÍ,Ö±µ½·¢ËÍÍê±Ï   
        USART1->DR = (u8) ch;      
    	return ch;
    }
    #endif 
    

    需要打开? 实际测试 不打开也没事
    在这里插入图片描述

    传感器数据解析

    传感器发送的数据协议

    协议
    在这里插入图片描述
    在这里插入图片描述

    以解析欧拉角x为例

    // ´«¸ÐÆ÷½â¶ÁµÄÅ·À­½Ç
    typedef struct 
    {
      float x;
    	float y;
    	float z;	
    }Euler_T;
    
    typedef struct 
    {
      uint8_t	x[4];
      uint8_t	y[4];
      uint8_t	z[4];
    }Euler_32bit_T;
    
                if     (USART3_DMA_Recieve_Buf[0]== 0x3A&&USART3_DMA_Recieve_Buf[1]== 0x01&&USART3_DMA_Recieve_Buf[2]== 0x00&&
    					USART3_DMA_Recieve_Buf[3]== 0x09&&USART3_DMA_Recieve_Buf[4]== 0x00&&USART3_DMA_Recieve_Buf[5]== 0x50&&
    					USART3_DMA_Recieve_Buf[6]== 0x00 )			
    			{		
    	              // 传感器32位数据 低位在前
    				Euler_32bit.x[0]  =   USART3_DMA_Recieve_Buf[63];
    			    Euler_32bit.x[1]  =   USART3_DMA_Recieve_Buf[64];
                    Euler_32bit.x[2]  =   USART3_DMA_Recieve_Buf[65];
                    Euler_32bit.x[3]  =   USART3_DMA_Recieve_Buf[66];
    				
    			    Euler.x = *((float*)Euler_32bit.x);	
    			}						
    

    手册上写了 数据是低位在前的
    在这里插入图片描述
    但是在通过 指针然后强制转换时 将数据 改成高位在前后不对了

    • 可能指针强制转换就是低位在前?也可能数据发的就是高位在前
    • 反正换了下顺序对了

    Euler.x = ((float)Euler_32bit.x)

    打印测试

     printf("Euler.x = %f \r\n ",		Euler.x/DEG2RAD);
    

    在这里插入图片描述
    按轴移动后没有问题

    把其它变量也解算出来

    和以解析欧拉角x 一样,解析了 欧拉角y 欧拉角z
    其中x和y 静态飘移 较小 z轴飘移很大 向一方向一直飘 可能是没有 校准的原因

    //// 传感器线加速度数据
    typedef struct 
    {
      float x;
    	float y;
    	float z;	
    }LinearAcceleration_T;
    
    typedef struct 
    {
      uint8_t	x[4];
      uint8_t	y[4];
      uint8_t	z[4];
    }LinearAcceleration_32bit_T;;
    
    		 LinearAcceleration_32bit.x[0] = USART3_DMA_Recieve_Buf[75];
    		 LinearAcceleration_32bit.x[1] = USART3_DMA_Recieve_Buf[76];
    		 LinearAcceleration_32bit.x[2] = USART3_DMA_Recieve_Buf[77];
    		 LinearAcceleration_32bit.x[3] = USART3_DMA_Recieve_Buf[78];
    					
    		 LinearAcceleration_32bit.y[0] = USART3_DMA_Recieve_Buf[79];
    		 LinearAcceleration_32bit.y[1] = USART3_DMA_Recieve_Buf[80];
    	     LinearAcceleration_32bit.y[2] = USART3_DMA_Recieve_Buf[81];
    		 LinearAcceleration_32bit.y[3] = USART3_DMA_Recieve_Buf[82];					
    					
    		 LinearAcceleration_32bit.z[0] = USART3_DMA_Recieve_Buf[83];
    		 LinearAcceleration_32bit.z[1] = USART3_DMA_Recieve_Buf[84];
    		 LinearAcceleration_32bit.z[2] = USART3_DMA_Recieve_Buf[85];
    		 LinearAcceleration_32bit.z[3] = USART3_DMA_Recieve_Buf[86];
              
              LinearAcceleration.x= *((float*)LinearAcceleration_32bit.x);
              LinearAcceleration.y= *((float*)LinearAcceleration_32bit.y);
              LinearAcceleration.z= *((float*)LinearAcceleration_32bit.z);
    

    打印加速度数据不能有延迟
    在这里插入图片描述

    做传感器碰撞检测

    简答做就根据 欧拉角和加速度判断
    变化大于阈值时 则 串口1发送当前值

    void CollisionCheck(float LinearAccelerationThreshold, float EulerThreshold)
    {  
       
       if (fabs(Euler_Last.x- Euler.x)>LinearAccelerationThreshold  &&fabs(Euler_Last.y- Euler.y)>LinearAccelerationThreshold&&fabs(Euler_Last.z- Euler.z)>LinearAccelerationThreshold 
    		   &&fabs(LinearAcceleration_Last.x- LinearAcceleration.x)>EulerThreshold 
    	     &&fabs(LinearAcceleration_Last.x- LinearAcceleration.x)>EulerThreshold
         	                                                                )
    	 {
    	      Euler_Last.x  =  Euler.x ;
    		    Euler_Last.y  =  Euler.y ;
    		    Euler_Last.z  =  Euler.z ;
    	 
    		    LinearAcceleration_Last.x = LinearAcceleration.x;
    		    LinearAcceleration_Last.y = LinearAcceleration.y;
    		    LinearAcceleration_Last.z = LinearAcceleration.z;
    		 
    		 
    		 
    		    UART1_ALARM_DATA[0] = 0x01;
    		    UART1_ALARM_DATA[1] = LinearAcceleration_32bit.x[0];
    		    UART1_ALARM_DATA[2] = LinearAcceleration_32bit.x[1];		 
    		    UART1_ALARM_DATA[3] = LinearAcceleration_32bit.x[2];		 
    		    UART1_ALARM_DATA[4] = LinearAcceleration_32bit.x[3];
    		 
    		    UART1_ALARM_DATA[5] = LinearAcceleration_32bit.y[0];
    		    UART1_ALARM_DATA[6] = LinearAcceleration_32bit.y[1];
    		    UART1_ALARM_DATA[7] = LinearAcceleration_32bit.y[2];
    		    UART1_ALARM_DATA[8] = LinearAcceleration_32bit.y[3];
    		 
    		    UART1_ALARM_DATA[9] =  Euler_32bit.x[0];
    		    UART1_ALARM_DATA[10] = Euler_32bit.x[1];
    		    UART1_ALARM_DATA[11] = Euler_32bit.x[2];				
    		    UART1_ALARM_DATA[12] = Euler_32bit.x[3];				
    		    UART1_ALARM_DATA[13] = Euler_32bit.y[0];				
    		    UART1_ALARM_DATA[14] = Euler_32bit.y[1];				
    		    UART1_ALARM_DATA[15] = Euler_32bit.y[2];				
    		    UART1_ALARM_DATA[16] = Euler_32bit.y[3];
    		    UART1_ALARM_DATA[17] = Euler_32bit.z[0];				
    		    UART1_ALARM_DATA[18] = Euler_32bit.z[1];				
    		    UART1_ALARM_DATA[19] = Euler_32bit.z[2];				
    		    UART1_ALARM_DATA[20] = Euler_32bit.z[3];
    		    
    		    UART1_Send_Buf(UART1_ALARM_DATA,21);
    		 
    		 
    	 }
    
    
    }
    
    展开全文
  • 串口驱动开发学习

    2017-01-04 22:16:42
    串口过滤驱动learning ----------------------------------------------------------------------------------...先看一张图,是我按照内核安全与驱动开发>内容编写编译的,然后用visualddk在win_7_x86 (目标机)d

    串口过滤驱动learning

    ----------------------------------------------------------------------------------Cherry-2016-5-31-----------------------

     

    先看一张图,是我按照<windows内核安全与驱动开发>内容编写编译的,然后用visualddkwin_7_x86 (目标机)debug显示出来的,首先在线 download 一个windows 7超级终端软件,然后新建终端,com1,输入数据传输,最后就在debugview 中显示出来如图一连串的1咯。

    Windows安全模型链接:

    http://blog.csdn.net/dpsying/article/details/47136191

     

     


    代码分为以下几层:

    DriverEntry()为主函数

    Dispatch()函数用来截取I/O管理器发来的irp,并做出相关处理。

    Unload()函数用来动态卸载,主要是为了清理分配的内存,因为内核开发的程序如果不手动清理,它会永久占用。

    AttachAllCom()用来绑定所有已知的com(串口),

    其中有AttachDevice()OpenComs()。利用一个循 环,OpenAllComs,然后将所有com attach to virtual device(IoCreateDevice).

    接下来分析代码:

    #define CCP_MAX_COM_ID 32   //表示最大的串口数

    Driver Entry:

    NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)

    {

    unsigned i;

    DbgPrint("Driver Entry");

    for(i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)

    {

    driver->MajorFunction[i] = ccpDispatch;

    }

    driver->DriverUnload = ccpUnload;

    ccpAttachAllComs(driver);

    return STATUS_SUCCESS;

    }

    注:那个DbgPrint不能写在unsigned i;的前面,会报错,很迷惑。

     

    其中,只在主函数调用一个函数:ccpAttachAllComs();链接所有串口。没错,是手动调用,它并不像其它比如ccpUnload函数那样,由driver的DriverUnload调用(这里说调用,不如说是将ccpUnload函数的指针赋给了driver->DriverUnload,进一步在driver结束时完成driver的卸载)。

    当然,ccpDispatch()也是同理,将driver 的所有MajorFunction交给ccpDispatch处理。然后可以在ccpDispatch中进行一系列分配操作。

     

     

    ccpDispatch:

    NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp)

    {

    PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);

    NTSTATUS status;

    ULONG i,j;

    for(i=0;i<CCP_MAX_COM_ID;i++){

    if(s_fltobj[i] == device){

    if(irpsp->MajorFunction == IRP_MJ_POWER){

    PoStartNextPowerIrp(irp);

    IoSkipCurrentIrpStackLocation(irp);

    return PoCallDriver(s_nextobj[i],irp);

    }

    if(irpsp->MajorFunction == IRP_MJ_WRITE){

    ULONG len = irpsp->Parameters.Write.Length;

    PUCHAR buf = NULL;

    if(irp->MdlAddress != NULL)

    buf = (PUCHAR)MmGetSystemAddressaForMdlSafe(irp->MdlAddress,

                      NormalPagePriority);

    else buf = (PUCHAR)irp->UserBuffer;

     

    if(buf == NULLbuf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;

    for(j=0;j<len;++jDbgPrint("comcap: Send Data: %2x\r\n",buf[j]);

    }

    IoSkipCurrentIrpStackLocation(irp);

    return IoCallDriver(s_nextobj[i],irp);

    }

    }

    irp->IoStatus.Information = 0;

    irp->IoStatus.Status = STATUS_INVALID_PARAMETER;

    IoCompleteRequest(irp,IO_NO_INCREMENT);

    return STATUS_SUCCESS;

    }

     

     

    有点小长,也有好多函数,好吧从头分析。

    此函数传入设备指针(PDEVICE_OBJECT和从I/O管理器获得的Irp指针(PIRP)

    PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);

    //Irp栈中获得当前传入的irp位置

    因为并不知道irp是传给哪个物理串口的,所以需要一个for循环来一个一个判断,如果目标设备和设备栈中的吻合,也就是相等,就进行下去。这儿有俩irp操作:IRP_MJ_POWER,IRP_MJ_WRITE,一个是电源操作,一个是写操作。(在这儿就可以拦截住对串口的一系列操作,比如想要给目标usb设备传入本机数据,这里就可以进行过滤操作)。

    假如是电源操作,PoStartNextPowerIrp(irp);文档里这样解释:

    Although power IRPs are completed only once, typically by the bus driver for a device, each driver in the device stack must call PoStartNextPowerIrp as the IRP travels down or back up the stack. Even if a driver fails the IRP, the driver must nevertheless call PoStartNextPowerIrp to signal the power manager that it is ready to handle another power IRP.

    表示提示电源管理去准备去处理另一个电源IRP,也就是说,略过当前powerirp。IoSkipCurrentIrpStackLocation(irp);文档解释:

    The IoSkipCurrentIrpStackLocation macro modifies the system's IO_STACK_LOCATION array pointer, so that when the current driver calls the next-lower driver, that driver receives the same IO_STACK_LOCATION structure that the current driver received.

    When your driver sends an IRP to the next-lower driver, your driver can call IoSkipCurrentIrpStackLocation if you do not intend to provide an IoCompletion routine (the address of which is stored in the driver's IO_STACK_LOCATION structure). If you call IoSkipCurrentIrpStackLocation before calling IoCallDriver, the next-lower driver receives the same IO_STACK_LOCATION that your driver received.

    最后返回PoCallDriver(s_nextobj[i],irp);说明已经被处理。

    Beginning with Windows Vista, drivers should call IoCallDriver, not PoCallDriver to pass a power IRP to the next-lower driver. However, on Windows Server 2003, Windows XP, and Windows 2000, drivers must call PoCallDriver, not IoCallDriver to pass a power IRP to the next-lower driver. On Windows Server 2003, Windows XP, an Windows 2000, drivers must also call PoStartNextPowerIrp before calling PoCallDriver.

    (补一句,这个代码是基于win xp 的,所以会出现这种老掉牙的情况)

    这里的意思是,只要是电源操作,一律放过,不做详细处理。

    假如是IRP_MJ_WRITE

    注:执行条件为,传输方式必须是DIRECT I/O.然后它的MajorFunction Code才会取到IRP_MJ_WRITE/IRP_MJ_READ...

    MdlAddress (可以说是用户缓冲区)

    Pointer to an MDL describing a user buffer, if the driver is using direct I/O, and the IRP major function code is one of the following:

     

    Every device driver that transfers data from the system to its device must handle write requests in a DispatchWrite or DispatchReadWrite routine, as must any higher-level driver layered over such a device driver.

    Possibly, a user-mode application or Win32 component with a handle for the file object representing the target device object has requested a data transfer to the device.

    Possibly, a higher-level driver has created and set up the write IRP.

     

     

     

    先读取PIO_STACK_LOCATION的参数写的长度

    ULONG len = irpsp->Parameters.Write.Length;

    struct {
                ULONG  Length;
                ULONG POINTER_ALIGNMENT  Key;
                LARGE_INTEGER  ByteOffset;
            } Write;

    文档里这样描述Parameters.Write的结构。

    The driver's I/O stack location in the IRP indicates how many bytes to transfer at Parameters.Write.Length

    然后获得缓冲区

     

     

    如果Irp的MdlAddress不为空:

    IRP_MJ_READ Irp的读指令

    The MDL describes an empty buffer that the device or driver fills in.

    IRP_MJ_WRITE Irp的写指令

    The MDL describes a buffer that contains data for the device or driver.

    IRP_MJ_DEVICE_CONTROL or IRP_MJ_INTERNAL_DEVICE_CONTROL (设备控制指令)

    If the IOCTL code specifies the METHOD_IN_DIRECT transfer type, the MDL describes a buffer that contains data for the device or driver.

    If the IOCTL code specifies the METHOD_OUT_DIRECT transfer type, the MDL describes an empty buffer that the device or driver fills in.

    For more information about the buffers that are associated with METHOD_IN_DIRECT and METHOD_OUT_DIRECT transfer types in IOCTL codes, see Buffer Descriptions for I/O Control Codes.

    If the driver is not using direct I/O, this pointer is NULL.

    则通过内存函数获得系统非分页内存的虚拟地址,传入参数分别为:irp指向的一个相应的虚拟地址映射的缓冲区普通优先级的虚拟内存页。

    The MmGetSystemAddressForMdlSafe macro returns a nonpaged system-space virtual address for the buffer that the specified MDL describes.

    PVOID 
      MmGetSystemAddressForMdlSafe(
        __in PMDL  Mdl,
        __in MM_PAGE_PRIORITY  Priority
        );

    Mdl (说白了,就是一虚拟空间,将分散的内存块连接起来,构成MDL链表

    Pointer to a buffer whose corresponding base virtual address is to be mapped.

    Priority 

    Specifies an MM_PAGE_PRIORITY value that indicates the importance of success under low available PTE conditions. Possible values include LowPagePriority, NormalPagePriority, and HighPagePriority.

    · LowPagePriority indicates that the mapping request can fail if the system is fairly low on resources. An example of this situation is a noncritical network connection where the driver can handle the mapping failure.

    · NormalPagePriority indicates that the mapping request can fail if the system is very low on resources. An example of this situation is a noncritical local file system request.

    · HighPagePriority indicates that the mapping request must not fail unless the system is completely out of resources. An example of this situation is the paging file path in a driver.

     

    MmGetSystemAddressForMdlSafe returns the base system-space virtual address that maps the physical pages that the specified MDL describes. If the pages are not already mapped to system address space and the attempt to map them fails, NULL is returned.

    如果Irp的MdlAddress为空:

    buf = (PUCHAR)irp->UserBuffer;

     

     

    UserBuffer 

    Contains the address of an output buffer if the major function code in the I/O stack location is IRP_MJ_DEVICE_CONTROL or IRP_MJ_INTERNAL_DEVICE_CONTROL and the I/O control code was defined with METHOD_NEITHER.

    buf传入irp的用户缓冲区。

    最后判断传入的缓冲区是否为空,如果为空,则传入系统缓冲区。

    buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;

     union {
        struct _IRP  *MasterIrp;
        .
        .
        PVOID  SystemBuffer;
      } AssociatedIrp;

     

    最后打印内容:(打印的是得到缓冲区内的字符)

    for(j=0;j<len;++j)

    {

    DbgPrint("comcap: Send Data: %2x\r\n",

    buf[j]);

    }

     

    irp->IoStatus.Information = 0;    

    //IoStatus 

    Contains the IO_STATUS_BLOCK structure in which a driver stores status and information before calling IoCompleteRequest.

    Information 

    This is set to a request-dependent value. For example, on successful completion of a transfer request, this is set to the number of bytes transferred. If a transfer request is completed with another STATUS_XXX, this member is set to zero.

     

    irp->IoStatus.Status = STATUS_INVALID_PARAMETER;

    //A parameter in the VRP is invalid.

     

    IoCompleteRequest(irp,IO_NO_INCREMENT);

    The IoCompleteRequest routine indicates that the caller has completed all processing for a given I/O request and is returning the given IRP to the I/O manager.

    VOID 
      IoCompleteRequest(
        IN PIRP  Irp,
        IN CCHAR  PriorityBoost
        );

    When a driver has finished all processing for a given IRP, it calls IoCompleteRequest.

    ccpAttachAllComs():

    传入PDRIVER_OBJECT driver 对象,然后设置一个PDEVICE_OBJECT来存储打开的Com对象。

    用一个for循环,先打开每一个Com,然后绑定在生成的Device Object数组内对应的对象(ccpAttachDevice

    for(i = 0;i<CCP_MAX_COM_ID;i++){

    com_ob = ccpOpenCom(i,&status);

    if(com_ob == NULL)

    continue;

    ccpAttachDevice(driver,com_ob,&s_fltobj[i],&s_nextobj[i]);

    }

    接下来,一一分析:

    ccpOpenCom()传入 i个串口打开状态的引用

    ccpAttachDevice()传入 驱动对象打开的串口设备生成的过滤设备的引用最终绑定后的设备的引用

    ccpOpenCom():

    PDEVICE_OBJECT ccpOpenCom(ULONG id,NTSTATUS *status){

    UNICODE_STRING name_str;

    static WCHAR name[32] = { 0 };

    PFILE_OBJECT fileobj = NULL;

    PDEVICE_OBJECT devobj = NULL;

    memset(name,0,sizeof(WCHAR)*32);

    RtlStringCchPrintfW(name,32,L"\\Device\\Serial%d",id);

    RtlInitUnicodeString(&name_str,name);

    //打开设备对象,并返回打开状态

    *status = IoGetDeviceObjectPointer(&name_str, FILE_ALL_ACCESS, &fileobj, &devobj);

    if (*status == STATUS_SUCCESS)

    ObDereferenceObject(fileobj);//若打开成功,则把文件对象解除引用(利用设备名获得的设备对象的指针devobj,同时也生成fileobj)

    return devobj;

    }

     

    其中,

     

    memset(name,0,sizeof(WCHAR)*32);

    RtlStringCchPrintfW(name,32,L"\\Device\\Serial%d",id);

    RtlInitUnicodeString(&name_str,name);

    根据传入的id来转换成表示串口的名字,并转换成Unicode_string

    Memset():  功能:将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值,块的大小由第三个参数指定,这个函数通常 为新申请的内存做初始化工作.

    memset() 函数常用于内存空间初始化。如:

    char str[100];

    memset(str,0,100);

    ccpAttachDevice():

    NTSTATUS

    ccpAttachDevice(PDRIVER_OBJECT driver, PDEVICE_OBJECT oldobj,

    PDEVICE_OBJECT *fltobj, PDEVICE_OBJECT *next){

    NTSTATUS status;

    PDEVICE_OBJECT topdev = NULL;

    status = IoCreateDevice(driver,//生成过滤设备

    0,

    NULL,

    oldobj->DeviceType,

    0,

    FALSE,

    fltobj);

    if (status != STATUS_SUCCESS)

    return status;

    if(oldobj->Flags & DO_BUFFERED_IO)//复制标志位 缓冲IO

    (*fltobj)->Flags |= DO_BUFFERED_IO;

    if(oldobj->Flags & DO_DIRECT_IO)

    (*fltobj)->Flags |= DO_DIRECT_IO;

    if(oldobj->Flags & DO_BUFFERED_IO)

    (*fltobj)->Flags |= DO_BUFFERED_IO;

    if(oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)

    (*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;

    (*fltobj)->Flags |=  DO_POWER_PAGABLE;

    //将fltobj附接到oldobj(打开的端口设备)上

    topdev = IoAttachDeviceToDeviceStack(*fltobj,oldobj);

    if (topdev == NULL){

    IoDeleteDevice(*fltobj);

    *fltobj = NULL;

    status = STATUS_UNSUCCESSFUL;

    return status;

    }

    *next = topdev;

    (*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;

    return STATUS_SUCCESS;

    }

     

    关于以上代码中的复制标志位,我参考了下文档:

    Propagating the DO_BUFFERED_IO and DO_DIRECT_IO Flags

    After attaching a filter device object to a file system or volume, always be sure to set or clear the DO_BUFFERED_IO and DO_DIRECT_IO flags as needed so that they match the values of the next-lower device object on the driver stack. (For more information about these flags, see Methods for Accessing Data Buffers.) In the FileSpy sample, this is done as follows:

    if (FlagOn( DeviceObject->Flags, DO_BUFFERED_IO )) {
        SetFlag( filespyDeviceObject->Flags, DO_BUFFERED_IO );
    }
    if (FlagOn( DeviceObject->Flags, DO_DIRECT_IO )) {
        SetFlag( filespyDeviceObject->Flags, DO_DIRECT_IO );
    }

    In the above code snippet, DeviceObject is a pointer to the device object to which the filter device object has just been attached; filespyDeviceObject is a pointer to the filter device object itself.

    关于DO_BUFFERED_IODO_DIRECT_IO

    DO_DIRECT_IO还是DO_BUFFERED_IO?这两种方式有什么区别?分别对应什么情况使用?
    答:
    DO_DIRECT_IO 常用于传输大块的讲求速度的数据。DO_BUFFERED_IO常用于传输小块的,慢速的数据。DO_DIRECT_IO的效率较高(只需内存锁定即可),代价是牺牲物理内存。DO_BUFFERED_IO的内存效率高,但速度差(它要分配内存,复制数据)。造成这种区别的主要原因还是因为处理器的 Ring 0Ring4的穿越造成的(微软只用了两层)。你无法两全其美,只能取其一(当然有时候能两全其美,后面会提到)。
    DO_DIRECT_IO主要用于使用DMA的底层驱动。Mass Storage也用。
    DO_BUFFERED_IO主要用于HID(包括Mouse,Keyboard), 一些Video, 还有串口,并口什么的。
    不过我看绝大多数设计者都挺自私的,上来就用DIRECT IO,

     

    用它们的作用:

    CPU进入内核空间后,先在内核空间申请合适大小的内存缓冲区然后把用户空间的数据复制到这里。因为所有的进程以及中断等都共享相同的内核空间页表,所以能保证CPU始终访问到正确的数据。这就是DO_BUFFERED_IO模式。
    二,临时为用户空间的缓冲区增添一个系统空间映射,这样就使同一组物理页面有了两个虚拟地址空间,其一就在原来的用户空间虚拟地址,其二则在系统空间的虚拟地址。这样即使发生进程切换,或者在中断函数、DPC函数里面访问都可以通过系统空间的虚拟地址来访问用户空间的缓冲区,直到操作完成返回用户空间时才撤销系统空间。这就是DO_DIRECT_IO模式。

     

     

    IoAttachDeviceToDeviceStack()绑定生成的过滤设备和传入的物理串口设备。根据设备对象的指针,而不是IoAttachDevice()的根据串口设备名来绑定两者,并返回相应绑定后的设备对象。

     

    之后如果绑定成功,则将最终绑定的设备赋值给s_next_obj[i],最终在ccpAttachAllComs()完成绑定。

     

    Unload():

    传入驱动对象,定义延迟时间...

    #define  DELAY_ONE_MICROSECOND  (-10)

    #define  DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)

    #define  DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)

    void ccpUnload(PDRIVER_OBJECT drv)

    {

    ULONG i;

    LARGE_INTEGER interval;         //定义大整型数据 interval

    for(i=0;i<CCP_MAX_COM_ID;i++){  //一个一个解除Attach

    if(s_nextobj[i] != NULL)IoDetachDevice(s_nextobj[i]);

    }

    interval.QuadPart = (5*1000 * DELAY_ONE_MILLISECOND);

    KeDelayExecutionThread(KernelMode,FALSE,&interval);//kernelmode表示内核模式,睡眠5s

    for(i=0;i<CCP_MAX_COM_ID;i++){ //删除所有解除绑定的过滤设备

    if(s_fltobj[i] != NULLIoDeleteDevice(s_fltobj[i]);

    }

    }

     

    typedef union _LARGE_INTEGER {

    struct {

    DWORD LowPart;

    LONG HighPart;

    };

    struct {

    DWORD LowPart;

    LONG HighPart;

    } u;

    LONGLONG QuadPart;

    } LARGE_INTEGER, *PLARGE_INTEGER;

     

    如果你有编译器直接支持64位整数可以直接使用QuadPart64位),否则分别对LowPart(32)HighPart32位)存取,HighPart的最高位为符号位。
      
    表示数的范围:--3689348814741910324+4611686018427387903
      
    LARGE_INTEGER的值等4000000000,在内存中的布局:
      
    00   28   6B   EE       00   00   00   00       
    (低字节          (高字节     

    以上是LARGE_INTEGERstruct和定义。(准确说应该是个union

    用法:

    LARGE_INTEGER litmp;

    LONGLONG qt1,qt2;

    double dft,dff,dfm;

    //获得时钟频率

    QueryPerformanceFrequency(&litmp);//获得时钟频率

    dff=(double)litmp.QuadPart;

    //获得初始值

    QueryPerformanceCounter(&litmp);

    qt1=litmp.QuadPart;

    //下面一些耗时的操作

    Sleep(100);

    //获得终止值

    QueryPerformanceCounter(&litmp);

    qt2=litmp.QuadPart;

    //获得对应的时间值,转到毫秒单位上

    dfm=(double)(qt2-qt1);

    dft=dfm/dff;

    printf("用时: %.3f 毫秒\n", dft*1000.0);

     

    上面的测量在工程中非常使用,基本可以在windows平台下测试一些使用时间的情况,比如用套接字发送一个大数据块、显示一个位图等等!对每个应用的程序段时间消耗都清楚,才能写出高质量的程序!

     

    这是MSDN的解释。

     

    UserBufferMdl在不同METHOD下的区别和利用:

    "缓冲"方法(METHOD_BUFFERED)
    备注:在下面的讨论中,"输入"表示数据从用户模式的应用程序到驱动程序,"输出"表示数据从驱动程序到应用程序。

    对于读取请求,I/O管理器分配一个与用户模式的缓冲区大小相同的系统缓冲区。IRP 中的 SystemBuffer字段包含系统地址。 UserBuffer段包含初始的用户缓冲区地址。当完成请求时,I/O管理器将驱动程序已经提供的数据从系统缓冲区复制到用户缓冲区。对于写入请 求,会分配一个系统缓冲区并将 SystemBuffer 设置为地址。用户缓冲区的内容会被复制到系统缓冲区,但是不设置 UserBuffer。对于IOCTL请求,会分配一个容量大小足以包含输入缓冲区或输出缓冲区的系统缓冲区,并将SystemBuffer设置为分配的缓冲区地址。输入 缓冲区中的数据复制到系统缓冲区。UserBuffer字段设置为用户模式输出缓冲区地址。内核模式驱动程序应当只使用系统缓冲区,且不应使用UserBuffer中存储的地址。

    对于IOCTL,驱动程序应当从系统缓冲区获取输入并将输出写入到系统缓冲区。当完成请求时,I/O系统将输出数据从系统缓冲区复制到用户缓冲区。

    "直接"方法(METHOD_IN/OUT_DIRECT)
    对于读取和写入请求,用户模式缓冲区会被锁定,并且会创建一个内存描述符列表(MDL)。MDL 地址会存储在IRP的MdlAddress字段中。SystemBuffer和UserBuffer均没有任何含义。但是,驱动程序不应当更改这些字段的值。
    对于 IOCTL 请求,如果在 METHOD_IN_DIRECT 和 METHOD_OUT_DIRECT 中同时有一个输出缓冲区,则分配一个系统 缓冲区(SystemBuffer 又有了地址)并将输入数据复制到其中。如果有一个输出缓冲区,且它被锁定,则会创建 MDL 并设 置 MdlAddress。UserBuffer 字段没有任何含义。

    "两者都不"方法(METHOD_NEITHER)
    对于读取和写入请求,UserBuffer 字段被设置为指向初始的用户缓冲区。不执行任何其他操作。 SystemAddress 和 MdlAddress 没有任何含义。对于 IOCTL 请求,I/O 管理器将 UserBuffer 设置为初始的 用户输出缓冲区,而且,它将当前 I/O 栈位置的 Parameters.DeviceIoControl.Type3InputBuffer 设置为 用户输入缓冲区。利用该 I/O 方法,由驱动程序来确定如何处理缓冲区:分配系统缓冲区或创建 MDL。

    通常,驱动程序在访问用户数据时不应当将 UserBuffer 字段用作地址,即使当用户缓冲区被锁定时也是如此。这是由于在调用驱动程序时,在系统中 可能看不到调用用户的地址空间。(对于该规则的一个例外是,在最高层驱动程序将 IRP 向下传递到较低层的驱动程序之前,它可能需要使 用 UserBuffer 来复制数据。)如果使用"直接"或"两者都不"方法,在创建 MDL 之后,驱动程序可以使 用 MmGetSystemAddressForMdl 函数来获取有效的系统地址以访问用户缓冲区。

     

    在驱动层,依传输类型的不同,输入缓冲区的位置亦不同,见下表:
    METHOD_IN_DIRECT                irp->AssociatedIrp.Sy

                                                               stemBuffer
    METHOD_OUT_DIRECT                irp->AssociatedIrp.Syst

                                                                 emBuffer
    METHOD_BUFFERED                irp->AssociatedIrp.Syst

                                                              emBuffer
    METHOD_NEITHER                irpStack->Parameters.De

                                           viceIoControl.Type3InputBuffer
    在驱动层,依传输类型的不同,输出缓冲区的位置亦不同,见下表:
    METHOD_IN_DIRECT                 irp->MdlAddress
    METHOD_OUT_DIRECT                   irp->MdlAddress
    METHOD_BUFFERED                 irp->AssociatedIrp.S

                                                          ystemBuffer
    METHOD_NEITHER                  irp->UserBuffer

     

     

    驱动开发之 设备读写方式:缓冲区方式

    1.

    设备对象一共有三种读写方式:缓冲区方式读写(Buffered方式);直接方式读写(Direct方式);Neither方式。这三种方式的Flags分别对应DO_BUFFERED_IO,DO_DIRECT_IO,0

    buffered方式中,I/O管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区。而你的驱动程序将使用这个系统缓冲区工作。I/O管理器负责在系统缓冲区和用户模式缓冲区之间复制数据。 
    direct方式中,I/O管理器锁定了包含用户模式缓冲区的物理内存页,并创建一个称为MDL(内存描述符表)的辅助数据结构来描述锁定页。因此你的驱动程序将使用MDL工作。 
    neither方式中,I/O管理器仅简单地把用户模式的虚拟地址传递给你。而使用用户模式地址的驱动程序应十分小心。

    2.

    下面介绍缓冲区方式读写。其优点是比较简单的解决了将用户地址传入驱动的问题,缺点是需要用户模式和内核模式之间数据复制,可想而知,运行效率会受到影响。适合少量内存操作时使用的一种方法。

    创建好设备IoCreateDevice后,需要设置DO_BUFFERED_IO,  pDevObj->Flags |= DO_BUFFERED_IO.

    现在以readfile为例,首先应用程序中需要提供一段缓冲区并把缓冲区大小作为参数传入,例如

    UCHAR OutputBuffer[10];
    DWORD RetLen = 0;
    readfile(hDevice,OutputBuffer,sizeof(OutputBuffer),&RetLen,NULL);

    OutputBuffer是提供的输出缓冲区,是用户模式的内存地址,操作系统将此缓冲区的数据复制到内核模式下的地址中,sizeof(OutputBuffer)是缓冲区的大小,而RetLen是真正的输出的字节数。

     

    那么内核模式怎么得到此内核模式地址呢?怎么得到writefile或readfile的字节数呢?答案在下面。

    此内核模式下的地址可以通过此readfile创建的IRP的AssociatedIrp.SystemBuffer得到。

    假如请求的IRP为PIRP pIrp(一般是派遣函数的参数),那么UCHAR* OutputBuffer= (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;

    readfile请求的字节数为IO_STACK_LOCATION中的Parameters.Read.Length,writefilew为IO_STACK_LOCATION中的Parameters.Write.Length

    //得到当前堆栈
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
    //得到readfile缓冲区大小
    ULONG cbread = stack->Parameters..Read.Length;
    //得到writefile缓冲区大小
    ULONG cbwrite = stack->Parameters.Write.Length;

     

    得到了内核模式下的缓冲区地址了就可以对此缓冲区操作了。比如:

    UCHAR* OutputBuffer= (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;

    ULONG cbread = stack->Parameters..Read.Length;

    memcpy(OutputBuffer,0xBB,cbread);

    这样用户模式下的缓冲区内得到的数据是0xBB。

     

    另外还要设置实际操作的字节数,pIrp->IoStatus.Information = cbread;(实际操作的字节数不一定要设置为缓冲区的大小,但也不应该大于缓冲区的大小)

    那么用户模式下readfile的RetLen被设置为cbread。

     

    下面是IRP_MJ_READ的派遣函数:

    NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)

    {

    KdPrint(("Enter DispatchRead\n"));

     

    //对一般IRP的简单操作,后面会介绍对IRP更复杂的操作

    NTSTATUS status = STATUS_SUCCESS;

     

    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);

    ULONG ulReadLength = stack->Parameters.Read.Length;

    // 完成IRP

    //设置IRP完成状态

    pIrp->IoStatus.Status = status;

     

    //设置IRP操作了多少字节

    pIrp->IoStatus.Information = ulReadLength;

     

    memset(pIrp->AssociatedIrp.SystemBuffer,0xAA,ulReadLength);

     

    //处理IRP

    IoCompleteRequest( pIrp, IO_NO_INCREMENT );

     

    KdPrint(("Leave DispatchRead\n"));

     

    return status;

    }


    设备读写方式:直接读取方式:http://blog.csdn.net/liyun123gx/article/details/38043849 

    Neither方式:http://blog.csdn.net/liyun123gx/article/details/38046865

     

     

    驱动开发之 设备读写方式:直接方式

    1.

    直接方式读写设备,操作系统会将用户模式下的缓冲区锁住,然后操作系统将这段缓冲区在内核模式地址再次映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。无论操作系统如何切换进程,内核模式地址都保持不变。

    创建好设备IoCreateDevice后,需要设置DO_DIRECT_IO,  pDevObj->Flags |= DO_DIRECT_IO.

    2.

    这里涉及到内存描述符表(MDL)

    MDL结构的声明如下:
    typedef struct _MDL {
      struct _MDL *Next;
      CSHORT Size;
      CSHORT MdlFlags;
      struct _EPROCESS *Process;
      PVOID MappedSystemVa;
      PVOID StartVa;               //给出了用户缓冲区的虚拟地址,第一个页地址,这个地址仅在拥有数据缓冲区的用户模式进程上下文中才有效
      ULONG ByteCount;       //是缓冲区的字节长度
      ULONG ByteOffset;       //是缓冲区起始位置在一个页帧中的偏移值,那么缓冲区的首地址是mdl->StartVa+mdl->ByteOffset
    } MDL, *PMDL;

    用图表示内存描述符表(MDL)结构为:



    由图可知用户模式的这段缓冲区在虚拟内存上是连续的,但在物理内存上可能是离散的。

    3.下面来看一些MDL相关的函数

    IoAllocateMdl

    创建MDL

    IoBuildPartialMdl

    创建一个已存在MDL的子MDL

    IoFreeMdl

    销毁MDL

    MmBuildMdlForNonPagedPool

    修改MDL以描述内核模式中一个非分页内存区域

    MmGetMdlByteCount

    取缓冲区字节大小(得到mdl->ByteCount)

    MmGetMdlByteOffset

    取缓冲区在第一个内存页中的偏移(得到mdl->ByteOffset)

    MmGetMdlVirtualAddress

    取虚拟地址((PVOID)(PCHAR)(mdl->StartVa+mdl->ByteOffset))

    MmGetSystemAddressForMdl

    创建映射到同一内存位置的内核模式虚拟地址

    MmGetSystemAddressForMdlSafe

    与MmGetSystemAddressForMdl相同,但Windows 2000首选

    MmInitializeMdl

    (再)初始化MDL以描述一个给定的虚拟缓冲区

    MmPrepareMdlForReuse

    再初始化MDL

    MmProbeAndLockPages

    地址有效性校验后锁定内存页

    MmSizeOfMdl

    取为描述一个给定的虚拟缓冲区的MDL所占用的内存大小

    MmUnlockPages

    为该MDL解锁内存页

    4.下面以readfile为例介绍直接方式读取设备 

    用户模式调用readfile:

    UCHAR OutputBuffer[10];
    DWORD RetLen = 0;
    readfile(hDevice,OutputBuffer,sizeof(OutputBuffer),&RetLen,NULL);

    内核模式得到要读取的字节数:(与以缓冲区读写方式一样)

    //得到当前堆栈
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
    //得到readfile要读取的字节数
    ULONG cbread = stack->Parameters..Read.Length;

    另外,通过IRP的pIrp->MdlAddress得到MDL数据结构,这个结构描述了被锁定的缓冲区的内存。

    下面是一个IRP_MJ_READ的派遣函数,仅供参考。

    NTSTATUS DispathRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)

    {

    KdPrint(("Enter DispathRead\n"));

     

    PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

    NTSTATUS status = STATUS_SUCCESS;

     

     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);

     

     ULONG ulReadLength = stack->Parameters.Read.Length;//得到读取的长度

    KdPrint(("ulReadLength:%d\n",ulReadLength));

     

    ULONG mdl_length = MmGetMdlByteCount(pIrp->MdlAddress);       //mdl虚拟内存的长度

    PVOID mdl_address = MmGetMdlVirtualAddress(pIrp->MdlAddress); //虚拟内存的起始地址

    ULONG mdl_offset = MmGetMdlByteOffset(pIrp->MdlAddress);      //虚拟内存首地址在第一页的偏移量

    KdPrint(("mdl_address:0X%08X\n",mdl_address));

    KdPrint(("mdl_length:%d\n",mdl_length));

    KdPrint(("mdl_offset:%d\n",mdl_offset));

     

    if (mdl_length!=ulReadLength)

    {

    //MDL的长度应该和读长度相等,否则该操作应该设为不成功

    pIrp->IoStatus.Information = 0;

    status = STATUS_UNSUCCESSFUL;

    }else

    {

    //用MmGetSystemAddressForMdlSafe得到MDL在内核模式下的映射,被映射到内核模式下的内存地址,必定在0X80000000-0XFFFFFFFF之间

    PVOID kernel_address = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);

    KdPrint(("kernel_address:0X%08X\n",kernel_address));

    memset(kernel_address,0XAA,ulReadLength);        //对内核模式下的内存地址进行操作

    pIrp->IoStatus.Information = ulReadLength;//设置实际操作字节数

    }

    pIrp->IoStatus.Status = status;

    IoCompleteRequest( pIrp, IO_NO_INCREMENT );

    KdPrint(("Leave DispatchRead\n"));

     

    return status;

    }
    驱动开发之 设备读写方式:缓冲区方式 请参考:http://blog.csdn.net/liyun123gx/article/details/38042125

    NEITHER方式参考:http://blog.csdn.net/liyun123gx/article/details/38046865

     

     


    展开全文
  • 上篇文章讲的 UART,更多的是硬件相关的知识。接下来进入正题了,串口驱动开发。 一、阅读原理图 我们用的是 UART2 串口,则接收管脚 XuRXD2 复用 GPA1_0,发送管脚 XuTXD2 复用 GPA1_1 二、S5PV210 UART...

    如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/78579074

    上篇文章讲的 UART,更多的是硬件相关的知识。接下来进入正题了,串口驱动开发。

    一、阅读原理图

    我们用的是 UART2 串口,则接收管脚 XuRXD2 复用 GPA1_0,发送管脚 XuTXD2 复用 GPA1_1

    二、S5PV210 UART

     

    (1)通用异步接收器和发送器的概述  (p-853)

    S5PV210中的通用异步接收器和发送器(UART)提供四个独立的异步和串行输入/输出(I / O)端口。所有端口都以基于中断或基于DMA的方式运行模式。 UART产生一个中断或DMA请求来传送数据到CPU和UART。
    UART支持高达3Mbps的比特率。每个UART通道包含两个FIFO来接收和传输数据:
    ch0中的256个字节,ch1中的64个字节以及ch2和ch3中的16个字节。
    UART包括可编程波特率,红外(IR)发送器/接收器,一个或两个停止位插入,5位,6位,7位或8位数据宽度和奇偶校验。每个UART包含一个波特率发生器,一个发送器,一个接收器和一个控制单元,如图1-1所示。

     

    波特率发生器使用PCLK或SCLK_UART。发送器和接收器包含FIFO和数据移。要发送的数据被写入Tx FIFO,并被复制到发送移位器。数据是由发送数据引脚(TxDn)移出。接收到的数据从接收数据引脚(RxDn)移位,从移位器复制到Rx FIFO。

     

    (2)通用异步接收器和发送器的关键特性

     

    基于DMA或基于中断的操作的RxD0,TxD0,RxD1,TxD1,RxD2,TxD2,RxD3和TxD3
    带有IrDA 1.0的UART Ch 0,1,2和3
    具有256字节FIFO的UART Ch 0,具有64字节FIFO的Ch 1,具有16字节FIFO的Ch2和3
    用于自动流量控制的UART Ch 0,1和2与nRTS0,nCTS0,nRTS1,nCTS1,nCTS2和nRTS2
    支持握手发送/接收。

     

     

     

    (3)UART描述

    以下各节将介绍UART操作,如数据传输,数据接收,中断生成,波特率生成,环回模式,红外模式和自动流量控制。

     

     

    《1》数据发送

    传输的数据帧是可编程的。 它由一个起始位,五到八个数据位,一个可选的奇偶校验组成位和由行控制寄存器(ULCONn)指定的一到两个停止位。 变送器也可以产生一个强制串行输出为逻辑0状态一个帧传输时间的中断条件。 这个块传送当前发送字之后的中断信号被完全发送。 中断信号后传输时,发送器不断向Tx FIFO发送数据(Tx保持寄存器,如果是非FIFO模式)

     

     

    《2》数据接收

    与数据发送类似,用于接收的数据帧也是可编程的。 它由一个5位的起始位组成8个数据位,一个可选的奇偶校验位以及线路控制寄存器(ULCONn)中的一到两个停止位。 收件人检测溢出错误,奇偶校验错误,帧错误和中断情况,每个错误标志都会设置一个错误标志。
        溢出错误(Overrun error)表示新数据在旧数据读取之前覆盖旧数据。
        奇偶校验错误(Parity error)表示接收器检测到意外的奇偶校验条件。
        帧错误(Frame error)表示接收到的数据没有有效的停止位。
        中断条件(Break condition)表示RxDn输入保持逻辑0状态,以进行多个帧传输时间。
    如果在3个字的时间内没有收到任何数据,则会发生接收超时情况(此时间间隔符合设置字长度位),FIFO模式下Rx FIFO不为空。

     

    后面内容有点多...  就不一一翻译了。想了解更多,自己看。

    三、UART 相关配置

    (1)配置串口管脚

    上面提到了,S5PV210 中的通用异步接收器和发送器(UART)提供四个独立的异步和串行输入/输出(I / O)端口。根据原理图可以看出,我们用的是 UART2 串口,则接收管脚 XuRXD2 复用 GPA1_0,发送管脚 XuTXD2 复用 GPA1_1

    查看GPA1寄存器(p-135)

    端口组GPA1控制寄存器有六个控制寄存器,分别是GPA1CON,GPA1DAT,GPA1PUD,GPA1DRV,GPA1CONPDN和端口组GPA1控制寄存器中的GPA1PUDPDN。

    《1》GPA1CON 寄存器  (配置寄存器) 将其配置成UART2收/发模式

    GPA1CON, R/W, Address = 0xE020_0020

    GPA1CON[1] [7:4] 0100 = UART_AUDIO_TXD
    GPA1CON[0] [3:0] 0100 = UART_AUDIO_RXD

    设置:

    GPA1CON 地址为 0xE020_0020,然后要将 GPA1CON[7:0] 进行位操作,使其变为0010 0010,即UART2收发模式。

    位操作方法,参看:C语言再学习 -- 位操作

        GPA1CON &= ~(0xff<<0);  //清空bit 0-7

        GPA1CON |= 0x22;  //设置 0010 0010  接受 发送

    《2》GPA1PUD 寄存器(上下拉电阻控制寄存器 )

    GPA1PUD, R/W, Address = 0xE020_0028

    GPA1PUD[n] [2n+1:2n] 00 = Pull-up/ down disabled

    设置:

    GPA1PUD 地址为 0xE020_0028,我们不需要上下拉,因此: 

    GPA1PUD [1:0]  00 = Pull-up/ down disabled

    GPA1PUD [3:2]  00 = Pull-up/ down disabled

    位操作方法:

        GPA1PUD &= ~(0x0f<<0);//0000 禁止上拉下拉

    (2)配置串口寄存器

    在 p-864 有一个 REGISTER MAP,可以看一下。太长了只粘贴一部分。

     

    S5PV10 UART 相关寄存器有如下几个,下面我们会一一讲解的:

    《1》UART 行控制器 ULCONn 

    UART模块中有四个UART行控制寄存器,分别是ULCON0,ULCON1,ULCON2和ULCON3

    我们用的是 UART2,所以使用 

    ULCON2, R/W, Address = 0xE290_0800

     

    设置:

    ULCON2 地址为 0xE290_0800

    数据位宽度为 8bit,停止位 1位,无校验位,正常模式。

    即设置数据格式寄存器 ULCON2 = 000 0011 = 0x03   

    位操作方法:

    ULCON2 = 0x03;

    《2》UART 模式控制寄存器 UCONn

    UART模块中有四个UART控制寄存器,分别是UCON0,UCON1,UCON2和UCON3

    我们用的是 UART2,所以使用 

    UCON2, R/W, Address = 0xE290_0804

     

    设置:

    UCON2 地址为 0xE290_0804

    接收模式选择轮询,发送模式也选择轮询,正常发送暂停信号,回环模式为正常,时钟源选择 PCLK

    即模式控制寄存器 UCON2 = 00 0101 = 0x05;

    位操作方法:

    UCON2 = 0x05;

    需要知道的小知识点:

    在收发模式里有中断、轮询、DMA这些有什么区别呢?

    参看:后续补充!!

    再者,时钟源选择里有 PCLK、SCLK_UART 这又是什么呢?

    参看:后续补充!!

    《3》UART FIFO 控制寄存器UFCONn

    UART模块中有四个UART FIFO控制寄存器,分别是UFCON0,UFCON1,UFCON2和UFCON3

    我们用的是 UART2,所以使用 

    UFCON2, R/W, Address = 0xE290_0808

    设置:

    UFCON2 地址为 0xE290_0808
    禁止 FIFO,即FIFO 控制寄存器 UFCON2 = 0x00;

    位操作方法:

    UFC0N2 = 0X00;

    《4》UART 收/发 状态寄存器 UTRSTATn

    UART模块中有四个UART Tx / Rx状态寄存器,分别是UTRSTAT0,UTRSTAT1,UTRSTAT2和UTRSTAT3

    我们用的是 UART2,所以使用 

    UTRSTAT2, R, Address = 0xE290_0810

    设置:

    UTRSTAT2 地址为 0xE290_0810 

    接收缓冲区数据准备好,缓冲区为空;发送缓冲区寄存器为空。即 UTRSTAT2 = 10 = 0x02;

    只有收发缓冲区为空,才能进行收发。所以首先要先判断 UART 收/发 状态寄存器 UTRSTATn

    位操作方法:

    UTRSTAT2 = 0x02;

    《5》UART 发送缓冲寄存器(保持寄存器和FIFO寄存器)UTXHn

    UART模块有四个UART发送缓冲寄存器,分别是UTXH0,UTXH1,UTXH2和UTXH3。

    UTXHn包含传输数据的8位数据。

    我们用的是 UART2,所以使用 

    UTXH2, W, Address = 0xE290_0820

    设置:

    UTXH2 地址为 0xE290_0820

    比如发送的数据:55,则 UTXH2 = 0101 0101 = 0x55;

     

    发送移位器:发送缓冲寄存器中的数据并不是直接传送到输出管脚TXD2(GPA1_1),还必须先送到发送移位器(Transmit shifter),然后再由Transmit shifter通过移位操作,将数据一位一位的发送到TXD2管脚上。

    《6》UART接收缓冲寄存器(保持寄存器和FIFO寄存器)URXHn

    UART模块中有四个UART接收缓冲寄存器,分别是URXH0,URXH1,URXH2和URXH3。
    URXHn包含接收数据的8位数据。

    我们用的是 UART2,所以使用 

    URXH2, R, Address = 0xE290_0824

    设置:

    URXH2 地址为 0xE290_0824

    比如接收到的数据:55,则 URXH2 = 0101 0101 = 0x55;

     

    接收移位器:从接收管脚RXD2上接收来的数据并不是直接放到接收缓冲寄存器,而是先一位一位的放到接收移位器中,当收满一个字节后,再放到接收缓冲寄存器中。

    《7》UART 通道波特率分频寄存器 UBRDIVn

    UART模块中有四个UART通道波特率分频寄存器,分别是UBRDIV0,UBRDIV1,UBRDIV2和UBRDIV3。

    我们用的是 UART2,所以使用 

    UBRDIV2, R/W, Address = 0xE290_0828

    注意:当UART时钟源是PCLK时,UBRDIVn必须大于比0(UBRDIVn> 0),如果UBRDIV的值为0,则UART波特率不受UDIVSLOT值的影响。

    设置:

    UBRDIV2 地址为 0xE290_0828

    《8》UART通道分割插槽寄存器 UDIVSLOTn

    UART模块中有四个UART通道波特率分频寄存器,分别是UDIVSLOT0,UDIVSLOT1,UDIVSLOT2和UDIVSLOT3。

    我们用的是 UART2,所以使用 

    UDIVSLOT2, R/W, Address = 0xE290_082C

     

    ================================================

    需要知道的小知识点:

    波特率,在UART里面是一个很重要的概念。

    参看:串口通信 -- 百度百科

    数据传输率

    数据传输率是指单位时间内传输的信息量,可用比特率和波特率来表示。

    比特率:比特率是指每秒传输的二进制位数,用bps(bit/s)表示。

    波特率:波特率是指每秒传输的符号数,若每个符号所含的信息量为1比特,则波特率等于比特率。在计算机中,一个符号的含义为高低电平,它们分别代表逻辑“1”和逻辑“0”,所以每个符号所含的信息量刚好为1比特,因此在计算机通信中,常将比特率称为波特率,即:

    1波特(B)= 1比特(bit)= 1位/秒(1bps) 例如:电传打字机最快传输率为每秒10个字符/秒,每个字符包含11个二进制位,则数据传输率为:10Baud。11位/字符×10个字符/秒=110位/秒=110bps。计算机中常用的波特率是:110、300、600、1200、2400、4800、9600、19200、28800、33600,目前最高可达56Kbps.

    ⑶位时间Td

    位时间是指传送一个二进制位所需时间,用Td 表示。Td = 1/波特率 = 1/B

    例如:B=110波特/秒 , 则Td = 1/110 ≈ 0.0091s

    发送时钟和接收时钟

    在串行通信中,二进制数据以数字信号的信号形式出现,不论是发送还是接收,都必须有时钟信号对传送的数据进行定位。在TTL标准表示的二进制数中,传输线上高电平表示二进制1,低电平表示二进制0,且每一位持续时间是固定的,由发送时钟和接收时钟的频率决定。

    ⑴ 发送时钟

    发送数据时,先将要发送的数据送入移位寄存器,然后在发送时钟的控制下,将该并行数据逐位移位输出。通常是在发送时钟的下降沿将移位寄存器中的数据串行输出,每个数据位的时间间隔由发送时钟的周期来划分。

    ⑵ 接收时钟

    在接收串行数据时,接收时钟的上升沿对接收数据采样,进行数据位检测,并将其移入接收器的移位寄存器中,最后组成并行数据输出

     波特率因子

    接收时钟和发送时钟与波特率有如下关系:F = n × B 这里F 是发送时钟或接收时钟的频率; B 是数据传输的波特率; n 称为波特率因子。设发送或接收时钟的周期为Tc,频率为F的位传输时间为Td,则: Tc = 1/F , Td = 1/B 得到: Tc = Td /n 在实际串行通信中,波特率因子可以设定。在异步传送时,n = 1,16,64,实际常采用n = 16,即发送或接收时钟的频率要比数据传送的波特率高n倍。在同步通信时,波特率因子n必须等于1。

    ================================================

     

    UART波特率配置:
    UART模块中有四个UART波特率除数寄存器,分别是UBRDIV0,UBRDIV1,UBRDIV2和UBRDIV3。
    存储在波特率除数寄存器(UBRDIVn)中的值:

    DIV_VAL = UBRDIVn + (num of 1's in UDIVSLOTn)/16
     

    PLCK:DIV_VAL = (PCLK / (bps x 16)) 
    或者
    SLCK_UART:DIV_VAL = (SCLK_UART / (bps x 16)) 

    简单来说:

    内部系统时钟源   PCLK
    外部时钟源          SCLK_UART

     

    其中,除数应该是从1到(216-1)。
    使用 UDIVSLOT,可以更准确地生成波特率。
    例如,如果波特率是 115200 bps,SCLK_UART 是 40 MHz,则 UBRDIVn 和 UDIVSLOTn 是:

    DIV_VAL = (40000000 / (115200 x 16)) -1
    = 21.7 -1
    = 20.7

     

    UBRDIVn = 20(DIV_VAL的整数部分
    (UDIVSLOTn中1的个数)/ 16 = 0.7  (21.7 小数点后为 0.7)
    那么(UDIVSLOTn中的1的个数)= 11 (取整数部分)
    所以,UDIVSLOTn 可以是16'b   1110_1110_1110_1010或 16'b 0111_0111_0111_0101 等

    建议按照下表所述选择UDIVSLOTn,即 UDIVSLOTn = 0xDDD5 

     

    回到我们自己的开发板上来:

    我们在 《2》UART 模式控制寄存器 UCONn 中有提到 PCLK、SCLK_UART,其中的我们选择的是 PCLK。

     PCLK:DIV_VAL1) = (PCLK / (bps x 16) ) 

     

     

    =============================================

    其中 PCLK 的值是怎么得到的呢?

    查看芯片手册 CLOCK DOMAINS (p-353) 

    S5PV210由三个时钟域组成,即主系统(MSYS),显示系统(DSYS)和外设系统(PSYS),如图3-1所示。
    •MSYS域包括Cortex A8处理器,DRAM内存控制器(DMC0和DMC1),3D内部SRAM(IRAM和IROM),INTC和配置接口(SPERI)。 Cortex A8仅支持同步模式,因此它必须与200MHz AXI总线同步运行。
    •DSYS域包含显示相关模块,包括FIMC,FIMD,JPEG和多媒体IP(所有其他模块)在X,L和T块中提到的IP),如图3-1所示。
    PSYS域用于安全性,I / O外围设备和低功耗音频播放。
    •每个总线系统分别工作在200 MHz(最大),166 MHz和133 MHz。 有异步总线桥(BRG)在两个不同的域之间。

     

    在这里,得出 PCLK_PSYS 为 66 MHz。

    =============================================

    如果波特率是 115200 bps,PCLK 是 66 MHz,则 UBRDIV2 和 UDIVSLOT2 是:

     

    DIV_VAL = (66000000 / (115200 x 16)) -1
    = 35.8 -1
    = 34

     

    UBRDIV2 = 34(DIV_VAL的整数部分)
    (UDIVSLOTn中1的个数)/ 16 = 0.8  (35.8  小数点后为 0.8)
    那么(UDIVSLOTn中的1的个数)= 13  (四舍五入)

    按照上表所述选择UDIVSLOT2,即 UDIVSLOT2 = 0xDFDD

    (3)总结

    《1》GPA1CON 寄存器  (配置寄存器) 将其配置成UART2收/发模式

    GPA1CON 地址为 0xE020_0020

    GPA1CON &= ~(0xff<<0);    //清空bit 0-7

    GPA1CON |= 0x22;    //设置 0010 0010  接受 发送

    《2》GPA1PUD 寄存器(上下拉电阻控制寄存器 )

    GPA1PUD 地址为 0xE020_0028

    GPA1PUD &= ~(0x0f<<0);  //0000 禁止上拉下拉

    《3》UART 行控制器 ULCONn (数据位宽度为 8bit,停止位 1位,无校验位,正常模式)

    ULCON2 地址为 0xE290_0800

    ULCON2 = 0x03;

    《4》UART 模式控制寄存器 UCONn(接收/发送模式选择轮询,正常发送暂停信号,回环模式为正常,时钟源选择 PCLK)

    UCON2 地址为 0xE290_0804

    UCON2 = 0x05;

    《5》UART FIFO 控制寄存器UFCONn(禁止 FIFO)

    UFCON2 地址为 0xE290_0808

    UFC0N2 = 0X00;

    《6》UART 收/发 状态寄存器 UTRSTATn(接收缓冲区数据准备好,缓冲区为空;发送缓冲区寄存器为空)

    UTRSTAT2 地址为 0xE290_0810 

    UTRSTAT2 = 0x02;

    《7》UART 发送缓冲寄存器(保持寄存器和FIFO寄存器)UTXHn

    UTXH2 地址为 0xE290_0820

    char c = 'A';

    UTXH2 = C;

    《8》UART接收缓冲寄存器(保持寄存器和FIFO寄存器)URXHn

    URXH2 地址为 0xE290_0824

    char d;
    d = URXH2 & 0xFF;

    《9》UART 通道波特率分频寄存器 UBRDIVn(设置波特率为 115200)

    UBRDIV2 地址为 0xE290_0828

    UBRDIV2 = 34;

    《10》UART通道分割插槽寄存器 UDIVSLOTn

    UDIVSLOT2 地址为 0xE290_082C

    UDIVSLOT2 = 0xDFDD;

    四、编写驱动程序

     

    此处用到关键字 volatile,参看:C语言再学习 -- 关键字volatile

    和预处理器 #define,参看:C语言再学习 -- C 预处理器

    对比:S5PV210开发 -- GPIO

    (1)头文件 uart.h

    #ifndef __UART_H__
    #define __UART_H__
    #define GPA1CON		*((volatile unsigned int *)0xE0200020)
    #define GPA1PUD		*((volatile unsigned int *)0xE0200028)
    #define ULCON2		*((volatile unsigned int *)0xE2900800)
    #define UCON2		*((volatile unsigned int *)0xE2900804)
    #define UFCON2		*((volatile unsigned int *)0xE2900808)
    #define UTRSTAT2	*((volatile unsigned int *)0xE2900810)
    #define UTXH2		*((volatile unsigned int *)0xE2900820)
    #define URXH2		*((volatile unsigned int *)0xE2900824)
    #define UBRDIV2		*((volatile unsigned int *)0xE2900828)
    #define UDIVSLOT2	*((volatile unsigned int *)0xE290082C)
    
    #define PCLK			66000000//66MHz
    
    extern void uart0_init(void);
    extern void uart_test(void);
    extern void uart_putc(char );
    extern void uart_puts(char *);
    extern char uart_getc(void);
    extern void uart_gets(char *, int);
    
    #endif
    

    (2)uart.c

    #include "uart.h"
    void uart2_init(void)
    {
    	/*配置GPA1_0 GPA1_1管脚为uart功能*/
            //配置串口管脚
    	GPA1CON &= ~(0xff<<0);
    	GPA1CON |= 0x22;
            //禁止两个管脚内部上拉下拉电阻
    	GPA1PUD &= ~(0x0f<<0);
    	//配置串口寄存器
    	/*8N1 115200 轮询*/
    	ULCON2		= 0x03;
    	UCON2	 		= 0x05;
    	UFCON2		= 0x00;
    	UBRDIV2 	= PCLK/(115200*16) - 1;
    	/*reference P879*/	
    	UDIVSLOT2	= 0xDFDD;
    }
    
    void uart_putc(char c)
    {
    	/*判断UTXH2中是否为空
    	 * UTRSTAT2 bit2 1,空
    	                 0,非空
    	 */
    	while(!(UTRSTAT2 & 0x02)) ;	
    		
    	UTXH2 = c;
    	
    	if(c == '\n')
    	{
    		uart_putc('\r');
    	}
    }
    void uart_puts(char *s)
    {
    	while(*s)
    	{
    		uart_putc(*s);
    		s++;
    	}
    }
    
    void uart_test(void)
    {
    	/*输出数据到PC*/
    	uart_puts("\n helloworld! \n");
    }
    
    char uart_getc(void)
    {
        /*
         * UTRSTAT2 bit0 1,代表接收到数据
         *               0,没有接收到数据
         * */
        while(!(UTRSTAT2 & 0x01));
        return (char)(URXH2&0xff);
    }
    
    void uart_gets(char *buf, int max)
    {
        int i = 0;
        char tmp = 0;
    
        while(i < (max-1))
        {
            tmp = uart_getc();
            /*回显*/
            uart_putc(tmp);
    
            buf[i] = tmp;
            
            if(tmp == '\r')
            {
                break;
            }
            i++;
        }
        /*"abc\r"  "abc\0"*/
        buf[i] = '\0';
    }

    (3)main.c

    #include "uart.h"
    
    #define CMD_MAX_LEN 32
    
    char cmd[CMD_MAX_LEN];
    int main()
    {
    	/*8n1 115200 轮询模式*/
    	uart2_init();
    	
    	while(1)
    	{
                /*发送命令提示符*/
                uart_puts("\narmshell##");
                /*接收PC机端的输入*/
                uart_gets(cmd, CMD_MAX_LEN);
                /*执行用户命令*/
    	}
    	return 0;	
    }

    (4)Makefile

    NAME=uart
    BIN=$(NAME).bin
    OBJ=main.o uart.o
    ELF=$(NAME).elf
    
    CFLAGS=-nostdlib
    LDFLAGS=-nostdlib -nostartfiles
    
    $(BIN):$(ELF)
    	arm-none-linux-gnueabi-objcopy -O binary $(ELF) $(BIN)
    $(ELF):$(OBJ)
    	arm-none-linux-gnueabi-ld	$(LDFLAGS) -T uart.lds $(OBJ) -o $(ELF)
    
    %.o:%.c
    	arm-none-linux-gnueabi-gcc $(CFLAGS) -c $< -o $@
    clean:
    	rm -f $(OBJ) $(ELF) $(BIN)

    (5)uart.lds

    ENTRY(main)
    
    SECTIONS
    {
    	. = 0xc0008000;
    	.text :
    	{
    		main.o(.text)
    		*(.text)
    	}
    	.data :
    	{
    		*(.data)
    	}
    	.bss :
    	{
    		*(.bss)
    	}
    }

    五、编译下载到开发板并执行

    使用 tftp 将 uart.bin下载到 SDRAM 的 0xc0008000 位置

        tftp c0008000 uart.bin

        go c0008000

     

    OK,此时可以回显 armshell##,输入32bit字符回车。

    六、问题分析

    不知道有没有人发现寄存器配置是有问题的。哪里有问题呢?

    答案是:波特率

    这里的UART很明显是RS232通信。其最大传输速率为20Kbps。

    因此波特率怎么用的是 115200bps呢?应该为 9600bps才对吧?

     

    然后网上有看到这样一句话:

    RS232协议并未规定最大传输速率,而是规定“速率低于20kb/s时,传输距离可以到15m”。随着距离减小,这个速率可以提高。RS232中一个波特包括一个bit。所以波特率与比特率相等。而随着现在电子科技发展,芯片驱动接受能力增强。这个速率也在提高,现在PC机都到921600了。不要为“最大传输速率”的定义纠结。 协议并未规定最大传输速率,而是规定“速率低于20kb/s时,传输距离可以到15m”。随着距离减小,这个速率可以提高。RS232中一个波特包括一个bit。所以波特率与比特率相等。而随着现在电子科技发展,芯片驱动接受能力增强。这个速率也在提高,现在PC机都到921600了。不要为“最大传输速率”的定义纠结。 

     

    我还是不甘心,继续查维基百科:

    目前的最新版本是由美国电信工业协会(TIA, Telecommunications Industry Association,由EIA所分出的一个组织)所发行的TIA-232-F,它同时也是美国国家标准ANSI/TIA-232-F-1997(R2002),此标准于2002年受到再确认。在 1997年由TIA/EIA发行当时的编号则是TIA/EIA-232-F与ANSI/TIA/EIA-232-F-1997。在此之前的版本是TIA/EIA-232-E。[1]

     

    最新标准是 TIA-232-F,谷歌这个标准:

    参看:Interface Circuits for TIA/EIA-232-F

    阅读翻译 TIA/EIA-232-F Electrical Specification 这部分内容:

     

    所有的232电路传送电压信号,而连接器引脚处的电压不是超过±25 V.所有的引脚必须能够承受任何其他引脚的短路
    而不会遭受永久性的损害。每一行应该有一个最小负载3kΩ,最大负载7kΩ,通常是接收器电路的一部分。
    逻辑0由5V和15V之间的驱动电压和逻辑1表示在-5V和-15V之间。在接收端,电压在3V和15V之间代表0和在-3V和-15V之间的电压代表1。电压在±3V之间是不确定的,位于过渡区域。这有效地给了接收器的最小2 V噪声容限。

    最初的电缆长度在RS-232-C中定义为15米;然而,这已经在EIA-232-D和TIA / EIA-232-E中进行了修改,现在已经进行了修改更正确地指定为2500 pF的最大容性负载。这等同于到约15到20米的线路长度,取决于电缆电容。如前所述,232指定了信号的最大转换速率驱动器输出为30 V /μs。这个限制与问题多与导线电缆中导体之间的串扰有关。越快越好过渡边缘,串扰量越大。这个限制,连同司机和接收机使用一个共同的信号地和事实由地电流引入的相关噪声严重限制了最大值数据吞吐量。为此,232标准规定了20 kbit / s的最大数据速率。标准还规定了单位间隔与上升时间之间的关系通过过渡区(3 V到-3 V)或tt。这是主要的区别D和E修订版。图2 中更清楚地显示了这一点。EIA-232-D和数据速率高达8 kbit / s,规定了转换时间与单位间隔或比特时间tb为最大数据速率的4%。在8 kbit / s以上,转换时间放宽到最大5μs,与数据速率无关。都C和E修订版将tt / tb的比率指定为4%,一直到20 kbit / s。

    可以用4%的数字进一步推断这一点。最大转换率为30 V /μs,最大可达数据速率为200 kbit / s; 然而,在实践中,
    这被限制在120kbit / s左右。一些软件程序运行在传输速率为116 kbit / s。
    此外,在更长的线路长度,最大线路驱动器的驱动电流成为影响数据速率的主要特征,取代了30-V /μs的压摆率。随着线路长度的增加,负载电容也增加,需要更多的电流保持不变过渡时间。

    图4中显示的曲线表示维持电源所需的驱动电流4%的关系在不同的数据速率。在当今的低功耗系统中,这个水平的输出电流在大约20kbit / s以上是不可持续的。在实践中,对于更高的数据速率,线路长度通常限制在3米左右。大多驱动可以在这条线路上处理更高的传输速率严重影响电力供应。还显示了 256kbit / s 的曲线数据速率超过了30V /μs的限制。如前所述,需要高数据速率(> 200 kbit / s)在整个行业中广泛流行。

    计算最大线路长度
    到目前为止,已经讨论了负载电容方面的线路长度。 为了实际目的,我们不能考虑把这个负载电容的值变成真实的线路长度。 该标准规定最大线路电容为2500 pF。该对于20pF的接收机,输入电容将2480pF作为最大线路
    电容。
    接下来,必须考虑使用的电缆类型。 标准232电缆由多家厂商提供有互电容的每米大约100 pF,但必须增加杂散电容。 流浪电容变化很大,取决于线是否是屏蔽。对于屏蔽电缆,杂散电容通常是互电容的两倍。如图5所示,对于屏蔽电缆,最大线路长度为20米;对于非屏蔽电缆,它是40多米。

     

    这么看来,短距离情况下,RS232使用115200波特率也是可以的。

    如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/78579074

    展开全文
  • 串口驱动及架构

    2018-07-30 13:33:28
    在Linux中,TTY(终端)是一类字符设备的统称,包括了3种类型:控制台,串口和伪终端。 1)控制台:供内核使用的终端为控制台。控制台在Linux启动时,通过命令 console=…指定,如果没有指定控制台,系统把第一个注册...
  • 1 usb转串口驱动 【驱动开发网tiamo的usb2com】 2 cp2102的比较全的资料 3 张帆《windows驱动开发技术详解》的随盘代码 4 ark3116的linux驱动 windows驱动代码 5 cp210x系列的linux驱动代码
  • MTK串口驱动开发

    2018-11-02 21:25:03
    MTK串口驱动开发 由于最近在工作中需要使用MTK的MT6261进行移动嵌入式设备的开发,所以将MTK串口驱动开发流程贴出来分享给大家。 1.使用串口工具配置UART管脚,此处配置的是UART2打开源码目录下的\custom\drv\Drv_...
  • UART串口驱动开发文档-------w83697/w83977 super I/O串口驱动开发文档作者: 侯辉华部 门:时 间: 2007/08/12内容简介: 介绍了Linux下的串口驱动的设计层次及接口, 并指出串口与TTY终端之间的关联层次(串口可作TTY...
  • linux UART串口驱动开发

    2012-11-17 10:13:48
    内容简介: 介绍了Linux下的串口驱动的设计层次及接口, 并指出串口与TTY终端之间的关联层次(串口可作TTY终端使用), 以及Linux下的中断处理机制/中断共享机制, 还有串口缓冲机制当中涉及的软中断机制; 其中有关w83697/...
  • 在Linux驱动开发中,一般都不会从0开始写,我们做的更多的是移植和修改,有时候还需要对驱动程序进行优化。当然具备移植、修改驱动能力的基础是能够读懂驱动程序,同时需要对这个驱动程序的核心功能非常了解。接下来...
  • 虚拟串口就是当本地并没有对应的串口硬件设备,而为应用层提供串口设备一样的系统调用接口,以兼容原本使用本地串口的应用软件的“虚”设备。本文作者给出了一种在Windows平台上实现虚拟串口的方法,由此实现的...
  • 【ARM&Linux】串口驱动

    2019-05-29 12:07:08
    串口驱动相关数据结构 struct uart_driver; //串口驱动结构 struct uart_port; //端口结构,串口驱动只有一个,端口却有多个,一个端口对应一个实际的串口 struct uart_ops; //函数操作集 struct uart_state; //...
  • 读取串口数据使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。 char buff[1024]; int Len; int readByte = read(fd,buff,Len); 可以...
  • DDK对串口驱动提供了专门接口。只要编写的驱动满足这些接口,并按照串口标准的命名方法,不管是真实的串口设备,还是虚拟设备,Windows操作系统都会认为 这个设备是一个标准的串口设备。用标准的串口调试工具都可以...
  • 在上一章我们已经说明了uart驱动开发流程,本章我们就不再介绍uart相关的接口实现,仅通过实现一个虚拟的串口控制器程序,用以说明虚拟串口开发流程。 本次开发的虚拟串口提供的功能如下: 提供两个串口实例 ...
  • 好久没有写blog了!也没有写读后感了!也觉得自己最近学习不够努力!前段时间太忙!一直没有时间(借口吧)!前段时间又要做一个PCI转串口驱动!...所以在开发驱动程序之前要首先熟悉8251的编程。起初难道这个课题没
  • 问题这两天为了做gps和arm的通讯,不得不捣鼓这个...驱动源码一般来说USB转串口的驱动已经集成在内核中,我们无需下载,只需要在编译时进行一些配置即可. 因此在编译之前我们需要确认自己的内核中有没有USB转串口的驱动
  • 请问usb转串口驱动的实现原理是什么?请从驱动开发的角度回答,谢谢!尽可能详细些!
  • 近期在64位Win7下开发一款PCIe接口的多串口卡驱动程序,做个小结: 1. 因为在Win下对WDF不熟悉,加上市面上DDK、WDM书籍较多,...这里用《Windows驱动开发技术详解(张帆等编写)》第16章Test5中的InitMyPCI函数。该
1 2 3 4 5 ... 20
收藏数 31,156
精华内容 12,462
关键字:

串口驱动开发