開發(fā)環(huán)境:
MDK:Keil 5.30
開發(fā)板:GD32F207I-EVAL
MCU:GD32F207IK
1 SPI簡介
SPI,是Serial Peripheral interface的縮寫,顧名思義就是串行外圍設(shè)備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。是一種高速全雙工的通信總線,它由摩托羅拉公司提出,當(dāng)前最新的為 V04.01—2004 版。它被廣泛地使用在ADC、LCD 等設(shè)備與 MCU 間通信的場合。SPI接口主要應(yīng)用在 EEPROM,F(xiàn)LASH,實(shí)時(shí)時(shí)鐘,AD轉(zhuǎn)換器,還有數(shù)字信號處理器和數(shù)字信號解碼器之間。SPI,是一種高速的,全雙工,同步的通信總線,并且在芯片的管腳上只占用四根線,節(jié)約了芯片的管腳,同時(shí)為PCB的布局上節(jié)省空間,提供方便,正是出于這種簡單易用的特性,現(xiàn)在越來越多的芯片集成了這種通信協(xié)議。
1.1 SPI 信號線
SPI 包含 4 條總線,SPI 總線包含 4 條總線,分別為SS、SCK、MOSI、MISO。它們的作用介紹如下 :
1)SS ( Slave Select):片選信號線,當(dāng)有多個(gè) SPI 設(shè)備與 MCU 相連時(shí),每個(gè)設(shè)備的這個(gè)片選信號線是與 MCU 單獨(dú)的引腳相連的,而其他的 SCK、MOSI、MISO 線則為多個(gè)設(shè)備并聯(lián)到相同的 SPI 總線上,見下圖。當(dāng) SS 信號線為低電平時(shí),片選有效,開始SPI 通信。
2)SCK (Serial Clock):時(shí)鐘信號線,由主通信設(shè)備產(chǎn)生,不同的設(shè)備支持的時(shí)鐘頻率不一樣,如 GD32 的 SPI 時(shí)鐘頻率最大為 f PCLK /2。
3)MOSI (Master Output, Slave Input):主設(shè)備輸出 / 從設(shè)備輸入引腳。主機(jī)的數(shù)據(jù)從這條信號線輸出,從機(jī)由這條信號線讀入數(shù)據(jù),即這條線上數(shù)據(jù)的方向?yàn)橹鳈C(jī)到從機(jī)。
4)MISO(Master Input, Slave Output):主設(shè)備輸入 / 從設(shè)備輸出引腳。主機(jī)從這條信號線讀入數(shù)據(jù),從機(jī)的數(shù)據(jù)則由這條信號線輸出,即在這條線上數(shù)據(jù)的方向?yàn)閺臋C(jī)到主機(jī)。
1.2 SPI模式
SPI通信中可作為從機(jī)也可以作為主機(jī),這取決于硬件設(shè)計(jì)和軟件設(shè)置。
當(dāng)器件作為主機(jī)時(shí),使用一個(gè)IO引腳拉低相應(yīng)從機(jī)的選擇引腳(NSS),傳輸?shù)钠鹗加芍鳈C(jī)發(fā)送數(shù)據(jù)來啟動,時(shí)鐘(SCK)信號由主機(jī)產(chǎn)生。通過MOSI發(fā)送數(shù)據(jù),同時(shí)通過MISO引腳接收從機(jī)發(fā)出的數(shù)據(jù)。
當(dāng)器件作為從機(jī)時(shí),傳輸在從機(jī)選擇引腳(NSS)被主機(jī)拉低后開始,接收主機(jī)輸出的時(shí)鐘信號,在讀取主機(jī)數(shù)據(jù)的同時(shí)通過MISO引腳輸出數(shù)據(jù)。
根據(jù) SPI 時(shí)鐘極性(CKPL)和時(shí)鐘相位(CKPH) 配置的不同,分為 4 種 SPI 模式。
時(shí)鐘極性是指 SPI 通信設(shè)備處于空閑狀態(tài)時(shí)(也可以認(rèn)為這是 SPI 通信開始時(shí),即SS 為低電平時(shí)),SCK 信號線的電平信號。CKPL=0 時(shí), SCK 在空閑狀態(tài)時(shí)為低電平,CKPL=1 時(shí)則相反。
時(shí)鐘相位是指數(shù)據(jù)采樣的時(shí)刻,當(dāng) CKPH =0 時(shí),MOSI 或 MISO 數(shù)據(jù)線上的信號將會在 SCK 時(shí)鐘線的奇數(shù)邊沿被采樣。當(dāng) CKPH=1 時(shí),數(shù)據(jù)線在 SCK 的偶數(shù)邊沿采樣。
我們來分析這個(gè) CKPH =0 的時(shí)序圖。首先,由主機(jī)把片選信號線SS 拉低,即為圖中的SS (O)時(shí)序,意為主機(jī)輸出,SS (I)時(shí)序?qū)嶋H上也是SS 線信號,SS (I)時(shí)序表示從機(jī)接收到SS 片選被拉低的信號。
在SS 被拉低的時(shí)刻,SCK 分為兩種情況,若我們設(shè)置為 CKPL=0,則 SCK 時(shí)序在這個(gè)時(shí)刻為低電平,若設(shè)置為 CKPL=1,則 SCK 在這個(gè)時(shí)刻為高電平。
無論 CKPL=0 還是=1,因?yàn)槲覀兣渲玫臅r(shí)鐘相位 CKPH =0,在采樣時(shí)刻的時(shí)序中我們可以看到,采樣時(shí)刻都是在 SCK 的奇數(shù)邊沿(注意奇數(shù)邊沿有時(shí)為下降沿,有時(shí)為上升沿)。因此,MOSI 和 MISO 數(shù)據(jù)線的有效信號在 SCK 的奇數(shù)邊沿保持不變,這個(gè)信號將在SCK 奇數(shù)邊沿時(shí)被采集,在非采樣時(shí)刻,MOSI 和 MISO 的有效信號才發(fā)生切換。
對于 CKPH =1 的情況也很類似,但數(shù)據(jù)信號的采樣時(shí)刻為偶數(shù)邊沿。使用 SPI 協(xié)議通信時(shí),主機(jī)和從機(jī)的時(shí)序要保持一致,即兩者都選擇相同的 SPI 模式。
1.3 SPI特性
GD32的小容量有一個(gè)SPI接口,中容量有2個(gè),大容量有3個(gè)接口,其特性如下所示。
- 具有全雙工和單工模式的主從操作;
- 16位寬度,獨(dú)立的發(fā)送和接收緩沖區(qū);
- 8位或16位數(shù)據(jù)幀格式;
- 低位在前或高位在前的數(shù)據(jù)位順序;
- 軟件和硬件NSS管理;
- 硬件CRC計(jì)算、發(fā)送和校驗(yàn);
- 發(fā)送和接收支持DMA模式;
- 支持SPI四線功能的主機(jī)模式(只有SPI0)。
2 SPI架構(gòu)
下圖所示為GD32的 SPI 架構(gòu)圖,可以看到 MISO 數(shù)據(jù)線接收到的信號經(jīng)移位寄存器處理后把數(shù)據(jù)轉(zhuǎn)移到接收緩沖區(qū),然后這個(gè)數(shù)據(jù)就可以由我們的軟件從接收緩沖區(qū)讀出了。
當(dāng)要發(fā)送數(shù)據(jù)時(shí),我們把數(shù)據(jù)寫入發(fā)送緩沖區(qū),硬件將會把它用移位寄存器處理后輸出到 MOSI 數(shù)據(jù)線。
SCK 的時(shí)鐘信號則由波特率發(fā)生器產(chǎn)生,我們可以通過波特率控制位(PSC)來控制它輸出的波特率。
控制寄存器 CTL0掌管著主控制電路,GD32的 SPI 模塊的協(xié)議設(shè)置(時(shí)鐘極性、相位等)就是由它來制定的。而控制寄存器 CTL1則用于設(shè)置各種中斷使能。
最后為 NSS 引腳,這個(gè)引腳扮演著 SPI 協(xié)議中的SS 片選信號線的角色,如果我們把 NSS 引腳配置為硬件自動控制,SPI 模塊能夠自動判別它能否成為 SPI 的主機(jī),或自動進(jìn)入 SPI 從機(jī)模式。但實(shí)際上我們用得更多的是由軟件控制某些 GPIO 引腳單獨(dú)作為SS信號,這個(gè) GPIO 引腳可以隨便選擇。
通常SPI通過4個(gè)引腳與外部器件相連:
● MISO:主設(shè)備輸入/從設(shè)備輸出引腳。該引腳在從模式下發(fā)送數(shù)據(jù),在主模式下接收數(shù)據(jù)。
● MOSI:主設(shè)備輸出/從設(shè)備輸入引腳。該引腳在主模式下發(fā)送數(shù)據(jù),在從模式下接收數(shù)據(jù)。
● SCK: 串口時(shí)鐘,作為主設(shè)備的輸出,從設(shè)備的輸入。
● NSS: 從設(shè)備選擇。這是一個(gè)可選的引腳,用來選擇主/從設(shè)備。它的功能是用來作為“片選引腳”,讓主設(shè)備可以單獨(dú)地與特定從設(shè)備通訊,避免數(shù)據(jù)線上的沖突。從設(shè)備的NSS引腳可以由主設(shè)備的一個(gè)標(biāo)準(zhǔn)I/O引腳來驅(qū)動。
3 SPI工作原理
3.1 (NSS)輸入輸出管理
- (NSS)輸出管理
對于每個(gè)SPI的NSS可以輸入,也可以輸出。所謂輸入,就是NSS的電平信號給自己,所謂輸出,就是將NSS的電平信號發(fā)送出去,給從機(jī)。配置為輸出,還是不輸出,我們可以通過SPI_CTL1寄存器的NSSDRV位。當(dāng)NSSDRV=1時(shí),并且SPI處于主模式控制時(shí)(MSTMOD=1),NSS就輸出低電平,也就是拉低,因此當(dāng)其他SPI設(shè)備的NSS引腳與它相連,必然接收到低電平,則片選成功,都成為從設(shè)備了。
- (NSS)輸入管理
NSS軟件模式:
- SPI主機(jī):
需要設(shè)置SPI_CTL0寄存器的SWNSSEN=1和SWNSS=1,SWNSSEN=1是為了使能軟件管理,NSS有內(nèi)部和外部引腳。這時(shí)候外部引腳留作他用(可以用來作為GPIO驅(qū)動從設(shè)備的片選信號)。內(nèi)部NSS引腳電平則通過SPI_CTL0寄存器的SWNSS位來驅(qū)動。SWNSS=1是為了使NSS內(nèi)電平為高電平。為什么主設(shè)備的內(nèi)部NSS電平要為1呢?
GD32手冊上說,要保持MSTMOD=1和SPIEN=1,也就是說要保持主機(jī)模式,只有NSS接到高電平信號時(shí),這兩位才能保持置‘1’。
- SPI從機(jī):
NSS引腳在完成字節(jié)傳輸之前必須連接到一個(gè)低電平信號。在軟件模式下,則需要設(shè)置SPI_CR1寄存器的SWNSSEN=1(軟件管理使能)和SWNSS=0.
NSS硬件模式:
對于主機(jī),我們的NSS可以直接接到高電平.對于從機(jī),NSS接低就可以。
3.2 單主和單從應(yīng)用
從上圖可以看出,主機(jī)和從機(jī)都有一個(gè)串行移位寄存器,主機(jī)通過向它的SPI串行寄存器寫入一個(gè)字節(jié)發(fā)起一次傳輸。寄存器通過MOSI信號將字節(jié)傳給從機(jī),從機(jī)也將自己的移位寄存器中的內(nèi)容通過MISO信號返還給主機(jī)。這樣,兩個(gè)移位寄存器中下的內(nèi)容就被交換,外設(shè)的寫操作是同步完成的。如果只進(jìn)行寫操作,主機(jī)只需忽略接收到的字節(jié);反之,若主機(jī)要讀取從機(jī)的一個(gè),就必須發(fā)送一個(gè)空字節(jié)來引發(fā)從機(jī)的傳輸。
3.3 時(shí)鐘信號的相位和極性
SPI_CTL0寄存器的CKPL和CKPH位,能夠組合成四種可能的時(shí)序關(guān)系。CKPL (時(shí)鐘極性)位控制在沒有數(shù)據(jù)傳輸時(shí)時(shí)鐘的空閑狀態(tài)電平,此位對主模式和從模式下的設(shè)備都有效。如果CKPL被清’0’,SCK引腳在空閑狀態(tài)保持低電平;如果CKPL被置’1’,SCK引腳在空閑狀態(tài)保持高電平。如果CKPH (時(shí)鐘相位)位被置’1’,SCK時(shí)鐘的第二個(gè)邊沿(CPOL位為0時(shí)就是下降沿,CKPL位為’1’時(shí)就是上升沿)進(jìn)行數(shù)據(jù)位的采樣,數(shù)據(jù)在第二個(gè)時(shí)鐘邊沿被鎖存。如果CKPH位被清’0’,SCK時(shí)鐘的第一邊沿(CPOL位為’0’時(shí)就是下降沿,CKPL位為’1’時(shí)就是上升沿)進(jìn)行數(shù)據(jù)位采樣,數(shù)據(jù)在第一個(gè)時(shí)鐘邊沿被鎖存。
CKPL時(shí)鐘極性和CKPH時(shí)鐘相位的組合選擇數(shù)據(jù)捕捉的時(shí)鐘邊沿。
3.4 數(shù)據(jù)幀格式
根據(jù)SPI_CTL0寄存器中的LF位,輸出數(shù)據(jù)位時(shí)可以MSB在先也可以LSB在先。根據(jù)SPI_CTL0寄存器的FF16位,每個(gè)數(shù)據(jù)幀可以是8位或是16位。所選擇的數(shù)據(jù)幀格式對發(fā)送和/或接收都有效。
3.5 SPI主從模式工作原理
配置SPI主模式的步驟如下:
設(shè)置SPI_CTL0寄存器的PSC [2:0]位,來定義串行時(shí)鐘波特率。
選擇CKPL和CKPH位,定義數(shù)據(jù)傳輸和串行時(shí)鐘間的相位關(guān)系。
設(shè)置FF16位來定義8或16位數(shù)據(jù)幀格式。
配置SPI_CTL0寄存器的LF位定義幀格式。
如果NSS引腳需要工作在輸入模式,硬件模式中在整個(gè)數(shù)據(jù)幀傳輸期間應(yīng)把NSS引腳連接到高電平;在軟件模式中,需設(shè)置SPI_CTL0寄存器的SWNSSEN=1和SWNSS=1。如果NSS引腳工作在輸出模式,則只需設(shè)置SSOE=1位。
設(shè)置MSTMOD=1和SPIEN=1,只當(dāng)NSS引腳被連到高電平,這些位才能保持置位。
配置SPI從模式的步驟如下:
設(shè)置FF16位以定義數(shù)據(jù)幀格式為8位或16位。
定義數(shù)據(jù)傳輸和串行時(shí)鐘之間的相位關(guān)系。
幀格式必須和主設(shè)備相同,MSB在前還是LSB在前取決于SPI_CTL0寄存器中的LF位。
硬件模式下,在完整的數(shù)據(jù)幀(8位或16位)發(fā)送過程中,NSS引腳必須為低電平。軟件模式下,設(shè)置SPI_CTL0寄存器中的SWNSSEN=1,SWNSS=0。
MSTMOD=0位,設(shè)置SPIEN=1,使相應(yīng)引腳工作于SPI模式下。
3.6 狀態(tài)標(biāo)志
應(yīng)用程序通過3個(gè)狀態(tài)標(biāo)志可以完全監(jiān)控SPI總線的狀態(tài)。
1.發(fā)送緩沖器空閑標(biāo)志(TBE)
此標(biāo)志為’1’時(shí)表明發(fā)送緩沖器為空,可以寫下一個(gè)待發(fā)送的數(shù)據(jù)進(jìn)入緩沖器中。當(dāng)寫入SPI_DATA時(shí),TBE標(biāo)志被清除。
2.接收緩沖器非空(RBNE)
此標(biāo)志為’1’時(shí)表明在接收緩沖器中包含有效的接收數(shù)據(jù)。讀SPI數(shù)據(jù)寄存器可以清除此標(biāo)志。
3.忙(Busy)標(biāo)志
TRANS標(biāo)志由硬件設(shè)置與清除(寫入此位無效果),此標(biāo)志表明SPI通信層的狀態(tài)。
3.7 SPI中斷
SPI的相關(guān)中斷標(biāo)志如下:
中斷事件 | 事件標(biāo)志 | 使能控制位 |
---|---|---|
發(fā)送緩沖器空標(biāo)志 | TBE | TBEIE |
接收緩沖器非空標(biāo)志 | RBNE | RBNEIE |
主模式失效事件 | CONFERR | ERRIE |
溢出錯(cuò)誤 | RXORERR | |
CRC****錯(cuò)誤標(biāo)志 | CRCERR |
4 硬件連接
GD25Q16BS是兆易創(chuàng)新推出的一款 SPI 接口的 NOR Flash 芯片,其存儲空間為 16Mbit,相當(dāng)于2M 字節(jié)。
GD25Q16BS可以支持 SPI 的模式 0 和模式 3,也就是 CKPL=0/CKPH=0和CKPL=1/CKPH=1這兩種模式。
GD25Q16BS芯片支持 standard spi,Dual/Quad I/O SPI。
GD25Q16BS的擦寫周期多達(dá)5W 次,具有10年的數(shù)據(jù)保存期限,支持電壓為1.65~3.6V,GD25Q16BS支持標(biāo)準(zhǔn)的 SPI,還支持雙輸出/四輸出的 SPI,最大 SPI 時(shí)鐘可以到133Mhz(雙輸出時(shí)相當(dāng)于266Mhz,四輸出時(shí)相當(dāng)于532M)。
GD25Q16BS內(nèi)部有一個(gè)“SPI Command & Control Logic”,可以通過 SPI 接口向其發(fā)送指令,從而執(zhí)行相應(yīng)操作。
【注】
①、Flash 寫入數(shù)據(jù)時(shí)和 EEPROM 類似,不能跨頁寫入,一次最多寫入一頁,GD25Q16BS的一頁是 256 字節(jié)。寫入數(shù)據(jù)一旦跨頁,必須在寫滿上一頁的時(shí)候,等待 Flash 將數(shù)據(jù)從緩存搬移到非易失區(qū),重新再次往里寫。
②、Flash 有一個(gè)特點(diǎn),就是可以將 1 寫成 0,但是不能將 0 寫成 1,要想將 0 寫成 1,必須進(jìn)行擦除操作。因此通常要改寫某部分空間的數(shù)據(jù),必須首先進(jìn)行一定物理存儲空間擦除,最小的擦除空間,通常稱之為扇區(qū),扇區(qū)擦除就是將這整個(gè)扇區(qū)每個(gè)字節(jié)全部變成 0xFF。
我的開發(fā)板選用的Flash是GD25Q16BS,容量為2M,掛載在SPI0上,如下圖所示。
5 SPI具體代碼實(shí)現(xiàn)
首先是SPI的硬件初始化。
/*
brief initialize SPI1 GPIO and parameter
param[in] none
param[out] none
retval none
*/
void spi_flash_init(void)
{
spi_parameter_struct spi_init_struct;
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
rcu_periph_clock_enable(RCU_SPI0);
/* SPI0_CLK(PA5), SPI0_MISO_IO1(PA6), SPI0_MOSI_IO0(PA7) GPIO pin configuration */
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);
/* SPI0_CS(PB1) GPIO pin configuration */
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
/* chip select invalid */
SPI_FLASH_CS_HIGH();
/* SPI0 parameter config */
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; /*SPI receive and send data at fullduplex communication*/
spi_init_struct.device_mode = SPI_MASTER; /* SPI as master*/
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; /* SPI frame size is 8 bits*/
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; /*SPI clock polarity is low level and phase is first edge*/
spi_init_struct.nss = SPI_NSS_SOFT; /* SPI NSS control by sofrware */
spi_init_struct.prescale = SPI_PSC_32; /* SPI clock prescale factor is 32 */
spi_init_struct.endian = SPI_ENDIAN_MSB; /* SPI transmit way is big endian: transmit MSB first */
spi_init(SPI0, &spi_init_struct);
/* enable SPI0 */
spi_enable(SPI0);
}
SPI的硬件初始化最重要的函數(shù)就是spi_init ()。
void spi_init(uint32_t spi_periph, spi_parameter_struct *spi_struct)
其中SPI參數(shù)配置的結(jié)構(gòu)體為spi_parameter_struct;。
/* SPI and I2S parameter struct definitions */
typedef struct {
uint32_t device_mode; /*!< SPI master or slave */
uint32_t trans_mode; /*!< SPI transfer type */
uint32_t frame_size; /*!< SPI frame size */
uint32_t nss; /*!< SPI NSS control by hardware or software */
uint32_t endian; /*!< SPI big endian or little endian */
uint32_t clock_polarity_phase; /*!< SPI clock phase and polarity */
uint32_t prescale; /*!< SPI prescaler factor */
} spi_parameter_struct;
spi_parameter_struct結(jié)構(gòu)體成員變量如下:
- trans_mode用來設(shè)置 SPI 的通信方式,可以選擇為半雙工,全雙工,以及串行發(fā)和串行收方式,這里設(shè)置的全雙工(SPI_TRANSMODE_FULLDUPLEX)。
- device_mode用來設(shè)置 SPI 的主從模式。SCK 的時(shí)序是由通訊中的主機(jī)產(chǎn)生的。若被配置為從機(jī)模式,GD32的 SPI 外設(shè)將接受外來的 SCK 信號。
- frame_size為 8 位還是 16 位幀格式選擇項(xiàng)。
- clock_polarity_phase 用來設(shè)置時(shí)鐘極性與設(shè)置時(shí)鐘相位,就是選擇在串行同步時(shí)鐘的第幾個(gè)跳變沿(上升或下降)數(shù)據(jù)被采樣。
- nss設(shè)置NSS 信號由硬件(NSS 管腳)還是軟件控制??梢赃x擇為硬件模式(SPI_NSS_HARD)與軟件模式(SPI_NSS_SOFT),在硬件模式中的 SPI 片選信號由 SPI 硬件自動產(chǎn)生,而軟件模式則需要我們親自把相應(yīng)的 GPIO 端口拉高或置低產(chǎn)生非片選和片選信號。實(shí)際中軟件模式應(yīng)用比較多。
- prescale設(shè)置 SPI 波特率預(yù)分頻值決定 SPI 的時(shí)鐘的參數(shù),從不分頻道 256 分頻 8 個(gè)可選值。2-156,凡是2的幾次方都可以。
- endian設(shè)置數(shù)據(jù)傳輸順序是 MSB 位在前還是 LSB 位在前
SPI Flash的讀寫操作如下:
/*
brief read a byte from the SPI flash
param[in] none
param[out] none
retval byte read from the SPI flash
*/
uint8_t spi_flash_read_byte(void)
{
return(spi_flash_send_byte(DUMMY_BYTE));
}
/*
brief send a byte through the SPI interface and return the byte received from the SPI bus
param[in] byte: byte to send
param[out] none
retval the value of the received byte
*/
uint8_t spi_flash_send_byte(uint8_t byte)
{
/* loop while data register in not emplty */
while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_TBE));
/* send byte through the SPI0 peripheral */
spi_i2s_data_transmit(SPI0, byte);
/* wait to receive a byte */
while(RESET == spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE));
/* return the byte read from the SPI bus */
return(spi_i2s_data_receive(SPI0));
}
發(fā)送數(shù)據(jù)前要等待發(fā)送緩沖區(qū)為空,靠TBE標(biāo)志判斷,所以開始的while循環(huán)是等待發(fā)送緩沖區(qū)為空,同時(shí),等待接收緩沖區(qū)是否有數(shù)據(jù),靠RBNE標(biāo)志來判斷,把接收緩沖區(qū)的數(shù)據(jù)作為返回值返回。由于發(fā)送和接收是同時(shí)進(jìn)行的,而且要接收一個(gè)數(shù)據(jù)時(shí)必須在有效的SCK下,而只有發(fā)送數(shù)據(jù)才能產(chǎn)生有效的SCK,所以接收數(shù)據(jù)的函數(shù)時(shí)在發(fā)送數(shù)據(jù)的函數(shù)的基礎(chǔ)上,將發(fā)送的數(shù)據(jù)設(shè)置為Dummy_Byte假數(shù)據(jù)來騙取有效的SCK。
SPI Flash讀寫B(tài)uffer操作如下:
/*
brief write block of data to the flash
param[in] pbuffer: pointer to the buffer
param[in] write_addr: flash's internal address to write
param[in] num_byte_to_write: number of bytes to write to the flash
param[out] none
retval none
*/
void spi_flash_buffer_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
uint8_t num_of_page = 0, num_of_single = 0, addr = 0, count = 0, temp = 0;
addr = write_addr % SPI_FLASH_PAGE_SIZE;
count = SPI_FLASH_PAGE_SIZE - addr;
num_of_page = num_byte_to_write / SPI_FLASH_PAGE_SIZE;
num_of_single = num_byte_to_write % SPI_FLASH_PAGE_SIZE;
/* write_addr is SPI_FLASH_PAGE_SIZE aligned */
if(0 == addr)
{
/* num_byte_to_write < SPI_FLASH_PAGE_SIZE */
if(0 == num_of_page)
{
spi_flash_page_write(pbuffer, write_addr, num_byte_to_write);
}
else
{
/* num_byte_to_write >= SPI_FLASH_PAGE_SIZE */
while(num_of_page--)
{
spi_flash_page_write(pbuffer, write_addr, SPI_FLASH_PAGE_SIZE);
write_addr += SPI_FLASH_PAGE_SIZE;
pbuffer += SPI_FLASH_PAGE_SIZE;
}
spi_flash_page_write(pbuffer, write_addr, num_of_single);
}
}
else
{
/* write_addr is not SPI_FLASH_PAGE_SIZE aligned */
if(0 == num_of_page)
{
/* (num_byte_to_write + write_addr) > SPI_FLASH_PAGE_SIZE */
if(num_of_single > count)
{
temp = num_of_single - count;
spi_flash_page_write(pbuffer, write_addr, count);
write_addr += count;
pbuffer += count;
spi_flash_page_write(pbuffer, write_addr, temp);
}
else
{
spi_flash_page_write(pbuffer, write_addr, num_byte_to_write);
}
}
else
{
/* num_byte_to_write >= SPI_FLASH_PAGE_SIZE */
num_byte_to_write -= count;
num_of_page = num_byte_to_write / SPI_FLASH_PAGE_SIZE;
num_of_single = num_byte_to_write % SPI_FLASH_PAGE_SIZE;
spi_flash_page_write(pbuffer, write_addr, count);
write_addr += count;
pbuffer += count;
while(num_of_page--)
{
spi_flash_page_write(pbuffer, write_addr, SPI_FLASH_PAGE_SIZE);
write_addr += SPI_FLASH_PAGE_SIZE;
pbuffer += SPI_FLASH_PAGE_SIZE;
}
if(0 != num_of_single)
{
spi_flash_page_write(pbuffer, write_addr, num_of_single);
}
}
}
}
/*
brief read a block of data from the flash
param[in] pbuffer: pointer to the buffer that receives the data read from the flash
param[in] read_addr: flash's internal address to read from
param[in] num_byte_to_read: number of bytes to read from the flash
param[out] none
retval none
*/
void spi_flash_buffer_read(uint8_t *pbuffer, uint32_t read_addr, uint16_t num_byte_to_read)
{
/* select the flash: chip slect low */
SPI_FLASH_CS_LOW();
/* send "read from memory " instruction */
spi_flash_send_byte(READ);
/* send read_addr high nibble address byte to read from */
spi_flash_send_byte((read_addr & 0xFF0000) >> 16);
/* send read_addr medium nibble address byte to read from */
spi_flash_send_byte((read_addr & 0xFF00) >> 8);
/* send read_addr low nibble address byte to read from */
spi_flash_send_byte(read_addr & 0xFF);
/* while there is data to be read */
while(num_byte_to_read--)
{
/* read a byte from the flash */
*pbuffer = spi_flash_send_byte(DUMMY_BYTE);
/* point to the next location where the byte read will be saved */
pbuffer++;
}
/* deselect the flash: chip select high */
SPI_FLASH_CS_HIGH();
}
主函數(shù)代碼如下:
/*
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
st_bsp_usart_dev bsp_usart_dev0 = USART_DEV0_CONFIG;
st_bsp_led_dev bsp_led_dev0 = LED_DEV0_CONFIG;
//systick init
sysTick_init();
// led init
bsp_led_init(&bsp_led_dev0);
//usart init 115200 8-N-1
bsp_usart_init(&bsp_usart_dev0, USART_MODE_EXTI, 115200, 0, 1);
/* configure SPI and parameter */
spi_flash_init();
/* GD32207i-EVAL start up */
printf("\\n\\rGD32207i-EVAL System is Starting up...\\n\\r");
printf("\\n\\rGD32207i-EVAL Flash:%dK\\n\\r", *(__IO uint16_t *)(0x1FFFF7E0));
/* get chip serial number */
get_chip_serial_num();
/* printf CPU unique device id */
printf("\\n\\rGD32207i-EVAL The CPU Unique Device ID:[%X-%X-%X]\\n\\r", int_device_serial[2], int_device_serial[1], int_device_serial[0]);
printf("\\n\\rGD32207i-EVAL SPI Flash:GD25Q16 configured...\\n\\r");
/* get flash id */
flash_id = spi_flash_read_id();
printf("\\n\\rThe Flash_ID:0x%X\\n\\r\\n\\r", flash_id);
/* flash id is correct */
if(SFLASH_ID == flash_id)
{
printf("\\n\\rWrite to tx_buffer:\\n\\r\\n\\r");
/* printf tx_buffer value */
for(i = 0; i < BUFFER_SIZE; i++)
{
tx_buffer[i] = i;
printf("0x%02X ", tx_buffer[i]);
if(15 == i % 16)
{
printf("\\n\\r");
}
}
printf("\\n\\r\\n\\rRead from rx_buffer:\\n\\r\\n\\r");
/* erase the specified flash sector */
spi_flash_sector_erase(FLASH_WRITE_ADDRESS);
/* write tx_buffer data to the flash */
spi_flash_buffer_write(tx_buffer, FLASH_WRITE_ADDRESS, 256);
delay_ms(10);
/* read a block of data from the flash to rx_buffer */
spi_flash_buffer_read(rx_buffer, FLASH_READ_ADDRESS, 256);
/* printf rx_buffer value */
for(i = 0; i < BUFFER_SIZE; i ++)
{
printf("0x%02X ", rx_buffer[i]);
if(15 == i % 16)
{
printf("\\n\\r");
}
}
if(ERROR == memory_compare(tx_buffer, rx_buffer, 256)) {
printf("\\n\\rErr:Data Read and Write aren't Matching.\\n\\r");
is_successful = 1;
}
/* spi qspi flash test passed */
if(0 == is_successful)
{
printf("\\n\\rSPI-GD25Q16 Test Passed!\\n\\r");
}
}
else
{
/* spi flash read id fail */
printf("\\n\\rSPI Flash: Read ID Fail!\\n\\r");
}
while(1)
{
bsp_led_toggle(&bsp_led_dev0);
delay_ms(1000);
}
}
首先對SPI進(jìn)行初始化,然后就極性FLASH的讀取,完整代碼請參看源碼。
6 實(shí)驗(yàn)現(xiàn)象
在電腦端打開串口調(diào)試助手工具,設(shè)置參數(shù)為115200 8-N-1。下載完程序之后,在串口調(diào)試助手窗口可接收到信息。
-
mcu
+關(guān)注
關(guān)注
146文章
16922瀏覽量
349992 -
SPI
+關(guān)注
關(guān)注
17文章
1688瀏覽量
91234 -
開發(fā)板
+關(guān)注
關(guān)注
25文章
4913瀏覽量
97086 -
Cortex-M
+關(guān)注
關(guān)注
2文章
227瀏覽量
29710 -
GD32
+關(guān)注
關(guān)注
7文章
403瀏覽量
24119
發(fā)布評論請先 登錄
相關(guān)推薦
評論