一、SPI通信
-
SPI(Serial Peripheral Interface)是由Motorola公司開(kāi)發(fā)的一種通用數(shù)據(jù)總線
-
四根通信線:
- SCK(Serial Clock)【CLK或SCL或CK】、
- MOSI(Master Output Slave Input)【DO(Data Output)】、
- MISO(Master Input Slave Output)【DI(Data Input)】、
- SS(Slave Select)【CS或NSS】
-
同步,全雙工
-
支持總線掛載多設(shè)備(一主多從)
-
SS(Slave Select)從機(jī)選擇線(可以不止一條),專門用來(lái)指定通信的從機(jī)地址,相比于I2C在起始條件后的尋址操作方便
-
沒(méi)有應(yīng)答機(jī)制
-
優(yōu)點(diǎn):最大傳輸速度取決于芯片廠商設(shè)計(jì)需求,比I2C最大400KHZ快
-
缺點(diǎn):SPI硬件開(kāi)銷較大,通信線個(gè)數(shù)較多
-
與I2C協(xié)議相比,I2C協(xié)議由于上拉電阻和開(kāi)漏輸出模式,導(dǎo)致其輸出高電平的能力弱,直觀的:上升沿的耗時(shí)長(zhǎng),限制了I2C的最大通信速度
二、硬件電路
-
所有SPI設(shè)備的SCK、MOSI、MISO分別連在一起
-
主機(jī)另外引出多條SS控制線,分別接到各從機(jī)的SS引腳【低電平有效】
-
輸出引腳配置為推挽輸出(高低電平驅(qū)動(dòng)能力好),輸入引腳配置為浮空或上拉輸入
-
由于SPI通信線都是單端信號(hào), 需要共地
-
對(duì)比:I2C的輸出引腳只能配置為復(fù)用開(kāi)漏輸出,而不能配置為推挽輸出,因?yàn)槭前腚p工要切換輸入輸出,且實(shí)現(xiàn)多主機(jī)的時(shí)鐘同步和總線仲裁,配置為推挽輸出容易電源短路。而SPI是只有一個(gè)主機(jī),無(wú)總線總裁,且是全雙工,不需要切換線的功能
-
當(dāng)從機(jī)的SS引腳為高電平時(shí),MISO必須切換為高阻態(tài),這樣引腳就不會(huì)輸出任何電平,此時(shí)主機(jī)的MISO端口就不會(huì)接收到三個(gè)從機(jī)發(fā)送過(guò)來(lái)的信號(hào),導(dǎo)致沖突
三、移位示意圖
高位先行,和I2C一樣
SCK低-高電平:主機(jī)和從機(jī)都會(huì):移位輸出
SCK高-低電平:主機(jī)和從機(jī)都會(huì):采樣輸入
SPI的數(shù)據(jù)收發(fā)都是基于字節(jié)交換
這個(gè)基本單元進(jìn)行的
如果只要讀取從機(jī)數(shù)據(jù)時(shí):主機(jī)可以發(fā)送0X00或0XFF去和從機(jī)換數(shù)據(jù)
波特率發(fā)生器:即移位寄存器里面的時(shí)鐘,是由主機(jī)提供的,通過(guò)SCK外接到從機(jī),保持兩個(gè)設(shè)備的同步
中間還少畫了輸出數(shù)據(jù)寄存器,保存一位數(shù)據(jù),作為SCK高低電平轉(zhuǎn)換時(shí)候存儲(chǔ)數(shù)據(jù)的中轉(zhuǎn)站
四、SPI時(shí)序基本單元
起始條件:SS從高電平切換到低電平
終止條件:SS從低電平切換到高電平
因?yàn)镾S是低電平,表示選中一個(gè)從機(jī),高電平表示結(jié)束從機(jī)的選中狀態(tài),通信結(jié)束
交換一個(gè)字節(jié)(模式0)【用的多】
可以配置兩個(gè)位,時(shí)鐘極性和時(shí)鐘相位
CPOL=0:空閑狀態(tài)時(shí),SCK為低電平
CPHA=0:SCK第一個(gè)邊沿移入數(shù)據(jù),第二個(gè)邊沿移出數(shù)據(jù)
SCK第一個(gè)邊沿之前就需要移出數(shù)據(jù),可以理解為在第零個(gè)邊沿移出數(shù)據(jù),那么在什么時(shí)候移出數(shù)據(jù)呢,此時(shí)不是用SCK線作為參考,而是使用SS線,在SS變?yōu)榈碗娖降臅r(shí)候就開(kāi)始移出數(shù)據(jù)
交換一個(gè)字節(jié)(模式1)
CPOL=0:空閑狀態(tài)時(shí),SCK為低電平
CPHA=1:SCK第一個(gè)邊沿移出數(shù)據(jù),第二個(gè)邊沿移入數(shù)據(jù)
ps:MISO一條線表示高阻態(tài)
MOSI和MISO出現(xiàn)X表示數(shù)據(jù)的移動(dòng)
交換一個(gè)字節(jié)(模式2)
CPOL=1:空閑狀態(tài)時(shí),SCK為高電平
CPHA=0:SCK第一個(gè)邊沿移入數(shù)據(jù),第二個(gè)邊沿移出數(shù)據(jù)
交換一個(gè)字節(jié)(模式3)
CPOL=1:空閑狀態(tài)時(shí),SCK為高電平
CPHA=1:SCK第一個(gè)邊沿移出數(shù)據(jù),第二個(gè)邊沿移入數(shù)據(jù)
五、SPI時(shí)序
通常采用:指令碼+讀寫數(shù)據(jù)的模型,起始條件后第一個(gè)字節(jié)為指令碼,在從機(jī)中會(huì)有指令集,指導(dǎo)從機(jī)完成相應(yīng)功能,指令集也定義了該指令需要攜帶什么數(shù)據(jù)
舉例W25Q64中指令的使用:
發(fā)送指令:寫使能
向SS指定的設(shè)備,發(fā)送指令(0x06),主機(jī)用0x06換來(lái)從機(jī)的0xFF
指定地址寫
向SS指定的設(shè)備,發(fā)送寫指令(0x02),隨后在指定地址(Address[23:0])下,寫入指定數(shù)據(jù)(Data)
- 由于從機(jī)W25Q64芯片容量是8M,單個(gè)字節(jié)不能表示所有地址,所以內(nèi)存地址為24位,所以起始條件后要發(fā)送三個(gè)字節(jié)指定地址,先發(fā)送的是高位地址,后發(fā)送的則是低位地址【這是由指令規(guī)定好的】
- 可以完成連續(xù)寫入,因?yàn)镾PI也有地址指針,會(huì)指向下一個(gè)字節(jié)的位置
指定地址讀
向SS指定的設(shè)備,發(fā)送讀指令(0x03),隨后在指定地址(Address[23:0])下,讀取從機(jī)數(shù)據(jù)(Data)
六、W25Q64簡(jiǎn)介
- W25Qxx系列是一種低成本、小型化、使用簡(jiǎn)單的非易失性存儲(chǔ)器,常應(yīng)用于數(shù)據(jù)存儲(chǔ)、字庫(kù)存儲(chǔ)(漢字字庫(kù)的點(diǎn)陣數(shù)據(jù))、固件程序存儲(chǔ)(XIP,就地執(zhí)行)等場(chǎng)景
- 存儲(chǔ)介質(zhì):Nor Flash(閃存)
- 時(shí)鐘頻率:80MHz / 160MHz (Dual SPI)/ 320MHz (Quad SPI)
- 后面兩個(gè)頻率分別表示:一個(gè)時(shí)鐘發(fā)2位 和 一個(gè)時(shí)鐘發(fā)4位
- WP引腳和HOLD引腳也可以用來(lái)充當(dāng)數(shù)據(jù)傳輸引腳
- 存儲(chǔ)容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByteW25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte【24位,最大尋址是16MB】
W25Q256: 256Mbit / 32MByte【可以選4字節(jié)地址模式】
習(xí)慣使用字節(jié)作為基本單元,所以W25Q64的64表示的是64M(位),即8M字節(jié)
七、硬件電路
寫保護(hù):配置寄存器配置,實(shí)現(xiàn)對(duì)芯片的寫操作失敗
數(shù)據(jù)保持:當(dāng)需要使用SPI外設(shè)完成其他任務(wù)時(shí),ss置高電平,此時(shí)會(huì)丟失時(shí)序,通過(guò)HOLD引腳,替SPI外設(shè)控制該從機(jī)的ss為低電平,此時(shí)時(shí)序就不會(huì)丟失【相當(dāng)于spi總線進(jìn)入中斷,保存了上下文】
八、W25Q64框圖
關(guān)于地址:
- 8MB的空間,最后一個(gè)字節(jié)是7F FF FF h
- 8MB的空間以64KB為一個(gè)基本單元?jiǎng)澐侄鄠€(gè)塊,一共128塊
- 一塊里以4KB為一個(gè)扇區(qū),則有16份,也就是說(shuō)每一塊分為16個(gè)扇區(qū)
- 頁(yè):可以當(dāng)做是扇區(qū)的進(jìn)一步劃分,也可以當(dāng)做是對(duì)整個(gè)存儲(chǔ)空間劃分。
- 一頁(yè)的大小是256(B)字節(jié),一個(gè)扇區(qū)是4KB(4*1024(B)字節(jié)),可以分16頁(yè)
Control Logic:SPI控制邏輯
,負(fù)責(zé)將外部主控芯片傳入的指令和數(shù)據(jù)進(jìn)行處理
狀態(tài)寄存器負(fù)責(zé)記錄:寫保護(hù),數(shù)據(jù)保持,忙狀態(tài)等
- 在W25Q64中,狀態(tài)寄存器有兩個(gè),其中狀態(tài)寄存器1有:busy位和寫使能鎖存位
- 寫使能鎖存位會(huì)在執(zhí)行完寫使能后置1,擦除操作會(huì)導(dǎo)致寫失能,即一個(gè)寫使能只能保證后續(xù)的一條指令執(zhí)行
寫控制邏輯:與外部的wp引腳相連,實(shí)現(xiàn)寫保護(hù)(連接狀態(tài)寄存器)
高電壓發(fā)生器:flash掉電不丟失的存儲(chǔ)器一般都有高壓源,使得數(shù)據(jù)保持在某個(gè)狀態(tài)
頁(yè)地址鎖存/計(jì)數(shù)器:保存前兩個(gè)字節(jié)的地址,通過(guò)寫保護(hù)和行解碼找到對(duì)應(yīng)的頁(yè)。計(jì)數(shù)器負(fù)責(zé)地址加一操作
字節(jié)地址鎖存/計(jì)數(shù)器:保存最后一個(gè)字節(jié)的地址,通過(guò)列解碼和256字節(jié)緩存找到對(duì)應(yīng)的地址。計(jì)數(shù)器負(fù)責(zé)地址加一操作
256字節(jié)緩存:RAM存儲(chǔ)器,因?yàn)镾PI高頻,而存儲(chǔ)空間操作是低頻的,需要緩沖。也規(guī)定了連續(xù)寫入的數(shù)據(jù)量不能超過(guò)256字節(jié),數(shù)據(jù)從緩沖區(qū)寫入flash時(shí)會(huì)將狀態(tài)設(shè)置為忙(有連接狀態(tài)寄存器)
芯片ID:
指令集:
九、Flash操作注意事項(xiàng)
寫入操作時(shí):
寫入操作前,必須先進(jìn)行寫使能
-
每個(gè)數(shù)據(jù)位只能由1改寫為0,不能由0改寫為1
,所以要先執(zhí)行擦除指令 寫入數(shù)據(jù)前必須先擦除,擦除后,所有數(shù)據(jù)位變?yōu)?
- 擦除必須按最小擦除單元進(jìn)行【最小的擦除單元是一個(gè)扇區(qū)】
- 連續(xù)寫入多字節(jié)時(shí),最多寫入一頁(yè)的數(shù)據(jù)【即256字節(jié),因?yàn)榫彌_區(qū)的緣故】,超過(guò)頁(yè)尾位置的數(shù)據(jù),會(huì)回到頁(yè)首覆蓋寫入
- 寫入操作結(jié)束后,芯片進(jìn)入忙狀態(tài),不響應(yīng)新的讀寫操作【ps:擦除操作也會(huì)進(jìn)入忙狀態(tài)】
讀取操作時(shí):
- 直接調(diào)用讀取時(shí)序,無(wú)需使能,無(wú)需額外操作,沒(méi)有頁(yè)的限制,讀取操作結(jié)束后不會(huì)進(jìn)入忙狀態(tài),但不能在忙狀態(tài)時(shí)讀取
十、軟件SPI讀寫W25Q64
電路設(shè)計(jì)
關(guān)鍵代碼
MySPI.c
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//輸出為推挽輸出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//輸入為上拉輸入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化后將片選信號(hào)置高電平,設(shè)置SPI為模式0
MySPI_W_SS(1);
MySPI_W_SCK(0);
}
//開(kāi)始的時(shí)序
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
//結(jié)束的時(shí)序
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
//模式0,置換一個(gè)字節(jié)的時(shí)序
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++)
{
//使用其他模式就是按不同的順序執(zhí)行下列代碼,以及修改SCK的初始值
MySPI_W_MOSI(ByteSend & (0x80 >> i));
MySPI_W_SCK(1);
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);
}
return ByteReceive;
}
MySPI.h
#ifndef __MYSPI_H
#define __MYSPI_H
void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);
#endif
W25Q64.c
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
void W25Q64_Init(void)
{
MySPI_Init();//底層spi時(shí)序初始化
}
//根據(jù)指令集寫功能函數(shù)
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID);
//不同時(shí)序調(diào)用得到的返回值是不同的
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
MySPI_Stop();
}
//寫使能
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
//直接讀取寄存器的busy位,如果為0則表示不忙,程序返回,否則會(huì)阻塞
void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
Timeout = 100000;
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
{
Timeout --;
if (Timeout == 0)
{
break;
}
}
MySPI_Stop();
}
//按頁(yè)寫入
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
uint16_t i;
W25Q64_WriteEnable();//寫入操作前,必須先進(jìn)行寫使能
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
//Count不能定義為uint8_t ,因?yàn)樽畲笫?55字節(jié),而不是256字節(jié)
for (i = 0; i < Count; i ++)
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop();
W25Q64_WaitBusy();
}
//扇區(qū)擦除
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();//寫入操作前,必須先進(jìn)行寫使能
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
W25Q64_WaitBusy();
}
//按頁(yè)讀取
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i ++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//置換有用數(shù)據(jù)
}
MySPI_Stop();
}
W25Q64.h
#ifndef __W25Q64_H
#define __W25Q64_H
void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);
#endif
W25Q64_Ins.h
#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H
#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3
#define W25Q64_DUMMY_BYTE 0xFF
#endif
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
uint8_t MID;
uint16_t DID;
uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
uint8_t ArrayRead[4];
int main(void)
{
OLED_Init();
W25Q64_Init();
OLED_ShowString(1, 1, "MID: DID:");
OLED_ShowString(2, 1, "W:");
OLED_ShowString(3, 1, "R:");
W25Q64_ReadID(&MID, &DID);
OLED_ShowHexNum(1, 5, MID, 2);
OLED_ShowHexNum(1, 12, DID, 4);
W25Q64_SectorErase(0x000000);
W25Q64_PageProgram(0x000000, ArrayWrite, 4);
W25Q64_ReadData(0x000000, ArrayRead, 4);
OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);
OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
OLED_ShowHexNum(3, 3, ArrayRead[0], 2);
OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
while (1)
{
}
}
十一、硬件SPI讀寫W25Q64
STM32——SPI外設(shè)總線文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-666354.html
參考視頻:江科大自化協(xié)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-666354.html
到了這里,關(guān)于STM32——SPI通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!