标签 驱动开发 下的文章
 2021-11-28T19:21:16.768395    |      驱动开发    |     AilsonJack    |     暂无评论    |     375 views
在调试芯片的某个外设的时候,如果该外设带有FIFO,那么一般情况下都有FIFO上溢和FIFO下溢的错误标志位,用于表示驱动对外设的FIFO操作是否正确,下面就是FIFO上溢和FIFO下溢对应的概念:FIFO上溢:写满fifo后继续写则导致上溢;FIFO下溢:读空fifo后继续读则导致下溢;如果外设驱动操作外设时出现了FIFO溢出,那么还需要根据手册分析驱动对FIFO的操作是否存在不合理的地方。
驱动开发嵌入式 阅读全文»
 2021-08-28T00:44:49.857842    |      Linux MMC子系统    |     AilsonJack    |     暂无评论    |     139 views
根据上一章《Linux内核与驱动学习记录-字符设备驱动程序框架》的内容,这一章编写了一个例程,作为实验进行说明,加深对字符设备驱动程序开发步骤的理解。实验代码如下:Makefile内容如下:将实验代码编译之后的chrdev_frame.ko,下载到板子。加载内核模块,执行命令:sudo insmod chrdev_frame.ko卸载内核模块,执行命令:sudo rmmod chrdev_frame
Linux内核学习驱动开发嵌入式 阅读全文»
 2021-07-31T21:24:33.581477    |      Linux MMC子系统    |     AilsonJack    |     暂无评论    |     234 views
字符设备的驱动程序开发步骤大致都是差不多的,这里绘制了一张图来形象的反应字符设备驱动程序的关键步骤:我们创建一个字符设备的时候,首先要得到一个设备号,分配设备号的途径有静态分配和动态分配;拿到设备的唯一 ID,我们需要实现 file_operation 并保存到 cdev 中,实现 cdev 的初始化;然后我们需要将我们所做的工作告诉内核,使用 cdev_add() 注册 cdev;最后我们还需要创建设备节点,以便我们后面调用 file_operation接口。注销设备时我们需释放内核中的 cdev,归还申请的设备号,删除创建的设备节点。1.字符设备的定义Linux 内核提供了两种方式来定义字符设备:第一种方式,就是我们常见的变量定义;第二种方式,是内核提供的动态分配方式,调用该函数之后,会返回一个 struct cdev 类型的指针,用于描述字符设备。第二种方式定义的字符设备,可以通过cdev_del函数来释放占用的内存。2.设备号的申请和归还2.1.设备号的静态申请register_chrdev_region 函数用于静态地为一个字符设备申请一个或多个设备编号。int register_chrdev_region(dev_t from, unsigned count, const char *name);参数:from:dev_t 类型的变量,用于指定字符设备的起始设备号,如果要注册的设备号已经被其他的设备注册了,那么就会导致注册失败;count:指定要申请的设备号个数, count 的值不可以太大,否则会与下一个主设备号重叠;name:用于指定该设备的名称,我们可以在/proc/devices 中看到该设备。返回值:返回 0 表示申请成功,失败则返回错误码。2.2.设备号的动态申请使用 register_chrdev_region 函数时,都需要去查阅内核源码的 Documentation/devices.txt 文件,这就十分不方便。因此,内核又为我们提供了一种能够动态分配设备编号的方式: alloc_chrdev_region。调用 alloc_chrdev_region 函数,内核会自动给我们分配一个尚未使用的主设备号。我们可以通过命令“cat /proc/devices”查询内核分配的主设备号。int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);参数:dev:指向 dev_t 类型数据的指针变量,用于存放分配到的设备编号的起始值;baseminor:次设备号的起始值,通常情况下,设置为 0;count:指定要申请的设备号个数, count 的值不可以太大,否则会与下一个主设备号重叠;name:用于指定该设备的名称,我们可以在/proc/devices 中看到该设备。返回值:返回 0 表示申请成功,失败则返回错误码。2.3.设备号的申请(静态和动态都支持)除了register_chrdev_region函数能够静态申请设备号,alloc_chrdev_region函数能够动态申请设备号之外,内核还提供了register_chrdev 函数用于分配设备号。该函数是一个内联函数,它不仅支持静态申请设备号,也支持动态申请设备号,并将主设备号返回。register_chrdev 函数原型如下:参数:major:用于指定要申请的字符设备的主设备号,等价于 register_chrdev_region 函数,当设置为 0 时,内核会自动分配一个未使用的主设备号;name:用于指定字符设备的名称;fops:用于操作该设备的函数接口指针。返回值:主设备号。我们从以上代码中可以看到,使用 register_chrdev 函数向内核申请设备号,同一类字符设备(即主设备号相同),会在内核中申请了256 个,通常情况下,我们不需要用到这么多个设备,这就造成了极大的资源浪费。因此通常情况下,并不使用该函数。2.4.设备号的归还当我们删除字符设备时候,我们需要把分配的设备编号交还给内核,对于使用 register_chrdev_region 函数以及 alloc_chrdev_region 函数分配得到的设备编号,可以使用 unregister_chrdev_region 函数将分配得到的设备号归还给内核。void unregister_chrdev_region(dev_t from, unsigned count);参数:from:指定需要注销的字符设备的设备编号起始值,我们一般将定义的 dev_t 变量作为实参;count:指定需要注销的字符设备编号的个数,该值应与申请函数的 count 值相等,通常采用宏定义进行管理。3.字符设备的初始化cdev_init()函数主要将file_operations结构体和我们的字符设备结构体相关联。void cdev_init(struct cdev *cdev, const struct file_operations *fops);参数:cdev:struct cdev 类型的指针变量,指向需要关联的字符设备结构体;fops:file_operations 类型的结构体指针变量,一般将实现操作该设备的结构体 file_operations 结构体作为实参。4.字符设备的注册和移除4.1.字符设备的注册cdev_add 函数用于向内核的 cdev_map 散列表添加一个新的字符设备。int cdev_add(struct cdev *p, dev_t dev, unsigned count);参数:p:struct cdev 类型的指针,用于指定需要添加的字符设备;dev:dev_t 类型变量,用于指定设备的起始编号;count:指定注册多少个设备。返回值:0或者错误码。4.2.字符设备的移除从内核中移除某个字符设备,则需要调用 cdev_del 函数。从系统中删除 cdev, cdev 设备将无法再打开,但任何已经打开的 cdev 将保持不变,即使在 cdev_del 返回后,它们的 fops 仍然可以调用。void cdev_del(struct cdev *p);参数:p:将已经注册的字符设备结构体的地址作为实参传递进去,就可以从内核中移除该字符设备了。5.设备节点的创建和销毁5.1.设备节点的创建可以在代码中使用device_create函数创建设备节点。参数:class:指向这个设备应该注册到的 struct 类的指针;parent:指向此新设备的父结构设备(如果有)的指针;devt:要添加的字符设备的设备号;drvdata:要添加到设备进行回调的数据;fmt:输入设备名称。返回值:成功时返回 struct device 结构体指针, 错误时返回 ERR_PTR()。5.2.设备节点的销毁可以使用device_destroy函数来删除 device_create 函数创建的设备节点。void device_destroy(struct class *class, dev_t devt);参数:class:指向注册此设备的 struct 类的指针;devt:以前注册设备时,使用的设备号。5.3.使用mknod命令创建设备节点除了使用代码创建设备节点,还可以使用 mknod 命令创建设备节点。用法: mknod 设备名 设备类型 主设备号 次设备号当类型为”p”时可不指定主设备号和次设备号,否则它们是必须指定的。如果主设备号和次设备号以”0x”或”0X”开头,它们会被视作十六进制数来解析;如果以”0”开头,则被视作八进制数;其余情况下被视作十进制数。可用的设备类型包括:b:创建 (有缓冲的) 区块特殊文件;c,u:创建 (没有缓冲的) 字符特殊文件;p:创建先进先出 (FIFO) 特殊文件。如: mkmod /dev/test c 2 0创建一个字符设备/dev/test,其主设备号为 2,次设备号为 0。当我们使用上述命令,创建了一个字符设备文件时,实际上就是创建了一个设备节点 inode 结构体,并且将该设备的设备编号记录在成员i_rdev,将成员 f_op 指针指向了 def_chr_fops 结构体。这就是 mknod 负责的工作内容。inode 上的 file_operation 并不是自己构造的 file_operation,而是字符设备通用的 def_chr_fops,那么自己构建的 file_operation 等在应用程序调用 open 函数之后,才会绑定在文件上。
Linux内核学习驱动开发嵌入式 阅读全文»
 2021-06-30T20:16:30.276882    |      Linux MMC子系统    |     AilsonJack    |     暂无评论    |     129 views
