第六章為重用外設驅動代碼,本文內容為6.2 SPI NOR Flash 存儲器。
6.2 SPI NOR Flash 存儲器
SPI NOR Flash 是一種SPI 接口的非易失閃存芯片,本節以***旺宏電子的MX25L1606為例詳細介紹在AMetal 中如何使用類似的Flash 存儲器。
>>>6.2.1 基本功能
MX25L1606 總容量為16M(16×1024×1024)bits,即2M字節。每個字節對應一個存儲地址,因此其存儲數據的地址范圍為0x000000 ~ 0x1FFFFF。
在MX25L1606 中,存儲器有塊(block)、扇區(sector)和頁(page)的概念。頁大小為256 字節,每個扇區包含16頁,扇區大小為4K(4096)字節,每個塊包含16 個扇區,塊的大小為64K(65536)字節,其組織結構示意圖詳見表6.5。
表6.5 MX25L1606 存儲器組織結構
MX25L1606 的通信接口為標準4 線SPI 接口(支持模式0 和模式3),即CS、MOSI、MISO、CLK,詳見圖6.3。其中,CS(#1)、SO(#2)、SI(#5)、SCLK(#6)分別為SPI 的CS、MISO、MOSI 和CLK 信號引腳。特別地,WP(#3)用于寫保護,HOLD(#7)用于暫停數據傳輸。一般來說,這兩個引腳不會使用,可通過上拉電阻上拉至高電平。MicroPort-NorFlash 模塊通過MicroPort 接口與AM824-Core 相連。
圖6.3 SPI Flash 電路原理圖
>>>6.2.2 初始化
AMetal 提供了支持常見的MX25L8006、MX25L1606……等系列SPI Flash 器件的驅動函數,使用其它各功能函數前必須先完成初始化,其函數原型(am_mx25xx.h)為:
該函數意在獲取器件的實例句柄mx25xx_handle。其中,p_dev 為指向am_mx25xx_dev_t類型實例的指針,p_devinfo 為指向am_mx25xx_devinfo_t 類型實例信息的指針。
(1)實例
定義am_mx25xx_dev_t 類型(am_mx25xx.h)實例如下:
其中,g_mx25xx_dev 為用戶自定義的實例,其地址作為p_dev 的實參傳遞。
(2)實例信息
實例信息主要描述了具體器件的固有信息,即使用的SPI 片選引腳、SPI 模式、SPI 速率和器件具體型號等,其類型am_mx25xx_devinfo_t 的定義(am_mx25xx.h)如下:
其中,spi_mode 為SPI 模式,MX25L1606 支持模式0(AM_SPI_MODE_0)和模式3(AM_SPI_MODE_3)。spi_cs_pin 為與實際電路相關的片選引腳,MicroPort-NorFlash 模塊通過MicroPort 接口與AM824-Core 相連時,默認片選引腳為PIO0_1。spi_speed 為時鐘信號的頻率,針對MX25L1606,其支持的最高頻率為86MHz,因此可以將該值直接設置為86000000。但由于LPC824 芯片的主頻為30MHz,所以SPI 最大速率僅30MHz。type 為具體器件的型號,其包含了具體型號相關的信息,比如,頁大小信息等,當前已經支持的器件型號詳見am_mx25xx.h 中對應的宏,MX25L1606 對應的宏為:AM_MX25XX_MX25L1606。
基于以上信息,實例信息定義如下:
其中,g_mx25xx_devinfo 為用戶自定義的實例信息,其地址作為p_devinfo 的實參傳遞。
(3)SPI 句柄spi_handle
若使用LPC824 的SPI0 與MX25L1606 通信,則通過LPC82x 的SPI0 實例初始化函數am_lpc82x_spi0_inst_init()獲得SPI 句柄。即:
SPI 句柄即可直接作為spi_handle 的實參傳遞。
(4)實例句柄
MX25L1606 初始化函數am_mx25xx_init ()的返回值MX25L1606 實例的句柄,作為其它功能接口(擦除、讀、寫)的第一個參數(handle)的實參。
其類型am_mx25xx_handle_t(am_mx25xx.h)定義如下:
若返回值為NULL,說明初始化失敗;若返回值不為NULL,說明返回了有效的handle。
基于模塊化編程思想,將初始化相關的實例、實例信息等的定義存放到對應的配置文件中,通過頭文件引出實例初始化函數接口,源文件和頭文件的程序范例分別詳見程序清單6.14 和程序清單6.15。
程序清單6.14 實例初始化函數范例程序(am_hwconf_mx25xx.c)
程序清單6.15 實例初始化函數接口(am_hwconf_mx25xx.h)
后續只需要使用無參數的實例初始化函數,即可獲取到MX25xx 的實例句柄。即:
注意,spi_handle 用于區分SPI0、SPI1,mx25xx_handle 用于區分同一系統中的多個MX25xx 器件。
>>>6.2.3 接口函數
SPI Flash 比較特殊,在寫入數據前必須確保相應的地址單元已經被擦除,因此除讀寫函數外,還有一個擦除函數,其接口函數詳見表6.6。
表6.6 MX25xx 接口函數
各API 的返回值含義都是相同的:AM_OK 表示成功,負值表示失敗,失敗原因可根據具體的值查看am_errno.h 文件中相對應的宏定義。正值的含義由各API 自行定義,無特殊說明時,表明不會返回正值。
1. 擦除
擦除就是將數據全部重置為0xFF,即所有存儲單元的位設置為1。擦除操作并不能直接擦除某個單一地址單元,擦除的最小單元為扇區,即每次只能擦除單個或多個扇區。擦除一段地址空間的函數原型為:
其中,handle 為MX25L1606 的實例句柄,addr 為待擦除區域的首地址,由于擦除的最小單元為扇區,因此該地址必須為某扇區的起始地址0x000000(0)、0x001000(4096)、0x002000(2×4096)……同時,擦除長度必須為扇區大小的整數倍。
如果返回AM_OK,說明擦除成功,反之失敗。假定需要從0x001000 地址開始,連續擦除2 個扇區,范例程序詳見程序清單6.16。
程序清單6.16 擦除范例程序
0x001000 ~ 0x3FFF 空間被擦除了,即可向該段地址空間內寫入數據。
2. 寫入數據
在寫入數據前,需確保寫入地址已被擦除。即將需要變為0 的位清0,但寫入操作無法將0 變為1。比如,寫入數據0x55 就是將bit1、bit3、bit5、bit7 清0,其余位的值保持不變。若存儲的數據已經是0x55,再寫入0xAA(寫入0xAA 實際上就是將bit0、bit2、bit4、bit6清0,其余位不變),則最終存儲的數據將變為0x00,而不是后面再寫入的0xAA。因此為了保證正常寫入數據,寫入數據前必須確保相應的地址段已經被擦除了。
從指定的起始地址開始寫入一段數據的函數原型為:
如果返回AM_OK,說明寫入數據成功,反之失敗。假定從0x001000 地址開始,連續寫入128 字節數據,范例程序詳見程序清單6.17。
程序清單6.17 寫入數據范例程序
雖然只寫入了128 字節數據,但由于擦除的最小單元為扇區,因此擦除了4096 字節(一個扇區)。已經擦除的區域后續可以直接寫入數據,而不必再次擦除,比如,緊接著寫入128字節數據后的地址,再寫入128 字節數據,詳見程序清單6.18。
程序清單6.18 寫入數據范例程序
若需要再次從0x001000 地址連續寫入128 字節數據,由于之前已經寫入過數據,因此必須重新擦除后方可再次寫入。
3. 讀取數據
從指定的起始地址開始讀取一段數據的函數原型為:
如果返回值為AM_OK,則說明讀取成功,反之失敗。假定從0x001000 地址開始,連續讀取128 字節數據,詳見程序清單6.19。
程序清單6.19 讀取數據范例程序
范例程序的實現和接口詳見程序清單6.20 和程序清單6.21。
程序清單6.20 MX25XX 測試程序實現(app_test_mx25xx.c)
由于讀寫數據需要的緩存空間較大(128 字節),因此在緩存的定義前增加了static 修飾符,使其內存空間從全局數據區域中分配。如果直接從函數的運行棧中分配128 字節空間,則完全有可能導致棧溢出,進而系統崩潰。
程序清單6.21 MX25XX 測試程序接口聲明(app_test_mx25xx.h)
相應的范例程序詳見程序清單6.22。
程序清單6.22 MX25L1602 讀寫范例程序
由于app_test_mx25xx()的參數為MX25XX 的實例handle,與MX25xx 器件具有依賴關系,因此無法實現跨平臺調用。
>>>6.2.4 MTD 通用接口函數
由于MX25L1606 是典型的FLASH 存儲器件,因此將其抽象為一個讀寫MX25L1606的MTD(Memory Technology Device),使之與具體器件無關,實現跨平臺調用,其函數原型詳見表6.7。
表6.7 MTD 通用接口函數
1 MTD 初始化函數
MTD 初始化函數意在獲取MTD 實例句柄,其函數原型為:
其中,MX25L1606 實例句柄(mx25xx_handle)作為實參傳遞給handle,p_mtd 為指向am_mtd_serv_t 類型實例的指針,reserved_nblks 作為實例信息,表明保留的塊數。
實例(MTD 存儲設備)
定義am_mtd_serv_t 類型(am_mtd.h)實例如下:
其中,g_mx25xx_mtd 為用戶自定義的實例,其地址作為p_mtd 的實參傳遞。
實例信息
reserved_nblks 表示實例相關的信息,用于MX25L1606 保留的塊數,這些保留的塊不會被MTD 標準接口使用。保留的塊從器件的起始塊開始計算,若該值為5,則MX25XX 器件的塊0~塊4 將不會被MTD 使用,MTD 讀寫數據將從塊5 開始。如果沒有特殊需求,則該值設置為0。
將MTD 初始化函數的調用存放到配置文件中,引出對應的實例初始化接口,詳見程序清單6.23 和程序清單6.24。
程序清單6.23 新增MTD 實例初始化函數(am_hwconf_mx25xx.c)
程序清單6.24 am_hwconf_mx25xx.h 文件內容更新(1)
am_mx25xx_mtd_inst_init()函數無任何參數,與其相關實例和實例信息的定義均在文件內部完成,因此直接調用該函數即可獲得MTD 句柄。即:
這樣一來,在后續使用其它MTD 通用接口函數時,均可使用該函數的返回值mtd_handle作為第一個參數(handle)的實參傳遞。
顯然,若使用MX25XX 接口,則調用am_mx25xx_inst_init()獲取MX25XX 實例句柄;若使用MTD 通用接口,則調用am_mx25xx_mtd_inst_init()獲取MTD 實例句柄。
2. 擦除
寫入數據前需要確保相應地址已經被擦除,其函數原型為:
擦除單元的大小可以使用宏AM_MTD_ERASE_UNIT_SIZE_GET()獲得。比如:
其中的addr 表示擦除區域的首地址,必須為擦除單元大小的整數倍。同樣地,len 也必須為擦除單元大小的整數倍。由于MX25L1606 擦除單元的大小與扇區大小(4096)一樣,因此addr 必須為某扇區的起始地址0x000000(0)、0x001000(4096)、0x002000(2×4096)……
如果返回AM_OK,說明擦除成功,反之失敗。假定從0x001000 地址開始,連續擦除2個扇區,范例程序詳見程序清單6.25。
程序清單6.25 擦除范例程序
使用該段程序后,地址空間0x001000 ~ 0x3FFF 即被擦除了,后續即可向該段地址空間內寫入數據。
3. 寫入數據
寫入數據前需要確保寫入地址已被擦除,其函數原型為:
如果返回AM_OK,說明寫入數據成功,反之失敗。假定從0x001000 地址開始,連續寫入128 字節數據的范例程序詳見程序清單6.26。
程序清單6.26 寫入數據范例程序
4. 讀取數據
從指定的起始地址開始讀取一段數據的函數原型為:
如果返回值為AM_OK,則說明讀取成功,反之失敗。假定從0x001000 地址開始,連續讀取128 字節數據的范例程序詳見程序清單6.27。
程序清單6.27 讀取數據范例程序
MTD 通用接口測試程序和接口分別詳見程序清單6.28 和程序清單6.29。
程序清單6.28 MTD 測試程序實現(app_test_mtd.c)
程序清單6.29 接口聲明(app_test_mtd.h)
由于該程序只需要MTD 句柄,因此與具體器件無關,可以實現跨平臺復用。若讀寫數據的結果完全相等,則返回AM_OK,反之返回AM_ERROR,范例程序詳見程序清單6.30。
程序清單6.30 MTD 讀寫范例程序
>>>6.2.5 FTL 通用接口函數
由于此前的接口需要在每次寫入數據前,確保相應的存儲空間已經被擦除,則勢必會給編程帶來很大的麻煩。與此同時,由于MX25L1606 的某一地址段擦除次數超過10 萬次的上限,則在相應段地址空間存儲數據將不再可靠。
假設將用戶數據存放到0x001000~0x001FFF 連續的4K 地址中,則每次更新這些數據都要重新擦除該地址段。而其它存儲空間完全沒有使用過,MX25L1606 的使用壽命大打折扣。AMetal 提供了FTL(Flash Translation Layer)通用接口供用戶使用,其函數原型詳見表6.8。
表6.8 FTL 通用接口函數(am_ftl.h)
1. FTL 初始化函數
FTL 初始化函數意在獲取FTL 實例句柄,其函數原型為:
其中,p_ftl 為指向am_ftl_serv_t 類型實例的指針,p_buf 和len 作為實例信息,為FTL驅動程序提供必要的RAM 空間,MTD 初始化函數獲得mtd_handle 為MTD 實例句柄。
(1)實例
定義am_ftl_serv_t 類型(am_mtd.h)實例如下:
其中,g_ftl 為用戶自定義的實例,其地址作為p_ftl 的實參傳遞。
(2)實例信息
FTL 驅動程序需要使用一定的RAM 空間,這也是使用FTL 通用接口所要付出的代價。由于該空間的大小與具體器件的容量大小、擦除單元大小相關,因此該內存空間由用戶根據實際情況提供。需要的內存大小(字節數)由下面的公式得到:
其中,sizeerase 為擦除單元的大小,對于MX25L1606,其為扇區大小,即4096。sizemtd_chip為MTD 實例的總容量。MX25L1606 對應的MTD 實例,其大小為除去保留塊的總容量,若保留塊為0,就是MX25L1606 的容量大小,即2M。需要的內存容量大小為:
對于MX25L1606,若使用FTL,則需要大約2.5KB 的RAM。顯然對于一些小型嵌入式系統來說,RAM 的耗費實在“太大”了,所以要根據實際情況選擇是否使用FTL。若RAM充足,而又比較在意Flash 的使用壽命,可以選擇使用FTL。容量大小使用am_ftl.h 中的宏:
該宏根據器件總容量和擦除單元大小,自動計算實際需要的RAM 大小。
若使用FTL 通用接口操作MX25L1606,則需要定義如下內存空間供FTL 使用。即:
其中,g_ftl_buf 為內存空間的首地址,其作為p_buf 的實參傳遞,內存空間的大小(即數組元素的個數)作為len 的實參傳遞。
(3)MTD 句柄mtd_handle
該MTD 句柄可以通過MTD 實例初始化函數獲得。即:
獲得的MTD 句柄即可直接作為mtd_handle 的實參傳遞。
(4)實例句柄
FTL 初始化函數am_ftl_init ()的返回值為FTL 實例句柄,該句柄將作為讀寫接口第一個參數(handle)的實參。其類型am_ftl_handle_t(am_ftl.h)定義如下:
若返回值為NULL,說明初始化失敗;若返回值不為NULL,說明返回了有效的handle。
將FTL 初始化函數的調用存放到配置文件中,引出對應的實例初始化接口,詳見程序清單6.31 和程序清單6.32。
程序清單6.31 新增FTL 實例初始化函數(am_hwconf_mx25xx.c)
程序清單6.32 am_hwconf_mx25xx.h 文件內容更新(2)
am_mx25xx_ftl_inst_init()無任何參數,與其相關實例和實例信息的定義均在文件內部完成,因此直接調用該函數即可獲得FTL 句柄。即:
這樣一來,在后續使用其它FTL 通用接口函數時,均可使用該函數的返回值ftl_handle作為第一個參數(handle)的實參傳遞。
2. 寫入數據
當調用FTL 通用接口時,讀寫數據都是以塊為單位,每塊數據的字節數固定為512 字節。其函數原型為:
為了延長Flash 的使用壽命,在實際寫入時,會數據寫入到擦除次數最少的區域。因此lbn 只是一個邏輯塊序號,與實際的存儲地址沒有關系。邏輯塊只是一個抽象的概念,每個邏輯塊的大小固定為512 字節,與MX25L1606 的物理存儲塊沒有任何關系。
由于MX25L1606 每個邏輯塊固定為512 字節,因此理論上邏輯塊的個數為4096(2×1024×1024÷512),lbn 的有效值為0 ~ 4095。但實際上擦除每個單元都要耗費一個邏輯塊,MX25L1606 擦除單元的大小為4096,即512 個擦除單元,因此FTL 消耗了512 個邏輯塊,則可用的邏輯塊為3584(4096~512)個,lbn 的有效值為0~3583。
由此可見,FTL 不僅要占用2.5K RAM,還要占用256K 的MX25L1606 存儲空間(512個邏輯塊,每個邏輯塊大小為512 字節),這也是使用FTL 要付出的“代價”。如果返回AM_OK,說明寫入數據成功,反之失敗。假定寫入一塊數據(512 字節)至邏輯塊2 中,其范例程序詳見程序清單6.33。
程序清單6.33 寫入數據范例程序
3. 讀取數據
讀取一塊數據的函數原型為:
如果返回值為AM_OK,則說明讀取成功,反之失敗。假定從邏輯塊2 中讀取一塊(512字節)數據,其范例程序詳見程序清單6.34。
程序清單6.34 讀取數據范例程序
FTL 通用接口測試程序和接口分別詳見程序清單6.35 和程序清單6.36。
程序清單6.35 FTL 測試程序實現(app_test_ftl.c)
程序清單6.36 FTL 測試接口聲明(app_test_ftl.h)
由于寫入前無需再執行擦除操作,則編寫應用程序更加便捷。同樣,由于應用程序僅僅只需要FTL 句柄,則所有接口也全部為FTL 通用接口,因此應用程序是可以跨平臺復用的,范例程序詳見程序清單6.37。
程序清單6.37 FTL 讀寫范例程序
>>>6.2.6 微型數據庫
由于哈希表所使用的鏈表頭數組空間、關鍵字和記錄值等都存儲在malloc 分配的動態空間中,這些信息在程序結束或系統掉電后都會丟失。在實際的應用中,往往希望將信息存儲在非易失存儲器中。典型的應用是將信息存儲在文件中,從本質上來看,只要掌握了哈希表的原理,無論信息存儲在什么地方,操作的方式都是一樣的。
在AMetal 中,基于非易失存儲器實現了一套可以直接使用的哈希表接口,由于數據不會因為掉電或程序終止而丟失,因此可以將其視為一個微型數據庫,相關接口詳見表6.9。
表6.9 數據庫接口(hash_kv.h)
顯然,除命名空間由 hash_db_*修改為了hash_kv_*(為了與之前的程序進行區分)外,僅僅是初始化函數中,多了一個文件名參數,即內部不再使用malloc 分配空間存儲記錄信息,而是使用該文件名指定的文件存儲相關信息。如此一來記錄存儲在文件中,信息不會因掉電或程序終止而丟失。其中,hash_kv_t 為數據庫結構體類型,使用數據庫前,應使用該類型定義一個數據庫實例,比如,“hash_kv_t hash;”。
由于各個函數的功能與《程序設計與數據結構》一書中介紹的哈希表的各個函數的功能完全一致,因此可以使用如程序清單6.38 所示的代碼進行測試驗證。
程序清單6.38 數據庫綜合范例程序
-
FlaSh
+關注
關注
10文章
1638瀏覽量
148193 -
SPI
+關注
關注
17文章
1711瀏覽量
91757 -
外設驅動器
+關注
關注
0文章
3瀏覽量
3441 -
ametal
+關注
關注
2文章
24瀏覽量
11411
原文標題:周立功:重用外設驅動代碼——SPI NOR Flash 存儲器
文章出處:【微信號:Zlgmcu7890,微信公眾號:周立功單片機】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論