两年前实习时的文档——MMC学习总结

1概述

驱动程序实际上是硬件与应用程序之间的中间层。在Linux操作系统中,设备驱动程序对各种不同的设备提供了一致的访问接口,把设备映射成一个特殊的设备文件,用户程序可以像其他文件一样对设备文件进行操作。

Linux2.6引入了新的设备管理机制kobject,通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,是构成Linux2.6设备模型的核心结构,它与sysfs文件系统紧密联系,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。

在这些内核对象机制的基础上,Linux的设备模型包括设备结构device、驱动程序driver、总线bus和设备类结构class几个关键组件。

一个现实的linux设备和驱动通常都需要挂接在一种总线上,比较常见的总线有USB、PCI总线等。但是,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附于此类总线。基于这样的背景下,2.6内核加入了platform虚拟总线。Platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源时使用统一的接口,这样提高了程序的可移植性。

Platform设备概念的引入是能够更好地描述设备的资源信息。Platform设备是系统中自治的实体,包括基于端口的设备、外围总线和集成入片上系统平台的大多数控制器,它们通常直接通过CPU的总线寻址。每个platform设备被赋予一个名称,并分配一定数量的资源。

Platform总线对加入到该总线的设备和驱动分别封装了结构体——platform_device和platform_driver并且提供了对应的注册函数。

图1 Platform虚拟总线

由上图可知,在platform虚拟总线上我们分别对device和driver进行注册,这样我们能够更加方便的进行驱动设备的管理。这样当有总线或者设备注册到该虚拟总线上时,内核自动的调用platform_match函数将platform_device绑定到platform_driver上。

2  SDIO启动过程

在kernel启动时,内核会自动的调用MODULE_INIT宏对模块进行加载,MODULE_INIT声明了模块的入口函数。在MMC中我们模块的执行顺序如下图所示:

图2 SDIO启动过程

在这个过程中内核首先调用xxx_init函数,init函数对device进行注册,无论什么设备在内核中都会调用driver_register函数,driver_register函数经过一系列的调用,最终会调用探测函数probe(后面详细讲解)。probe函数会对模块进行探测工作,不断的对SD/MMC/SDIO卡进行扫描。

3  platform相关

3.1  platform数据结构

Linux在启动的时候就注册了platform总线,看内核源码:

struct bus_type platform_bus_type = {

.name             ="platform",

.dev_attrs       = platform_dev_attrs,

.match            =platform_match,

.uevent           =platform_uevent,

.pm         =&platform_dev_pm_ops,

};

可以看到,总线中定义了.match函数,当有总线或者设备注册到platform总线时,内核自动调用.match函数,判断device和driver的name是否一致。

platform_device的结构体定义如下:

struct platform_device {

constchar       * name;//设备名字,这将代替device->dev_id,用作sys/device下显示目录名

int          id;//设备ID,用于给插入该总线并且具有相同name的设备编号,如果只有一个设备的话填-1

structdevice   dev;//结构体中内嵌的device结构体

u32         num_resources;//资源数

structresource * resource;//用于存放资源的数组

conststruct platform_device_id     *id_entry;

/*arch specific additions */

structpdev_archdata      archdata;

};

可以看出,在platform_device中定义了name,并且内嵌了structdevice结构体。另外,包含的structresource如下:

struct resource {

resource_size_tstart;

resource_size_tend;

constchar *name;

unsignedlong flags;

structresource *parent, *sibling, *child;

};

platform_driver的结构体定义如下:

struct platform_driver {

int(*probe)(struct platform_device *);

int(*remove)(struct platform_device *);

void(*shutdown)(struct platform_device *);

int(*suspend)(struct platform_device *, pm_message_t state);

int(*resume)(struct platform_device *);

structdevice_driver driver;

conststruct platform_device_id *id_table;

};

可以看出,在platform_driver中内嵌了structdevice_driver,以及一些回调函数。structdevice_driver的具体定义如下:

struct device_driver {

constchar              *name;

int(*probe) (struct device *dev);

int(*remove) (struct device *dev);

void(*shutdown) (struct device *dev);

int(*suspend) (struct device *dev, pm_message_t state);

int(*resume) (struct device *dev);

conststruct attribute_group **groups;

conststruct dev_pm_ops *pm;

structdriver_private *p;

};

