
1.兩條總線線路:一條串行數據SDA,一條串行時鐘線SCL(主從設備使用同一時鐘,屬于同步通信)來完成數據的傳輸及外圍器件的擴展
2.I2C總線上的每一個設備都可以作為主設備或者從設備,而且每一個設備都會對應一個唯一的地址,通常是7位,有時候是10位
3.I2C總線數據傳輸速率在標準模式下可達100kbit/s,快速模式下可達400kbit/s,高速模式下可達3.4Mbit/s。在開發配置的時候,最好檢查從設備的傳輸速率從而對主設備(一般是MCU)進行相應的配置。一般通過I2C總線接口可編程時鐘來實現傳輸速率的調整,同時也跟所接的上拉電阻的阻值有關。
(資料圖片)
4.I2C總線上的主設備與從設備之間以字節(8位)為單位進行單雙工的數據傳輸。
拓撲結構——總線型I2C 總線在物理連接上分別由SDA(串行數據線)和SCL(串行時鐘線)及上拉電阻組成,SCL由主機發出,SCL越快,通訊速率越快。通信原理是通過對SCL和SDA線高低電平時序的控制來產生I2C總線協議所需要的信號進行數據的傳遞。在總線空閑狀態時,這兩根線一般被上面所接的上拉電阻拉高,保持著高電平。
I2C總線協議1.I2C協議規定: 總線上數據的傳輸必須以一個起始信號作為開始條件,以一個結束信號作為傳輸的停止條件。起始和結束信號總是由主設備產生。
2.空閑狀態:SCL和SDA都保持著高電平。
3.起始信號: 當SCL為高電平而SDA由高到低的跳變,表示產生一個起始條件,所有的從設備都能感受到這個跳變,做好準備等待被選擇。
4.結束信號:當SCL為高而SDA由低到高的跳變,表示產生一個 停止條件
5.數據傳輸:數據傳輸以字節為單位 , 主設備在SCL線上產生每個時鐘脈沖的過程中將在SDA線上傳輸一個數據位,數據在時鐘的高電平被采樣這時候采集到是1就是1,是0就是0,所以在傳輸數據時,當時鐘處于高電平時一定要保持穩定,時鐘處于低電平時可以變換數據。(高電平采樣,低電平變換)一個字節按數據位從高位到低位的順序進行傳輸。主設備在傳輸有效數據之前 要先指定從設備的地址,一般為7位,然后再發生數據傳輸的方向位, 0表示主設備向從設備寫數據,1表示主設備向從設備讀數據。主從設備以字節為單位(8位)進行數據傳輸,開始傳輸數據時把從設備地址加上方向位組成一個8位的字節進行發送并接收一個應答。
6.應答信號:接收數據的器件在接收到 8bit 數據后,向發送數據的器件發出低電平的應答信號,表示已收到數據。這個信號可以是主控器件發出,也可以是從動器件發出。總之,由接收數據的器件發出。
a.主設備向從設備寫數據:
b.主設備讀從設備的數據:
c.主設備讀從設備的某個寄存器:讀設備的寄存器首先應該對該設備發送寫命令,很多設備都可以看成是一段內存,所以寫命令寫給從設備,指明要讀取哪個地址(寄存器)的數據,接下來才是真正的讀數據。不同的從設備是由區別的,在驅動I2C從設備時應當查明設備的時序圖,又怎樣的要求,不同的時序對應了不同的命令。
STM32F4-I2C控制器特性軟件模擬I2C時序:由于直接控制 GPIO 引腳電平產生通訊時序時,需要由 CPU控制每個時刻的引腳狀態,所以稱之為“軟件模擬協議”方式。我們知道,驅動I2C設備只需要兩根管腳,即使單片機上沒有I2C控制器,根據協議控制每根管腳每一時刻的電平狀態,一根模擬數據線,一根模擬時鐘線,就可以驅動從設備,相對而言效率低,但是可以實現控制驅動。STM32內部具備專門的I2C控制器,使用時只需對其進行相應的配置即可。
硬件控制產生I2C時序:STM32 的 I2C 片上外設專門負責實現 I2C 通訊協議,只要配置好該外設,它就會自動根據協議要求產生通訊信號,收發數據并緩存起來,CPU只要檢測該外設的狀態和訪問數據寄存器,就能完成數據收發。這種由硬件外設處理I2C協議的方式減輕了 CPU 的工作,且使軟件設計更加簡單。
控制器功能:配置主從模式(一般都把STM32當作主機使用,作為從機時應當對其賦一個地址),通過配置其內部的寄存器產生一些中斷和錯誤信號,配置通信速率位標準模式、快速模式、超快速模式等
STM32芯片有3組I2C外設,可以同時進行3組I2C傳輸。它們的I2C通訊信號引出到不同的GPIO引腳上,使用時必須配置到這些指定的引腳。
EEPROM(AT24CXX)存儲芯片介紹一個典型的I2C接口的從設備,專門用于存儲數據的芯片。EEPROM (Electrically ErasableProgrammable readonly memory),帶電可擦可編程只讀存儲器,一種掉電后數據不丟失的存儲芯片。EEPROM可以在電腦上或專用設備上擦除已有信息,重新編程。
EEPROM常用來存儲一些配置信息,以便系統重新上電的時候加載之,容量不會很高。EEPOM 芯片最常用的通訊方式就是I2C協議。XX表示容量,常用值為01、02、04、16、32、64等,單位Kbit。一般的存儲芯片都具有寫保護功能,對WP管腳加一個高電平就開啟了寫保護功能,就無法往芯片內寫數據了。在開發中通常將該管腳接地,確保能夠寫數據
典型24CXX芯片引腳如下:
例:24C65的設備地址為7位,高4位恒定為1010,低3位取決于A0-A2的電平狀態,般主機在讀寫24CXX都是把設備地址連同讀寫位組合成一個字節一起發送。
24C65的電氣連線如下,根據電氣連線可知,A0-A2均接地,因此讀地址為1010 0001,即0xA1;寫地址為10100000,即0xA0 ,且WP接地,用戶隨時可向芯片內部寫入數據。
24C65寫時序:首先發送一個起始信號,接著發送從設備地址以及方向位,收到應答后,向從設備發送要寫的存儲區域的首地址,24C65的存儲地址是16位,先發送高8位,收到應答后再發送低8位,再次收到應答后開始寫數據。64Kbit大小位8K字節,需要13位即可表示,所以高3位固定定為0,如下圖。
這里是BYTE WRITE,一次寫一個字節,此芯片還支持PAGE WRITE,一次寫一頁,也就是8個字節,如果想寫更多,可設置一個for循環實現。
24C65 讀時序與寫時序基本相同,只不過在讀之前要發送再發送重復開始位進行讀操作。
I2C讀寫EEPROM實例由電氣原理圖可知SCL和SDA分別接入了PB6和PB7管腳,讀地址為1010 0001,即0xA1;寫地址為10100000,即0xA0
步驟:
1.配置RCC
2.配置PB6和PB7管腳
3.配置I2C協議參數
4.編寫代碼
//mian.c#include "main.h"#include "stm32f4xx_hal.h"#include "i2c.h"#include "usart.h"#include "gpio.h"#define ReadAddr 0xA1#define WriteAddr 0xA0uint8_t Wbuf[20] = "EEPROM TEST OK!";uint8_t Rbuf[20] = {0};/********* 24C65寫數據函數*****************************/void Eeprom_Write(uint16_t MemAddr, uint8_t *Wbuf, uint16_t len ){ while(len--){ //I2C_MEMADD_SIZE_16BIT表示存儲單元大小 //默認為兩個參數,分別是I2C_MEMADD_SIZE_16BIT和I2C_MEMADD_SIZE_8BIT //由于24C65的存儲地址是16位的 //所以我們選擇I2C_MEMADD_SIZE_16BIT //1表示一次寫一個字節 while(HAL_I2C_Mem_Write(&hi2c1, WriteAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Wbuf, 1, 100) != HAL_OK){};MemAddr++;Wbuf++;}}/********* 24C65讀數據函數*****************************/void Eeprom_Read(uint16_t MemAddr, uint8_t *Rbuf, uint16_t len ){ //可以連續讀,所以無需循環 while(HAL_I2C_Mem_Read(&hi2c1, ReadAddr, MemAddr, I2C_MEMADD_SIZE_16BIT, Rbuf, len, 100) != HAL_OK );}int mian(){ MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); printf("this is i2c eeprom testn"); Eeprom_Write(0, Wbuf, sizeof(Wbuf) ); HAL_Delay(500); Eeprom_Read(0 , Rbuf, sizeof(Rbuf)); printf("READ: %sn", Rbuf); while(){ }}
STM32 SPI總線通信專題講解SPI接口是Motorola 首先提出的全雙工三線同步串行外圍接口,采用主從模式(Master Slave)架構;支持多slave模式應用,一般僅支持單Master。時鐘由Master控制,在時鐘移位脈沖下,數據按位傳輸,是高位在前還是低位在前是可以配置的,配置時根據從設備的通信進行相應配置,一般是高位在前,低位在后(MSB first)。SPI接口有2根單向數據線,為全雙工通信,目前應用中的數據速率可達幾Mbps的水平。
SPI總線被廣泛地使用在FLASH、ADC、LCD等設備與MCU間,要求通訊速率較高的場合。
SPI接口共有4根信號線,分別是:設備選擇線、時鐘線、串行輸出數據線、串行輸入數據線。
(1)MOSI:主器件數據輸出,從器件數據輸入,連接從機的MOSI,與串口不同,串口需要反著連接(Rx-----Tx)
(2)MISO:主器件數據輸入,從器件數據輸出,連接從機的MISO
(3)SCLK :時鐘信號,由主器件產生
(4)/SS:從器件使能信號,由主器件控制(片選),一般情況下為地電平選中設備,高電平釋放設備。
SPI總線協議1.數據交換邏輯:主機和從機都包含一個串行移位寄存器,主機通過向它的SPI串行寄存器寫入一個字節發起一次傳輸。寄存器通過MOSI信號線將字節傳送給從機,從機也將自己的移位寄存器中的內容通過MISO信號線返回給主機。這樣兩個移位寄存器中的內容就被交換了。從機的寫操作和讀操作時同步完成的,因此SPI成為一個很有效的協議。
如果主機只想寫不想讀,只需把數據放在數據寄存器,SPI控制器會自動傳給外設,同時忽略掉外設傳過來的數據即可;如果主機只想讀不想寫,主機寫給外設一個空字符或者隨便寫一個數據,外設就會把數據傳過來,不管是只讀還是只寫,主機與外設的讀和寫都h會發生且同時進行。
2.起始信號: NSS信號線由高變低,是SPI通訊的起始信號。
3.結束信號:NSS信號由低變高,是SPI通訊的停止信號。
4.數據傳輸:SPI使用MOSI及MISO信號線來傳輸數據,使用SCK信號線進行數據同步。MOSI及MISO數據線在SCK的每個時鐘周期傳輸一位數據,按位傳輸,且數據輸入輸出是同時進行的。SPI每次數據傳輸可以 8 位或 16 位為單位,每次傳輸的單位數不受限制,要么是8位,要么是16位,可以配置。
SPI的4種通信模式在SPI操作中,最重要的兩項設置就是時鐘極性(CPOL)和時鐘相位(CPHA)這兩項即是主從設備間數據采樣的約定方式。由CPOL及CPHA的不同狀態,SPI分成了四種模式,主機與從機需要工作在相同的模式下才可以正常通訊,因此通常主機要按照從機支持的模式去設置。同樣在配置時一定要弄明白從機支持什么通信模式進行相應的配置。
1.時鐘極性CPOL : 設置時鐘空閑時的電平:
a.當CPOL= 0 ,SCK引腳在空閑狀態保持低電平;
b.當CPOL= 1 ,SCK引腳在空閑狀態保持高電平。
2.時鐘相位CPHA :設置數據采樣時的時鐘沿:
a.當 CPHA=0時,MOSI或 MISO 數據線上的信號將會在 SCK時鐘線的奇數邊沿被采樣
b.當 CPHA=1時, MOSI或 MISO 數據線上的信號將會在 SCK時鐘線的偶數邊沿被采樣
STM32F4-SPI控制器特性1.通訊引腳:
STM32F4芯片最多支持6個SPI外設控制器,它們的SPI通訊信號引出到不同的GPIO引腳上,使用時必須配置到這些指定的引腳,以《STM32F4xx規格書》為準。f407只有SPI1、SPI2、SPI3。
其中SPI1、SPI4、SPI5、SPI6是APB2上的設備,最高通信速率達42Mbtis/s,SPI2、SPI3是APB1上的設備,最高通信速率為21Mbits/s。其它功能上沒有差異。
2.時鐘控制邏輯:
SCK線的時鐘信號,由波特率發生器根據“控制寄存器CR1”中的BR[0:2]位控制,該位是對f pclk 時鐘的分頻因子,對f pclk 的分頻結果就是SCK引腳的輸出時鐘頻率。
其中的fpclk 頻率是指SPI所在的APB總線頻率,APB1為fpclk1 ,APB2為fpckl2
3.數據控制邏輯:
STM32F4的MOSI及MISO都連接到數據移位寄存器上,數據移位寄存器的數據來源來源于接收緩沖區及發送緩沖區。
a.通過寫SPI的“數據寄存器DR”把數據填充到發送緩沖區中。
b.通過讀“數據寄存器DR”,可以獲取接收緩沖區中的內容。
c.其中數據幀長度可以通過“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可選擇MSB先行(高位在前)還是LSB先行(低位在前)。
4.整體控制邏輯:
a.整體控制邏輯負責協調整個SPI外設,控制邏輯的工作模式根據“控制寄存器(CR1/CR2)”的參數而改變,基本的控制參數包括前面提到的SPI模式、波特率、LSB先行、主從模式、單雙向模式(同時發送和接收、只發送關掉接收、只接收關掉發送)等等。
b.在外設工作時,控制邏輯會根據外設的工作狀態修改“狀態寄存器(SR)”,只要讀取狀態寄存器相關的寄存器位,就可以了解SPI的工作狀態了。除此之外,控制邏輯還根據要求,負責控制產生SPI中斷信號、DMA請求及控制NSS信號線。
c.實際應用中,一般不使用STM32 SPI外設的標準NSS信號線,而是更簡單地使用普通的GPIO,軟件控制它的電平輸出,從而產生通訊起始和停止信號。
串行FLASH_W25X16簡介FLSAH 存儲器又稱閃存,它與EEPROM都是掉電后數據不丟失的存儲器,但FLASH存儲器容量普遍大于 EEPROM,現在基本取代了它的地位。我們生活中常用的 U盤、SD卡、SSD 固態硬盤以及我們 STM32 芯片內部用于存儲程序的設備,都是 FLASH 類型的存儲器。在存儲控制上,最主要的區別是FLASH 芯片只能一大片一大片地擦寫,而EEPROM可以單個字節擦寫。
W25X16有8192個可編程頁,每頁256字節。用“頁編程指令”每次就可以編程256個字節。用扇區擦除指令每次可以擦除16頁,即一個扇區包含16頁,用塊擦除指令每次可以擦除256頁,用整片擦除指令即可以擦除整個芯片。W25X16有512個可擦除扇區或32個可擦除塊。
1.W25X16的硬件連線如下:
CS: 片選引腳,低電平有效,連接到STM32-PH2管腳
SO: 連接到STM32-PB4管腳(MISO)
SI: 連接到STM32-PB5管腳(MOSI)
CLK: 連接到STM32-PA5管腳(CLK)
WP: 寫保護管腳,低電平有效,有效時禁止寫入數據。接電源未使用
HOLD: HOLD 引腳可用于暫停通訊,該引腳為低電平時,通訊暫停,未使用
2.W25X16控制指令:
我們需要了解如何對FLASH芯片進行讀寫。FLASH 芯片自定義了很多指令,我們通過控制 STM32利用 SPI總線向 FLASH 芯片發送指令,FLASH芯片收到后就會執行相應的操作。
而這些指令,對主機端(STM32)來說,只是它遵守最基本的 SPI通訊協議發送出的數據,但在設備端(FLASH 芯片)把這些數據解釋成不同的意義,所以才成為指令。
a.讀制造商/設備ID(90):該指令通常在調試程序的時候用到,判斷SPI通信是否正常。該指令通過主器件拉低/CS片選使能器件開始傳輸,首先通過DI線傳輸“90H”指令,接著傳輸000000H的24位地址(A23-A0),之后從器件會通過DO線返回制造商ID(EFH)和設備ID。(注:SPI為數據交換通信,主器件在發送“90H”指令時也會接收到一個字節FFH,但此數據為無效數據)
b.寫使能命令(06H):在向 FLASH 芯片存儲矩陣寫入數據前,首先要使能寫操作,通過“Write Enable”命令即可寫使能。
c.扇區擦除(20H):由于 FLASH 存儲器的特性決定了它只能把原來為“1”的數據位改寫成“0”,而原來為“0”的數據位不能直接改寫為“1”。所以這里涉及到數據“擦除”的概念。
在寫入前,必須要對目標存儲矩陣進行擦除操作,把矩陣中的數據位擦除為“1”,在數據寫入的時候,如果要存儲數據“1”,那就不修改存儲矩陣 ,在要存儲數據“0”時,才更改該位。
d.讀狀態寄存器(05H):FLASH 芯片向內部存儲矩陣寫入數據需要消耗一定的時間,并不是在總線通訊結束的一瞬間完成的,所以在寫操作后需要確認FLASH芯片“空閑”。我們只需要讀取FLASH芯片內部的狀態寄存器SRP的S0即可(當這個位為“1”時,表明 FLASH芯片處于忙碌狀態,它可能正在對內部的存儲矩陣進行“擦除”或“數據寫入”的操作)
e.讀數據(03H):讀數據指令可從存儲器依次一個或多個數據字節,該指令通過主器件拉低/CS電平使能設備開始傳輸,然后傳輸“03H”指令,接著通過DI管腳傳輸24位芯片存儲地址,從器件接到地址后,尋址存儲器中的數據通過DO引腳輸出。每傳輸一個字節地址自動遞增,所以只要時鐘繼續傳輸,可以不斷讀取存儲器中的數據。
f.寫數據——頁編程(02H):頁編程指令可以在已擦除的存儲單元中寫入256個字節。該指令先拉低/CS引腳電平,接著傳輸“02H”指令和24位地址。后面接著傳輸至少一個數據字節,最多256字節。
注:當數據寫到一個新的扇區的時候,需要重新發起一個頁編程信號才能繼續寫入數據。
STM32 SPI_FLASH基本配置和操作根據如下的硬件連線圖進行配置
步驟:
1.使能時鐘RCC
2.使能SPI1,配置相應管腳
3.配置SPI協議
4.編碼
//main.c#include "w25x16.h"uint8_t RD_Buffer[5000] = {0};uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TESTn";int main(){ uint16_t FLASH_ID = 0; uint32_t i; MX_GPIO_Init(); MX_SPI1_Init(); FLASH_ID = sFLASH_ReadID(); /******測試擦除******/ sFLASH_EraseSector(4096*0); //sFLASH_EraseSector(4096*1); sFLASH_ReadBuffer(RD_Buffer,0,4096); printf("讀數據開始n"); for(i=0; i< 4096; i++) { printf("%x ",RD_Buffer[i]); } printf("讀數據結束n"); /******測試寫操作1*****/ //寫之前都需要擦除扇區 sFLASH_EraseSector(4096*0); sFLASH_WritePage(WR_Buffer,0, 20); sFLASH_ReadBuffer(RD_Buffer,0,20); printf("READ DATA: %sn",RD_Buffer); /******測試寫操作2*****/ //寫之前都需要擦除扇區 sFLASH_EraseSector(4096*0); sFLASH_EraseSector(4096*1); for(i=0; i< 4096; i++) { WR_Buffer[i] = 0x55; } sFLASH_WriteBuffer(WR_Buffer,4090, 1000); sFLASH_ReadBuffer(RD_Buffer,4090,1000); for(i=0; i< 1000; i++) { printf("%x ",RD_Buffer[i]); } /*****************/ while(){}}
//w25x16.h#ifndef __W25X16_H#define __W25X16_H#include "stm32f4xx_hal.h" //使用宏定義芯片指令#define W25X_ManufactDeviceID 0x90 /* Read identification */#define sFLASH_CMD_WREN 0x06/* Write enable instruction */#define sFLASH_CMD_RDSR 0x05/* Read Status Register instruction */#define sFLASH_CMD_SE 0x20/* Sector Erase instruction */#define sFLASH_CMD_WRITE 0x02 /* Write to Memory instruction */#define sFLASH_CMD_READ 0x03/* Read from Memory instruction */#define sFLASH_DUMMY_BYTE 0x00 //空字節,用于只讀傳回來的數據#define sFLASH_BUSY_FLAG 0x01#define sFLASH_SPI_PAGESIZE 0x100/* 選中芯片,拉低信號 */#define sFLASH_CS_LOW() HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)/* 釋放芯片,拉高信號 */#define sFLASH_CS_HIGH() HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)//定義函數uint8_t sFLASH_SendByte(uint8_t byte);uint16_t sFLASH_ReadID(void);void sFLASH_EraseSector(uint32_t SectorAddr);void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);#endif
//w25x16.c#include "w25x16.h"extern SPI_HandleTypeDef hspi1;/*讀寫一個字節函數,因為SPI讀和寫同時完成*//*發送數據一定會接收到一個數據*/uint8_t sFLASH_SendByte(uint8_t byte){ uint8_t TX_DATA = byte; uint8_t RX_DATA = 0; HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000); return RX_DATA;}/*等待擦除或者寫數據完成*/void sFLASH_WaitForEnd(void){ uint8_t sr_value = 0; sFLASH_CS_LOW(); sFLASH_SendByte(sFLASH_CMD_RDSR); //讀S0的值,為1表示忙碌,為0表示停止 do{ //發一個空字節,得到S0的值 sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE); }while( sr_value & sFLASH_BUSY_FLAG); sFLASH_CS_HIGH();}void sFLASH_WriteEnable(void){ sFLASH_CS_LOW(); sFLASH_SendByte(sFLASH_CMD_WREN); sFLASH_CS_HIGH();}/*讀設備ID*/ uint16_t sFLASH_ReadID(void){ uint16_t FLASH_ID; uint8_t temp0,temp1; sFLASH_CS_LOW(); sFLASH_SendByte(W25X_ManufactDeviceID); //讀設備指令后要發24位地址,所以要發三次 sFLASH_SendByte(sFLASH_DUMMY_BYTE); sFLASH_SendByte(sFLASH_DUMMY_BYTE); sFLASH_SendByte(sFLASH_DUMMY_BYTE); //制造商ID temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE); //設備商ID temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE); sFLASH_CS_HIGH(); FLASH_ID = (temp0 < < 8) | temp1; return FLASH_ID;} //擦除扇區,擦除為1,因為只能由1變為0 ,不能0變1void sFLASH_EraseSector(uint32_t SectorAddr){ //SectorAddr表示擦除第幾個扇區 sFLASH_WriteEnable(); //開啟寫使能 sFLASH_CS_LOW();//拉低,片選 //擦除命令 sFLASH_SendByte(sFLASH_CMD_SE); //傳24位地址 //傳送高8位,將中8位和低8位一共16位移出去,得到高8位 sFLASH_SendByte( (SectorAddr >>16) & 0xff); sFLASH_SendByte( (SectorAddr >>8) & 0xff); //傳送中8位 sFLASH_SendByte( (SectorAddr >>0) & 0xff); //傳送低8位 sFLASH_CS_HIGH(); /*讀狀態寄存器,等待擦除完成*/ sFLASH_WaitForEnd();} //讀數據 //讀命令和讀地址發送后,芯片內部會自動不斷遞增讀數據void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead){ sFLASH_CS_LOW(); sFLASH_SendByte(sFLASH_CMD_READ); sFLASH_SendByte( (ReadAddr >>16) & 0xff); //傳送高8位 sFLASH_SendByte( (ReadAddr >>8) & 0xff); //傳送中8位 sFLASH_SendByte( (ReadAddr >>0) & 0xff); //傳送低8位 while(NumByteToRead--) { * pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE); pBuffer++; } sFLASH_CS_HIGH();}//寫一頁最多只能寫256個字節,一個扇區16頁,一個塊16個扇區 void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite){ if(NumByteToWrite > sFLASH_SPI_PAGESIZE ) { NumByteToWrite = sFLASH_SPI_PAGESIZE; printf("寫數據量過大,超過一頁大小n"); } sFLASH_WriteEnable(); //開啟寫使能 sFLASH_CS_LOW(); sFLASH_SendByte(sFLASH_CMD_WRITE); sFLASH_SendByte( (WriteAddr >>16) & 0xff); //傳送高8位 sFLASH_SendByte( (WriteAddr >>8) & 0xff); //傳送中8位 sFLASH_SendByte( (WriteAddr >>0) & 0xff); //傳送低8位 while(NumByteToWrite--) { sFLASH_SendByte(* pBuffer); pBuffer++; } sFLASH_CS_HIGH(); /*擦除和寫數據都涉及到寫動作,一定要等待完成*/ sFLASH_WaitForEnd();}//寫任意地址、任意長度void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite){ uint16_t NumOfPage, NumOfBytes, count, offset; //求WriteAddr在某一頁的位置 offset = WriteAddr % sFLASH_SPI_PAGESIZE; //求某一頁剩余的大小 count = sFLASH_SPI_PAGESIZE - offset; /*處理頁不對齊的情況,防止頁內覆蓋*/ //先把某一頁剩下的部分寫掉,之后的就能新頁的起始處開始寫 /*offset有值表示需要頁對齊,如果要寫的字節數小于某一頁剩余的部分,那就無需對齊*/ /*這兩個條件必須同時滿足*/ if(offset && (NumByteToWrite > count )) { sFLASH_WritePage(pBuffer,WriteAddr,count); NumByteToWrite -= count;//去掉已經寫了的,從新頁開始 pBuffer += count; WriteAddr += count; } /*最多可分多少頁*/ NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE; /*剩余多少字節*/ NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE; if(NumOfPage) { while(NumOfPage--) { //每一頁都發起頁編程 sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE); pBuffer += sFLASH_SPI_PAGESIZE; WriteAddr += sFLASH_SPI_PAGESIZE; } } if(NumOfBytes) { sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes); }}
為什么會有兩種寫操作函數,是因為這里的寫操作有兩個特點:1.無法突破頁限制,超過一頁需要重新發起頁編程信號。另外如果要寫的數據大于剩余一頁剩余的容量,那么超出的數據會寫到當前頁起始地址出。例如,初始輸入的寫地址為200,而要寫的數據大小為100,那么要寫的前56個字節會從地址200開始依次寫入,剩下的44個字節會從當前頁的0地址開始依次寫入,這很有可能覆蓋之前的數據。
2.無法突破扇區的限制,當數據寫到一個新的扇區的時候,需要重新發起一個頁編程信號才能繼續寫入數據。
標簽: