第一篇:Linux下的USB程序分析
Linux下的USB程序分析
关键字:USB、LINUX、设备驱动、总线 参考文献:
LINUX 设备驱动程序 ALESSANDRO RUBINI & JONATHAN CORBET
著 魏拥明 骆刚 姜君 译
USB 2.0 原理与工程开发 王成儒、李英伟 编著 LINUX 内核源代码情景分析 毛德操 胡希明 著
一、USB概述 1.USB的优点
USB与计算机的接口采用的不是传统的I/O模式,它和传统的通信接口相比,存在以下优点:
(1)热插拔:用户可以把USB外设直接连接到一台正在运行的计算机上,操作系统能自动识别,不用时可将USB在操作系统中卸载,不会损伤计算机
(2)即插即用:用户将USB设备插入后可立即使用(3)共享式接口:不同的USB外设使用同样的接口
(4)节省系统资源:只有USB主控制器需要使用一根IRQ线和一些I/O地址空间,对USB外设来说,它需要的仅仅是为其分配一个唯一的地址
(5)传输速率高;USB2.0的传输速率可达25MB/S以上(6)提供电源(7)兼容性强
2.USB系统描述
USB系统由USB主机和USB设备构成。
USB主机内部含有USB主控制器,负责完成主机和USB设备之间的物理数据传输。USB主机中还应有设备驱动程序。USB的数据驱动是基于令牌的,其所有的通信都由USB主机启动。
二、Linux设备驱动概述 1.设备驱动程序的作用
Linux是“单块结构”的操作系统,设备驱动作为设备控制模块中的一部分,在整个操作系统中扮演着非常重要的角色。设备驱动使某个特定的硬件响应一个良好定义的内部编程接口,同时完全隐藏了设备工作的细节。用户操作通过一组标准化的调用完成,而这些调用是和特定的驱动程序无关的。将这些调用映射到作用于实际硬件的设备特定的操作上,则是设备驱动程序的任务。这个编程接口能够使得驱动程序独立于内核的其它部分而建立,在需要的时候,可在运行时“插入”内核。
如果从另一个角度来看驱动程序,那么它可以被看作是应用和实际设备之间的一个软件层。这种定位使驱动程序具有了“个性化”的特点:即对于相同的设备,不同的驱动程序也可以提供不同的功能。
2.Linux内核功能划分
Linux内核从功能上分可以分为以下几个部分;进程管理: 进程管理功能负责创建和撤销进程以及处理它们和外部世界的连接(输入和输出)。不同进程之间的通信(通过信号、管道或进程见通信原语)是整个模块的基本功能。除此之外,控制进程如何共享CPU的调度程序也是进程管理的一部分。概括地说,内核的进程管理活动就是在单个或多个CPU上实现多个进程的抽象。内存管理:
内存是计算机的主要资源之一,用来管理内存的策略是决定系统性能的一个关键因素。内核在有限的可用资源上为每一个进程都创建了一个虚拟寻址空间,内核的不同部分和在内存管理子系统交互时使用一套相同的系统调用,包括从简单的malloc/free到对其它一些不常用的系统调用。文件系统:
Linux中的每个对象几乎都可以被看作文件。内核在没有结构的硬件上构造结构化的文件系统,所构造的文件系统抽象在整个系统中广泛使用。另外,Linux支持多种文件系统类型,即在物理介质上组织数据的不同方式。设备控制:
除了处理器、内存以及其他很有限的几个实体外,所有设备控制操作都由与被控制设备相关的代码来完成。这段代码就是设备驱动程序,内核必须为系统中的每件外设嵌入相应的驱动程序、包括硬盘驱动器、键盘、鼠标等。
3.设备和模块分类
Linux系统将设备分成三种类型:字符设备、块设备和网络接口。每个模块通常实现其中一种类型,相应地,模块可分为字符模块(char modual)、块模块(block modual)和网络模块(network modual)三种。
字符设备(character device)
字符设备是能够象字节流(比如文件)一样被访问地设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少需要实现open、close、read和write系统调用。字符终端(/dev/console)和串口(/dev/ttySO以及类似设备)就是字符设备的两个例子,他们能够用流很好地表示。字符设备可以通过文件系统节点(如/dev/tty1和/dev/lp0)来访问,它和普通文件之间的唯一差别在于,对普通文件的访问可以前后移动访问指针,而大多数字符设备是只能顺序访问的数据通道。然而,也存在和数据区特性类似的字符设备,访问它们时可前后移动访问指针。
块设备(block device)
和字符设备一样,块设备也是通过/dev目录下的文件系统节点被访问的。块设备(例如硬盘)上能够容纳文件系统。Linux允许应用程序象字符设备那样读写块设备,可以一次传递任意多字节的数据。因此,块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核和驱动程序的接口不同。象字符设备一样,块设备也是通过文件系统节点被访问的,他们之间的差异对用户来说是透明的。块驱动程序除了给内核提供和字符驱动程序一样的接口以外,还提供了专门面向块设备的接口,不过这些接口对于那些从/dev目录下某个目录打开块设备的用户和应用程序都是不可见的。另外,块设备的接口必须支持挂装(mount)文件系统。
网络接口(network interface)
任何网络事务都要经过一个网络接口,即一个能够和其它主机交换数据的设备。通常接口是个硬件设备,但也可能是个纯软件设备,比如回环(loopback)接口。网络接口由内核中的网络子系统驱动,负责发送和接收数据包,它无须了解每项事务是如何映射到实际传送的数据包的。尽管Telnet和FTP连接都是面向流的,它们都使用了同一个设备,但这个设备看到的只是数据包,而不是独立的流。
三、Linux下USB程序分析
USB总线的初始化和USB设备的枚举
首先,我们先看一下USB总线本身的初始化。USB控制器(连同根集中器)连接在PCI总线上,是一个PCI设备,在PCI总线的初始化过程中会受到枚举。PCI设备的初始化完成后,在PCI总线树中就游乐代表着具体USB总线控制器的PCI_DEV数据结构,并已为控制器的I/O区间和RAM区间分配和设置了总线地址。
在USB总线控制器的设备驱动程序方面,则要为其准备下一个PCI_DRIVER数据结构,其类型定义在include/linux/pci.h中: struct pci_driver { struct list_head node;char *name;const struct pci_device_id *id_table;int(*probe)(struct pci_dev *dev,const struct pci_device_id *id);void(*remove)(struct pci_dev *dev);void(*suspend)(struct pci_dev *dev);void(*resume)(struct pci_dev *dev);};
这个数据结构为通用的PCI设备管理机制提供了几个函数指针,特别是为一个通用的、一般化的PCI设备初始化过程提供了函数指针PROBE。供这个PCI设备的初始化过程叫“回叫”,以完成具体设备的初始化。对于遵循UHCI界面的USB控制器,其PCI_DRIVER数据结构为uhci_pci_driver,定义于drivers/usb/uhci.c:
static struct pci_driver uhci_pci_driver= { name: “usb-uhci”, id_table: &uhci_pci_ids [0],probe: uhci_pci_probe, remove: uhci_pci_remove, #ifdef CONFIG_PM suspend: uhci_pci_suspend, resume: uhci_pci_resume, #endif /*PM*/ };
结构中的指针id_table应该指向一个pci_device_id结构数组,表明由这个数据结构所确定的设备驱动程序适用于哪一些PCI设备。对此,drivers/usb/uhci.c中相应地定义了数组uhci_pci_ids[]: static const struct pci_device_id__devinitdata uhci_pci_ids []={{ /*handle any USBUHCI controller */ class:((PCI_CLASS_SERIAL_USB<<8)|0x00), class_mask: ~0, /* no matter who makes it */ vendor: PCI_ANY_ID, device: PCI_ANY_ID, subvendor: PCI_ANY_ID, subdevice: PCI_ANY_ID,},{/* end:all zeroes*/} };从其定义可以看出,它适用于所有类型为PCI_CLASS_SERIAL_USB,子类型为0的PCI设备,即USB控制器,而不管是由哪一家厂商提供。
准备好这些数据结构以后,就可以通过inline函数pci_module_init()向系登记具体的设备驱动程序,并对设备进行初始化。其代码在include/linux/pci.h中。这里通过pci_register_driver()完成PCI设备驱动程序的登记和初始化,这就是前面所讲的通用、一般化的PCI设备初始化过程。这个函数返回一个计数,表示在PCI总线上找到了几个这样的设备。一般化的PCI设备初始化过程。这个函数返回一个计数,表示在PCI总线上找到了几个这样的设备。一般,如果这个函数返回0就要调用pci_unregister_driver()撤消登记,但是如果PCI总线允许“热插入”。即在加电后运行过程中带电插入设备,而驱动程序又并非通过可以安装模块实现,则不应该撤消登记;因为以后热插入此种设备仍需要执行这个驱动程序,应该保留着,以备插入此种设备之需。
如果一个设备的pci_dev结构尚未与任何驱动程序挂钩,并且其所有地址区都尚未启用,则pci_dev_driver()返回0。这样的pci_dev结构需要通过pci_announce_device()加以对比(drivers/pci/pci.c)。
可想而知,所谓比对是将具体设备的类型、厂家等等在PCI枚举阶段从设备收集的信息与USB驱动程序的数组uhci_pci_ids[]进行对比,具体由pci_match_device()完成的(drivers/pci/pci.c)如果比队结果相符,就找到了一个USB总线控制器,此时便通过驱动程序提供的函数指针probe对其进行初始化。对于遵循UHCI界面的USB总线控制器,这个函数是uhci_pci_probe(),其代码在drivers/usb/uhci.c中。
我们在前面曾经说USB设备没有向主机发出中断请求的能力,而只能等待,受主机的查询。但是这并不意味着USB控制器(在主机中)没有向CPU发出中断请求的能力,这是完全不同的两回事,不能混淆。事实上,USB控制器是有能力向CPU发出中断请求的,所以要接通它的中断请求线。
USB控制器本身带有微处理器,在USB总线上发送/接收的信息都由USB控制器通过DMA直接从内存读/写,主机的CPU只要提供缓冲区指针就可以了。而且,CPU也不需要逐次地为USB总线上的操作提供缓冲区指针,而只要把缓冲区指针纪录在相应的交互请求,或曰交互描述块就可以了。USB控制器自会顺着交互请求队列逐个地完成对这些缓冲区的操作。类似的DMA操作称为“智能化DMA”。其实,USB总线控制器的DMA操作甚至比一般的智能化DMA还要复杂,因为CPU为之准备的并不只是一个缓冲区队列,而是许多交互请求队列,称为一个“调度”。PCI设备(的接口)要进行DMA操作就得具有竞争成为“主总线”的能力。另一个方面,PCI设备的DMA功能还要服从CPU的统一管理,在PCI配置寄存器组的命令寄存器中有一个控制位PCI_COMMAND_MASTER,就是用来打开或关闭具体PCI设备竞争成为主总线主的能力。在完成PCI总线初始化时,所有PCI设备的DMA功能都是关闭的,所以这里要通过pci_set_master()启用USB控制器竞争成为主总线主的能力(drivers/pci/pci.c)。
数据结构中有几个特别重要的成分。首先是指针f1,指向一个uhci_framelist数据结构,这就是具体USB总线的“框架表”,这种数据结构也定义于drivers/usb/uhci.h: struct uhci_framelist{ __u32 frame[UHCI_NUMFRAMES];} __attribute__((aligned(4096)));
USB总线的框架表实际上是个指针数组,每个指针都指向一个等时交互队列。常数UHCI_NUMFRAME在同一个文件中定义为1024,所以整个数组代表着1024个框架。数组的起始地址必须与4K字节边界对齐,这样其起始地址的低12位就全都是0。USB控制器内部有个“框架表基地址寄存器”,用来记录这个基地址。同时,USB控制器内部还有个10位的“框架计数器”,这个计数器从0开始每过1毫秒(1/1024秒)就加一,直至0x3ff即1023,然后又变为0,如此周而复始。在框架计数的后面添上两位0,再与框架表的基地址连在一起,就成了指向框架表中某个表项的指针。在框架表中的每个表项都指向一个uhci_td结构的队列,每个uhci_td结构是对一个交互的描述,我们称之为“交互描述块”或“交互请求”。USB控制器在每个框架中首先就执行这个队列。每个等时交互队列的最后一个数据结构指向一个(实际上是一截)中断交互队列。中断交互队列与框架之间并不是一一对应的关系,uhci结构中uhci_td结构数组skeltd[],其中的每个元素都指向一截中断交互请求队列,常数UHCL_NUM_SKELTD定义为10,其中skeltd[0]是整个队列的终点,而skeltd[9]实际上不用,所以一共有8截这样的中断交互请求队列。这些中断交互请求队列又在链接在一起,成为一个总的中断交互请求队列。但是,链接在不同部位上的中断交互请求受到执行的频率是不一样的。当将一个代表着中断交互的uhci_td结构链入队列时,可以根据所要求的执行周期选择链入中断交互请求队列的不同部位。而skeltd[]中的各个元素,则起着链入点的作用,所以这个数组称为中断交互请求队列的“骨架”(skeleton)。这些链入点本身也是uhci_td结构,不过是空闲的uhci_td结构,USB控制器在执行时会自动跳过。此外,虽然中断交互请求队列并不是与框架一一对应,二者间还是有着某种静态的对应关系。
等时交互和中断交互都是周期性的,在每个框架中二者的流量加在一起不超过90%。这样,在每个框架中,USB控制器在执行完这两种交互请求以后总是还有一些时间(至少10%),可以用来执行控制交互以及成块交互。这两种交互都不是周期性的,其队列与框架没有静态的对应关系,USB控制器对这些队列的执行完全是动态的,有时间就执行,没有时间就不执行,时间多就多执行,时间少就少执行。在uhci结构中还有个uhci_qh结构数组skelqh[],数组中的每个元素都是一个队列头,用来维持一个“队列的队列”,或者说传输请求的队列。如前所述,每个传输请求是一个交互请求的队列。所以,这个数组是控制/成块传输请求队列的骨架,其大小是UHCI_NUM_SKELQH,在drivers/usb/uhci.h中定义为4。从逻辑上说,只要有两个链入点就够了,可是实际上USB设备有全速和低速之分,还有一个有着特殊的用途,所以共有4个。
因此,除还有其他一些成分以外,uhci数据结构实际上代表着主机CPU为一条USB总线排好的“日程表”,或者说执行程序,这就称为一个“调度”(schedule)。不言而喻,初始化时要为USB控制器分配、建立一个uhci数据结构。这是由alloc_uhci()完成的,其代码在drivers/usb/uhci.c中。
USB总线的根集中器总是与USB控制器集成在一起。对于UHCI界面的总线控制器,其I/O地址区间的前16个地址用于总线控制器本身,其余的就用于根集中器。根集中器的每个“端口”(port)占用两个地址。端口的数量则取决于具体的芯片,至少两个,最多八个。每个端口的状态寄存器中的bit7总是1,所以代码中通过一个循环试读,以确定根集中器中端口的数量。
前面讲过,uhci结构中的skeltd[]用于8截中断交互队列,是个uhci_td数据结构的数组。结构名中的“td”是“交互描述结构”(transaction descriptor)的意思。这种数据结构定义于drivers/usb/uhci.h: struct uhci_td{ /*Hardware fields*/ __u32 link;__u32 status;__32 info;__32 buffer;
/*Software fields*/ unsigned int *frameptr;struct uhci_td *prevtd, *nexttd;
struct usb_device *dev;struct urb *urb;
struct list_head list;}__attribute__((aligned(16)));
每个uhci_td数据结构代表着一个交互请求,就好象是对USB控制器的一条指令。结构中前4个32位长字的作用是由USB控制器的硬件结构所确定了的,因而不能改变;不过硬件只认开头这4个字段,其余的字段则由软件定义和使用。数据结构的起点必须与16字节的边界对齐,这也是USB总线控制器的硬件结构所要求的。这4个32位长字实际上相当于一组寄存器。我们把硬件使用的这一部分称为“交互描述块”,以区别于整个交互描述结构。结构头部的4个字段中,link是指向下一个uhci_td数据结构的连接指针,buffer则指向用于发送或接收的缓冲区,二者均为物理地址。此外,info就好象是命令寄存器,相当于指令中的操作码。内核在drivers/usb/uhci.c中提供了一个inline函数uhci_fill_td(),用于设置 uhci_td数据结构头部除link外的三个字段:
static void inline uhci_fill_td(struct uhci_td *td,__u32 status,__u32 info,__u32 buffer){ td->status = status;td->info = info;td->buffer = buffer;} 与目标设备建立连接的过程就是对目标设备的“枚举”。枚举过程的步骤如下:(1)为设备制订地址(2)从设备读入其usb_device_descriptor数据结构(3)从设备读入其所有的“配置”描述结构(4)枚举或改变设备的配置
USB设备的初始化
每个USB设备都有usb_driver数据结构,定义再include/linux/usb.h中: struct usb_driver{ const char * name;void *(*probe)(struct usb_device *dev, unsigned intf, const struct usb_device_id *id);void(*disconnect)(struct usb_device *,void *);struct list_head driver_list;struct file_operations *fops;int minor;struct semaphore serialize;int(*ioctl)(struct usb_device* dev,unsigned int code,void *buf);const struct usb_device_id *id_table;};
通过usb_scan_devices()扫描所有USB总线上的所有设备,让每个USB设备驱动模块都试着来“认领”与其对口的设备。
所谓“认领”就是使一个usb_interface_descriptor数据结构与相应的设备的usb_driver结构挂钩。这样,从具体设备的数据结构出发,就可以找到其设备驱动程序了。就这样,当usb_scan_devices()完成了对所有的USB设备的扫描时,对扫描器设备的登记和初始化就完成了。最后,以次设备号中的低4位为下标的指针数组p_scn_table[]记录着指向每个具体scn_usb_data结构的地址。
USB设备的驱动
所有的USB设备都有相同的主设备号USB_MAJOR,而根据次设备号划分具体的设备及其类型。所以,根据设备文件节点提供的主设备号,CPU首先找到USB总线的file_operations数据结构usb_fops,从中得到用于open操作的函数指针,这个指针指向usb_open()。
Static int usb_open(struct inode * inode,struct file *file){ int minor = MINOR(inode->i_rdev);struct usb_driver *c = usb_minors[minor/16];int err =-ENODEV;struct file_operations *old_fops,*new_fops = NULL;if(!c ||!(new_fops = fops_get(c->fops)))return err;old_fops = file->f_op;file->f_op = new_fops;if(file->f_op->open)err = file->f_op->open(inode,file);if(err){ fops_put(file->f_op);file->f_op = fops_get(old);} fops_put(old_fops);return err;} 然后,进一步根据次设备号从指针数组usb_minors[]中找到扫描器的usb_driver结构。
对于USB总线上的每个传输,需要为之创建一个“USB传输请求块”,即usb数据结构。发送控制报文的过程是:根据参数建立一个usb数据结构,把这个usb结构交给低层,让低层据以调度相应的控制传输,然后睡眠等待传输的完成。
对于采用UHCI界面的USB总线控制器,其usb_bus结构中的指针hcpriv指向一个uhci数据结构,而uhci结构中有个次层结构rh,里面是有关根集中器的信息,这是在根集中器初始化时设置好的。如果目标设备恰好就是根集中器,那么通信的过程可以简化,因为CPU直接就可以访问其各个寄存器,不需要通过USB总线就能进行通信,所以此时由rh_submit_urb()完成操作。
与其他所有USB设备的通信(传输)都要通过USB总线上的交互来完成,因而需要为具体的传输调度一个或几个交互。除等时传输之外,在调度中不允许同时存在对同一对象的两个同种传输。
第二篇:USB Device实验程序解析
// USB device实验程序解析
//头文件
#include
// SYSTICKS_PER_SECOND 每秒中断次数 #define SYSTICKS_PER_SECOND 100 #define SYSTICK_PERIOD_MS(1000 / SYSTICKS_PER_SECOND)// 全局systick计数
volatile ui32 g_ulSysTickCount = 0;//全局变量记录发送和接收字节数 volatile ui32 g_ulTxCount = 0;volatile ui32 g_ulRxCount = 0;#ifdef DEBUG ui32 g_ulUARTRxErrors = 0;#endif // 与debug相关的定义和声明
// 如果在编译时定义了DEBUG那么通过UART0进行Debug的输出 #ifdef DEBUG //将所有Debug打印请求映射到UARTprintf输出 #define DEBUG_PRINT UARTprintf #else // 编译所有的Debug打印请求
#define DEBUG_PRINT while(0)((int(*)(char *,...))0)#endif // 图形上下文(context)用于OLED屏幕显示 tContext g_sContext;// 宏定义标志,用于在中断时向主程序发送命令.#define COMMAND_PACKET_RECEIVED 0x00000001 #define COMMAND_STATUS_UPDATE
0x00000002 volatile ui32 g_ulFlags = 0;char *g_pcStatus;// 全局标志显示USB设置是否完成 static volatile bool g_bUSBConfigured = false;// 错误处理 #ifdef DEBUG void __error__(char *pcFilename, ui32 ulLine){
UARTprintf(“Error at line %d of %sn”, ulLine, pcFilename);
while(1)
{
} } #endif // systick中断处理 void SysTickIntHandler(void){
// 更新tick计数
g_ulSysTickCount++;}
//函数static ui32 // 功能:device接收数据并返回给主机。
// 当从主机数据发送就绪后,该程序被调用,逐个字节读取数据,并且翻转 //大小写,最后回传给主机
// 变量 psDevice 指向要处理的设备数据实例 // 变量 pcData 指向USB接收缓冲去新接收到的数据 // 变量ulNumBytes 是程序要处理的字节数。// 返回:处理的数据字节数 static ui32 EchoNewDataToHost(tUSBDBulkDevice *psDevice, ui8 *pcData,ui32 ulNumBytes){
ui32 ulLoop, ulSpace, ulCount;
ui32 ulReadIndex;
ui32 ulWriteIndex;
tUSBRingBufObject sTxRing;
// 获取当前缓冲区信息以允许设备能直接写入发送缓冲区。
//(变量中已经有足够信息与接收缓冲区直接连接)
USBBufferInfoGet(&g_sTxBuffer, &sTxRing);
// 获取发送缓冲区的剩余空间
ulSpace = USBBufferSpaceAvailable(&g_sTxBuffer);
// 判断此次可以处理的字节数
ulLoop =(ulSpace < ulNumBytes)? ulSpace : ulNumBytes;
ulCount = ulLoop;
// 更新接收字节数
g_ulRxCount += ulNumBytes;
// 显示Debug信息
DEBUG_PRINT(“Received %d bytesn”, ulNumBytes);
// 与USB缓冲区连接启动字节处理
ulReadIndex =(ui32)(pcData'a')+ 'A';
}
else
{
// 判断是否是大写字母
if((g_pucUSBRxBuffer[ulReadIndex] >= 'A')&&
(g_pucUSBRxBuffer[ulReadIndex] <= 'Z'))
{ // 转换为小写字母,写到发送缓冲区中
g_pucUSBTxBuffer[ulWriteIndex] =
(g_pucUSBRxBuffer[ulReadIndex]1;
sRect.i16YMax = 9;
GrContextForegroundSet(&g_sContext, ClrDarkBlue);
GrRectFill(&g_sContext, &sRect);
// OLED屏幕背景为白色文本
GrContextForegroundSet(&g_sContext, ClrWhite);
// 屏幕中间显示应用名称“usb-dev-bulk”
GrContextFontSet(&g_sContext, g_psFontFixed6x8);
GrStringDrawCentered(&g_sContext, “usb-dev-bulk”,-1,|
GrContextDpyWidthGet(&g_sContext)/ 2, 4, 0);
// 显示当前的发送字节数、接收字节数
GrStringDraw(&g_sContext, “Tx bytes:”,-1, 0, 32, false);
GrStringDraw(&g_sContext, “Rx bytes:”,-1, 0, 42, false);
// 将GPIO外围设备设置为GPIO功能,并且配置USB引脚
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL);
ROM_GPIOPinTypeUSBAnalog(GPIO_PORTB_BASE, GPIO_PIN_0 GPIO_PIN_1);
ROM_GPIOPinTypeUSBAnalog(GPIO_PORTL_BASE, GPIO_PIN_6 GPIO_PIN_7);
// 使能系统节拍中断(system tick)
ROM_SysTickPeriodSet(ROM_SysCtlClockGet()SYSTICKS_PER_SECOND);
ROM_SysTickIntEnable();
ROM_SysTickEnable();
// 显示应用程序名称和UART输出
DEBUG_PRINT(“nStellaris USB bulk device examplen”);
DEBUG_PRINT(“--nn”);
// 显示当前USB设备运行状况
DisplayStatus(&g_sContext, “Configuring USB”);
// 初始化USB发送和接收缓冲区
USBBufferInit((tUSBBuffer *)&g_sTxBuffer);
USBBufferInit((tUSBBuffer *)&g_sRxBuffer);
// USB设备初始化
// 将USB设备信息发送给主机USB库并且将设备连接在总线上
USBDBulkInit(0,(tUSBDBulkDevice *)&g_sBulkDevice);
// 等待初始化设置完成
DisplayStatus(&g_sContext, “Waiting for host”);
// 清除发送、接收字节数
ulRxCount = 0;
| | /
ulTxCount = 0;
// 主程序循环
while(1)
{ // 判断是否请求更新显示状态
if(g_ulFlags & COMMAND_STATUS_UPDATE)
{
// 清除命令标志
g_ulFlags &= ~COMMAND_STATUS_UPDATE;
DisplayStatus(&g_sContext, g_pcStatus);
}
// 判断是否有发送错误
if(ulTxCount!= g_ulTxCount)
{ // 更新最后的发送字节数
ulTxCount = g_ulTxCount;
// 通过UART更新显示
usnprintf(pcBuffer, 16, “ %d ”, ulTxCount);
GrStringDraw(&g_sContext, pcBuffer,-1, 48, 32, true);
}
// 判断是否有接收错误
if(ulRxCount!= g_ulRxCount)
{ // 更新最后的接收字节数
ulRxCount = g_ulRxCount;
// 通过UART更新显示
usnprintf(pcBuffer, 16, “ %d ”, ulRxCount);
GrStringDraw(&g_sContext, pcBuffer,-1, 48, 42, true);
}
} }
第三篇:6程序分析
《单片机原理及应用》题库六
(程序分析)
程序1
流水灯程序
#include
//包含8952单片机头文件 void delaym(unsigned int t)
// 延时子程序,入口参数ms,延迟时间=t*1ms,t取值范围0~65535 {
unsigned char j;
//j取值范围0~255 while(t--){ for(j = 0;j < 250;j++);
//j进行的内部循环,1次延迟8us } }
void main(){
unsigned int i;
while(1)
{
unsigned char a=0xfe;
for(i=0;i<8;i++)
{
P0=a;
delaym(200);
a<<=1;
//循环左移1位
} } }
程序2 电子秒表工作程序 void main(){ TMOD=0x01;
//定时器T0工作在方式1 TH0=0xD8;
// T0装入时间常数
TL0=0xf0;
EA=1;
//允许 中断
ET0=1;
//允许 定时器T0中断
TR0=1;
//启动定时器T0
while(1)
//无条件循环
{
for(k=0;k<3;k++)
{
P0=b[k];
//送出数码管段码
P2=k;
//送出数码管位选码
delaym(3);//调用延时函数
} } } } time0_int(void)interrupt 1
//T0中断服务程序 {
TH0=0xD8;
TL0=0xF0;
i++;
if(i==100)
//到1秒
{
i=0;
second1++;
}
if(second1==10)//到10秒
{
second2++;
second1=0;
}
if(second2==10)//到100秒
{second3++;
second2=0;
second1=0;
}
b[2]=a[second3];//转换为段码显示
b[1]=a[second2];
b[0]=a[second1];
} 程序3 电子时钟程序 void main(){ TMOD=0x01;
//定时器T0工作在方式1 TH0=0xD8;
// T0延时长度延时10mS TL0=0xf0;
EA=1;
//允许中断
ET0=1;
//允许定时器T0中断 TR0=1;while(1){
for(k=0;k<5;k++)
//显示时钟的分秒
{
P0=b[k];
P2=k;
delaym(3);
} } } time0_int(void)interrupt 1
//T0中断服务程序 {
TH0=0xD8;
TL0=0xF0;
i++;
if(i==100)
//到1秒
{
i=0;
second++;
}
if(second==60)//到1分钟
{second=0;
fen++;
}
w=fen/10;
x=fen%10;
y=second/10;
z=second%10;
b[4]=a[w];
//显示分十位段码
b[3]=a[x];
//显示分个位段码
b[2]=0xbf;
//数码管显示“-”
b[1]=a[y];
//显示秒十位段码
b[0]=a[z];
//显示秒个位段码
}
程序4
矩阵式按键扫描,读取按键的行列编码。unsigned char Keycan(void){
unsigned char rcode, ccode;
P1 = 0xF0;
// P1口对键盘输出 键盘扫描信号
if((P1&0xF0)!= 0xF0)
{
delay(1);// 调用岩石函数函数实现软件去抖动
if((P1&0xF0)!= 0xF0)
//如果不相等说明 有键按下
{ rcode = 0xFE;
// 逐行扫描初值
while((rcode&0x10)!= 0)
{
P1 = rcode;
// 输出行扫描码
if((P1&0xF0)!= 0xF0)//
{
ccode =(P1&0xF0)|0x0F;//取入列代码
//do{;}
while((P1&0xF0)!= 0xF0);//等待键释放
return((~rcode)+(~ccode));// 返回
}
else
rcode =(rcode<<1)|0x01;//行输出代码移1位
} }
}
return 0;// 无键按下,返回
}
程序5 按键取值计算程序
void KeyDeal(unsigned char Key)
{
if(Key!=0)
//再次判断是否有按键按下
{
switch(Key)
//对按键的行列码译码成0~15
{
case 0x11: K=0;break;
case 0x21: K=1;break;
case 0x41: K=2;break;
case 0x81: K=3;break;
case 0x12: K=4;break;
case 0x22: K=5;break;
case 0x42: K=6;break;
case 0x82: K=7;break;
case 0x14: K=8;break;
case 0x24: K=9;break;
case 0x44: K=10;break;
case 0x84: K=11;break;
case 0x18: K=12;break;
case 0x28: K=13;break;
case 0x48: K=14;break;
case 0x88: K=15;break;
default: break;
}
if(K<17)
{
c[7]=c[6];
//改变显示位置,显示代码顺序前移
c[6]=c[5];
c[5]=c[4];
c[4]=c[3];
c[3]=c[2];
c[2]=c[1];
c[1]=c[0];
c[0]=b[K];
//将0~15译成段码供显示
}
} } 程序6 从计算机键盘输入月份,通过计算机串口显示相应的日子。#include
//包含8952单片机头文件 #include
//包含计算机串口头文件 #define uint unsigned int void main(){ uint month;uint day;SCON=0x50;
//串行口工作在方式1,允许接收。
TMOD=0x20;//定时器1工作在方式2
TCON=0x40;TH1=0xE8;
//装入波特率对应的时间常数
TL1=0xE8;TI=1;TR1=1;printf(“input monthn”);//要求输入月份
scanf(“%d”,&month);//读取键盘输入的月份数值。
switch(month)//查阅月份
{ case 1: case 3: case 5: case 7: case 8: case 10: case 12:day=31;//1、3、5、7、8、10、12上显示31日
break;case 4: case 6: case 9: case 11:day=30;//4、6、9、11显示30日
break;case 2:day=28;//2月份显示28日
break;default:day=1;//都不是,日期1
} printf(“month,has dayn”,&month,&day);//在屏幕上显示对应月份的日期
}
程序7 延时1秒子程序A void delay(void)
{ unsigned char m,n,s;//定义m,n,s为无符号字符型数值(0~255)
for(m=10;m>0;m--)
//m的用途控制第1重循环
for(n=100;n>0;n--)
for(s=124;s>0;s--);
//3个for指令总延时1mS×100× 10=1秒
延时子程序B void delay(unsigned char t){
while(t--)
// 由调用程序规定延时时间
{
unsigned char j;//j的取值范围 0~255
for(j = 0;j<123;j++);//语句 延时1mS
} }
程序8 数码显示程序 unsigned char b[17]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xff};数码管段码,加1个全灭段码
unsigned char c[8];//规定显示段码数组为8
void display()
{ unsigned char i;
for(i=0;i<8;i++)//显示8个数据
{
P0=c[i];//送出段码
P2=i;//送出位选码
delay(2);
} } 程序96 串口接收程序
#include
//规定串口工作方式1,允许接收。,PCON=0;
//SMOD=0 TMOD= 0x20;
//定时器1 工作在定时方式2
TH1= 0xe6;
//12MHz 1200波特率
//16个
TL1= 0xe6;TR1= 1;
//启动钉子定时器1
while(1)
//不断接收数据 { for(i=0;i<9;i++)
{
while(RI==0)
//查询接收一帧数据完毕否
RI=0;
buf[i]=SBUF;
//从串口接收数据存入数组 buf[]
} } } 程序10 串口发送程序,发送89S51 #include
uint j;SCON= 0x40;
//规定串口工作在方式1,发送数据
PCON=0;
//SMOD=0 TMOD= 0x20;
//定时器1工作在方式2
TH1= 0xe6;
//12MHz 1200波特率 TL1= 0xe6;TR1= 1;
//启动定时器1
while(1)
//不断发送数据
{
i=0;
while(trdata[i]!=0x00)
//00结束 { SBUF=trdata[i];
//将数据送到串口发送器SBUF
while(TI==0);
TI=0;
i++;
}
for(j=0;j<50000;j++);//延时8μS×50000 =0.4秒
} }
第四篇:专利意见程序分析
关于专利法对公众意见的处理程序分析
1984年专利法第41条和第42条规定了异议程序的法律效力:“第四十一条 专利申请自公告之日起三个月内,任何人都可以依照本法规定向专利局对该申请提出异议。专利局应当将异议的副本送交申请人,申请人应当在收到异议副本之日起三个月内提出书面答复;无正当理由逾期不提出书面答复的,该申请即被视为撤回。第四十二条 专利局经审查认为异议成立的,应当作出驳回申请的决定,并通知异议人和申请人。”
在1992年修改专利法时,专利异议的程序都删去了,所以说从1992年起已经没有异议程序了,授权前只能提“意见”并仅供参考。2010年新颁布的专利法第48条规定:“自发明专利申请公布之日起至公告授予专利权之日止,任何人均可以对不符合专利法规定的专利申请向国务院专利行政部门提出意见,并说明理由。”
新专利法审查指南中关于对公众意见的处理的原话摘录细则: “任何人对不符合专利法规定的发明专利申请向专利局提出的意见,应当存入该申请文档中供审查员在实质审查时考虑。如果公众的意见是在审查员发出授予专利权的通知之后收到的,就不必考虑。专利局对公众意见的处理情况,不必通知提出意见的公众。”
通过分析我国现行的专利审查公众意见提交制度,我们不难发现:无论是专利法实施细则还是《专利审查指南》,都没有关于公众意见的法律地位、作用,以及社会公众如何提供意见等更为细致的规定,尤其是对公众意见的使用情况,审查员是没有义务告知提出意见的公众的,也就是说,我国目前的专利审查公众意见提交制度中是不存在反馈机制的。
从法律层面讲,关于专利意见的提交没有明确的程序规定,具体事宜周一将和专利事务所的专家进行商讨。
第五篇:程序法案例分析
程序法案例分析
案例1
2002年9月30日,河南省孟州市质量技术监督局稽查人员对河南省孟州市电业乐万家有限责任公司经销的商品进行执法检查时,发现该公司销售的“五粮液”酒防伪标签无暗记标记,涉嫌假冒,遂当场对该公司的177瓶“五粮液”酒进行了封存,并在公证人员的现场公证下提取酒样品,经中国宜宾五粮液股份有限公司进行鉴定为假冒五粮液产品。之后,孟州市质量技术监督局对剩余的176瓶“五粮液”酒予以登记扣押。由于此案涉及货值金额和社会影响较大,孟州市质量技术监督局依法将此案移交给该局上级单位焦作市质量技术监督局。同年11月26日,焦作市质量技术监督局向孟州市电业乐万家有限责任公司送达了《行政处罚告知书》,确认该公司经销的“五粮液”酒系以假充真产品,已违反了我国《产品质量法》第39条的规定,将依据我国《产品质量法》第50条的规定给予行政处罚。要求该公司在11月28日前将陈述意见送到焦作市质量技术监督局,逾期视为放弃权利;并说明如要求公开听证,应于收到告知书之日起3日内提出,逾期未提出的,视为放弃权利。孟州市电业乐万家有限责任公司在收到焦作市质量技术监督局送达告知书的当天就用邮政快件邮送了陈述意见,同时提出公开听证的申请。可是次日,即11月29日,焦作市质量技术监督局就向该公司送达了(豫焦)质技监罚字[2002]第067号《行政处罚决定书》:
1、责令停止销售以假充真的“五粮液”酒。
2、没收176瓶以假充真的“五粮液”酒。
3、并处该公司以假充真“五粮液”酒货值金额二倍罚款94560元。孟州市电业乐万家有限责任公司对此处罚决定不服,以焦作市质量技术监督局„„„„为由,向焦作市解放区法院提起行政诉讼。
讨论内容:
1.技监局的执法有无疑点?
2.孟州市电业乐万家有限责任公司的起诉理由可以是什么?
案例2
2002年8月13日,某市星光大酒店接待了一批“特殊客人”。这些客人用他们自带的秤称了他们所点的海鲜后,亮出了工作证:市质量技术监督局,并指出,他们的海鲜缺斤少两。检查结束后,拿出一张临时手写的便条式的检查证明要求店方签字,店方觉得事态严重,没有签。店方解释称是厨师抓海鲜时将两个包厢的海鲜搞混了,并让厨师亲自向检查人员解释。8月24日,市质量技术监督局举行了一个新闻发布会,指出经过明查暗访,发现多家宾馆、酒楼的海鲜缺斤少两,并指出,按销售单价计算,星光大酒店一次克扣消费者金额最多。各大媒体对此纷纷作了报道。星光成了众矢之的。9月15日,星光大酒店以市质量技术监督局认定自己“故意缺斤少两、克扣消费者”的行为已构成行政侵权为由,要求法院判定其通报批评的行为违法,令其为星光恢复名誉、消除不良影响。
问:此案争论的焦点是什么?
若是作为被告应进行怎样的辩论?
若是原告呢?他们会从哪些方面对被告的行为进行起诉呢?
案例三简易程序的细节
某单位执法人员在处理一件违章案件时,在证据确凿的前提下对当事人做出处罚决定。当事人对行政处罚告知书的内容无异议,对处罚金额也无异议。并在告知书上写下“放弃陈述和申辩权利”,并按要求签下自己的名字,落款日期是某年某月某日。随后,执法人员给当事人开具行政处罚决定书,当事人也在送达回证签上名,落款的日期也是某年某月某日。表面上看,这个案件调查取证程序合法,证据确凿,法律文书制作程序上也并无大的疏漏,案件办理得天衣无缝。但就是这样一起看似已办成铁案的案件,时过数日,当事人却一纸诉状将行政执法机关告上法院,声称执法人员在程序上违法。在法庭对质时,行政机关举充足的证据证明当事人的行为是违法行为,且当事人已在陈述告知笔录上表示放弃申辩权利。而当事人的辩护律师却辩称:„„„„„(由学生添加)案例四执法程序错误行政诉讼案
一、案情简介
1997 年9 月17 日,某省技术监督局接到群众投诉,称其所购买的由鸿鑫企业集团有限责任公司开发的怡园公寓商品房面积不足,要求维护购房者利益。根据群众投诉,省局稽查大队进行了调查,并委托省房地产计量公正站进行实地测量。测量结果表明,有住户投诉的4 栋商品房每套实际建筑面积都少于销售建筑面积,其计量偏差不符合供需双方事先约定的1 %。省局拟对鸿鑫企业集团有限责任公司进行行政处罚,依照政处罚法,向该公司履行了告知程序,鸿鑫企业集团有限责任公司进行了陈述和申辩,并要求举行听证。省局认为符合听证条件,依法举行了听证。1998 年2月23 日下发了行政处罚决定书,决定对鸿鑫企业集团有限责任公司处以28000元的罚款。
鸿鑫企业集团有限公司对上述处罚不服,于1998 年2 月28日以行政处罚认定主体错误为由向人民法院提起行政诉讼。值此之时,省局经核查发现,怡园公寓真正的开发商是某房地产开发有限责任公司。该公司是鸿鑫企业集团有限责任公司的集团成员,是经工商登记注册的有独立法人资格的企业,是独立的民事法律主体,其经营行为应自行独立承担法律责任,省局主动撤销了原行政处罚决定,鸿鑫企业集团有限责任公司自愿撤诉,法院裁定撤诉。1998 年1 月26 日,省局更换了行政处罚对象,再次下发了行政处罚决定书。
某房地产开发有限责任公司和法定代表人对第二次行政处罚决定仍然不服,向法院再次提起了行政诉讼,其诉讼理由为?