此处定义了name用于和platform_device匹配。因为platform_device和platform_driver的匹配就是通过内嵌的structdevice和structdevice_driver的name进行匹配的。

static int platform_match(structdevice *dev, struct device_driver *drv)

{

structplatform_device *pdev = to_platform_device(dev);

structplatform_driver *pdrv = to_platform_driver(drv);

/*match against the id table first */

if(pdrv->id_table)

returnplatform_match_id(pdrv->id_table, pdev) != NULL;

/*fall-back to driver name match */

return(strcmp(pdev->name, drv->name) == 0);

}

在数据结构设计上,总线、设备及驱动三者相互关联platform  device包含device,根据device可以获得相应的bus及driver。

设备添加到总线上之后形成一个双向循环链表,根据总线可以获得其上挂接的所有device,进而获得了platform  device。根据device也可以获得驱动该总线上所有设备相应的driver。Platform包含driver,根据driver获得相应的bus,进而获得bus上所有的device,进一步获得platformdevice。根据name对driver和platform  device进行匹配,匹配成功后将device与相应的driver关联起来,即实现了platformdevice与platformdriver的关联。

匹配成功后调用driver的probe进而调用platformdriver的probe,在probe里实现驱动特定的功能。

Match函数只是简单的进行字符串匹配,这就是强调platformdevice和platform  driver中那么属性需要一致的原因。

3.2  platform device

3.2.1  register

注册一个platform层的设备。注册后,会在sys/device目录下创建一个以name命名的目录,并且创建软连接到/sys/bus/platform/device下。

int platform_device_register(structplatform_device *pdev)

{

device_initialize(&pdev->dev);

returnplatform_device_add(pdev);

}

其中,第一步device_initialize(在kernel文件夹下)用于初始化一个structdevice。函数的定义如下:

void device_initialize(struct device*dev)

{

dev->kobj.kset= devices_kset;

kobject_init(&dev->kobj,&device_ktype);

INIT_LIST_HEAD(&dev->dma_pools);

mutex_init(&dev->mutex);

lockdep_set_novalidate_class(&dev->mutex);

spin_lock_init(&dev->devres_lock);

INIT_LIST_HEAD(&dev->devres_head);

device_pm_init(dev);

set_dev_node(dev,-1);

}

第二步,添加一个platform device到device层 。函数的定义如下:

int platform_device_add(structplatform_device *pdev)

{

inti, ret = 0;

if(!pdev)

return -EINVAL;

if(!pdev->dev.parent)

pdev->dev.parent= &platform_bus;//如果p->dev.parent不存在则赋值&platform_bus

pdev->dev.bus= &platform_bus_type;//设置pdev->dev.bus的bus类型

if(pdev->id != -1)    //pdev->id!=-1说明存在多于一个的设备

dev_set_name(&pdev->dev,"%s.%d", pdev->name, pdev->id);

Else   //否则对唯一的设备进行命名

dev_set_name(&pdev->dev,"%s", pdev->name);

for(i = 0; i < pdev->num_resources; i++) {

//遍历资源并且资源加入到资源数组中

struct resource *p, *r =&pdev->resource[i];

if (r->name == NULL)

r->name= dev_name(&pdev->dev);

p = r->parent;

if (!p) {

if(resource_type(r) == IORESOURCE_MEM)

p = &iomem_resource;

elseif (resource_type(r) == IORESOURCE_IO)

p = &ioport_resource;

}

if (p && insert_resource(p, r)) {

printk(KERN_ERR

"%s: failed to claim resource%d\n",

dev_name(&pdev->dev), i);

ret= -EBUSY;

gotofailed;

}

}

pr_debug("Registeringplatform device ‘%s‘. Parent at %s\n",

dev_name(&pdev->dev),dev_name(pdev->dev.parent));

ret= device_add(&pdev->dev);//(在core/core.c中定义)

if(ret == 0)

return ret;

failed:

while(--i >= 0) {

struct resource *r =&pdev->resource[i];

unsigned long type = resource_type(r);

if (type == IORESOURCE_MEM || type ==IORESOURCE_IO)

release_resource(r);

}

returnret;

}

