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