1.Linux设备分类按照读写存储数据方式,我们可以把Linux设备分为以下几种:字符设备、块设备和网络设备。字符设备: 指应用程序按字节/字符来读写数据的设备。它通常不支持随机存取数据。字符设备在实现时,大多不使用缓存器,系统直接从设备读取/写入每一个字符。块设备: 通常支持随机存取和寻址,并使用缓存器。它与字符设备不同之处就是,是否支持随机存储。字符型是流形式,逐一存储。数据的读写只能以块的倍数进行。网络设备: 是一种特殊设备,它并不存在于/dev 下面,主要用于网络数据的收发。Linux 内核中处处体现面向对象的设计思想,为了统一形形色色的设备, Linux 系统将设备分别抽象为 struct cdev, struct block_device,struct net_devce 三个对象,具体的设备都可以包含着三种对象从而继承和三种对象属性和操作,并通过各自的对象添加到相应的驱动模型中,从而进行统一的管理和操作。2.字符设备抽象Linux 内核中将字符设备抽象成一个具体的数据结构 (struct cdev), 我们可以理解为字符设备对象,cdev 记录了字符设备的相关信息(设备号、内核对象),字符设备的打开、读写、关闭等操作接口(file_operations)。在我们想要添加一个字符设备时,就是将这个对象注册到内核中,通过创建一个文件(设备节点)绑定对象的cdev,当我们对这个文件进行读写操作时,就可以通过虚拟文件系统,在内核中找到这个对象及其操作接口,从而控制设备。C 语言中没有面向对象语言的继承的语法,但是我们可以通过结构体的包含来实现继承,这种抽象提取了设备的共性,为上层提供了统一接口,使得管理和操作设备变得很容易。在硬件层,我们可以通过查看硬件的原理图、芯片的数据手册,确定底层需要配置的寄存器,这类似于裸机开发。将对底层寄存器的配置,读写操作放在文件操作接口里面,也就是实现 file_operations 结构体。其次在驱动层,我们将文件操作接口注册到内核,内核通过内部散列表来登记记录主次设备号。在文件系统层,新建一个文件绑定该文件操作接口,应用程序通过操作指定文件的文件操作接口来设置底层寄存器。3.相关概念及数据结构在 linux 中,我们使用设备编号来表示设备,主设备号区分设备类别,次设备号标识具体的设备。字符设备的cdev 结构体记录了设备号,在使用设备时,我们通常会打开设备节点,通过设备节点的 inode 结构体、 file 结构体最终找到 file_operations 结构体,并从file_operations 结构体中得到操作设备的具体方法。3.1.设备号Linux对于设备的访问是通过文件系统的名称进行的,这些名称被称为特殊文件、设备文件,或者简单称为文件系统树的节点, Linux 根目录下有/dev 这个文件夹,专门用来存放设备中的驱动程序。通过执行命令:ls -l /dev,可以查看/dev目录下每个文件的详细信息,每一行的第一个字符表示设备的类型,'c'用来标识字符设备,'b'用来标识块设备。一般来说,主设备号指向设备的驱动程序,次设备号指向某个具体的设备。如上图,I2C-0,I2C-1 属于不同设备但是共用一套驱动程序。在内核中,dev_t 用来表示设备编号,dev_t 是一个 32 位的数,其中,高 12 位表示主设备号,低 20 位表示次设备号。理论上主设备号取值范围:0~2^12-1,次设备号 0~2^20-1。实际上在内核源码中 __register_chrdev_region() 函数中,major 被限定在0~CHRDEV_MAJOR_MAX, CHRDEV_MAJOR_MAX 是一个宏,值是 512。宏定义 MAJOR 和 MINOR,可以根据设备的设备号来获取设备的主设备号和次设备号。宏定义MKDEV,用于将主设备号和次设备号合成一个设备号,主设备号可以通过查阅内核源码的 Documentation/devices.txt 文件,而次设备号通常是从编号 0 开始。内核通过一个散列表 (哈希表) 来记录设备编号。哈希表由数组和链表组成,吸收数组查找快,链表增删效率高,容易拓展等优点。以主设备号为 cdev_map 编号,使用哈希函数 f(major)=major%255 来计算组数下标 (使用哈希函数是为了链表节点尽量平均分布在各个数组元素中,提高查询效率);主设备号冲突, 则以次设备号为比较值来排序链表节点。内核用 struct cdev 结构体来描述一个字符设备,并通过 struct kobj_map 类型的散列表cdev_map 来管理当前系统中的所有字符设备。3.2.设备节点设备节点(设备文件): Linux 中设备节点是通过“mknod”命令来创建的。一个设备节点其实就是一个文件,Linux 中称为设备文件。设备节点被创建在/dev下,是连接内核与用户层的枢纽,就是设备是接到对应哪种接口的哪个 ID 上。设备节点是Linux内核对设备的抽象,一个设备节点就是一个文件。应用程序通过一组标准化的调用执行访问设备,这些调用独立于任何特定的驱动程序。而驱动程序负责将这些标准调用映射到实际硬件的特有操作。3.3.数据结构在驱动开发过程中,不可避免要涉及到三个重要的的内核数据结构分别包括文件操作方式(file_operations),文件描述结构体(struct file)以及inode 结构体。file_operation 就是把系统调用和驱动程序关联起来的关键数据结构。内核中用 file 结构体来表示每个打开的文件,每打开一个文件,内核会创建一个结构体,并将对该文件上的操作函数传递给该结构体的成员变量f_op,当文件所有实例被关闭后,内核会释放这个结构体。在file 结构体中,我们需要关心的数据成员有f_op和private_data:const struct file_operations *f_op:存放与文件操作相关的一系列函数指针,如 open、 read、 wirte 等函数。void *private_data:该指针变量只会用于设备驱动程序中,内核并不会对该成员进行操作。因此,在驱动程序中,通常用于指向描述设备的结构体。inode 结构体是 Linux 管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。内核使用 inode 结构体在内核内部表示一个文件。因此,它与表示一个已经打开的文件描述符的结构体 (即 file 文件结构) 是不同的,我们可以使用多个 file 文件结构表示同一个文件的多个文件描述符,但此时,所有的这些 file 文件结构全部都必须只能指向一个 inode 结构体。在inode 结构体中,我们需要关心的数据成员有i_rdev和i_cdev:dev_t i_rdev:表示设备文件的结点,这个域实际上包含了设备号。struct cdev *i_cdev:struct cdev 是内核的一个内部结构,它是用来表示字符设备的,当 inode 结点指向一个字符设备文件时,此域为一个指向 inode 结构的指针。
Linux内核学习驱动开发嵌入式 阅读全文»
 2021-05-23T20:27:54.231148    |      Linux MMC子系统    |     AilsonJack    |     暂无评论    |     391 views