3.2.2  unregister

platform_device_unregister用于注销一个platform-leveldevice。函数的定义如下:

voidplatform_device_unregister(struct platform_device *pdev)

{

platform_device_del(pdev);

platform_device_put(pdev);

}

在注销一个设备时,第一步调用platform_device_del函数。此函数的定义如下:

void platform_device_del(struct platform_device*pdev)

{

inti;

if(pdev) {

device_del(&pdev->dev);

//遍历所有的resource如果是IORESOURCE_MEM或者IORESOURCE_IO类型则调用release_resource函数释放掉resource。

for (i = 0; i <pdev->num_resources; i++) {

structresource *r = &pdev->resource[i];

unsignedlong type = resource_type(r);

if(type == IORESOURCE_MEM || type == IORESOURCE_IO)

release_resource(r);

}

}

}

此函数的作用是:移除一个platform-leveldevice。其中的IOSOURCE_MEM和IORESOURCE_IR是CPU对外设I/O端口物理地址的两种编制方式。Resource是一个指向platform资源数组的指针,该数组有num_resource个资源,下面是资源结构体的定义(linux/ioport.h):

struct resource {

resource_size_tstart;  //起始地址

resource_size_tend;   //终止地址

constchar *name;     //名称

unsignedlong flags;    //标志

structresource *parent, *sibling, *child;

};

在structplatform_device中可以设置多种资源信息。资源的flags标志包括:

#define  IORESOURCE_IO      0x00000100   //IO资源

#define  IORESOURCE_MEM   0x00000200  //内存资源

#define  IORESOURCE_IRQ    0x00000400  //中断资源

#define  IORESOURCE_DMA   0x00000800  //DMA资源

第二步,调用platform_device_put函数。

void platform_device_put(structplatform_device *pdev)

{

if(pdev)

put_device(&pdev->dev);

}

销毁一个platformdevice,并且释放所有与这个platformdevice相关的内存。

3.3  platform driver

3.3.1  register

通过调用函数platform_driver_register实现为platformlevel的设备注册一个驱动。注册成功后,内核会在/sys/bus/platform/driver/目录下创建一个名字为driver->name的目录。具体的函数定义如下:

int platform_driver_register(structplatform_driver *drv)

{

drv->driver.bus= &platform_bus_type;

if(drv->probe)

drv->driver.probe =platform_drv_probe;

if(drv->remove)

drv->driver.remove =platform_drv_remove;

if(drv->shutdown)

drv->driver.shutdown =platform_drv_shutdown;

returndriver_register(&drv->driver);

}

此函数首先对struct  platform_driver变量的driver进行赋值。然后调用driver_register并且返回。driver_register函数的定义如下:

int driver_register(structdevice_driver *drv)

{

intret;

structdevice_driver *other;

BUG_ON(!drv->bus->p);

//如果driver的方法和总线上的方法不能匹配则驱动的名称需要更新

if((drv->bus->probe && drv->probe) ||

(drv->bus->remove &&drv->remove) ||

(drv->bus->shutdown &&drv->shutdown))

printk(KERN_WARNING "Driver ‘%s‘needs updating - please use "

"bus_typemethods\n", drv->name);

other= driver_find(drv->name, drv->bus);

if(other) {

put_driver(other);

printk(KERN_ERR "Error: Driver ‘%s‘is already registered, "

"aborting...\n",drv->name);

return -EBUSY;

}

ret= bus_add_driver(drv);//将驱动加载到总线上,如果成功就返回

if(ret)

return ret;

ret= driver_add_groups(drv, drv->groups);

if(ret)

bus_remove_driver(drv);

returnret;

}

3.3.2  unregister

通过调用函数platform_driver_unregister函数实现platform_driver级设备的注销。

void platform_driver_unregister(structplatform_driver *drv)

{

driver_unregister(&drv->driver);

}

在此函数中调用了driver_unregister函数,将驱动从系统中移除。函数的具体定义如下:

void driver_unregister(structdevice_driver *drv)

{

if(!drv || !drv->p) {

WARN(1, "Unexpected driverunregister!\n");

return;

}

driver_remove_groups(drv,drv->groups);

bus_remove_driver(drv);

}

