Linux usb gadget框架概述
大大小小开发了四个与gadget相关的驱动,字符驱动、g_multi、g_ether、g_zero,在这里把自己对gadget的开发中自己的感悟记录之。
想要了解gadget,必须了解其框架,知道composite、gadget、udc三者之间的联系,知道usb描述符的作用。
一个usb device有一个设备描述符。
有一个或者多个配置描述符
一个配置描述符有一个或者多个接口(在gadget端,接口正式命名是usb_func)。
一个接口有0个或者多个端点。
编写gadget的关键是在于了解udc、gadget、composite三者之间的联系和架构层次,在实际应用中gadget是不需要我们去编写的,需要我们自己去编写的是composite层,以及地对udc层的修改,下面开始详细介绍着三者。
1、composite英文意思是复合的意思,估计是编写usb gadget层设备驱动都整合到一起,通过统一的函数usb_composite_register注册。功能各异,杂七杂八,所以称为复合层吧。
在该层,我们需要注意的相关结构体和函数有如下:
struct usb_composite_dev { //作为composite复合设备,所有composite设备都必须实现该设备。struct usb_gadget *gadget; //设备和gadget交互,gadget和udc交互。struct usb_request *req; //每个设备自带一个usb请求,所有的数据交互都是通过该请求发送的。struct usb_configuration *config; 一个设备有一个或者多个配置。/* private: *//* internals */unsigned int suspended:1;struct usb_device_descriptor desc; //设备描述符,唯一struct list_head configs; //配置struct list_head gstrings; //字符描述struct usb_composite_driver *driver; //设备绑定的驱动u8 next_string_id; char *def_manufacturer; //默认制造商/* the gadget driver won't enable the data pullup* while the deactivation count is nonzero.*/unsigned deactivations;/* the composite driver won't complete the control transfer's* data/status stages till delayed_status is zero.*/int delayed_status;/* protects deactivations and delayed_status counts*/spinlock_t lock;
};
307 struct usb_composite_driver { //所有compesite驱动必须填充该结构体。
308 const char *name;
309 const struct usb_device_descriptor *dev; //必须实现
310 struct usb_gadget_strings **strings;
311 enum usb_device_speed max_speed;
312 unsigned needs_serial:1;
313
314 int (*bind)(struct usb_composite_dev *cdev); //必须实现的
315 int (*unbind)(struct usb_composite_dev *); //必须实现
316
317 void (*disconnect)(struct usb_composite_dev *);
318
319 /* global suspend hooks */
320 void (*suspend)(struct usb_composite_dev *);
321 void (*resume)(struct usb_composite_dev *);
322 struct usb_gadget_driver gadget_driver; //这个地方的驱动由composite提供,所有和composite相关的驱动都会默认分配该驱动。该驱动是
323 };
852 struct usb_gadget_driver { //该驱动是usbcore和composite之间交互必不可少的一环,两者之间的联系主要靠他来维持,内核已经提供好了,不需要我们去实现。853 char *function;854 enum usb_device_speed max_speed;855 int (*bind)(struct usb_gadget *gadget,856 struct usb_gadget_driver *driver);857 void (*unbind)(struct usb_gadget *);858 int (*setup)(struct usb_gadget *, //枚举过程中必不可少的函数。不需要驱动去实现。859 const struct usb_ctrlrequest *);860 void (*disconnect)(struct usb_gadget *);861 void (*suspend)(struct usb_gadget *);862 void (*resume)(struct usb_gadget *);863 864 /* FIXME support safe rmmod */865 struct device_driver driver;866 };
1772 static const struct usb_gadget_driver composite_driver_template = { //所有的composite设备都会在注册gadet驱动的时候采用该实例填充。//笔者认为这么做的原因是gadget驱动永远只有一个,composite可以随便实现。体现分层的思想。
1773 .bind = composite_bind,
1774 .unbind = composite_unbind,
1775
1776 .setup = composite_setup,
1777 .disconnect = composite_disconnect,
1778
1779 .suspend = composite_suspend,
1780 .resume = composite_resume,
1781
1782 .driver = {
1783 .owner = THIS_MODULE,
1784 },
1785 };
下面首先介绍composite驱动的注册过程,讲完后介绍驱动的编写过程。
以zero.c为例:
static int __init init(void) { return usb_composite_register(&zero_driver); } usb_composite_register(&zero_driver); 1========》 driver->gadget_driver = composite_driver_template; //此过程并未涉及到对compoite设备的注册的操作,//而是将composite驱动中注册的相关信息填充到gadget中,利用gadget去和udc打交道---->return usb_gadget_probe_driver(gadget_driver); 该函数首先判定bind setup等函数是否实现了。不需要我们去实现。----> list_for_each_entry(udc, &udc_list, list) //查找注册在内核中的udc实例,找到了进行下一步操作,没找到退出。驱动注册失败。----> ret = udc_bind_to_driver(udc, driver); //将udc和gadget驱动绑定在一起。2======》 udc_bind_to_driver(udc, driver);---->404 ret = driver->bind(udc->gadget, driver);//最关键的莫过于该函数了,最初笔者分析的时候,以为是composite的bind函数,后来才弄清楚是gadget层//的bind函数composite_bind ,将在后面介绍。-----> ret = usb_gadget_udc_start(udc->gadget, driver); //此处可以理解为一切就绪,udc相关设置已经写入寄存器。-----> ret = usb_gadget_connect(udc->gadget); //插入host usb口,检查D+电平的变化。也就是枚举过冲3=====》 composite_bind //最重要的函数了。是理解gadget设计的关键
1672 static int composite_bind(struct usb_gadget *gadget,//该函数zhu要是实现配置描述符接口等操作。 1673 struct usb_gadget_driver *gdriver) 1674 { 1675 struct usb_composite_dev *cdev; 1676 struct usb_composite_driver *composite = to_cdriver(gdriver); 1677 int status = -ENOMEM; 1678 1679 cdev = kzalloc(sizeof *cdev, GFP_KERNEL); 1680 if (!cdev) 1681 return status; 1682 1683 spin_lock_init(&cdev->lock); 1684 cdev->gadget = gadget; 1685 set_gadget_data(gadget, cdev); 1686 INIT_LIST_HEAD(&cdev->configs); 1687 INIT_LIST_HEAD(&cdev->gstrings); 1688 1689 status = composite_dev_prepare(composite, cdev); 1690 if (status) 1691 goto fail; 1692 1693 /* composite gadget needs to assign strings for whole device (like 1694 ┊* serial number), register function drivers, potentially update 1695 ┊* power state and consumption, etc 1696 ┊*/ 1697 /*此处才是开始调用驱动的bind函数*/ 1698 status = composite->bind(cdev); 1699 if (status < 0) 1700 goto fail; 1701 1702 update_unchanged_dev_desc(&cdev->desc, composite->dev); 1703 1704 /* has userspace failed to provide a serial number? */ 1705 if (composite->needs_serial && !cdev->desc.iSerialNumber) 1706 WARNING(cdev, "userspace failed to provide iSerialNumber\n"); 1707 1708 INFO(cdev, "%s ready\n", composite->name); 1709 return 0; 1710 1711 fail: 1712 __composite_unbind(gadget, false); 1713 return status; } 1====》 composite_bind---->1676 struct usb_composite_driver *composite = to_cdriver(gdriver); //这个函数就是将gadet转换为composite的关键。在gadget驱动注册时联系在一起。//return container_of(gdrv, struct usb_composite_driver, gadget_driver); ---->cdev = kzalloc(sizeof *cdev, GFP_KERNEL); //实现cdev设备。---->set_gadget_data(gadget, cdev); //填充私有数据,以便内核中可以通过gadget获取cdev.---->INIT_LIST_HEAD(&cdev->configs); //初始化配置描述链表,在开头本人介绍过,一个设备有一个或者多种配置。---->1689 status = composite_dev_prepare(composite, cdev); //这个函数十分重要,包括usb_request、complete(回调函数实现)、设备和驱动的绑定等)---->1698 status = composite->bind(cdev) //此处才是开始调用驱动的bind函数,后面会详细介绍该函数。----> INFO(cdev, "%s ready\n", composite->name); //至此,composite设备创建成功。
1610 int composite_dev_prepare(struct usb_composite_driver *composite, //该函数主要是实现了至关重要的usb_request,针对端点0,即控制端点 1611 struct usb_composite_dev *cdev) 1612 { 1613 struct usb_gadget *gadget = cdev->gadget; 1614 int ret = -ENOMEM; 1615 1616 /* preallocate control response and buffer */ 1617 cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); //申请控制端点usb请求 1618 if (!cdev->req) 1619 return -ENOMEM; 1620 1621 cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL); //请求发送内容将保持在此 1622 if (!cdev->req->buf) 1623 goto fail; 1624 1625 ret = device_create_file(&gadget->dev, &dev_attr_suspended);//创建设备文件,位于/sys/class/dev目录下 1626 if (ret) 1627 goto fail_dev; 1628 1629 cdev->req->complete = composite_setup_complete; //回调函数。 1630 gadget->ep0->driver_data = cdev; //通过端点即可获取设备。 1631 1632 cdev->driver = composite; //将composite设备和驱动绑定在一起,所以usb gadget端是没有枚举过程的,驱动直接注册成功,创建设备。 1633 1634 /* 1635 ┊* As per USB compliance update, a device that is actively drawing 1636 ┊* more than 100mA from USB must report itself as bus-powered in 1637 ┊* the GetStatus(DEVICE) call. 1638 ┊*/ 1639 if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW) 1640 usb_gadget_set_selfpowered(gadget); 1641 1642 /* interface and string IDs start at zero via kzalloc. 1643 ┊* we force endpoints to start unassigned; few controller 1644 ┊* drivers will zero ep->driver_data. 1645 ┊*/ 1646 usb_ep_autoconfig_reset(gadget); // 1647 return 0; 1648 fail_dev1649 kfree(cdev->req->buf);1650 fail: /* 此处知识初始化了usb配置链表,并未实例化接口和端点,所以将端点driver_data又重设为空,批量输入和输出点号为0;1642 /* interface and string IDs start at zero via kzalloc. 1643 ┊* we force endpoints to start unassigned; few controller 1644 ┊* drivers will zero ep->driver_data. 1645 ┊*/ 1646 usb_ep_autoconfig_reset(gadget);*/ 1651 usb_ep_free_request(gadget->ep0, cdev->req); 1652 cdev->req = NULL;1653 return ret;1654 }
下面即将进行到至关重要的一环,调用composite驱动的bind函数。
275 static int __init zero_bind(struct usb_composite_dev *cdev) //里面是实现composite设备的关键,设计到strings,配置描述符,接口(function)、端点的实例化。 /276 { 277 struct f_ss_opts *ss_opts; 278 struct f_lb_opts *lb_opts; 279 int status; 280 281 /* Allocate string descriptor numbers ... note that string 282 ┊* contents can be overridden by the composite_dev glue. 283 ┊*/ 284 status = usb_string_ids_tab(cdev, strings_dev); 285 if (status < 0) 286 return status; 287 288 device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id;//制造商 289 device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;//产品Id 290 device_desc.iSerialNumber = strings_dev[USB_GADGET_SERIAL_IDX].id;//设备序列号功能索引,个人认为是针对多function设备而言的。 291 292 setup_timer(&autoresume_timer, zero_autoresume, (unsigned long) cdev); 293 294 func_inst_ss = usb_get_function_instance("SourceSink"); //获取function,此处的实现十分巧妙,通过usb_function_register实现。 295 if (IS_ERR(func_inst_ss)) 296 return PTR_ERR(func_inst_ss); 297 298 ss_opts = container_of(func_inst_ss, struct f_ss_opts, func_inst); 299 ss_opts->pattern = gzero_options.pattern; 300 ss_opts->isoc_interval = gzero_options.isoc_interval; 301 ss_opts->isoc_maxpacket = gzero_options.isoc_maxpacket; 302 ss_opts->isoc_mult = gzero_options.isoc_mult; 303 ss_opts->isoc_maxburst = gzero_options.isoc_maxburst; 304 ss_opts->bulk_buflen = gzero_options.bulk_buflen; //每次传送的buf大小 305 306 func_ss = usb_get_function(func_inst_ss); //获取source_link实例,同样通过usb_function_register注册获取。 307 if (IS_ERR(func_ss)) { 308 status = PTR_ERR(func_ss); 309 goto err_put_func_inst_ss; 310 } 311 312 func_inst_lb = usb_get_function_instance("Loopback"); 313 if (IS_ERR(func_inst_lb)) { 314 status = PTR_ERR(func_inst_lb); 315 goto err_put_func_ss; 316 } 317 318 lb_opts = container_of(func_inst_lb, struct f_lb_opts, func_inst); 319 lb_opts->bulk_buflen = gzero_options.bulk_buflen; //在usb_function_registe注册时就已经分配了。 320 lb_opts->qlen = gzero_options.qlen; 321 322 func_lb = usb_get_function(func_inst_lb); 323 if (IS_ERR(func_lb)) { 324 status = PTR_ERR(func_lb); 325 goto err_put_func_inst_lb; 326 } 327 328 sourcesink_driver.iConfiguration = strings_dev[USB_GZERO_SS_DESC].id;//设置配置描述符索引 329 loopback_driver.iConfiguration = strings_dev[USB_GZERO_LB_DESC].id; 330 331 /* support autoresume for remote wakeup testing */ 332 sourcesink_driver.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP; 333 loopback_driver.bmAttributes &= ~USB_CONFIG_ATT_WAKEUP; 334 sourcesink_driver.descriptors = NULL; 335 loopback_driver.descriptors = NULL; 336 if (autoresume) { 337 sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; 338 loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; 339 autoresume_step_ms = autoresume * 1000; 340 } 341 342 /* support OTG systems */ 343 if (gadget_is_otg(cdev->gadget)) { 344 sourcesink_driver.descriptors = otg_desc; 345 sourcesink_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; 346 loopback_driver.descriptors = otg_desc; 347 loopback_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; 348 } 349 350 /* Register primary, then secondary configuration. Note that 351 ┊* SH3 only allows one config... 352 ┊*/ 353 if (loopdefault) {//若只支持loopback即回环模式。 354 usb_add_config_only(cdev, &loopback_driver);//则loopback配置先注册 355 usb_add_config_only(cdev, &sourcesink_driver);//后注册 356 } else { 357 usb_add_config_only(cdev, &sourcesink_driver);//同上 358 usb_add_config_only(cdev, &loopback_driver); 359 } 360 status = usb_add_function(&sourcesink_driver, func_ss);//将功能即接口绑定到配置描述符,此处还有一次bind操作,隐藏的极深。 361 if (status) 362 goto err_conf_flb; 363 364 usb_ep_autoconfig_reset(cdev->gadget);//重新设置ep 365 status = usb_add_function(&loopback_driver, func_lb); 366 if (status) 367 goto err_conf_flb; 368 369 usb_ep_autoconfig_reset(cdev->gadget); 370 usb_composite_overwrite_options(cdev, &coverwrite);//支持传参。修改iverdor iproduct等。 371 372 INFO(cdev, "%s, version: " DRIVER_VERSION "\n", longname); 373 374 return 0; 375 376 err_conf_flb: 377 usb_put_function(func_lb); 378 func_lb = NULL; 379 err_put_func_inst_lb: 380 usb_put_function_instance(func_inst_lb); 381 func_inst_lb = NULL; 382 err_put_func_ss: 383 usb_put_function(func_ss); 384 func_ss = NULL; 385 err_put_func_inst_ss: 386 usb_put_function_instance(func_inst_ss); 387 func_inst_ss = NULL; 388 return status; 389 }
189 int usb_add_function(struct usb_configuration *config, //配置中实例化接口。190 struct usb_function *function)191 { 192 int value = -EINVAL;193 194 DBG(config->cdev, "adding '%s'/%p to config '%s'/%p\n",195 function->name, function,196 config->label, config);197 198 if (!function->set_alt || !function->disable)//接口是否设置了set_alt函数,该函数调用表示当前接口可用,其他接口不可用。199 goto done;200 201 function->config = config;202 list_add_tail(&function->list, &config->functions);203 204 /* REVISIT *require* function->bind? */205 if (function->bind) {206 value = function->bind(config, function);//对于一个配置多个接口的cdev设备,再次对function进行bin操作。207 if (value < 0) {208 list_del(&function->list);209 function->config = NULL;210 }211 } else212 value = 0;213 214 /* We allow configurations that don't work at both speeds.215 ┊* If we run into a lowspeed Linux system, treat it the same216 ┊* as full speed ... it's the function drivers that will need217 ┊* to avoid bulk and ISO transfers.218 ┊*/219 if (!config->fullspeed && function->fs_descriptors)220 config->fullspeed = true;221 if (!config->highspeed && function->hs_descriptors)222 config->highspeed = true;223 if (!config->superspeed && function->ss_descriptors)224 config->superspeed = true;225 226 done:227 if (value)228 DBG(config->cdev, "adding '%s'/%p --> %d\n",229 function->name, function, value);230 return value; 231 }
以f_loopback.c中的bind为例。
175 static int loopback_bind(struct usb_configuration *c, struct usb_function *f) //将配置和功能绑定在一起 176 { 177 struct usb_composite_dev *cdev = c->cdev; 178 struct f_loopback *loop = func_to_loop(f); 179 int id; 180 int ret; 181 182 /* allocate interface ID(s) */ 183 id = usb_interface_id(c, f);//一般从0开始配置。分配接口id号 184 if (id < 0) 185 return id; 186 loopback_intf.bInterfaceNumber = id; 187 188 id = usb_string_id(cdev);//获取字符描述符索引 189 if (id < 0) 190 return id; 191 strings_loopback[0].id = id; 192 loopback_intf.iInterface = id; 193 194 /* allocate endpoints */ 195 196 loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);//分配批量输入端点 197 if (!loop->in_ep) { 198 autoconf_fail: 199 ERROR(cdev, "%s: can't autoconfigure on %s\n", 200 f->name, cdev->gadget->name); 201 return -ENODEV; 202 } 203 loop->in_ep->driver_data = cdev; /* claim */ 204 205 loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);//分配批量输出端点 206 if (!loop->out_ep) 207 goto autoconf_fail; 208 loop->out_ep->driver_data = cdev; /* claim */ 209 210 /* support high speed hardware */ 211 hs_loop_source_desc.bEndpointAddress = 212 fs_loop_source_desc.bEndpointAddress; 213 hs_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress; 214 215 /* support super speed hardware */ 216 ss_loop_source_desc.bEndpointAddress = 217 fs_loop_source_desc.bEndpointAddress; 218 ss_loop_sink_desc.bEndpointAddress = fs_loop_sink_desc.bEndpointAddress; 219 220 ret = usb_assign_descriptors(f, fs_loopback_descs, hs_loopback_descs, //此处主要设设置usb速度。 221 ss_loopback_descs); 222 if (ret) 223 return ret; 224 225 DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", 226 ┊ (gadget_is_superspeed(c->cdev->gadget) ? "super" : 227 ┊ ┊(gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")), 228 f->name, loop->in_ep->name, loop->out_ep->name); 229 return 0; 230 }
至此gadget框架只驱动注册过程已经介绍完成。下面再来介绍下驱动的注册流程。
- 填充usb_composite_driver驱动实例,调用usb_composite_probe进行注。
- 移花接木,填充usb_composite驱动中gadget_driver,调用usb_gadget_probe_driver(gadget_driver);使得udc能够和composite设备联系起来。
- 调用udc_bind_to_driver,慢慢的将udc和composite绑定在一起。
- 调用driver->bind(udc->gadget, driver);实际上是调用composite_bind函数,该函数由内核实现。该函数主要是创建cdev设备,将真正的驱动和cdev绑定在一起。
- 此后再调用编写的驱动的bind函数。此时主要是讲cdev设备的配置和function进行填充。设备必须有配置,配置必须有接口。
- 针对多function的驱动,必须再次绑定bind函数,此次主要是设置接口id,实例化ep等。
- 前6步操作的完成,表示composite设备驱动已经注册成功了。成功了之后呢?那就涉及到对udc的操作了,udc进入请求连接状态,等待中断的响应。
- 中断响应也就是响应主设备发起的枚举操作,完成枚举过程,枚举响应主要是调用function->setup函数。枚举过程将在另外一篇文章中介绍。
Linux usb gadget框架概述相关推荐
- linux usb gadget驱动详解(一)
由于PC的推广,USB(通用串行总线)是我们最熟知的通信总线规范之一,其他的还有诸如以太网.PCIE总线和RS232串口等.这里我们主要讨论USB. USB是一个主从通信架构,但只能一主多从.其中us ...
- 使用USB Gadget框架让USB变成串口设备
1. 背景介绍 串口是一种经典且对软件工程师友好的通信方式,因为可以在直接使用echo进行通信,不需要驱动,非常的方便,但当使用串口时,往往需要用到杜邦线和usb2ttl设备,虽然软件上面方便了,但是 ...
- Linux USB驱动框架分析 【转】
转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...
- linux usb gadget驱动详解(二)
在上篇<linux usb gadget驱动详解(一)>中,我们了解到gadget的测试方法,但在最后,我们留下一个问题,就是怎样使用新的方法进行usb gadget驱动测试. 我们发现l ...
- linux usb gadget 日志
1,USB 协议入门 几种USB控制器类型:OHCI,UHCI,EHCI,XHCI 遇到过一些关于USB的东西(如下),一直没搞明白什么USB1.0/1.1/2.0/3.0之类的,当然我知道它们的各自 ...
- linux usb gadget驱动详解(三)
本文将对linux4.4.19版本usb gadget源码进行简单分析.鉴于前文反复测试U盘设备驱动,现从linux-4.4.19/drivers/usb/gadget/legacy/mass_sto ...
- linux usb gadget驱动详解(四)
现从msg_bind()函数(drivers/usb/gadget/legacy/mass_storage.c)开始讲起. U盘的gadget驱动比较复杂,因为它包含几部分,包括gadget驱动.U盘 ...
- 【随笔记】linux usb gadget ncm wrong ndp sign 问题修复
一.模拟网卡简介 在 Linux 通过 usb 模拟网卡时,有四种方式: 1. 使用 usb gadget rndis 2. 使用 usb gadget ecm 3. 使用 usb gadget nc ...
- linux 开发板模拟u盘,linux 开发板(Linux USB Gadget) 模拟u盘
前几天,搞了 hid设备,通过usb hid设备和windows进行通信.现在开始搞这个 mass storge 首先说明 环境:linux版本:3.15 使用开发板:SAMa5d3 ...
最新文章
- 计算机二级ppt为客户制作演示文稿,计算机二级ppt真题:制作介绍北京ppt
- artTemplate 简介语法模板
- 5.UiScrollable API 详细介绍
- python与java前景-java和python学习哪个未来发展前景更好?
- 优化大数据分析的五个小技巧
- [2018.07.26 T2] 背包问题
- Jmeter压力测试-微信小程序
- 树莓派3B的摄像头模块
- 金山词霸2009牛津版完整破解版+绿色精简版下载
- 主板检测卡c5_主板测试卡代码及解决方法
- DaHua工业相机开发中调试遇到的相机断开问题
- 网络控制系统分析与设计——时延问题的网络控制系统建模
- c语言卡诺图算法实现,多变量卡诺图化简的算法实现.pdf
- mac移动硬盘安装linux系统安装教程,移动硬盘上安装ubuntu系统
- t00lsudf.php,udf提权
- 印象笔记服务器自动备份,印象笔记跨平台自动备份短信图文教程
- 对字符串进行信道编码C语言,【单选题】PCM编码主要用于实现模拟信号数字化,它属于( )
A. 信道编码 B. 纠错编码 C. 信源编码 D. 压缩编码...
- 用sealed修饰的类有什么特点
- linux里的chdir()
- 法拉帝港股上市:市值76亿港元 9个月交付179艘游艇
热门文章
- excel函数公式加减乘除法大全
- 倍加福NJ50-FP-N-P1电感式传感器
- 毕业生基本要素之计算机水平,2017年非上海生源应届高校毕业生进沪就业评分办法...
- matlab生成exe执行doc太快,matlab编写的程序生成exe可执行文件的方法.doc
- 在线摸鱼网站,可以在线养各种鱼的网站,做你的鱼塘
- 装修验收自备“精明眼”居住半年就后悔
- SuperMap iPortal门户安全性最佳做法
- 6 RabbitMQ幂等性问题
- 论文的DOI号是什么东东
- 致经典初选的60首备选篇目_致经典双语朗诵篇目中英