1.内核模块传参内核模块作为一个可拓展的动态模块,为 Linux 内核提供了灵活性,但是有时我们需要根据不同的应用场景给内核模块传递不同的参数,例如在程序中开启调试模式、设置详细输出模式以及制定与具体模块相关的选项,为了满足这种需求,内核允许对内核模块指定参数,而这些参数可以在装载内核模块时改变。Linux内核提供一个宏module_param来实现模块的参数传递,module_param宏定义在include/linux/moduleparam.h文件中。module_param宏的定义形式如下:#define module_param(name, type, perm)module_param需要三个参数:name:变量的名称;type:变量的类型,目前内核支持的类型有 byte, short, ushort, int, uint, long, ulong, charp, bool,invbool。其中 charp 表示的是字符指针, bool 是布尔类型,其值只能为 0 或者是 1; invbool 是反布尔类型,其值也是只能取 0 或者是 1,但是 true 值表示 0, false 表示 1。变量是 char 类型时,传参只能是 byte,char * 时只能是 charp;perm:sysfs入口项的访问许可掩码,可选的值:S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH,S_IXOTH,S_IRUGO,S_IWUGO(这些值可以通过或的方式进行组合,比如S_IRUSR | S_IWUSR表示用户拥有读写权限)。对于这些宏的定义可以参考文件:include/linux/stat.h。上述文件权限唯独没有关于可执行权限的设置,请注意,该文件不允许它具有可执行权限。如果强行给该参数赋予表示可执行权限的参数值 S_IXUGO,那么最终生成的内核模块在加载时会提示错误。这里列举一个简单的内核模块参数传递的例子,module_param.c文件内容:Makefile文件内容:编译module_param内核模块,将生成的module_param.ko文件通过nfs或者scp拷贝到开发板,然后在开发板中执行命令:sudo insmod module_param.ko int_type=12 bool_type=1 char_type=201 str_type="hello!"我们定义的四个模块参数,会在'/sys/module/模块名/parameters'下存在以模块参数为名的文件。由于 int_type 和 char_type 的权限是 0,所以我们没有权限查看该参数。2.符号共享符号共享是指内核模块能够使用其他内核模块导出的符号,或者内核模块将自己模块内的符号导出给其他内核模块使用。这里的符号指的是内核模块中导出的函数或者变量,在加载模块时被记录在公共内核符号表中,以供其他模块调用。这个机制,允许我们使用分层的思想解决一些复杂的模块设计。我们在编写一个驱动的时候,可以把驱动按照功能分成几个内核模块,借助符号共享去实现模块与模块之间的接口调用,变量共享。通常情况下我们无需导出任何符号,但是如果其他模块想要从我们这个模块中获取某些符号的时候,就可以考虑导出符号为其提供服务,这被称为模块层叠技术。例如 msdos 文件系统依赖于由 fat 模块导出的符号;USB 输入设备模块层叠在 usbcore 和 input 模块之上。也就是我们可以将模块分为多个层,通过简化每一层来实现复杂的项目。如果一个模块需要向其他模块导出符号,则应该使用下面的宏:符号必须在模块文件的全局部分导出,不能在函数中使用,EXPORT_SYMBOL_GPL使得导出的模块只能被 GPL 许可的模块使用。这里使用内核模块传参小节的module_param.c文件为基础进行修改,作为一个导出内核模块参数的内核模块,module_param.c文件的内容如下:module_param.h文件内容:symbol_share.c文件内容:Makefile文件内容:编译module_param内核模块和symbol_share内核模块,将生成的module_param.ko和symbol_share.ko文件通过nfs或者scp拷贝到开发板,然后在开发板中执行命令:注意,要先加载module_param内核模块,再加载symbol_share内核模块,因为symbol_share内核模块会依赖module_param内核模块导出的符号,如果先加载symbol_share内核模块,symbol_share内核模块将会加载失败。查看module_param内核模块导出的符号:
Linux内核学习驱动开发嵌入式 阅读全文»
 2021-05-09T16:28:09.912354    |      Linux MMC子系统    |     AilsonJack    |     暂无评论    |     332 views