分两步走,第一步调用driver_remove_groups。函数的具体定义如下:

static voiddriver_remove_groups(struct device_driver *drv,

const struct attribute_group **groups)

{

inti;

if(groups)

for (i = 0; groups[i]; i++)

sysfs_remove_group(&drv->p->kobj,groups[i]);

}

其中,调用了

static inline voidsysfs_remove_group(struct kobject *kobj,

const struct attribute_group *grp)

{

}

第二步从总线中将驱动删除。调用如下函数:

void bus_remove_driver(structdevice_driver *drv)

{

if(!drv->bus)

return;

if(!drv->suppress_bind_attrs)

remove_bind_files(drv);

driver_remove_attrs(drv->bus,drv);

driver_remove_file(drv,&driver_attr_uevent);

klist_remove(&drv->p->knode_bus);

pr_debug("bus:‘%s‘: remove driver %s\n", drv->bus->name, drv->name);

driver_detach(drv);

module_remove_driver(drv);

kobject_put(&drv->p->kobj);

bus_put(drv->bus);

}

此函数的作用是:将驱动从它控制的设备中卸载,并且从驱动的总线链表中将它移除。

4probe函数

在kernel加载模块的时候启动了设备注册函数,因为所有的设备的driver都继承自device_driver,所以从driver_register看起,函数的源码如下:

int driver_register(structdevice_driver * drv)

{

if ((drv->bus->probe &&drv->probe) ||

(drv->bus->remove &&drv->remove) ||

(drv->bus->shutdown &&drv->shutdown)) {

printk(KERN_WARNING "Driver ‘%s‘needs updating - please use bus_type methods\n", drv->name);

}

klist_init(&drv->klist_devices,NULL, NULL);

return bus_add_driver(drv);

}

klist_init不相关,不用管他,具体再去看bus_add_driver:

int bus_add_driver(structdevice_driver *drv)

{

1.先kobject_set_name(&drv->kobj,"%s", drv->name);

2.再kobject_register(&drv->kobj)

3.然后调用了:driver_attach(drv)

}

int driver_attach(struct device_driver * drv)

{

return bus_for_each_dev(drv->bus,NULL, drv, __driver_attach);

}

真正起作用的是__driver_attach:

static int __driver_attach(structdevice * dev, void * data)

{

……

if (!dev->driver)

driver_probe_device(drv, dev);

……

}

int driver_probe_device(struct device_driver * drv, struct device * dev)

{

……

//1.先是判断bus是否match:

if (drv->bus->match &&!drv->bus->match(dev, drv))

goto done;

//2.再具体执行probe:

ret = really_probe(dev, drv);

……

}

really_probe才是我们要找的函数:

static int really_probe(struct device *dev, struct device_driver *drv)

{

……

//1.先是调用的驱动所属总线的probe函数:

if (dev->bus->probe) {

ret = dev->bus->probe(dev);

if (ret)

goto probe_failed;

} else if (drv->probe) {

//2.再调用你的驱动中的probe函数:

ret = drv->probe(dev);

if (ret)

goto probe_failed;

}

……

}

其中,drv->probe(dev),才是真正调用的驱动实现的具体的probe函数。从此出开始probe函数正式被调用。

至此,platform成功挂接到platform  bus上了,并与特定的设备实现了绑定,并对设备进行了probe处理。

5request函数

当SDIO设备启动之后,probe函数会调用mmc_alloc_host函数不断的检测连接的MMC/SD/SDIO卡,并且通过device_add完成对设备的添加,当设备添加完成之后,会调用mmc_blk_probe完成驱动定义的特定功能。函数的调用关系图如下:

图3 request调用过程

Request函数的定义如下:

static void

v8sdio_request( struct mmc_host *mmc,struct mmc_request *mrq )

