摘抄于大学期间记录在QQ空间的一篇自学笔记,当前清理空间,本来想直接删除掉的,但是感觉有些舍不得,因此先搬移过来。
RAM vs ROM vs FLASH
2013-09-05记录,ROM和RAM指的都是半导体存储器,ROM是Read Only Memory的缩写,RAM是Random Access Memory的缩写。ROM在系统停止供电的时候仍然可以保持数据,而RAM通常都是在掉电之后就丢失数据,典型的RAM就是计算机的内存。
RAM 有两大类,一种称为静态RAM(Static RAM/SRAM),SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓 冲。另一种称为动态RAM(Dynamic RAM/DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很 多,计算机内存就是DRAM的。
DRAM分为很多种,常见的主要有FPRAM/FastPage、EDORAM、SDRAM、DDR RAM、RDRAM、SGRAM以及WRAM等,这里介绍其中的一种DDR RAM。
DDR RAM(Date-Rate RAM)也称作DDR SDRAM,这种改进型的RAM和SDRAM是基本一样的,不同之处在于它可以在一个时钟读写两次数据,这样就使得数据传输速度加倍了。这是目前电脑中用 得最多的内存,而且它有着成本优势,事实上击败了Intel的另外一种内存标准-Rambus DRAM。在很多高端的显卡上,也配备了高速DDR RAM来提高带宽,这可以大幅度提高3D加速卡的像素渲染能力。
内存工作原理:
内存是用来存放当前正在使用的(即执行中)的数据和程序,我们平常所提到的计算机的内存指的是动态内存(即 DRAM),动态内存中所谓的"动态",指的是当我们将数据写入DRAM后,经过一段时间,数据会丢失,因此需要一个额外设电路进行内存刷新操作。
具体的工作过程是这样的:一个DRAM的存储单元存储 的是0还是1取决于电容是否有电荷,有电荷代表1,无电荷代表0。但时间一长,代表1的电容会 放电,代表0的电容会吸收电荷,这就是数据丢失的原因;刷新操作定期对电容进行检查,若电量大于满电量的1/2,则认为其代表1,并把电容充满电;若电量 小于1/2,则认为其代表0,并把电容放电,藉此来保持数据的连续性。
ROM也有很多种,PROM是可编程的ROM,PROM和EPROM(可擦除可编程ROM)两者区别是,PROM是 一次性的,也就是软件灌入后,就 无法修改了,这种是早期的产品,现在已经不可能使用了,而EPROM是通过紫外光的照射擦出原先的程序,是一种通用的存储器。另外一种EEPROM是通过 电子擦出,价格很高,写入时间很长,写入很慢。
举个例子,手机软件一般放在EEPROM中,我们打电话,有些最后拨打的号码,暂时是存在SRAM中的,不是马上写 入通过记录(通话记录保存在EEPROM中),因为当时有很重要工作(通话)要做,如果写入,漫长的等待是让用户忍无可忍的。
FLASH存储器又 称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据 (NVRAM的优势),U盘和MP3里用的就是这种存储器。在过去的20年里,嵌入式系统一直使用ROM(EPROM)作为它们的存储设备,然而近年来 Flash全面代替了ROM(EPROM)在嵌入式系统中的地位,用作存储Bootloader以及操作系统或者程序代码或者直接当硬盘使用(U盘)。
目前Flash主要有两种NOR Flash和NADN Flash
NOR Flash的读取和我们常见的SDRAM的读取是一样,用户可以直接运行装载在NOR FLASH里面的代码,这样可以减少SRAM的容量从而节约了成本。
NAND Flash没有采取内存的随机读取技术,它的读取是以一次读取一块的形式来进行的,通常是一次读取512个字节,采用这种技术的Flash比较廉价。用户 不能直接运行NAND Flash上的代码,因此好多使用NAND Flash的开发板除了使用NAND Flah以外,还作上了一块小的NOR Flash来运行启动代码。
一般小容量的用NOR Flash,因为其读取速度快,多 用来存储操作系统等重要信息,而大容量的用NAND FLASH,最常见的NAND FLASH应 用是嵌入式系统采用的DOC(Disk On Chip)和我们通常用的"闪盘",可以在线擦除。目前市面上的FLASH 主要来自Intel,AMD,Fujitsu和Toshiba,而生产NAND Flash的主要厂家有Samsung和Toshiba。
NAND Flash和NOR Flash的比较
NOR和NAND是现在市场上两种主要的非易失闪存技术。Intel于1988年首先开发出NOR flash技术,彻底改变了原先由EPROM和EEPROM一统天下的局面。紧接着,1989年,东芝公司发表了NAND flash结构,强调降低每比特的成本,更高的性能,并且象磁盘一样可以通过接口轻松升级。但是经过了十多年之后,仍然有相当多的硬件工程师分不清NOR 和NAND闪存。
相"flash存储器"经常可以与相"NOR存储器"互换使用。许多业内人士也搞不清楚NAND闪存技术相对于 NOR技术的优越之处,因为大多数情况下闪存只是用来存储少量的代码,这时NOR闪存更适合一些。而NAND则是高数据存储密度的理想解决方案。
NOR是现在市场上主要的非易失闪存技术。NOR一般 只用来存储少量的代码;NOR主要应用在代码存储介质中。NOR的特点是应用简单、无需专门的 接口电路、传输效率高,它是属于芯片内执行(XIP, eXecute In Place),这样应用程序可以直接在(NOR型)flash闪存内运行,不必再把代码读到系统RAM中。在1~4MB的小 容量时具有很高的成本效益,但 是很低的写入和擦除速度大大影响了它的性能。NOR flash带有SRAM接口,有足够的地址引脚来寻址,可以很容易地存取其内部的每一个字节。NOR flash占据了容量为1~16MB闪存市场的大部分。
NAND结构能提供极高的单元密度,可以达到高存储密度,并且写入和擦除的速度也很快。应用NAND的困难在于 flash的管理和需要特殊的系统接口。
1、性能比较:
flash闪存是非易失存储器,可以对称为块的存储器单元块进行擦写和再编程。任何flash器件的写入操作只 能在空或已擦除的单元内进行,所 以大多数情况下,在进行写入操作之前必须先执行擦除。NAND器件执行擦除操作是十分简单的,而NOR则要求在进行擦除前先要将目标块内所有的位都写为 1。
由于擦除NOR器件时是以64~128KB的块进行的,执行一个写入/擦除操作的时间为5s,与此相反, 擦除NAND器件是以8~32KB的块进 行的,执行相同的操作最多只需要4ms。
执行擦除时块尺寸的不同进一步拉大了NOR和NADN之间的性能差距,统计表明,对于给定的一套写入操作(尤其 是更新小文件时),更多的擦除操作必须在基于NOR的单元中进行。这样,当选择存储解决方案时,设计师必须权衡以下的各项因素:
● NOR的读速度比NAND稍快一些。
● NAND的写入速度比NOR快很多。
● NAND的4ms擦除速度远比NOR的5s快。
● 大多数写入操作需要先进行擦除操作。
● NAND的擦除单元更小,相应的擦除电路更少。
(注:NOR FLASH SECTOR擦除时间视品牌、大小不同而不同,比如,4M FLASH,有的SECTOR擦除时间为60ms,而有的需要最大6s。)
2、接口差别:
NOR flash带有SRAM接口,有足够的地址引脚来寻址,可以很容易地存取其内部的每一个字节。
NAND器件使用复杂的I/O口来串行地存取数据,各个产品或厂商的方法可能各不相同。8个引脚用来传送控制、 地址和数据信息。
NAND读和写操作采用512字节的块,这一点有点像硬盘管理此类操作,很自然地,基于NAND的存储器就可以 取代硬盘或其他块设备。
3、容量和成本:
NAND flash的单元尺寸几乎是NOR器件的一半,由于生产过程更为简单,NAND结构可以在给定的模具尺寸内提供更高的容量,也就相应地降低了价格。
NOR flash占据了容量为1~16MB闪存市场的大部分,而NAND flash只是用在8~128MB的产品当中,这也说明NOR主要应用在代码存储介质中,NAND适合于数据存储,NAND在CompactFlash、 Secure Digital、PC Cards和MMC存储卡市场上所占份额最大。
4、可靠性和耐用性:
采用flahs介质时一个需要重点考虑的问题是可靠性。对于需要扩展MTBF的系统来说,Flash是非常合适 的存储方案。可以从寿命(耐用性)、位交换和坏块处理三个方面来比较NOR和NAND的可靠性。
A) 寿命(耐用性)
在NAND闪存中每个块的最大擦写次数是一百万次,而NOR的擦写次数是十万次。NAND存储器除了具有10比1的块擦除周期优势,典型的NAND块尺寸要比NOR器件小8 倍,每个NAND存储器块在给定的时间内的删除次数要少一些。
B) 位交换
所有flash器件都受位交换现象的困扰。在某些情况下(很少见,NAND发生的次数要比NOR多),一个比特(bit)位会发 生反转或被报告反转了。
一位的变化可能不很明显,但是如果发生在一个关键文件上,这个小小的故障可能导致系统停机。如果只是报告有问 题,多读几次就可能解决了。
当然,如果这个位真的改变了,就必须采用错误探测/错误更正 (EDC/ECC)算法。位反转的问题更多见于NAND闪存,NAND的供应商建议使用NAND闪存的时候,同时使用EDC/ECC算法。
这个问题对于用NAND存储多媒体信息时倒不是致命的。当然,如果用本地存储设备来存储操作系统、配置文件或其 他敏感信息时,必须使用EDC/ECC系统以确保可靠性。
C) 坏块处理
NAND器件中的坏块是随机分布的。以前也曾有过消除坏块的努力,但发现成品率太低,代价太高,根本不划算。
NAND器件需要对介质进行初始化扫描以发现坏块,并将坏块标记为不可用。在已制成的器件中,如果通过可靠的方 法不能进行这项处理,将导致高故障率。
5、易于使用:
可以非常直接地使用基于NOR的闪存,可以像其他存储器那样连接,并可以在上面直接运行代码。
由于需要I/O接口,NAND要复杂得多。各种NAND器件的存取方法因厂家而异。
在使用NAND器件时,必须先写入驱动程序,才能继续执行其他操作。向NAND器件写入信息需要相当的技巧,因 为设计师绝不能向坏块写入,这就意味着在NAND器件上自始至终都必须进行虚拟映射。
6、软件支持:
当讨论软件支持的时候,应该区别基本的读/写/擦操作和高一级的用于磁盘仿真和闪存管理算法的软件,包括性能优 化。
在NOR器件上运行代码不需要任何的软件支持,在NAND器件上进行同样操作时,通常需要驱动程序,也就是内存 技术驱动程序(MTD),NAND和NOR器件在进行写入和擦除操作时都需要MTD。
使用NOR器件时所需要的MTD要相对少一些,许多厂商都提供用于NOR器件的更高级软件,这其中包括M- System的TrueFFS驱动, 该驱动被Wind River system、Microsoft、QNX Software system、Symbian和Intel等厂商所采用。
驱动还用于对DiskOnChip产品进行仿真和NAND闪存的管理,包括纠错、坏块处理和损耗平衡。
NOR FLASH的主要供应商是INTEL ,MICRO等厂商,曾经是FLASH的主流产品,但现在被NAND FLASH挤的比较难受。它的优点是可以直接从FLASH中运行程序,但是工艺复杂,价格比较贵。
NAND FLASH的主要供应商是SAMSUNG和东芝,在U盘、各种存储卡、MP3播放器里面的都是这种FLASH,由于工艺上的不同,它比NOR FLASH拥有更大存储容量,而且便宜。但也有缺点,就是无法寻址直接运行程序,只能存储数据。另外NAND FLASH 非常容易出现坏区,所以需要有校验的算法。
在掌上电脑里要使用NAND FLASH 存储数据和程序,但是必须有NOR FLASH来启动。除了SAMSUNG处理器,其他用在掌上电脑的主流处理器还不支持直接由NAND FLASH 启动程序。因此,必须先用一片小的NOR FLASH 启动机器,在把OS等软件从NAND FLASH 载入SDRAM中运行才行,挺麻烦的。
DRAM 利用MOS管的栅电容上的电荷来存储信息, 一旦掉电信息会全部的丢失,由于栅极会漏电,所以每隔一定的时间就需要一个刷新机构给这些栅电容补充电荷,并且 每读出一次数据之后也需要补充电荷,这个就叫动态刷新,所以称其为动态随机存储器。由于它只使用一个MOS管来存信息,所以集成度可以很高,容量能够做的 很大。SDRAM比它多了一个与CPU时钟同步。
SRAM 利用寄存器来存储信息,所以一旦掉电,资料就会全部丢失,只要供电,它的资料就会一直存在,不需要动态刷新,所以叫静态随机存储器。
以上主要用于系统内存储器,容量大,不需要断电后仍保存数据的。
Flash ROM 是利用浮置栅上的电容存储电荷来保存信息,因为浮置栅不会漏电,所以断电后信息仍然可以保存。也由于其机构简单所以集成度可以做的很高,容量可以很大。 Flash rom写入前需要用电进行擦除,而且擦除不同与EEPROM可以以byte(字节)为单位进行,flash rom只能以sector(扇区)为单位进行。不过其写入时可以byte为单位。flash rom主要用于bios,U盘,Mp3等需要大容量且断电不丢数据的设备。
PSRAM,假静态随机存储器
背景:
PSRAM具有一个单晶体管的DRAM储存格,与传统具有六个晶体管的SRAM储存格或是四个晶体管与two- load resistor SRAM 储存格大不相同,但它具有类似SRAM的稳定接口,内部的DRAM架构给予PSRAM一些比low-power 6T SRAM优异的长处,例如体积更为轻巧,售价更具竞争力。目前在整体SRAM市场中,有90%的制造商都在生产PSRAM组件。在过去两年,市场上重要的 SRAM/PSRAM供货商有Samsung、Cypress、Renesas、Micron与Toshiba等。
基本原理:
PSRAM就是伪SRAM,内部的内存颗粒跟SDRAM的颗粒相似,但外部的接口跟SRAM相似,不需要SDRAM 那样复杂的控制器和刷新机制,PSRAM的接口跟SRAM的接口是一样的。
PSRAM容量有8Mbit,16Mbit,32Mbit等等,容量没有SDRAM那样密度高,但肯定是比SRAM 的容量要高很多的,速度支持突发 模式,并不是很慢,Hynix,Coremagic, WINBOND .MICRON. CY 等厂家都有供应,价格只比相同容量的SDRAM稍贵一点点,比SRAM便宜很多。
PSRAM主要应用于手机,电子词典,掌上电脑,PDA,PMP.MP3/4,GPS接收器等消费电子产品与 SRAM(采用6T的技术)相 比,PSRAM采用的是1T+1C的技术,所以在体积上更小,同时,PSRAM的I/O接口与SRAM相同.在容量上,目前有 4MB,8MB,16MB,32MB,64MB和128MB。比较于SDRAM,PSRAM的功耗要低很多。所以对于要求有一定缓存容量的很多便携式产品 是一个理想的选择。
各种Flash卡:
数码闪存卡:主流数码存储介质
数码相机、MP3播放器、掌上电脑、手 机等数字设备是闪存最主要的市场。前面提到,手机领域以NOR型闪存为主、闪存芯片被直接做在内部的电路板上,但数 码相机、MP3播放器、掌上电脑等设备要求存储介质具备可更换性,这就必须制定出接口标准来实现连接,闪存卡技术应运而生。闪存卡是以闪存作为核心存储部 件,此外它还具备接口控制电路和外在的封装,从逻辑层面来说可以和闪盘归为一类,只是闪存卡具有更浓的专用化色彩、而闪盘则使用通行的USB接口。由于历 史原因,闪存卡技术未能形成业界统一的工业标准,许多厂商都开发出自己的闪存卡方案。目前比较常见的有CF卡、SD卡、SM卡、MMC卡和索尼的 Memory Stick记忆棒。
CF卡(CompactFlash)
CF卡是美国SanDisk 公司于1994引入的闪存卡,可以说是最早的大容量便携式存储设备。它的大小只有43mm×36mm×3.3mm,相当于笔记本电脑的PCMCIA卡体积 的四分之一。CF卡内部拥有独立的控制器芯片、具有完全的PCMCIA-ATA 功能,它与设备的连接方式同PCMCIA卡的连接方式类似,只是CF卡的针脚数多达五十针。这种连接方式稳定而可靠,并不会因为频繁插拔而影响其稳定性。
CF 卡没有任何活动的部件,不存在物理坏道之类的问题,而且拥有优秀的抗震性能, CF卡比软盘、硬盘之类的设备要安全可靠。CF卡的功耗很低,它可以自适应3.3伏和5伏两种电压,耗电量大约相当于桌面硬盘的百分之五。这样的特性是出 类拔萃的,CF卡出现之后便成为数码相机的首选存储设备。经过多年的发展,CF卡技术已经非常成熟,容量从最初的4MB飙升到如今的3GB,价格也越来越 平实,受到各数码相机制造商的普遍喜爱,CF卡目前在数码相机存储卡领域的市场占有率排在第二位。
MMC卡 (MultiMediaCard)
MMC卡是SanDisk公司和德国西门子公司于1997年 合作推出的新型存储卡,它的尺寸只有32mm×24mm×1.4mm、大小同一枚邮票差不多; 其重量也多在2克以下,并且具有耐冲击、可反复读写30万次以上等特点。从本质上看,MMC与CF其实属于同一技术体系,两者结构都包括快闪存芯片和控制 器芯片,功能也完全一样,只是MMC卡的尺寸超小,而连接器也必须做在狭小的卡里面,导致生产难度和制造成本都很高、价格较为昂贵。MMC主要应用与移动 电话和MP3播放器等体积小的设备
总线通信协议
2013-11-20记录,微机中总线一般有内部总线、系统总线和外部总线。内部总线是微机内部各外围芯片与处理器之间的总线,用于芯片一级的互连;而系统总线是微机中各插件板与系统板之间的总线,用于插件板一级的互连;外部总线则是微机和外部设备之间的总线,微机作为一种设备,通过该总线和其他设备进行信息与数据交换,它用于设备一级的互连。
另外,从广义上说,计算机通信方式可以分为并行通信和串行通信,相应的通信总线被称为并行总线和串行总线。并行通信速度快、实时性好,但由于占用的口线多,不适于小型化产品;而串行通信速率虽低,但在数据通信吞吐量不是很大的微处理电路中则显得更加简易、方便、灵活。串行通信一般可分为异步模式和同步模式。
1、内部总线
1.1.I 2C总线
I2C(Inter-IC)总线10多年前由Philips公司推出,是近年来在微电子通信控制领域广泛采用的一种新型总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简化,器件封装形式小,通信速率较高等优点。在主从通信中,可以有多个I2C总线器件同时接到I2C总线上,通过地址来识别通信对象。
1.2.SPI总线
串行外围设备接口SPI(serial peripheral interface)总线技术是Motorola公司推出的一种同步串行接口。Motorola公司生产的绝大多数MCU(微控制器)都配有SPI硬件接口,如68系列MCU。SPI总线是一种三线同步总线,因其硬件功能很强,所以,与SPI有关的软件就相当简单,使CPU有更多的时间处理其他事务。
1.3.SCI总线
串行通信接口SCI(serial communication interface)也是由Motorola公司推出的。它是一种通用异步通信接口UART,与MCS-51的异步通信功能基本相同。
2、系统总线
2.1、ISA总线
ISA(industrial standard architecture)总线标准是IBM 公司1984年为推出PC/AT机而建立的系统总线标准,所以也叫AT总线。它是对XT总线的扩展,以适应8/16位数据总线要求。它在80286至 80486时代应用非常广泛,以至于现在奔腾机中还保留有ISA总线插槽。ISA总线有98只引脚。
2.2、EISA总线
EISA总线是1988年由Compaq等9家公司联合推出的总线标准。它是在ISA总线的基础上使用双层插座,在原来ISA总线的98 条信号线上又增加了98条信号线,也就是在两条ISA信号线之间添加一条EISA信号线。在实用中,EISA总线完全兼容ISA总线信号。
2.3、VESA总线
VESA(video electronics standard association)总线是 1992年由60家附件卡制造商联合推出的一种局部总线,简称为VL(VESA local bus)总线。它的推出为微机系统总线体系结构的革新奠定了基础。该总线系统考虑到CPU与主存和Cache 的直接相连,通常把这部分总线称为CPU总线或主总线,其他设备通过VL总线与CPU总线相连,所以VL总线被称为局部总线。它定义了32位数据线,且可通过扩展槽扩展到64位,使用33MHz时钟频率,最大传输率达132MB/s,可与CPU同步工作。是一种高速、高效的局部总线,可支持386SX、386DX、 486SX、486DX及奔腾微处理器。
2.4、Compact PCI
以上所列举的几种系统总线一般都用于商用PC机中,在计算机系统总线中,还有另一大类为适应工业现场环境而设计的系统总线,比如STD总线、 VME总线、PC/104总线等。这里仅介绍当前工业计算机的热门总线之一------Compact PCI。
Compact PCI的意思是"坚实的PCI",是当今第一个采用无源总线底板结构的PCI系统,是PCI总线的电气和软件标准加欧式卡的工业组装标准,是当今最新的一种工业计算机标准。Compact PCI是在原来PCI总线基础上改造而来,它利用PCI的优点,提供满足工业环境应用要求的高性能核心系统,同时还考充分利用传统的总线产品,如 ISA、STD、VME或PC/104来扩充系统的I/O和其他功能。
3、外部总线
3.1、RS-232-C总线
RS-232-C是美国电子工业协会EIA(Electronic Industry Association)制定的一种串行物理接口标准。RS是英文"推荐标准"的缩写,232为标识号,C表示修改次数。RS-232-C总线标准设有25条信号线,包括一个主通道和一个辅助通道,在多数情况下主要使用主通道,对于一般双工通信,仅需几条信号线就可实现,如一条发送线、一条接收线及一条地线。RS-232-C标准规定的数据传输速率为每秒50、75、 100、150、300、600、1200、2400、4800、9600、19200波特。RS-232-C标准规定,驱动器允许有2500pF的电容负载,通信距离将受此电容限制,例如,采用150pF/m的通信电缆时,最大通信距离为 15m;若每米电缆的电容量减小,通信距离可以增加。传输距离短的另一原因是RS-232属单端信号传送,存在共地噪声和不能抑制共模干扰等问题,因此一般用20m以内的通信。
3.2、RS-485总线
在要求通信距离为几十米到上千米时,广泛采用RS-485 串行总线标准。RS-485采用平衡发送和差分接收,因此具有抑制共模干扰的能力。加上总线收发器具有高灵敏度,能检测低至200mV的电压,故传输信号 能在千米以外得到恢复。 RS-485采用半双工工作方式,任何时候只能有一点处于发送状态,因此,发送电路须由使能信号加以控制。RS-485用于多点互连时非常方便,可以省掉许多信号线。应用RS-485 可以联网构成分布式系统,其允许最多并联32台驱动器和32台接收器。
2.3、IEEE-488总线
上述两种外部总线是串行总线,而IEEE-488 总线是并行总线接口标准。IEEE-488总线用来连接系统,如微计算机、数字电压表、数码显示器等设备及其他仪器仪表均可用IEEE-488总线装配起来。它按照位并行、字节串行双向异步方式传输信号,连接方式为总线方式,仪器设备直接并联于总线上而不需中介单元,但总线上最多可连接15台设备。最大传输距离为20米,信号传输速度一般为500KB/s,最大传输速度为1MB/s。
2.4、USB总线
通用串行总线USB(universal serial bus)是由Intel、Compaq、Digital、IBM、Microsoft、NEC、Northern Telecom等7家世界著名的计算机和通信公司共同推出的一种新型接口标准。它基于通用连接技术,实现外设的简单快速连接,达到方便用户、降低成本、扩展PC连接外设范围的目的。它可以为外设提供电源,而不像普通的使用串、并口的设备需要单独的供电系统。另外,快速是USB技术的突出特点之一,USB的最高传输率可达12Mbps比串口快100倍,比并口快近10倍,而且USB还能支持多媒体。
CooCox CoIDE
2016-03-11记录,STM32的开发环境有很多总,官方手册也提供了IAR Embedded Workbench、MDK-ARM和TrueSTUDIO这3种。今天我试用了CooCox CoIDE,是免费的集成开发环境,同TI公司提供的Code Composer Studio CCS一样,基于Eclipse和GCC的全功能集成开发环境(IDE),两者的界面都差不多,用惯了CCSv5的话,就很容易上手了。下面开始我的试用。
1. 下载并安装CooCox CoIDE:CooCox CoIDE V1.7.5 9/29/2013更新
下载地址:http://www.coocox.org/CoIDE/CoIDE_Updates.htm,下面是界面:
2. 首先你需要自己下载安装编译交叉工具链gcc-arm-none-eabi-4_7-2013q3-20130916-win32.exe。下载地址:GNU Arm Embedded Toolchain project files : GNU Arm Embedded Toolchain
安装完之后的目录:
3. 配置CoIDE的编译器,选择Project->Select Toolchain Path
选择你先前安装工具链的目录:例如我的是D:\\Program Files (x86)\\GNU Tools ARM Embedded\\4.7 2013q3\\bin
4. 接下来开始创建工程了,一步步按照下面操作,简单点击下就行了,省了好多事儿!
按照我下面的图打上勾,我第一个测试的GPIO控制
接下来点击Build:
啊哦!怎么会有warning呢?好吧,先解决下。当然找度娘啦!
找到出现警告的原因后,解决办法如下。(个人方法,仅供参考)
再来编译下,没有警告了。
5. 接下来就是编写程序了[code]#include \"stm32f0xx.h\"
cpp
#include \"stm32f0xx_rcc.h\"
#include \"stm32f0xx_gpio.h\"
GPIO_InitTypeDef GPIO_InitStructure;
int main(void)
{
uint32_t times;
/* GPIOC Periph clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
/* Configure PC8 and PC9 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOC, &GPIO_InitStructure);
while (1)
{
/* Set PC8 and PC9 */
GPIO_SetBits(GPIOC, GPIO_Pin_8);
/* Delay some time */
for(times = 0; times < 500000; times++);
GPIO_SetBits(GPIOC, GPIO_Pin_9);
for(times = 0; times < 500000; times++);
GPIO_ResetBits(GPIOC, GPIO_Pin_8);
for(times = 0; times < 500000; times++);
GPIO_ResetBits(GPIOC, GPIO_Pin_9);
for(times = 0; times < 500000; times++);
}
}
6. 你也可以查看官方提供的例程
7. 总结:有人问这么多的开发环境,该怎么选择呢?其实你大可以每款软件都尝试用一下,根据自己的口味来选择。比如人家分享的心得,在Ubuntu下开发STM32,其实也是基于Eclipse和GCC,这就涉及到了开源了。我喜欢开源。过两天有时间也配置下Ubuntu13.10下的STM32F0的开发环境,图文并茂哦
STM32之SD
2016-3-23记录,很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有U盘,FLASH芯片,SD卡等。他们各有优点,综合比较,最适合单片机系统的莫过于SD卡了,它不仅容量可以做到很大(32Gb以上),而且支持SPI接口,方便移动,并且有几种体积的尺寸可供选择(标准的SD卡尺寸,以及TF卡尺寸等),能满足不同应用的要求。
只需要4个IO口即可外扩一个最大达32GB以上的外部存储器,容量从几十M到几十G选择尺度很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。
ALIENTKE 战舰STM32开发板自带了标准的SD卡接口,可使用STM32自带的SPI/SDIO接口驱动(通过跳线帽选择驱动方式),本章我们使用SPI驱动,最高通信速度可达18Mbps,每秒可传输数据2M字节以上,对于一般应用足够了。在本章中,我们将向大家介绍,如何在ALIENTEK战舰STM32开发板上实现SD卡的读取。本章分为如下几个部分:
- 44.1 SD卡简介
SD卡(Secure Digital Memory Card)中文翻译为安全数码卡,它是在MMC的基础上发展而来,是一种基于半导体快闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。大小犹如一张邮票的SD记忆卡,重量只有2克,但却拥有高记忆容量、快速数据传输率、极大的移动灵活性以及很好的安全性。按容量分类,可以将SD卡分为3类:SD卡、SDHC卡、SDXC卡。如表44.1.1所示
SD卡和SDHC卡协议基本兼容,但是SDXC卡,同这两者区别就比较大了,本章我们讨论的主要是SD/SDHC卡(简称SD卡)。
SD卡一般支持2种操作模式:SD卡模式(通过SDIO通信 和 SPI模式。
主机可以选择以上任意一种模式同SD卡通信,SD卡模式允许4线的高速数据传输。SPI模式允许简单的通过SPI接口来和SD卡通信,这种模式同SD卡模式相比就是丧失了速度。
后面省略,请直接参考芯片手册
- 44.2 硬件设计
- 44.3 软件设计
打开上一章的工程,首先在HARDWARE文件夹下新建一个SD的文件夹。然后新建一个MMC_SD.C和MMC_SD.H的文件保存在SD文件夹下,并将这个文件夹加入头文件包含路径。
打开MMC_SD.C文件,在该文件里面,我们输入与SD卡相关的操作代码,这里由于篇幅限制,我们不贴出所有代码,仅介绍两个最重要的函数,第一个是SD_Initialize函数,该函数源码如下:
cpp
//初始化SD卡
u8 SD_Initialize(void)
{
u8 r1; // 存放SD卡的返回值
u16 retry; // 用来进行超时计数
u8 buf[4];
u16 i;
SD_SPI_Init(); //初始化IO
SD_SPI_SpeedLow(); //设置到低速模式
for(i=0;i<10;i++)SD_SPI_ReadWriteByte(0XFF);//发送最少74个脉冲
retry=20;
do
{
r1=SD_SendCmd(CMD0,0,0x95);//进入IDLE状态
}while((r1!=0X01) && retry--);
SD_Type=0;//默认无卡
if(r1==0X01)
{
if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
{
for(i=0;i<4;i++)buf=SD_SPI_ReadWriteByte(0XFF);//得到R7相应值
if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V
{
retry=0XFFFE;
do
{
SD_SendCmd(CMD55,0,0X01); //发送CMD55
r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41
}while(r1&&retry--);
if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
{
for(i=0;i<4;i++)buf=SD_SPI_ReadWriteByte(0XFF);//得到OCR值
if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC; //检查CCS
else SD_Type=SD_TYPE_V2;
}
}
}else//SD V1.x/ MMC V3
{
SD_SendCmd(CMD55,0,0X01); //发送CMD55
r1=SD_SendCmd(CMD41,0,0X01); //发送CMD41
if(r1<=1)
{
SD_Type=SD_TYPE_V1;
retry=0XFFFE;
do //等待退出IDLE模式
{
SD_SendCmd(CMD55,0,0X01); //发送CMD55
r1=SD_SendCmd(CMD41,0,0X01);//发送CMD41
}while(r1&&retry--);
}else//MMC卡不支持CMD55+CMD41识别
{
SD_Type=SD_TYPE_MMC;//MMC V3
retry=0XFFFE;
do //等待退出IDLE模式
{
r1=SD_SendCmd(CMD1,0,0X01);//发送CMD1
}while(r1&&retry--);
}
if(retry==0||SD_SendCmd(CMD16,512,0X01)!=0)SD_Type=SD_TYPE_ERR;
//错误的卡
}
}
SD_DisSelect();//取消片选
SD_SPI_SpeedHigh();//高速
if(SD_Type)return 0;
else if(r1)return r1;
return 0xaa;//其他错误
}
该函数先设置与SD相关的IO口及SPI初始化,然后发送CMD0,进入IDLE状态,并设置SD卡为SPI模式通信,然后判断SD卡类型,完成SD卡的初始化,注意该函数调用的SD_SPI_Init等函数,实际是对SPI2的相关函数进行了一层封装,方便移植。另外一个要介绍的函数是 SD_ReadDisk,该函数用于从SD卡读取一个扇区的数据(这里一般为512字节),该函数代码如下:
//读SD卡
//buf:数据缓存区
//sector:扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//转换为字节地址
if(cnt==1)
{
r1=SD_SendCmd(CMD17,sector,0X01); //读命令
if(r1==0) r1=SD_RecvData(buf,512); //命令发送成功,接收512个字节
}else
{
r1=SD_SendCmd(CMD18,sector,0X01);//连续读命令
do
{
r1=SD_RecvData(buf,512);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
SD_SendCmd(CMD12,0,0X01); //发送停止命令
}
SD_DisSelect();//取消片选
return r1;//
}
此函数先发送CMD17命令,然后读取一个扇区的数据,详细见代码,这里我们就不多介绍了。保存MMC_SD.C文件,并加入到HARDWARE组下,然后打开MMC_SD.H,在该文件里面输入如下代码:
#ifndef _MMC_SD_H_
#define _MMC_SD_H_
#include "sys.h"
#include <stm32f10x_map.h>
// SD卡类型定义
#define SD_TYPE_ERR 0X00
#define SD_TYPE_MMC 0X01
#define SD_TYPE_V1 0X02
#define SD_TYPE_V2 0X04
#define SD_TYPE_V2HC 0X06
// SD卡指令表
#define CMD0 0 //卡复位
#define CMD1 1
#define CMD8 8 //命令8 ,SEND_IF_COND
#define CMD9 9 //命令9 ,读CSD数据
#define CMD10 10 //命令10,读CID数据
#define CMD12 12 //命令12,停止数据传输
#define CMD16 16 //命令16,设置SectorSize 应返回0x00
#define CMD17 17 //命令17,读sector
#define CMD18 18 //命令18,读Multi sector
#define CMD23 23 //命令23,设置多sector写入前预先擦除N个block
#define CMD24 24 //命令24,写sector
#define CMD25 25 //命令25,写Multi sector
#define CMD41 41 //命令41,应返回0x00
#define CMD55 55 //命令55,应返回0x01
#define CMD58 58 //命令58,读OCR信息
#define CMD59 59 //命令59,使能/禁止CRC,应返回0x00
//数据写入回应字意义
#define MSD_DATA_OK 0x05
#define MSD_DATA_CRC_ERROR 0x0B
#define MSD_DATA_WRITE_ERROR 0x0D
#define MSD_DATA_OTHER_ERROR 0xFF
//SD卡回应标记字
#define MSD_RESPONSE_NO_ERROR 0x00
#define MSD_IN_IDLE_STATE 0x01
#define MSD_ERASE_RESET 0x02
#define MSD_ILLEGAL_COMMAND 0x04
#define MSD_COM_CRC_ERROR 0x08
#define MSD_ERASE_SEQUENCE_ERROR 0x10
#define MSD_ADDRESS_ERROR 0x20
#define MSD_PARAMETER_ERROR 0x40
#define MSD_RESPONSE_FAILURE 0xFF
//这部分应根据具体的连线来修改!
//战舰STM32开发板使用的是PD2作为SD卡的CS脚.
#define SD_CS PDout(2) //SD卡片选引脚 extern u8 SD_Type;//SD卡的类型
//函数申明区
u8 SD_SPI_ReadWriteByte(u8 data);
void SD_SPI_SpeedLow(void);
void SD_SPI_SpeedHigh(void);
u8 SD_WaitReady(void); //等待SD卡准备
u8 SD_GetResponse(u8 Response); //获得相应
u8 SD_Initialize(void); //初始化
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt); //读块
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt); //写块
u32 SD_GetSectorCount(void); //读扇区数
u8 SD_GetCID(u8 *cid_data); //读SD卡CID
u8 SD_GetCSD(u8 *csd_data); //读SD卡CSD
#endif
该部分代码主要是一些命令的宏定义以及函数声明,在这里我们设定了SD卡的CS管脚为PD2。保存MMC_SD.H,就可以在主函数里面编写我们的应用代码了,打开test.c文件,在该文件中修改main函数如下:
int main(void)
{
u8 key; u8 t=0; u8 *buf;
u32 sd_size;
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600); //串口初始化为9600
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
LCD_Init(); //初始化LCD
usmart_dev.init(72); //初始化USMART
KEY_Init(); //按键初始化
FSMC_SRAM_Init(); //初始化外部SRAM
mem_init(SRAMIN); //初始化内部内存池
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"WarShip STM32");
LCD_ShowString(60,70,200,16,16,"SD CARD TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2012/9/17");
LCD_ShowString(60,130,200,16,16,"KEY0:Read Sector 0");
while(SD_Initialize())//检测不到SD卡
{
LCD_ShowString(60,150,200,16,16,"SD Card Error!");
delay_ms(500);
LCD_ShowString(60,150,200,16,16,"Please Check! ");
delay_ms(500);
LED0=!LED0;//DS0闪烁
}
POINT_COLOR=BLUE;//设置字体为蓝色
//检测SD卡成功
LCD_ShowString(60,150,200,16,16,"SD Card OK ");
LCD_ShowString(60,170,200,16,16,"SD Card Size: MB");
sd_size=SD_GetSectorCount();//得到扇区数
LCD_ShowNum(164,170,sd_size>>11,5,16);//显示SD卡容量(MB)
while(1)
{
key=KEY_Scan(0);
if(key==KEY_RIGHT)//KEY0按下了
{
buf=mymalloc(0,512); //在内部内存池,申请512字节内存
if(SD_ReadDisk(buf,0,1)==0) //读取0扇区的内容
{
LCD_ShowString(60,190,200,16,16,"USART1 Sending Data...");
printf("SECTOR 0 DATA:\r\n");
for(sd_size=0;sd_size<512;sd_size++)printf("%x ",buf[sd_size]);
//打印0扇区数据
printf("\r\nDATA ENDED\r\n");
LCD_ShowString(60,190,200,16,16,"USART1 Send Data Over!");
}
myfree(0,buf); //释放内存
}
t++;
delay_ms(10);
if(t==20)
{
LED0=!LED0;
t=0;
}
}
}
这里我们通过SD_GetSectorCount函数来得到SD卡的扇区数,间接得到SD卡容量,然后在液晶上显示出来,接着我们通过按键KEY0控制读取SD卡的扇区0,然后把读到的数据通过串口打印出来。这里,我们对上一章学过的内存管理小试牛刀,稍微用了下,以后我们会尽量使用内存管理来设计。
- 44.4 下载验证
STM32之FATFS
FATFS是一个完全免费开源的FAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准C 语言编写,所以具有良好的硬件平台独立性,可以移植到8051、PIC、AVR、SH、Z80、H8、ARM 等系列单片机上而只需做简单的修改。它支持FATl2、FATl6 和FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对8 位单片机和16 位单片机做了优化。
FATFS的特点有:
- Windows兼容的FAT文件系统(支持FAT12/FAT16/FAT32)
- 与平台无关,移植简单
- 代码量少、效率高
- 多种配置选项
- 支持多卷(物理驱动器或分区,最多10个卷)
- 多个ANSI/OEM代码页包括DBCS
- 支持长文件名、ANSI/OEM或Unicode
- 支持RTOS
- 支持多种扇区大小
- 只读、最小化的API和I/O缓冲区等
FATFS的这些特点,加上免费、开源的原则,使得FATFS应用非常广泛。
最顶层是应用层,使用者无需理会FATFS的内部结构和复杂的FAT 协议,只需要调用FATFS模块提供给用户的一系列应用接口函数,如f_open,f_read,f_write 和f_close等,就可以像在PC 上读/写文件那样简单。
中间层FATFS模块,实现了FAT 文件读/写协议。FATFS模块提供的是ff.c和ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。
需要我们编写移植代码的是FATFS模块提供的底层接口,它包括存储媒介读/写接口(disk I/O)和供给文件创建修改时间的实时时钟。
FATFS的源码,大家可以在:http://elm-chan.org/fsw/ff/00index_e.html 这个网站下载到,目前最新版本为R0.09a。本章我们就使用最新版本的的FATFS作为介绍,下载最新版本的FATFS软件包,解压后可以得到两个文件夹:doc和src。doc里面主要是对FATFS的介绍,而src里面才是我们需要的源码。
其中,与平台无关的是:
- ffconf.h FATFS模块配置文件
- ff.h FATFS和应用模块公用的包含文件
- ff.c FATFS模块
- diskio.h FATFS和disk I/O模块公用的包含文件
- interger.h 数据类型定义
- option 可选的外部功能(比如支持中文等)
与平台相关的代码(需要用户提供)是:
- diskio.c FATFS和disk I/O模块接口层文件
FATFS模块在移植的时候,我们一般只需要修改2个文件,即ffconf.h和diskio.c。FATFS模块的所有配置项都是存放在ffconf.h里面,我们可以通过配置里面的一些选项,来满足自己的需求。接下来我们介绍几个重要的配置选项。
1)_FS_TINY。这个选项在R0.07版本中开始出现,之前的版本都是以独立的C文件出现(FATFS和Tiny FATFS),有了这个选项之后,两者整合在一起了,使用起来更方便。我们使用FATFS,所以把这个选项定义为0即可。
2)_FS_READONLY。这个用来配置是不是只读,本章我们需要读写都用,所以这里设置为0即可。
3)_USE_STRFUNC。这个用来设置是否支持字符串类操作,比如f_putc,f_puts等,本章我们需要用到,故设置这里为1。
4)_USE_MKFS。这个用来定时是否使能格式化,本章需要用到,所以设置这里为1。
5)_USE_FASTSEEK。这个用来使能快速定位,我们设置为1,使能快速定位。
6)_CODE_PAGE。这个用于设置语言类型,包括很多选项(见FATFS官网说明),我们这里设置为936,即简体中文(GBK码,需要c936.c文件支持,该文件在option文件夹)。
7)_USE_LFN。该选项用于设置是否支持长文件名(还需要_CODE_PAGE支持),取值范围为0~3。0,表示不支持长文件名,1~3是支持长文件名,但是存储地方不一样,我们选择使用3,通过ff_memalloc函数来动态分配长文件名的存储区域。
8)_VOLUMES。用于设置FATFS支持的逻辑设备数目,我们设置为2,即支持2个设备。
9)_MAX_SS。扇区缓冲的最大值,一般设置为512。
其他配置项,我们这里就不一一介绍了,FATFS的说明文档里面有很详细的介绍,大家自己阅读即可。
FATFS的移植
下面我们来讲讲FATFS的移植,FATFS的移植主要分为3步:
- 数据类型:在integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数
- 据类型,并根据编译器定义好数据类型。
- 配置:通过ffconf.h配置FATFS的相关功能,以满足你的需要。
- 函数编写:打开diskio.c,进行底层驱动编写,一般需要编写6 个接口函数,
通过以上三步,我们即可完成对FATFS的移植。
第一步,我们使用的是MDK3.80a编译器,器数据类型和integer.h里面定义的一致,所以此步,我们不需要做任何改动。
第二步,关于ffconf.h里面的相关配置,我们在前面已经有介绍(之前介绍的9个配置),我们将对应配置修改为我们介绍时候的值即可,其他的配置用默认配置。
第三步,因为FATFS模块完全与磁盘I/O 层分开,因此需要下面的函数来实现底层物理磁盘的读写与获取当前时间。底层磁盘I/O 模块并不是FATFS的一部分,并且必须由用户提供。这些函数一般有6个,在diskio.c里面
以上六个函数,我们将在软件设计部分一一实现。通过以上3个步骤,我们就完成了对FATFS的移植,就可以在我们的代码里面使用FATFS了。
FATFS提供了很多API函数,这些函数FATFS的自带介绍文件里面都有详细的介绍(包括参考代码),我们这里就不多说了。这里需要注意的是,在使用FATFS的时候,必须先通过f_mount函数注册一个工作区,才能开始后续API的使用,关于FATFS的介绍,我们就介绍到这里。大家可以通过FATFS自带的介绍文件进一步了解和熟悉FATFS的使用。
硬件设计
本章实验功能简介:开机的时候先初始化SD卡,初始化成功之后,注册两个工作区(一个给SD卡用,一个给SPI FLASH用),然后获取SD卡的容量和剩余空间,并显示在LCD模块上,最后等待USMART输入指令进行各项测试。本实验通过DS0指示程序运行状态。
本实验用到的硬件资源有:
- 指示灯DS0
- 串口
- TFTLCD模块
- SD卡
- SPI FLASH
这些,我们在之前都已经介绍过,如有不清楚,请参考之前内容。
软件设计
本章,我们将FATFS部分单独做一个分组,在工程目录下新建一个FATFS的文件夹,然后将FATFS R0.09a程序包解压到该文件夹下。同时,我们在FATFS文件夹里面新建一个exfuns的文件夹,用于存放我们针对FATFS做的一些扩展代码。
然后,打开上一章的工程,新建一个FATFS分组,然后将图45.3.1的src文件夹里面的ff.h、diskio.h以及option文件夹下的cc936.c等3个文件加入到FATFS组下,并将src文件夹加入头文件包含路径。
cpp
打开diskio.c,修改代码如下:
#include "mmc_sd.h"
#include "diskio.h"
#include "flash.h"
#include "malloc.h"
#define SD_CARD 0 //SD卡,卷标为0
#define EX_FLASH 1 //外部flash,卷标为1
#define FLASH_SECTOR_SIZE 512
//对于W25Q64
//前6M字节给fatfs用,6M字节后~6M+500K给用户用,6M+500K以后,用于存放字库,
//字库占用1.5M.
u16 FLASH_SECTOR_COUNT=2048*6; //6M字节,默认为W25Q64
#define FLASH_BLOCK_SIZE 8 //每个BLOCK有8个扇区
//初始化磁盘
DSTATUS disk_initialize (
BYTE drv /* Physical drive nmuber (0..) */
)
{
u8 res=0;
switch(drv)
{
case SD_CARD://SD卡
res = SD_Initialize();//SD_Initialize()
if(res)//STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,
{ //可能导致SPI读写异常
SD_SPI_SpeedLow();
SD_SPI_ReadWriteByte(0xff);//提供额外的8个时钟
SD_SPI_SpeedHigh();
}
break;
case EX_FLASH://外部flash
SPI_Flash_Init();
if(SPI_FLASH_TYPE==W25Q64)FLASH_SECTOR_COUNT=2048*6;
else FLASH_SECTOR_COUNT=2048*2; //其他
break;
default:
res=1;
}
if(res)return STA_NOINIT;
else return 0; //初始化成功
}
//获得磁盘状态
DSTATUS disk_status (
BYTE drv /* Physical drive nmuber (0..) */
)
{
return 0;
}
//读扇区
//drv:磁盘编号0~9
//*buff:数据接收缓冲首地址
//sector:扇区地址
//count:需要读取的扇区数
DRESULT disk_read (
BYTE drv, /* Physical drive nmuber (0..) */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to read (1..255) */
)
{
u8 res=0;
if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误
switch(drv)
{
case SD_CARD://SD卡
res=SD_ReadDisk(buff,sector,count);
if(res) //STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,
{ //可能导致SPI读写异常
SD_SPI_SpeedLow();
SD_SPI_ReadWriteByte(0xff);//提供额外的8个时钟
SD_SPI_SpeedHigh();
}
break;
case EX_FLASH://外部flash
for(;count>0;count--)
{
SPI_Flash_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
sector++; buff+=FLASH_SECTOR_SIZE;
}
res=0;
break;
default:
res=1;
}
//处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
if(res==0x00)return RES_OK;
else return RES_ERROR;
}
//写扇区
//drv:磁盘编号0~9
//*buff:发送数据首地址
//sector:扇区地址
//count:需要写入的扇区数
#if _READONLY == 0
DRESULT disk_write (
BYTE drv, /* Physical drive nmuber (0..) */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to write (1..255) */
)
{
u8 res=0;
if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误
switch(drv)
{
case SD_CARD://SD卡
res=SD_WriteDisk((u8*)buff,sector,count);
break;
case EX_FLASH://外部flash
for(;count>0;count--)
{
SPI_Flash_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,
FLASH_SECTOR_SIZE);
sector++;
buff+=FLASH_SECTOR_SIZE;
}
res=0;
break;
default:
res=1;
}
//处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
if(res == 0x00)return RES_OK;
else return RES_ERROR;
}
#endif /* _READONLY */
//其他表参数的获得
//drv:磁盘编号0~9
//ctrl:控制代码
//*buff:发送/接收缓冲区指针
DRESULT disk_ioctl (
BYTE drv, /* Physical drive nmuber (0..) */
BYTE ctrl, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res;
if(drv==SD_CARD)//SD卡
{
switch(ctrl)
{
case CTRL_SYNC:
SD_CS=0;
if(SD_WaitReady()==0)res = RES_OK;
else res = RES_ERROR;
SD_CS=1;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = 512; res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = 8; res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = SD_GetSectorCount();res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
}else if(drv==EX_FLASH) //外部FLASH
{
switch(ctrl)
{
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = FLASH_SECTOR_SIZE;
res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = FLASH_BLOCK_SIZE;
res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = FLASH_SECTOR_COUNT;
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
}else res=RES_ERROR;//其他的不支持
return res;
}
//获得时间
//User defined function to give a current time to fatfs module */
//31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20-16: Day(1-31) */
//15-11: Hour(0-23), 10-5: Minute(0-59), 4-0: Second(0-29 *2) */
DWORD get_fattime (void)
{
return 0;
}
//动态分配内存
void *ff_memalloc (UINT size)
{
return (void*)mymalloc(SRAMIN,size);
}
//释放内存
void ff_memfree (void* mf)
{
myfree(SRAMIN,mf);
}
该函数实现了我们45.1节提到的6个函数,同时因为在ffconf.h里面设置对长文件名的支持为方法3,所以必须实现ff_memalloc和ff_memfree这两个函数。本章,我们用FATFS管理了2个磁盘:SD卡和SPI FLASH。SD卡比较好说,但是SPI FLASH,因为其扇区是4K字节大小,我们为了方便设计,强制将其扇区定义为512字节,这样带来的好处就是设计使用相对简单,坏处就是擦除次数大增,所以不要随便往SPI FLASH里面写数据,非必要最好别写,如果频繁写的话,很容易将SPI FLASH写坏。
保存diskio.c,然后打开ffconf.h,修改相关配置,并保存,此部分就不贴代码了,请大家参考光盘源码。
前面提到,我们在FATFS文件夹下还新建了一个exfuns的文件夹,该文件夹用于保存一些FATFS一些针对FATFS的扩展代码,本章,我们编写了4个文件,分别是:exfuns.c、exfuns.h、fattester.c和fattester.h。其中exfuns.c主要定义了一些全局变量,方便FATFS的使用,同时实现了磁盘容量获取等函数。而fattester.c文件则主要是为了测试FATFS用,因为FATFS的很多函数无法直接通过USMART调用,所以我们在fattester.c里面对这些函数进行了一次再封装,使得可以通过USMART调用。这几个文件的代码,我们就不贴出来了,请大家参考光盘源码,我们将exfuns.c和fattester.c加入FATFS组下,同时将exfuns文件夹加入头文件包含路径。
然后,我们打开test.c,修改main函数如下:
int main(void)
{
u32 total,free;
u8 t=0;
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,9600); //串口1初始化
exfuns_init(); //为fatfs相关变量申请内存
LCD_Init(); //初始化液晶
LED_Init(); //LED初始化
usmart_dev.init(72);
mem_init(SRAMIN); //初始化内部内存池
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"WarShip STM32");
LCD_ShowString(60,70,200,16,16,"FATFS TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"Use USMART for test");
LCD_ShowString(60,130,200,16,16,"2012/9/18");
while(SD_Initialize()) //检测SD卡
{
LCD_ShowString(60,150,200,16,16,"SD Card Error!");
delay_ms(200);
LCD_Fill(60,150,240,150+16,WHITE);//清除显示
delay_ms(200);
LED0=!LED0;//DS0闪烁
}
exfuns_init(); //为fatfs相关变量申请内存
f_mount(0,fs[0]); //挂载SD卡
f_mount(1,fs[1]); //挂载FLASH.
while(exf_getfree("0",&total,&free)) //得到SD卡的总容量和剩余容量
{
LCD_ShowString(60,150,200,16,16,"Fatfs Error!");
delay_ms(200);
LCD_Fill(60,150,240,150+16,WHITE);//清除显示
delay_ms(200);
LED0=!LED0;//DS0闪烁
}
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,150,200,16,16,"FATFS OK!");
LCD_ShowString(60,170,200,16,16,"SD Total Size: MB");
LCD_ShowString(60,190,200,16,16,"SD Free Size: MB");
LCD_ShowNum(172,170,total>>10,5,16); //显示SD卡总容量 MB
LCD_ShowNum(172,190,free>>10,5,16); //显示SD卡剩余容量 MB
while(1)
{
t++;
delay_ms(200);
LED0=!LED0;
}
}
在main函数里面,我们为SD卡和FLASH都注册了工作区(挂载),在初始化SD卡并显示其容量信息后,进入死循环,等待USMART测试。
最后,我们在usmart_config.c里面的usmart_nametab数组添加如下内容:
(void*)mf_mount,"u8 mf_mount(u8 drv)",
(void*)mf_open,"u8 mf_open(u8*path,u8 mode)",
(void*)mf_close,"u8 mf_close(void)",
(void*)mf_read,"u8 mf_read(u16 len)",
(void*)mf_write,"u8 mf_write(u8*dat,u16 len)",
(void*)mf_opendir,"u8 mf_opendir(u8* path)",
(void*)mf_readdir,"u8 mf_readdir(void)",
(void*)mf_scan_files,"u8 mf_scan_files(u8 * path)",
(void*)mf_showfree,"u32 mf_showfree(u8 *drv)",
(void*)mf_lseek,"u8 mf_lseek(u32 offset)",
(void*)mf_tell,"u32 mf_tell(void)",
(void*)mf_size,"u32 mf_size(void)",
(void*)mf_mkdir,"u8 mf_mkdir(u8*pname)",
(void*)mf_fmkfs,"u8 mf_fmkfs(u8 drv,u8 mode,u16 au)",
(void*)mf_unlink,"u8 mf_unlink(u8 *pname)",
(void*)mf_rename,"u8 mf_rename(u8 *oldname,u8* newname)",
(void*)mf_gets,"void mf_gets(u16 size)",
(void*)mf_putc,"u8 mf_putc(u8 c)",
(void*)mf_puts,"u8 mf_puts(u8*c)",
STM32之GPIO四种模式
2016-3-27记录,首先是GPIO的四种输入模式:
GPIO_Mode_AIN 模拟输入
模拟输入模式(GPIO_Mode_AIN )则关闭了施密特触发器,不接上、下拉电阻,经由另一线路把电压信号传送到片上外设模块。如传送至给ADC模块,由ADC采集电压信号。所以使用ADC外设的时候,必须设置为模拟输入模式。
GPIO_Mode_IN_FLOATING 浮空输入
浮空输入模式(GPIO_Mode_IN_FLOATING)在芯片内部既没有接上拉,也没有接下拉电阻,经由触发器输入。配置成这个模式直接用电压表测量其引脚电压为1点几伏,这是个不确定值。由于其输入阻抗较大,一般把这种模式用于标准的通讯协议如I2C、USART的接收端。
GPIO_Mode_IPD 下拉输入
结构图的上半部分为输入模式结构。接下来就遇到了两个开关和电阻,与VDD相连的为上拉电阻,与VSS相连的为下拉电阻。再连接到施密特触发器就把电压信号转化为0、1的数字信号存
储在输入数据寄存器(IDR)。我们可以通过设置配置寄存器(CRL、CRH),控制这两个开关,于是就可以得到GPIO的上拉输入(GPIO_Mode_IPU ) 和下拉输入模式(GPIO_Mode_IPD )了。
GPIO_Mode_IPU 上拉输入
然后是GPIO的四种输出模式:
普通推挽输出(GPIO_Mode_Out_PP)
使用场合:一般用在0V和3.3V的场合。线路经过两个P_MOS 和N_MOS 管,负责上拉和下拉电流。
使用方法:直接使用
输出电平:推挽输出的低电平是0V,高电平是3.3V。
普通开漏输出(GPIO_Mode_Out_OD)
使用场合:一般用在电平不匹配的场合,如需要输出5V的高电平。
使用方法:就需要再外部接一个上拉电阻,电源为5V,把GPIO设置为开漏模式, 当输出高组态时,由上拉电阻和电源向外输出5V的电压。
输出电平:在开漏输出模式时,如果输出为0,低电平,则使N_MOS 导通,使输 出接地。若控制输出为1(无法直接输出高电平),则既不输出高电平 也不输出低电平,为高组态。为正常使用,必须在外部接一个上拉电阻。
特性: 它具"线与"特性,即很多个开漏模式 引脚连接到一起时,只有当所有 引脚都输出高阻态,才由上拉电阻提供高电平,此高电平的电压为外部 上拉电阻所接的电源的电压。若其中一个引脚为低电平,那线路就相当 于短路接地,使得整条线路都为低电平,0 伏。
复用推挽输出(GPIO_Mode_AF_PP):用作串口的输出。
复用开漏输出(GPIO_Mode_AF_OD):用在IIC。
所有的开漏输出都需要接上拉电阻。
STM32之堆栈
2016.04.06记录,关于堆和栈已经是程序员的一个月经话题,大部分有是基于os层来聊的。那么,在赤裸裸的单片机下的堆和栈是什么样的分布呢?刚接手STM32时,你只编写一个
cpp
int main()
{
while(1);
}
BUILD://Program Size: Code=340 RO-data=252 RW-data=0 ZI-data=1632
编译后,就会发现这么个程序已用了1600多的RAM,要是在51单片机上,会心疼死了,这1600多的RAM跑哪儿去了,分析map,你会发现是堆和栈占用的,在startup_stm32f10x_md.s文件中,它的前面几行就有以上定义,这下该明白了吧。
Stack_Size EQU 0x00000400
Heap_Size EQU 0x00000200
以下引用网上资料 理解堆和栈的区别
(1)栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈。
(2)堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。
(3)全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统自动释放。
(4)文字常量区:常量字符串就是存放在这里的。
(5)程序代码区:存放函数体的二进制代码。
cpp
int a=0; //全局初始化区
char *p1; //全局未初始化区
main()
{
int b; //栈
char s[]="abc"; //栈
char *p3= "1234567"; //在文字常量区Flash
static int c =0 ; //静态初始化区
p1= (char *)malloc(10); //堆区
strcpy(p1,"123456"); //"123456"放在常量区
}
所以堆和栈的区别:
stack的空间由操作系统自动分配/释放,heap上的空间手动分配/释放。
stack的空间有限,heap是很大的自由存储区。
程序在编译期和函数分配内存都是在栈上进行,且程序运行中函数调用时参数的传递也是在栈上进行。
堆和栈大小
定义大小在startup_stm32f2xx.s
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; Heap Configuration
; Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
堆和栈位置
通过MAP文件可知
HEAP 0x200106f8 Section 512 startup_stm32f2xx.o(HEAP)
STACK 0x200108f8 Section 1024 startup_stm32f2xx.o(STACK)
__heap_base 0x200106f8 Data 0 startup_stm32f2xx.o(HEAP)
__heap_limit 0x200108f8 Data 0 startup_stm32f2xx.o(HEAP)
__initial_sp 0x20010cf8 Data 0 startup_stm32f2xx.o(STACK)
显然 Cortex-m3资料可知:__initial_sp是堆栈指针,它就是FLASH的0x8000000地址前面4个字节(它根据堆栈大小,由编译器自动生成)
显然堆和栈是相邻的。
堆和栈空间分配
栈:向低地址扩展
堆:向高地址扩展
显然如果依次定义变量
先定义的栈变量的内存地址比后定义的栈变量的内存地址要大
先定义的堆变量的内存地址比后定义的堆变量的内存地址要小
堆和栈变量
栈:临时变量,退出该作用域就会自动释放
堆:malloc变量,通过free函数释放
另外:堆栈溢出,编译不会提示,需要注意
如果使用了HEAP,则必须设置HEAP大小。
如果是STACK,可以设置为0,不影响程序运行。
IAR STM8定义STACK,是预先在RAM尾端分配一个字节的区域作为堆栈预留区域。
当程序静态变量,全局变量,或者堆与预留堆栈区域有冲突,编译器连接的时候就会报错。
你可以吧STACK设置为0,并不影响运行。(会影响调试,调试会报堆栈溢出警告)。
其实没必要这么做。
一般程序,(在允许范围内)设置多少STACK,并不影响程序真实使用的RAM大小,
(可以试验,把STACK设置多少,编译出来的HEX文件都是一样),
程序还是按照它原本的状态使用RAM,把STACK设置为0,并不是真实地减少RAM使用。
仅仅是欺骗一下编译器,让程序表面上看起来少用了RAM。
而设置一定size的STACK,也并不是真的就多使用了RAM,只是让编译器帮你
检查一下,是否能够保证有size大小的RAM没有被占用,可以用来作为堆栈。
以上仅针对IAR STM8.
从以上网摘来看单片机的堆和栈是分配在RAM里的,有可能是内部也有可能是外部,可以读写;
栈:存函数的临时变量,即局部变量,函数返回时随时有可能被其他函数栈用。所以栈是一种分时轮流使用的存储区,编译器里定义的Stack_Size,是为了限定函数的局部数据活动的范围,操过这么范围有可以跑飞,也就是栈溢出;Stack_Size不影响Hex,更不影响Hex怎么运行的,只是在Debug调试时会提示错。栈溢出也有是超过了国界进行活动,只要老外没有意见,你可以接着玩,有老外不让你玩,你就的得死,或是大家都死(互相撕杀),有的人写单片机代码在函数里定义一个大数组 int buf[8192],栈要是小于8192是会死的很惨。
堆:存的是全局变量,这变量理论上是所有函数都可以访问的,全局变量有的有初始值,但这个值不是存在RAM里的,是存在Hex里,下载到Flash里,上电由代码(编译器生成的汇编代码)搬过去的。有的人很"霸道",上电就霸占已一块很大的RAM(Heap_Size),作为己有(malloc_init),别人用只能通过他们管家借(malloc),用完还得换(free)。所以 一旦有"霸道"的人出现是编译器里必须定义Heap_Size,否则和他管家借也没有用。
总之:堆和栈有存在RAM里,他两各分多少看函数需求,但是他两的总值不能超过单片机硬件的实际RAM尺寸,否则只能到海里玩(淹死了)或是自己打造船接着玩(外扩RAM)。
Code, RO-data,RW-data,ZI-data
Code为程序代码部分
RO-data 表示 程序定义的常量const temp;
RW-data 表示 已初始化的全局变量
ZI-data 表示 未初始化的全局变量
Program Size: Code="18248" RO-data=320 RW-data=260 ZI-data=3952
Code, RO-data,RW-data ............flash
RW-data, ZIdata...................RAM
例如stm32f103c8的flash为64k,sram为20k,那么keil编译的时候,如果Code+RO-data+RW-data超过了64k编译时报L6407E错误,如果RW-data+ZIdata超过了20k编译时报L6407E错误
Program Size: Code=86496 RO-data=9064 RW-data=1452 ZI-data=16116
Code是代码占用的空间,RO-data是 Read Only 只读常量的大小,如const型,RW-data是(Read Write) 初始化了的可读写变量的大小,ZI-data是(Zero Initialize) 没有初始化的可读写变量的大小。ZI-data不会被算做代码里因为不会被初始化。
简单的说就是在烧写的时候是FLASH中的被占用的空间为:Code+RO Data+RW Data
程序运行的时候,芯片内部RAM使用的空间为: RW Data + ZI Data
详细分析
初始化时RW-data从flash拷贝到RAM
生成的map文件位于list文件夹下 (KEIL)
Total RO Size (Code + RO Data) 18568 ( 18.13kB)
Total RW Size (RW Data + ZI Data) 4212 ( 4.11kB)
Total ROM Size (Code + RO Data + RW Data) 18828 ( 18.39kB)
ARM指令的长度刚好是1个字(分配为占用4个字节),Thumb指令的长度刚好是半字(占用2个字节)R0-R15 (R15-PC,R14-LR,R13-SP) 32位每个异常模式还带有一个程序状态保存寄存器 (SPSR),它用于保存在异常事件发生之前的CPSRLDMIA R1!,{R2-R7, R12} ;将R1单兀中的数据读出到R2-R7,R12, R1自动加1
STMIA RO!,{R3-R6,R10} ;将R3-R6,R10中的数据保存到RO指向的地址,RO自动加1在数据传送之前,将偏移量加到Rn中,其结果作为传送数据的存储地址.若使用后缀"!",则结果写回到Rn中,且Rn值不允许为R15.指令举例如下:
LDR Rd, [Rn, #Ox4]!
LDMFD SP!,{R0-R3,PC}^ ;中断返回
"^"符号表示这是一条特殊形式的指令。这条指令在从存储器中装载PC的同时(PC是最后恢复的),CPSR也得到恢复
大端格式(Big-endian)
小端格式(Little-endian)
数据0x12345678存储格式
大端格式
低地址<----0x12|0x34|0x56|0x78---->高地址
小端格式
低地址<----0x78|0x56|0x34|0x12---->高地址
ARM微处理器支持7种运行模式,分别为: CPSR M[4:0]
1.用户模式(usr):ARM处理器正常的程序执行状态。 10000
2.快速中断模式(fiq):用于高速数据传输或通道处理。 10001
3.外部中断模式(irq):用于通用的中断处理。 10010
4.管理模式(svc):操作系统使用的保护模式。 10011
5.数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。10111
6.系统模式(sys):运行具有特权的操作系统任务。 11111
7.定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 11011
ARM正常工作一般工作在用户模式和系统模式,复位的时候进入管理模式。
对于ARM指令集来说,PC指向当前指令的下两条指令的地址,
注意pc,在调试的时候显示的是当前指令地址,而用mov lr,pc的时候lr保存的是此指令向后数两条指令的地址
假设反汇编代码: 0x000001 : mov lr pc
(此时查看PC寄存器的值是0x000001,但实际PC值是0x000003, lr里面保存的就是0x000003)
fields 指定传送的区域(psr CPSR或SPSR)
c 控制域屏蔽字节(psr[7..0])
x 扩展域屏蔽字节(psr[15..8])
s 状态域屏蔽字节(psr[23..16])
f 标志域屏蔽字节(psr[31..24])
例如:MSR cpsr_c, #0xD3 ; CPSR[7...0] = 0xD3
STM32之掉电数据保存
2016-05-09记录,经过两天的研究终于完成了STM32未知情况下断电瞬间能将重要动态参数保存下来,因为STM32内部FLASH擦写次数有限,动态参数每改变一次就擦写一次FLASH将会导致flash寿命缩短。
查阅资料发现BKP后备寄存器貌似能满足实时保存数据,BKP提供了48个16位寄存器能够在Vdd电源被切断的情况下由Vbat维持供电,在系统复位或者被唤醒的情况下BKP寄存器里面的数据也不会被复位
cpp
//初始化BKP
void BkpInit(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器的访问
BKP_ClearFlag(); //清楚BKP侵入检测目前还不知道这有什么用
}
//读取BKP
tempSysCfg.u16FlashState=BKP_ReadBackupRegister(BKP_DR1);
tempSysCfg.u32FlashMosAddressR=BKP_ReadBackupRegister(BKP_DR2);
tempSysCfg.u32FlashMosAddressW=BKP_ReadBackupRegister(BKP_DR3);
tempSysCfg.u32FlashMosSectorR=BKP_ReadBackupRegister(BKP_DR4);
tempSysCfg.u32FlashMosSectorW=BKP_ReadBackupRegister(BKP_DR5);
tempSysCfg.u16CRCVal=BKP_ReadBackupRegister(BKP_DR6);
tempCrc=CalcCrcAll(0xffff,(uint8_t*)&tempSysCfg,sizeof(SYS_Config)-2);
//写BKP
BKP_WriteBackupRegister(BKP_DR1,g_cSysConfig.u16FlashState);
BKP_WriteBackupRegister(BKP_DR2,g_cSysConfig.u32FlashMosAddressR);
BKP_WriteBackupRegister(BKP_DR3,g_cSysConfig.u32FlashMosAddressW);
BKP_WriteBackupRegister(BKP_DR4,g_cSysConfig.u32FlashMosSectorR);
BKP_WriteBackupRegister(BKP_DR5,g_cSysConfig.u32FlashMosSectorW);
BKP_WriteBackupRegister(BKP_DR6,g_cSysConfig.u16CRCVal);
经过测试发现BKP在电源断电的情况下数据还是被复位了,不能满足需求,经过继续查阅资料终于发现了STM32内部有一个PVD可编程电压检测器,可以利用PVD对Vdd电压与电源控制寄存器PWR_CR中的PLS[2:0]进行比较来监控电源。电源控制/状态寄存器PWR_CSR中的PVDO标志来表明Vdd是高于还是低于PVD的阀值电压,该事件在内部连接到外部中断的第16线,如果该中断被使能,那么该事件就会产生PVD中断,通过该特性可以用来执行紧急关闭任务。
cpp
//初始化PVD中断使能
void PvdInit(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); //使能PWR时钟
PWR_PVDLevelConfig(PWR_PVDLevel_2V9); //设置Vdd比较的电压阀值
PWR_PVDCmd(ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //中断优先级配置
NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
EXTI_DeInit(); //外部中断配置
EXTI_StructInit(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line=EXTI_Line16;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising; //PVD上升沿中断表示Vdd电压由3.3下降至阀值即即将断电
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
//实现PVD中断处理
void PVD_IRQHandler(void)
{
EXTI_ClearITPendingBit(EXTI_Line16);
if(PWR_GetFlagStatus(PWR_FLAG_PVDO))
SysConfigSaveFLASH(); //将动态数据保存在FLASH中
}
//保存数据至FLASH
int SysConfigSaveFLASH(void)
{
SYS_Config tempSysCfg;
uint16_ttempCrc;
tempSysCfg.u16FlashState=BKP_ReadBackupRegister(BKP_DR1);
tempSysCfg.u32FlashMosAddressR=BKP_ReadBackupRegister(BKP_DR2);
tempSysCfg.u32FlashMosAddressW=BKP_ReadBackupRegister(BKP_DR3);
tempSysCfg.u32FlashMosSectorR=BKP_ReadBackupRegister(BKP_DR4);
tempSysCfg.u32FlashMosSectorW=BKP_ReadBackupRegister(BKP_DR5);
tempSysCfg.u16CRCVal=BKP_ReadBackupRegister(BKP_DR6);
tempCrc=CalcCrcAll(0xffff,(uint8_t*)&tempSysCfg,sizeof(SYS_Config)-2);
//if(tempSysCfg.u16CRCVal==tempCrc)
{
//EraseFlashSiglePage(INDEX_FLASH_SYSCFG_SECTOR);
WriteFlashWord(INDEX_FLASH_SYSCFG_SECTOR,0,(uint8_t *)(&tempSysCfg),sizeof(SYS_Config));
}
}
调试过程中出现了几个问题:
1、 只产生了上升沿中断,下降沿中断没有产生,这个问题郁闷了几个时间,最后才发现是我自己理解错了,误理解为PVD下降沿中断表示Vdd下降沿即表示即将断电触发的,最后通过论坛上大神注解重新查阅了手册上面图6部分才知道Vdd下降才会触发PVD上升沿,Vdd上升才会触发PVD下降沿,而下降沿中断触发没有产生估计可能上电已经稳定了我才初始化PVD
2、擦除FLASH耗时过长,导致断电一瞬间只完成了擦除操作,而FLASH没有被擦除是无法写进去的,因此程序上电初始化后我将数据读取出之后立即擦除FLASH,断电瞬间直接写数据到FLASH
硬件干扰总结
2016-09-07记录,搞过产品的朋友都有体会,一个设计看似简单,硬件设计和代码编写很快就搞定,但在调试过程中却或多或少的意外,这些都是抗干扰能力不够的体现。
下面讨论一下如何让你的设计避免走弯路:
抗干扰体现在2个方面,一是硬件设计上,二是软件编写上。
这里重点提醒:在MCU设计中主要抗干扰设计是在硬件上,软件为辅。因为MCU的计算能力有限,所以要在硬件上花大工夫。
看看干扰的途径:
1、干扰信号干扰MCU的主要路径是通过I/O口,一是影响了MCU的数据采集,二是影响内部其它寄存器。
解决方法:后面讨论。
2、电源干扰:MCU虽然适应电压较宽(3-5。5V),但对于电源的波动却很敏感,比如说MCU可以在3V电压下稳定工作,但却不能在电压在3V-5。5V波动的情况下稳定工作。
解决方法:用电源稳压块,做好电源的滤波等工作,提示:一定要在电源旁路并上0。1UF的瓷片电容来滤除高频干扰,因为电解电容对超过几十KHZ的高频干扰不起作用。
3、上下电干扰:但每个MCU系统在上电时候都要经过这样一个过程,所以要尤其注意。
MCU虽然可以在3V电压下稳定工作,但并不是说它不能在3V以下的电压下工作,当然在如此低的电压下MCU是超不稳定状态的。在系统加电时候,系统电源电压是从0V上升到额定电压的,比如当电压到2V时候,MCU开始工作了,但这时是超不稳定的工作,极容易跑飞。
解决方法:1让MCU在电源稳定后才开始工作。PIC在片内集成了POR(内部上电延时复位),这功能一定要在配置位中打开。
外部上电延时复位电路。有多种形式,低成本的就是在复位脚接个阻容电路。高成本的是用专用芯片。这方面的资料特多,到处都可以查找。
最难排除的就是上面第一种干扰,并且干扰信号随时可以发生,干扰信号的强度也不尽相同。
但它们也有相同点:干扰信号也遵循欧姆定律,干扰信号偶合路径无非是电磁干扰,一是电火花,二是磁场。
其中干扰最厉害的是电火花干扰,其次是磁场干扰。电火花干扰表现场合主要是附近有大功率开关、继电器、接触器、有刷电机等。磁场干扰表现场合主要是附近有大功率的交流电机、变压器等。
解决方法:第一点:也是最经典的,就是在PCB步线和元件位置安排上下工夫,这中间学问很多,说几天都说不完^^。
二:综合考虑各I/O口的输入阻抗,采集速率等因素设计I/O口的外围电路。
一般决定一个I/O口的输入阻抗有3种情况:
A:I/O口有上拉电阻,上拉电阻值就是I/O口的输入阻抗。
一般大家都用4K-20K电阻做上拉,(PIC的B口内部上拉电阻约20K)。
由于干扰信号也遵循欧姆定律,所以在越存在干扰的场合,选择上拉电阻就要越小,因为干扰信号在电阻上产生的电压就越小。
由于上拉电阻越小就越耗电,所以在家用设计上,上拉电阻一般都是10-20K,而在强干扰场合上拉电阻甚至可以低到1K。
(如果在强干扰场合要抛弃B口上拉功能,一定要用外部上拉。)
B:I/O口与其它数字电路输出脚相连,此时I/O口输入阻抗就是数字电路输出口的阻抗,一般是几十到几百欧。
可以看出用数字电路做中介可以把阻抗减低到最理想,在许多工业控制板上可以看见大量的数字电路就是为了保证性能和保护MCU的。
C:I/O口并联了小电容。
由于电容是通交流阻直流的,并且干扰信号是瞬间产生,瞬间熄灭的,所以电容可以把干扰信号滤除。但不好的是造成I/O口收集信号的速率下降,比如在串口上并电容是绝不可取的,因为电容会把数字信号当干扰信号滤掉。
对于一些检测开关、干簧管、霍尔元件之类的是可以并电容的,因为这些开关量的变化是不可能有很高的速率的,并一个小电容对信号的采集是没任何影响的。