1.内核模块的概念因为Linux 操作系统采用了宏内核结构,宏内核的优点是执行效率非常高,但缺点也是十分明显的,一旦我们想要修改、增加内核某个功能时(如增加设备驱动程序)都需要重新编译一遍内核。为了解决这一缺点,Linux 中引入了内核模块这一机制。内核模块就是实现了某个功能的一段内核代码,在内核运行过程,可以加载这部分代码到内核中,从而动态地增加了内核的功能。基于这种特性,我们进行设备驱动开发时,以内核模块的形式编写设备驱动,只需要编译相关的驱动代码即可,无需对整个内核进行编译。内核模块的引入不仅提高了系统的灵活性,对于开发人员来说更是提供了极大的方便。内核模块定义:内核模块全称 Loadable Kernel Module(LKM),是一种在内核运行时加载一组目标代码来实现某个特定功能的机制。内核模块特点:(1).模块本身不被编译入内核映像,这控制了内核的大小;(2).模块一旦被加载,它就和内核中的其它部分完全一样。我们编写的内核模块,经过编译,最终形成以.ko为后缀的文件。ko 文件在数据组织形式上是 ELF(Excutable And Linking Format) 格式,是一种普通的可重定位目标文件。2.编写Hello内核模块对于程序入门学习来说,Hello World程序是经典的例子,这里我们也实现一个简单的Hello内核模块用于了解内核模块编程的基本框架。hello_module.c文件的内容如下所示:2.1.Hello内核模块代码框架分析Linux 内核模块的代码框架通常由下面几个部分组成:(1).模块加载函数 (必须):当通过 insmod 或 modprobe 命令加载内核模块时,模块的加载函数就会自动被内核执行,完成本模块相关的初始化工作。(2).模块卸载函数 (必须):当执行 rmmod 命令卸载模块时,模块卸载函数就会自动被内核自动执行,完成相关清理工作。(3).模块许可证声明 (必须):许可证声明描述内核模块的许可权限,如果模块不声明,模块被加载时,将会有内核被污染的警告。(4).模块参数:模块参数是模块被加载时,可以传值给模块中的参数。(5).模块导出符号:模块可以导出准备好的变量或函数作为符号,以便其他内核模块调用。(6).模块的其他相关信息:可以声明模块作者等信息。2.2.内核模块头文件Hello内核模块中,使用3个头文件,下面说说这3个头文件具体提供的信息:(1).#include <linux/module.h>:包含内核模块信息声明的相关函数;(2).#include <linux/init.h>:包含了 module_init() 和 module_exit() 函数的声明;(3).#include <linux/kernel.h>: 包含内核提供的各种函数,如 printk。2.3.内核模块加载/卸载函数module_init():声明内核模块加载函数,加载内核模块的时候会调用声明的内核模块加载函数,模块加载成功,会在/sys/module下新建一个以模块名为名的目录。module_exit():声明内核模块卸载函数,卸载内核模块的时候会调用声明的内核模块卸载函数。__init 用于修饰函数, __initdata 用于修饰变量。带有 __init 的修饰符,表示将该函数放到可执行文件的 __init 节区中,该节区的内容只能用于模块的初始化阶段,初始化阶段执行完毕之后,这部分的内容就会被释放掉,真可谓是“针尖也要削点铁”。__exit 用于修饰函数,__exitdata 用于修饰变量。带有__exit的修饰符,表示将该函数放到可执行文件的__exit节区,当执行完模块卸载阶段之后,就会自动释放该区域的空间。注意:hello_module_init()函数的返回值是int,hello_module_exit()的返回值是void,并且这两个函数都使用static进行修饰,表示函数只能在本文件进行调用,不能被其他文件调用。2.4.内核打印函数-printkprintk函数的打印等级:printk函数可以指定打印等级,当不指定打印等级的时候,会使用默认的打印等级。查看当前系统 printk 打印等级: cat /proc/sys/kernel/printk,从左到右依次对应控制台日志级别、默认消息日志级别、最小的控制台日志级别、默认控制台日志级别。控制台日志级别:优先级高于该值得消息将被打印到到控制台;默认消息日志级别:将用该优先级来打印没有指定优先级的消息;最小的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级);默认控制台日志级别:控制台日志级别的缺省值。以上的数值设置,数值越小,优先级越高。假设你想让hello_module_init()或者hello_module_exit()函数中,没有指定打印等级的printk的内容输出到控制台,那么你可以将"默认消息日志级别"设置为小于4,可以设置为3(只需要数值小于控制台日志级别即可),执行的命令如下:sudo sh -c "echo '4 3 1 7' > /proc/sys/kernel/printk"然后执行加载或者卸载模块,就可以看到未指定打印等级的消息输出到控制台了。查看内核所有打印信息: dmesg,注意内核 log 缓冲区大小有限制,缓冲区数据可能被覆盖掉。3.内核模块的makefile对于内核模块而言,它是属于内核的一段代码,只不过它并不在内核源码中。为此,我们在编译时需要到内核源码目录下进行编译。编译内核模块使用的 Makefile 文件,和我们前面编译 C 代码使用的 Makefile 大致相同,这得益于编译 Linux 内核所采用的 Kbuild 系统,因此在编译内核模块时,我们也需要指定环境变量 ARCH 和CROSS_COMPILE 的值。编译Hello内核模块使用的Makefile文件内容如下:在内核模块的目录中,执行make命令编译内核模块,生成hello_module.ko文件,将hello_module.ko文件通过nfs或者scp拷贝到开发板,即可加载该内核模块。4.内核模块常用命令4.1.lsmodlsmod 列出当前内核中的所有模块,格式化显示在终端,其原理就是将/proc/module 中的信息调整一下格式输出。 lsmod 输出列表有一列 Used by,它表明此模块正在被其他模块使用,显示了模块之间的依赖关系。4.2.insmod如果要将一个模块加载到内核中, insmod 是最简单的办法, insmod+模块完整路径就能达到目的,前提是你的模块不依赖其他模块,还要注意需要 sudo 权限。如果你不确定是否使用到其他模块的符号,你也可以尝试modprobe,后面会有它的详细用法。4.3.rmmodrmod 工具仅仅是将内核中运行的模块删除,只需要传给它路径就能实现。rmmod 不会卸载一个模块所依赖的模块,需要依次卸载,当然用 modprobe -r 可以一键卸载。4.4.modprobemodprobe 和 insmod 具备同样的功能,同样可以将模块加载到内核中,除此以外 modprobe 还能检查模块之间的依赖关系,并且按照顺序加载这些依赖,可以理解为按照顺序多次执行 insmod。4.5.depmodmodprobe 是怎么知道一个给定模块所依赖的其他的模块呢?在这个过程中, depend 起到了决定性作用,当执行 modprobe 时,它会在模块的安装目录下搜索 module.dep 文件,这是 depmod 创建的模块依赖关系的文件。4.6.modinfomodinfo 用来显示内核模块一些信息。比如:modinfo hello_module.ko5.系统自动加载内核模块我们自己编写了一个模块,或者说怎样让它在板子开机自动加载呢?这里就需要用到上述的 depmod 和 modprobe 工具了。首先需要将我们想要自动加载的模块统一放到”/lib/modules/内核版本”目录下,内核版本使用 'uname -r'查询;其次使用 depmod 建立模块之间的依赖关系,命令’ depmod -a’;这个时候我们就可以在 modules.dep 中看到模块依赖关系。最后在/etc/modules 加上我们自己的模块,注意在该配置文件中,模块不写成.ko 形式代表该模块与内核紧耦合,有些是系统必须要跟内核紧耦合,比如 mm 子系统,一般写成.ko 形式比较好,如果出现错误不会导致内核出现 panic 错误,如果集成到内核,出错了就会出现panic。
Linux内核学习驱动开发嵌入式 阅读全文»
 2019-11-02T20:51:06.511000    |      驱动开发    |     AilsonJack    |     暂无评论    |     4001 views