{

unsignedlong iflags = 0;

structv8sdio_host *host = mmc_priv( mmc );//首先将struct  mmc_host设备转化成

//struct  v8sdio_host

#if IRQ_STAT_DBG

if(host->id == DEBUG_CHN )

{

do_gettimeofday( &tv_now );//获取时间

timersub( &tv_now, &tv_last,&tv_delta );

if( tv_delta.tv_sec >= 5 )

{

u32i = 0;

tv_last.tv_sec  = tv_now.tv_sec;

//                   tv_last.tv_usec= tv_now.tv_usec;

printk("\n" );

printk("[sdio%d_irq]: irq_ALL = %u\n", host->id, host->irq_cnt[0] );

printk("[sdio%d_irq]: irq_ERR = %u\n", host->id, host->irq_cnt[1] );

printk("[sdio%d_irq]: irq_DMA = %u\n", host->id, host->irq_cnt[2] );

printk("[sdio%d_irq]: irq_CMD = %u\n", host->id, host->irq_cnt[3] );

printk("[sdio%d_irq]: irq_TRN = %u\n", host->id, host->irq_cnt[4] );

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

{

if( cmd_stats[i] )

{

printk( "[sdio%d_cmd]: cmd[%02u] =%u\n", host->id, i, cmd_stats[i] );

}

}

}

}

#endif

V8LOGV(V8TAG_SDIO, "" );

if(host->mrq )

V8LOGW( V8TAG_SDIO, "[Ch%d]host->mrq is NOT NULL.", host->id );

clk_enable(host->aclk );   //使能主机aclk

host->mrq= mrq;        //请求队列赋值

local_irq_save(iflags);    //保存本地中断标志

v8sdio_prepare_data(host, mrq );  //开启DMA通道,并且填充lli

v8sdio_start_command(host, mrq->cmd, mrq->data );  //开始执行命令

local_irq_restore(iflags);            //重新保存本地中断标志

}

6rescan过程

内核通过mmc_rescan(drivers/mmc/core/core.c)不断扫描MMC/SD卡:

void mmc_rescan(struct work_struct*work)

{

structmmc_host *host =

container_of(work, struct mmc_host,detect.work);

u32ocr;

interr;

unsignedlong flags;

intextend_wakelock = 0;

spin_lock_irqsave(&host->lock,flags);

if(host->rescan_disable) {

spin_unlock_irqrestore(&host->lock,flags);

return;

}

spin_unlock_irqrestore(&host->lock,flags);

mmc_bus_get(host);             //取得总线

/*如果是个已经注册过的卡, 检查它是否存在 */

if((host->bus_ops != NULL) && host->bus_ops->detect &&!host->bus_dead)

host->bus_ops->detect(host);

/*如果卡已经被移除,总线将被标记为死卡

* —声明唤醒锁

* 使得用户空间能够相应*/

if(host->bus_dead)

extend_wakelock = 1;

mmc_bus_put(host);

mmc_bus_get(host);

/*如果当前还有卡,将它停止*/

if(host->bus_ops != NULL) {

mmc_bus_put(host);

goto out;

}

/*检查新插入的卡 */

/*

* 只有我们能够添加新的处理器, 所以在这里释放锁是安全的。

*/

mmc_bus_put(host);

if(host->ops->get_cd && host->ops->get_cd(host) == 0)

goto out;

mmc_claim_host(host);

mmc_power_up(host);

#ifdef CONFIG_MMC_VC088X

if(host->caps & MMC_CAP_SDIO_IRQ){

sdio_reset(host);

}

#else

sdio_reset(host);

#endif

mmc_go_idle(host);//发送CMD0使卡进入IDLE状态

mmc_send_if_cond(host,host->ocr_avail);

/*

* 首先我们搜索SDIO...

*/

err= mmc_send_io_op_cond(host, 0, &ocr);

if(!err) {

if (mmc_attach_sdio(host, ocr))

mmc_power_off(host);

extend_wakelock = 1;

goto out;

}

/*

* ...然后普通的SD...

*/

err= mmc_send_app_op_cond(host, 0, &ocr);

if(!err) {

if (mmc_attach_sd(host, ocr))

mmc_power_off(host);

extend_wakelock = 1;

goto out;

}

/*

* ...最后MMC.

*/

err= mmc_send_op_cond(host, 0, &ocr);

if(!err) {

if (mmc_attach_mmc(host, ocr))

mmc_power_off(host);

extend_wakelock = 1;

goto out;

}

mmc_release_host(host);

mmc_power_off(host);

out:

if(extend_wakelock)

wake_lock_timeout(&mmc_delayed_work_wake_lock,HZ / 2);

else

wake_unlock(&mmc_delayed_work_wake_lock);

if(host->caps & MMC_CAP_NEEDS_POLL)

mmc_schedule_delayed_work(&host->detect,HZ);

}