这篇文章总结了I2S协议的一些知识点,作为自己以后调试音频的参考,当然了文章中的内容也参考了一些网友的总结。一、数字音频技术1.声音数字化概念及过程现实生活中的声音是通过一定介质传播的连续的波,它可以由周期和振幅两个重要指标描述。正常人可以听到的声音频率范围为 20Hz~20KHz。现实存在的声音是模拟量,这对声音保存和长距离传输造成很大的困难,一般的做法是把模拟量转成对应的数字量保存,在需要还原声音的地方再把数字量的转成模拟量输出如下图所示:模拟量转成数字量一般可以分为三个过程,分别为采样、量化、编码,如下图所示。用一个比源声音频率高的采样信号去量化源声音,记录每个采样点的值,最后如果把所有采样点数值连接起来与源声音曲线是互相吻合的,只是它不是连续的。在图中,两条蓝色虚线距离就是采样信号的周期,即对应一个采样频率(FS),可以想象得到采样频率越高最后得到的结果就与源声音越吻合,但此时采样数据量越越大,一般使用 44.1KHz 采样频率即可得到高保真的声音。每条蓝色虚线长度决定着该时刻源声音的量化值,该量化值有另外一个概念与之挂钩,就是量化位数。量化位数表示每个采样点用多少位表示数据范围,常用有 16bit、 24bit 或 32bit,位数越高最后还原得到的音质越好,数据量也会越大。2.声音数字化三要素采用频率:每秒钟抽取声波幅度样本的次数。采样频率越高,声音质量越好,数据量也越大。常用的采样频率有11.025KHz,22.05KHz,44.1KHz,48KHz,96KHz等。量化位数:每个采样点用多少二进制位表示数据范围。量化位数也叫采样位数。量化位数越多,音质越好,数据量也越大。常用的采样位数有8位,16位,24位,32位等。声道数:使用声道的个数。立体声比单声道的表现力丰富,但是数据量翻倍。常用的声道数有单声道,立体声(左声道和右声道)。3.声音数字化的数据量声音数字化后的数据量计算公式为:音频数据量 = 采样频率(Hz) * 量化位数 * 声道数 / 8,单位:字节/秒。这里举个例子:对一个声音信号进行数字化处理,采样频率为44.1KHz,量化位数为16位,那么:单声道的音频数据量为:44100 * 16 * 1 / 8 = 88200 字节/秒;立体声的音频数据量为:44100 * 16 * 2 / 8 = 176400 字节/秒。4.声卡声卡是负责录音、播音和声音合成的一种多媒体板卡。其功能包括:(1).录制、编辑和回放数字音频文件;(2).控制和混合各声源的音量;(3).记录和回放时进行压缩和解压缩;(4).语音合成技术(朗读文本);(5).具有MIDI接口(乐器数字接口)。声卡的芯片类型:(1).CODEC芯片(依赖CPU,价格便宜);(2).数字信号处理器DSP(不依赖CPU)。二、I2S总线协议1.I2S总线概述音响数据的采集、处理和传输是多媒体技术的重要组成部分。众多的数字音频系统已经进入消费市场,例如数字音频录音带、数字声音处理器。对于设备和生产厂家来说,标准化的信息传输结构可以提高系统的适应性。I2S(Inter-IC Sound)总线, 又称集成电路内置音频总线,是飞利浦半导体公司(现为恩智浦半导体公司)针对数字音频设备之间的音频数据传输而制定的一种总线标准。该总线专门用于音频设备之间的数据传输,广泛应用于各种多媒体系统。它采用了沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。2.I2S信号线I2S总线主要有3个信号线:(1).串行时钟SCK串行时钟SCK,也叫位时钟BCLK,对应数字音频的每一位数据,SCK都有1个脉冲。SCK的频率 = 声道数 * 采样频率 * 采样位数。(2).字段选择信号WS字段选择信号WS,也叫LRCLK,用于切换左右声道的数据。WS的频率 = 采样频率。字段选择信号WS表明了正在被传输的声道。I2S Philips标准WS信号的电平含义如下:WS为0,表示正在传输的是左声道的数据;WS为1,表示正在传输的是右声道的数据。(3).串行数据SD串行数据SD,就是用二进制补码表示的音频数据。I2S串行数据在传输的时候,由高位(MSB)到低位(LSB)依次进行传输。(4).主时钟MCLK一般还有MCLK,主时钟。MCLK的频率 = 128或者256或者512 * 采样频率。对于系统而言,能够产生SCK和WS的信号端就是主设备,用MASTER表示,简单系统示意图如下:3.几种常见的I2S数据格式随着技术的发展,在统一的I2S硬件接口下,出现了多种不同的I2S数据格式,可分为左对齐(MSB)标准、右对齐(LSB)标准、I2S Philips 标准。对于所有数据格式和通信标准而言,始终会先发送最高有效位(MSB 优先)。发送端和接收端必须使用相同的数据格式,确保发送和接收的数据一致。(1).I2S Philips 标准使用LRCLK信号来指示当前正在发送的数据所属的声道,为0时表示左声道数据。LRCLK信号从当前声道数据的第一个位(MSB)之前的一个时钟开始有效。LRCLK信号在BCLK的下降沿变化。发送方在时钟信号BCLK的下降沿改变数据,接收方在时钟信号BCLK的上升沿读取数据。正如上文所说,LRCLK频率等于采样频率Fs,一个LRCLK周期(1/Fs)包括发送左声道和右声道数据。对于这种标准I2S格式的信号,无论有多少位有效数据,数据的最高位总是出现在LRCLK变化(也就是一帧开始)后的第2个BCLK脉冲处。这就使得接收端与发送端的有效位数可以不同。如果接收端能处理的有效位数少于发送端,可以放弃数据帧中多余的低位数据;如果接收端能处理的有效位数多于发送端,可以自行补足剩余的位。这种同步机制使得数字音频设备的互连更加方便,而且不会造成数据错位。I2S Philips 标准时序图如下所示:(2).左对齐(MSB)标准在LRCLK发生翻转的同时开始传输数据。该标准较少使用。注意此时LRCLK为1时,传输的是左声道数据,这刚好与I2S Philips标准相反。左对齐(MSB)标准时序图如下所示:(3).右对齐(LSB)标准声音数据LSB传输完成的同时,LRCLK完成第二次翻转(刚好是LSB和LRCLK是右对齐的,所以称为右对齐标准)。注意此时LRCLK为1时,传输的是左声道数据,这刚好与I2S Philips标准相反。右对齐(LSB)标准时序图如下所示:如果觉得文章写的不错,对你有帮助,欢迎点赞,关注博主哟!
驱动开发嵌入式I2S 阅读全文»
 2015-11-27T21:44:05.634873    |      嵌入式学习    |     AilsonJack    |     1 条评论    |     2687 views
最近在学习NandFlash相关的知识,NandFlash的操作貌似比NorFlash的操作还要复杂些,这里讲讲NandFlash的地址计算以及如何将物理地址转换为NandFlash所要求的格式,以下内容参考了网友的总结,对那位网友表示感谢。1、计算物理地址在使用NandFlash进行读、写、擦除数据之前,还要做一件事,那就是,先要搞懂我们要访问的地址,以及这些地址如何分解后,一点点传入进去,使得硬件能识别才行。此处是以K9K8G08U0A为例,此NandFlash,一共有8192个块,每个块内有64页,每个页是2K+64Bytes,假设,我们要访问其中的第7000个块中的第64页中的1208字节处的地址,此时,我们就要先把具体的地址算出来:物理地址 = 块大小×块号 + 页大小×页号 + 页内地址       = 2K*64*7000  + 2K*64     + 1208       = 0x36B204B82、将物理地址转化为Nand所要求的格式接下来,我们就看看,怎么才能把这个实际的物理地址,转化为NandFlash所要求的格式。在解释地址组成之前,先要来看看其datasheet中关于地址周期的介绍:结合图,我们可以看出,此NandFlash地址周期共有5个,2个列(Column),3个行(Row)周期,而对应地,我们可以看出,实际上,列地址A0~A10,就是页内地址,地址范围是从0到2047,而对于多出的A11,理论上可以表示2048~4095,但是实际上,我们最多也只用到了2048~2111,用于表示页内的oob区域,其大小是64字节。对应地,A12~A30,称作页号,页的号码,可以定位到具体是哪一个页。而其中,A18~A30,表示对应的块号,即属于哪个块。简单解释完了地址组成,那么就很容易分析上面例子中的地址了:0x36B204B8=0011  0110  1011  0010  0000  0100  1011  1000,分别分配到5个地址周期就是:1st  周期      A7~A0:    10111000=0xB82nd  周期      A11~A8:    00000100=0x043rd  周期      A19~A12:   00100000=0x204th  周期      A27~A20:   01101011=0x6B5th  周期      A30~A28:   00000011=0x03注意,与图中对应的*L,意思是地电平,由于未用到那些位,datasheet中强制要求设为0,所以,才有上面的2nd周期中的高4位是0000。其他的A30之后的位也是类似原理,都是0。因此,接下来要介绍的,我们要访问第7000个块中的第64页中的1208字节处的话,所要传入的地址就是分5个周期,分别传入两个列地址:0xB8,0x04,然后再传3个行地址:0x20,0x6B,0x03,这样硬件才能识别。3、小知识1)、块,是Nand Flash的擦除操作的基本/最小单位。2)、页,是Nand Flash的写入操作的基本/最小的单位。3)、Flash名称的由来:Flash的擦除操作是以block块为单位的,与此相对应的是其他很多存储设备,是以bit位为最小读取/写入的单位,Flash是一次性地擦除整个块:在发送一个擦除命令后,一次性地将一个block,常见的块的大小是128KB/256KB。全部擦除为1,也就是里面的内容全部都是0xFF了,由于是一下子就擦除了,相对来说,擦除用的时间很短,可以用一闪而过来形容,所以,叫做Flash Memory。中文有的翻译为(快速)闪存。
嵌入式驱动开发Flash 阅读全文»
 2015-11-25T11:02:00.347002    |      嵌入式学习    |     AilsonJack    |     暂无评论    |     1346 views