在设置完每个卡进入空状态,而不管当前卡是存在于何种状态之后调用了mmc_send_if_cond命令,此命令的定义如下:

int mmc_send_if_cond(struct mmc_host*host, u32 ocr)

{

structmmc_command cmd;

interr;

staticconst u8 test_pattern = 0xAA;

u8result_pattern;

/*

* To support SD 2.0 cards, we must alwaysinvoke SD_SEND_IF_COND

* before SD_APP_OP_COND. This command willharmlessly fail for

* SD 1.0 cards.

*/

cmd.opcode= SD_SEND_IF_COND;

cmd.arg= ((ocr & 0xFF8000) != 0) << 8 | test_pattern;

cmd.flags= MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;

err= mmc_wait_for_cmd(host, &cmd, 0);

if(err)

return err;

if(mmc_host_is_spi(host))

result_pattern = cmd.resp[1] & 0xFF;

else

result_pattern = cmd.resp[0] & 0xFF;

if(result_pattern != test_pattern)

return -EIO;

return0;

}

流程图如下:

图7  SD卡的状态图

⑴取得总线

⑵检查总线操作结构指针bus_ops,如果为空,则重新利用各总线对端口进行扫描,检测顺序依次为:SDIO、NormalSD、MMC。当检测到相应的卡类型后,就使用mmc_attach_bus()把相对应的总线操作与host连接起来。

voidmmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)

{

...

host->bus_ops = ops;

...

}

⑶初始化卡按以下流程初始化:

①发送CMD0使卡进入IDLE状态

②发送CMD8,检查卡是否SD2.0。SD1.1是不支持CMD8的,因此在SD2.0Spec中提出了先发送CMD8,如响应为无效命令,则卡为SD1.1,否则就是SD2.0(请参考SD2.0Spec)。

③发送CMD5读取OCR寄存器。

④发送ACMD55、CMD41,使卡进入工作状态。MMC卡并不支持ACMD55、CMD41,如果这步通过了,则证明这张卡是SD卡。

⑤如果d步骤错误,则发送CMD1判断卡是否为MMC。SD卡不支持CMD1,而MMC卡支持,这就是SD和MMC类型的判断依据。

⑥如果ACMD41和CMD1都不能通过,那这张卡恐怕就是无效卡了,初始化失败。

 

两年前实习时的文档——MMC学习总结

时间: 2024-11-03 05:44:17

两年前实习时的文档——MMC学习总结的相关文章

两年前实习时的文档——Platform学习总结

1  概述 驱动程序实际上是硬件与应用程序之间的中间层.在Linux操作系统中,设备驱动程序对各种不同的设备提供了一致的访问接口,把设备映射成一个特殊的设备文件,用户程序可以像其他文件一样对设备文件进行操作. Linux2.6引入了新的设备管理机制kobject,通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,是构成Linux2.6设备模型的核心结构,它与sysfs文件系统紧密联系,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录.

使用Git Wiki 管理文档时,文档编写的基本用法

自己初次接触GitLab,通过百度和自己查找资料,了解了一部分.在自己的工作中,主要用到GitLab的Wiki文档版本管理能力.我总结了一小部分文本编辑需要用到的东西. 一.文本的排版 为了让文本/文档的结构清晰明了,我们需要一下标题结构和文本格式.Wiki 主要的文本编辑语法用到的是Markdown.Markdown语法兼容HTML,可以直接在文档里用HTML撰写,只是有一些区块元素<div><table><pre><p>等标签,必须在前后加空行与其他内容

如何在使用itext生成pdf文档时给文档添加背景图片

这个问题我在网上搜了很久,没有找到什么解决方案,需求其实很简单,就是添加背景图片.在解决这个问题之前,我们需要了解什么是背景图片?背景图片就是位于文档最底层的图片,文字和其他内容可以浮在它的上面.这又分为两种情况,一是局部的背景图片,一是全局的背景图片.局部的背景图片一般很少遇到,这里我要加的是整体上的一个背景图片.它往往是一些单纯的颜色图片,跟网页的背景图片一样.所以怎么做?我突发奇想,just do it,把图片加进去再说,试试居然成功了,很简单,像往常一样将图片加载到文档即可,只不过,要将