最近由于要用到NoFlash因此就学习了一下,发现NorFlash的操作和使用都还是比较简单的,这里给大家分享一篇关于NorFlash操作的文章,比较通俗易懂,一看就会,这里我整理了多位网友的讲解,谢谢这些网友!以下进入正文吧。1、NorFlash的简单介绍NORFLASH是Intel在1988年推出的一款商业性闪存芯片,它需要很长的时间进行抹写,大半生它能够提供完整的寻址与数据总线总线的供应商,并允许随机存取存储器存储器的供应商上的任何区域,而且它可以忍受一万次到一百万次抹写循环,是早期的可移除式闪存储媒体的基础。NORFLASH是很常见的一种存储芯片,数据掉电不会丢失。NORFLASH支持ExecuteOnChip,即程序可以直接在FLASH片内执行(这意味着存储在NORFLASH上的程序不需要复制到RAM就可以直接运行)。这点和NANDFLASH不一样。因此,在嵌入式系统中,NORFLASH很适合作为启动程序的存储介质。NORFLASH的读取和RAM很类似(只要能够提供数据的地址,数据总线就能够正确的给出数据),但不可以直接进行写操作。对NORFLASH的写操作需要遵循特定的命令序列,最终由芯片内部的控制单元完成写操作。谈到Nor Flash通常就会涉及到CFI(Common Flash Interface)接口,一般Nor Flash都支持发命令来读取厂家ID和设备ID等基本信息,但并不是所有的Nor Flash都支持发命令来获取和芯片本身容量大小、扇区数、擦除块大小等信息。为了让将来的Nor Flash兼容性更好,引进了CFI接口,将芯片有关的信息都写入芯片内部,通过CFI命令就可以获取这些信息。从支持的最小访问单元来看,NORFLASH一般分为8位的和16位的(当然,也有很多NORFLASH芯片同时支持8位模式和是16位模式,具体的工作模式通过特定的管脚进行选择)。对8位的NORFLASH芯片,或是工作在8-BIT模式的芯片来说,一个地址对应一个BYTE(8-BIT)的数据。例如一块8-BIT的NORFLASH,假设容量为4个BYTE。那芯片应该有8个数据信号D7-D0和2个地址信号,A1-A0。地址0x0对应第0个BYTE,地址0x1对应于1BYTE,地址0x2对应于第2个BYTE,而地址0x3则对应于第3个BYTE。对16位的NORFLASH芯片,或是工作在16-BIT模式的芯片来说,一个地址对应于一个HALF-WORD(16-BIT)的数据。例如,一块16-BIT的NORFLASH,假设其容量为4个BYTE。那芯片应该有16个数据信号线D15-D0和1个地址信号A0。地址0x0对应于芯片内部的第0个HALF-WORD,地址0x1对应于芯片内部的第1个HALF-WORD。FLASH一般都分为很多个SECTOR,每个SECTOR包括一定数量的存储单元。对有些大容量的FLASH,还分为不同的BANK,每个BANK包括一定数目的SECTOR。FLASH的擦除操作一般都是以SECTOR,BANK或是整片FLASH为单位的。在对FLASH进行写操作的时候,每个BIT可以通过编程由1变为0,但不可以由0修改为1。为了保证写操作的正确性,在执行写操作前,都要执行擦除操作。擦除操作会把FLASH的一个SECTOR,一个BANK或是整片FLASH的值全修改为0xFF。这样,写操作就可以正确完成了。由于NORFLASH没有本地坏区管理,所以一旦存储区块发生毁损,软件或驱动程序必须接手这个问题,否则可能会导致设备发生异常。在解锁、抹除或写入NORFLASH区块时,特殊的指令会先写入已绘测的记忆区的第一页(Page)。接着快闪记忆芯片会提供可用的指令清单给实体驱动程序,而这些指令是由一般性闪存接口(Common Flash Interface,CFI)所界定的。与用于随机存取的ROM不同,NORFLASH也可以用在存储设备上;不过与NANDFLASH相比,NORFLASH的写入速度一般来说会慢很多。2、说明以下内容,如无特别说明,处理器指的是ARM处理器,FLASH指的都是NORFLASH。另外,BYTE指的是8-BIT的数据单元,HALF-WORD代表的是16-BIT的数据单元,而WORD则代表了32-BIT的数据单元。ARM可以说是目前最流行的32位嵌入式处理器。在这里只提一下ARM处理器的寻址,为后面做个展垫。从处理器的角度来看,系统中每个地址对应的是一个BYTE的数据单元。这和很多别的处理器都是一样的。3、处理器和NORFLASH的硬件连接从前面的先容,我们知道从处理器的角度来看,每个地址对应的是一个BYTE的数据单元。而NORFLASH的每个地址有可能对应的是一个BYTE的数据单元,也有可能对应的是一个HALF-WORD的数据单元。所以在硬件设计中,连接ARM处理器和NORFLASH时,必须根据实际情况对地址信号做特别的处理。假如ARM处理器外部扩展的是8-BIT的NORFLASH,数据线和地址线的连接应该如图1所示。从图中我们可以看到,处理器的数据信号D0-D7和FLASH的数据信号D0-D7是逐一对应连接的,处理器的地址信号A0-An和NORFLASH的地址信号A0-An也是逐一对应连接的。假如ARM处理器外部扩展的是16-BIT的NORFLASH,地址线必须要错位连接。图2给了一个ARM处理器和16-BITNORFLASH的连接示意图。如图2所示,ARM处理器的数据信号D0-D15和FLASH的数据信号D0-D15是逐一对应的。而ARM处理器的地址信号和NORFLASH的地址信号是错位连接的,ARM的A0悬空,ARM的A1连接FLASH的A0,ARM的A2连接FLASH的A1,依次类推。需要错位连接的原因是:ARM处理器的每个地址对应的是一个BYTE的数据单元,而16-BIT的FLASH的每个地址对应的是一个HALF-WORD(16-BIT)的数据单元。为了保持匹配,所以必须错位连接。这样,从ARM处理器发送出来的地址信号的最低位A0对16-BITFLASH来说就被屏蔽掉了。补充说明:一般来说,ARM处理器内部要设置相应的寄存器,告诉处理器外部扩展的FLASH的位宽(8-BIT/16-BIT/32-BIT)。这样,处理器才知道在访问的时候如何从FLASH正确的读取数据;有些ARM处理器内部可以设置地址的错位。对于支持软件选择地址错位的处理器,在连接16-BITFLASH的时候,硬件上可以不需要把地址线错位。读者设计的时候,请参考MCU的数据手册,以手册为准,以免造成不必要的麻烦;假如处理器支持内部设置地址错位,在实际访问的时候,送出的地址实际上是在MCU内部做了错位处理,其作用是等效于硬件连接上的错位的。上面的描述可能比较抽象,下面让我们来看2个ARM处理器访问16-BITFLASH的例子:例1:ARM处理器需要从地址0x0读取一个BYTEa、ARM处理器在地址线An-A0上送出信号0x0;b、16-BITFLASH在自己的地址信号An-A0上看到的地址是0x0,然后将地址0x0对应的16-BIT数据单元输出到D15-D0上;c、ARM处理器知道访问的是16-BIT的FLASH,从D7-D0上读取所需要的一个BYTE的数据。例2:ARM处理器需要从地址0x1读取一个BYTEa、ARM处理器在地址线An-A0上送出信号0x1;b、16-BITFLASH在自己的地址信号An-A0上看到的地址依然是0x0,然后将地址0x0对应的16-BIT数据单元输出到D15-D0上;c、ARM处理器知道访问的是16-BIT的FLASH,从D15-D8上读取所需要的一个BYTE的数据。4、从软件角度来看ARM处理器和NORFLASH的连接从软件的角度来理解ARM处理器和FLASH的连接。对于8-BIT的FLASH的连接,很好理解,由于ARM处理器和8-BITFLASH的每个地址对应的都是一个BYTE的数据单元。所以地址连接毫无疑问是逐一对应的。假如ARM处理器连接的是16-BIT的处理器,由于ARM处理器的每个地址对应的是一个BYTE的数据单元,而16-BITFLASH的每个地址对应的是一个HALF-WORD的16-BIT的数据单元。所以,也毫无疑问,ARM处理器访问16-BIT处理器的时候,地址肯定是要错开一位的。在写FLASH驱动的时候,我们不需要知道地址错位是由硬件实现的,还是是通过设置ARM处理器内部的寄存器来实现的,只需要记住2点:a、ARM处理器访问8-BITFLASH的时候,地址是逐一对应的;b、ARM处理器访问16-BITFLASH的时候,地址肯定是错位的。5、8-BITFLASH烧写驱动实例-HY29F040HY29F040是现代公司的一款8-BIT的NORFLASH。在这个小节里,我们以这个芯片为例子,讲述如何对8-BITNORFLASH进行操作。HY29F040的容量为512K-BYTE,总共包括8个SECTOR,每个SECTOR的容量是64K-BYTE。该芯片支持SECTOR擦除,整片擦除和以BYTE为基本单位的写操纵。HY29F040的命令定义如表-1所示。下面,我们来看看如何实现基本的擦除和编程操作。在本节后面的描述中,我们使用了下面的2个定义:SysAddr16(sysbase,offset)首先定义了一个16-BITHALF-WORD的指针,指针的地址为sysbase,然后根据offset做偏移操作。由于HALF-WORD指针的地址是2个BYTE对齐的,所以每个偏移操纵会使得地址加2。最终,SysAddr16(sysbase,offset)相当于定义了一个HALF-WORD的指针,其最终地址为(sysbase+2*offset)。在使用SysAddr16的时候,将sysbase设置成FLASH的起始地址,offset则可以理解为相对于FLASH起始地址的HALF-WORD偏移量或是偏移地址。假设FLASH的起始地址为0x10000000,SysAddr16(0x10000000,0)指向16-BITFLASH的第0个HALF-WORD,SysAddr16(0x10000000,1)指向16-BITFLASH的第1个HALF-WORD。依次类推。假如要将0xABCD分别写到FLASH的第0个和第1个HALF-WORD中,可以用下面的代码:接下来,我们分别从ARM处理器的角度和FLASH的角度来具体分析一下。从ARM的角度来看:假设FLASH的起始地址为0x10000000,由于ARM处理器知道FLASH的地址空间为0x10000000~(0x10000000+FLASH容量–1),所以在对这个地址空间进行访问的时候,会设置好FLASH的片选信号,并将低位的地址输出到地址信号上。以*SysAddr16(0x10000000,0x1)=0xABCD为例。从ARM处理器的角度来看,该操作是把0xABCD写到地址0x10000002上。所以ARM处理器最终会在它的地址信号An-A0输出地址0x2,同时会在D15-D0上输出0xABCD。从FLASH的角度来看:还是以*SysAddr16(0x10000000,0x1)=0xABCD为例,FLASH看到的地址是多少呢?接着分析。ARM处理器在执行操纵的时候,会设置好相应的FLASH片选使能信号,并在ARM的地址信号An-A0上输出0x2。由于ARM和16-BITFLASH的地址信号的连接是错开一位的,所以,FLASH最终在自己的地址An-A0上看到的信号是0x1,相当于将ARM处理器输出的地址往右做了一个移位操作,恰好对应的是FLASH的第1个HALF-WORD。同时,FLASH会在自己的D15-D0上看到数据0xABCD。通过上面的分析,我们知道SysAddr16中指定的offset的值就是16-BITFLASH在自己的地址An-A0上看到的值。所以,我们可以很方便的通过SysAddr16(sysbase,offset)对FLASH进行操纵,其中sysbase代表FLASH起始地址,offset则代表了FLASH的第几个HALF-WORD(HALF-WORD偏移量或偏移地址)。注意:在本节后面的描述中,SysAddr16中的SYSBASE代表的是FLASH的起始地址,而SysAddr16中的OFFSET则代表了相对于FLASH起始地址的HALF-WORD偏移量或偏移地址。OFFSET的值也是16-BITFLASH在自己的地址信号An-A0上看到的值;在SST39VF160的命令定义中,所有的地址都是针对FLASH的HALF-WORD地址,指的是在FLASH自己的地址信号An-A0上看到的地址。(1)、整片擦除操作整片擦除操作共需要6个周期的总线写操作:a、将0x00AA写到FLASHHALF-WORD地址0x5555;b、将0x0055写到FLASHHALF-WORD地址0x2AAA;c、将0x0080写到FLASHHALF-WORD地址0x5555;d、将0x00AA写到FLASHHALF-WORD地址0x5555;e、将0x0055写到FLASHHALF-WORD地址0x2AAA;f、将0x0010写到FLASHHALF-WORD地址0x5555。对应的代码:(2)、SECTOR擦除操作SECTOR的擦除操作共需要6个周期的总线写操作:a、将0x00AA写到FLASHHALF-WORD地址0x5555;b、将0x0055写到FLASHHALF-WORD地址0x2AAA;c、将0x0080写到FLASHHALF-WORD地址0x5555;d、将0x00AA写到FLASHHALF-WORD地址0x5555;e、将0x0055写到FLASHHALF-WORD地址0x2AAA;f、将0x0030写到要擦除的SECTOR对应的HALF-WORD地址。对应的代码:注意:上面的代码中第6个操作周期中的ADDR是从ARM处理器的角度来看的BYTE地址,由于在擦除的时候,用户希望指定的是从ARM的角度看到的地址,这样更方便和更直观。而在SysAddr16的宏定义中,OFFSET表示的是相对于FLASH起始地址的HALF-WORD偏移量,或是FLASH在自己的地址信号An-A0上看到的地址。所以需要执行一个右移操作,把ADDR转换成HALF-WORD地址。举例说明,SST39VF160每个SECTOR的大小是4K-BYTE。从ARM处器的角度和用户的角度来看,SECTOR-0相对于FLASH起始地址的BYTE地址是0x0;从FLASH来看SECTOR-0的HALF-WORD地址是0x0。从ARM处理器的角度和用户的角度来看,FLASHSECTOR-1相对于FLASH起始地址的BYTE地址0x1000;从FLASH来看,SECTOR-1的HALF-WORD地址应该是(0x1000>>1)=0x800。假如要擦除SECTOR-0,上面代码的第6条指令应该是:*SysAddr16(sysbase,0x0>>1) = 0x0030;假如要擦除SECTOR-1,上面代码的第6条指令应该是:*SysAddr16(sysbase,0x1000>>1) = 0x0030;(3)、HALF-WORD编程操作写一个HALF-WORD的数据到FLASH中,需要4个周期的总线写操作:a、将0x00AA写到FLASHHALF-WORD地址0x5555;b、将0x0055写到FLASHHALF-WORD地址0x2AAA;c、将0x00A0写到FLASHHALF-WORD地址0x5555;d、将编程数据(HALF-WORD)写到对应的HALF-WORD地址。对应的代码:注意:上面的代码中第4个操作周期中的ADDR是从ARM处理器的角度来看的BYTE地址,由于在执行写操作的时候,用户希望指定的是从ARM的角度看到的地址,这样会更方便和更直观。而在SysAddr16的宏定义中,OFFSET表示的是相对于FLASH起始地址的HALF-WORD偏移量。所以需要执行一个右移操纵,把它转换成HALF-WORD地址。例如要将数据0x0123写到地址0x0处,对应的是FLASH的第0个HAFL-WORD,对应的HALF-WORD地址应该是0x0,上面代码的第4条指令应该是:*SysAddr16(sysbase,0x0>>1) = 0x0123;又如要将数据0x4567写到地址0x2处,对应的是FLASH的第1个HALF-WORD,对应的HALF-WORD地址应该是0x1,上面代码的第4条指令应该是:*SysAddr16(sysbase,0x2>>1) = 0x4567;再如要将数据0x89AB写到地址0x4处,对应的是FLASH的第2个HALF-WORD,对应的HALF-WORD地址应该是0x2,上面代码的第4条指令应该是:*SysAddr16(sysbase,0x4>>1) = 0x89AB;还如要将数据0xCDEF写到地址0x6处,对应的是FLASH的第3个HALF-WORD,对应的HALF-WORD地址应该是0x3,上面代码的第4条指令应该是:*SysAddr16(sysbase,0x6>>1) = 0xCDEF;AM29LV160的一些数据位说明:NorFlash(AM29LV160)提供几个数据位来确定一个写操作的状态,它们分别是:DQ2, DQ3, DQ5,DQ6,DQ7, and RY/BY# 。其中DQ7, RY/BY#引脚, 和 DQ6 中的每一个都提供了一种方法来判断一个编程或者擦除操作是否已经完成或正在进行中。实际编程中只需要使用其中的一种。DQ7:Data# Polling bit,在编程过程从正在编程的地址中读出的数据的DQ7为要写入数据位的补码。比如写入的数据为0x0000,即输入的DQ7为0,则在编程中读出的数据为1;当编程完成时读出的数据又变回输入的数据0。在擦除过程中DQ7输出为 0;擦除完成后输出为1;注意读取的地址必须是擦除范围内的地址。RY/BY#:高电平表示‘就绪’,低电平表示‘忙’。DQ6:轮转位1(Toggle Bit1),在编程和擦除期间,读任意地址都会导致DQ6的轮转(0,1间相互变换)。当操作完成后,DQ6停止转换。DQ2:轮转位2(Toggle Bit 2),当某个扇区被选中擦除时,读有效地址(地址都在擦除的扇区范围内)会导致DQ2的轮转。注意:DQ2只能判断一个特定的扇区是否被选中擦除,但不能区分这个扇区是否正在擦除中或者正处于擦除暂停状态。相比之下,DQ6可以区分Nor Flash是否处于擦除中或者擦除暂停状态,但不能区分哪个扇区被选中擦除,因此需要这2个位来确定扇区和模式状态信息。DQ5: 超时位(Exceeded Timing Limits),当编程或擦除操作超过了一个特定内部脉冲计数时DQ5=1,表明操作失败。当编程时把0改为1就会导致DQ5=1,因为只有擦除擦做才能把0改为1。当错误发生后需要执行复位命令才能返回到读数据状态。DQ3:(扇区擦除计时位)Sector Erase Timer ,只在扇区擦除指令时起作用。当擦除指令真正开始工作时 DQ3=1 ,此时输入的命令(除擦除暂停命令外)都被忽略,DQ3=0 时,可以添加附加的扇区用于多扇区擦除。例:判断扇区擦除操作是否完成:例:判断编程操作是否结束:
嵌入式驱动开发Flash 阅读全文»
  • 1

  本站信息

目前本站共被浏览 154786 次
目前本站已经运行 3306 天
目前本站共有 165 篇文章
目前本站共有 6 条评论信息
目前本站共有 104 个标签
目前本站共有 0 条留言信息
网站创建时间: 2015年03月01日
最近更新时间: 2023年11月26日
Copyright © 2015~2023  说好一起走   保留所有权利   |  百度统计  蜀ICP备15004292号