产品需求文档的学习记录(五)

在产品和技术领域里都有UML的技能知识,而对于产品人员的UML则更多的是指用例图,也就是我所称呼的用户流程图.在讲PRD文档写作的第二篇文章里,我提到了用户流程图的制作,实际上用户流程图是我在产品规则的初期对用例图的一种结构化的表达方式,由于以结构化的方式描述用例太抽象,缺少逻辑性表达,并且那篇文章更偏向于功能性用户流程,还不是实际意义上的用例,因此今天我补文一篇,细讲一下UML用例图和用例文档. 用例文档是由多个用例组成的一份文档,主要用于技术开发与测试使用,他是PRD中的重要辅助文档,用于讲

产品需求文档的学习记录(四)

前三篇文章我们逐步梳理了产品的信息结构.框架结构.界面结构(原型),这一步我们就要根据之前完成的工作,开始正式撰写产品需求文档了(PRD文档). 通过之前的准备工作,我们更加清楚了产品的需求,并细致的考虑了方案的可行性,从而减少与避免了撰写文档时容易忽略的细节黑洞. PRD文档没有标准的规范,也没有统一的模板,每个公司都不一样,并且每个人也不一样,这个取决于个人习惯和团队要求.虽然PRD文档没有标准的规范,但是有两项是必不可少的,那就是文件标识和修改记录.文档在撰写过程中,我们可以自行不断的修改

bootstrap文档的学习

就像刚开始的 优雅,直观,强大的前端框架,让web开发更快,更容易,bootstrap给我的感觉就是把常用的布局,组件(导航,列表,按钮,表格),还有规范化颜色等等,同时它的遍历不至于此,他还支持了自定义,利用less,全局定义这些变量,让你自定义区修改,同时还有更多的图标插件可以用.在布局方面,更好的迎合了目前市场上移动端的出现,有了流网格,我们就不用再去考虑去兼容不同屏幕的展示端. 一 .框架 bootsrap构建于12列响应式网格,布局和组件.同时基于html5和jquery <!DOCT

产品需求文档的学习记录(一)

PRD是英文Product Requirement Document的缩写,中文的意思是产品需求文档,具体的名词介绍大家可以询问Google.PRD文档是基于BRD.MRD的延续文档,主要用于产品设计和开发使用,因此阅读这份文档的人群绝大多数是设计与技术人员.在这类人群中,设计师更多依赖于原型进行交互或视觉的设计,因此看这份文档的人就会偏向于技术人员.相对于技术人员,他们不太关注产品的商业需求和市场愿景,因为在进行产品讨论立项时,产品的定义就已经向参与设计和研发的人员宣讲过,因此技术人员更多的是

产品需求文档的学习记录(二)

我们将概念想法形成了信息结构,罗列出了产品的所有信息内容,现在我们就要依据信息结构,开始规划产品的功能需求,绘制出产品结构图和用户流程图.首先我们要规划出产品的频道及子频道.子模块或子页面.(如下图) 图注:讲解一下我对于这个思维导图的名词理解1.频道:某一个同性质的功能或内容的共同载体,也可称为功能或内容的类别.2.子频道:某频道下细分的另一类别3.页面:单个或附属某个频道或分类下的界面4.模块:页面中多个元素组成的一个区域内容,可以有一个或多个,也可以循环出现(例如:文章列表)5.模块元素:

产品需求文档的学习记录(三)

我们通过思维导图将想法进行了结构化梳理,接下来我们就需要进行方案的可行性推演,验证产品功能是否可行,预估项目要花多少人力物力,因此我们就要通过原型设计进行相关需求的论证.一开始就撰写PRD文档,我们很难对产品进行各方面的评估,也无法得知方案的可行性,并且无法直观细致的考虑产品. 原型设计是帮助我们更细致的思考,并做各项需求的评估,同时也是将自己脑海里的想法进行输出,通过原型设计后,我们就可以进行产品宣讲了.相对于之前抽象的文字描述,原型则更加清晰产品的需求,设计和技术人员或者老板也能够更加直观的