一、SPI簡介
SPI是串行外設接口(Serial Peripheral Interface)的縮寫,是美國摩托羅拉公司(Motorola)最先推出的一種同步串行傳輸規(guī)范,也是一種單片機外設芯片串行擴展接口,是一種高速、全雙工、同步通信總線,所以可以在同一時間發(fā)送和接收數(shù)據(jù),SPI沒有定義速度限制,通常能達到甚至超過10M/bps。
SPI有主、從兩種模式,通常由一個主模塊和一個或多個從模塊組成(SPI不支持多主機),主模塊選擇一個從模塊進行同步通信,從而完成數(shù)據(jù)的交換。提供時鐘的為主設備(Master),接收時鐘的設備為從設備(Slave),SPI接口的讀寫操作,都是由主設備發(fā)起,當存在多個從設備時,通過各自的片選信號進行管理。
SPI通信原理很簡單,需要至少4根線,單向傳輸時3根線,它們是MISO(主設備數(shù)據(jù)輸入)、MOSI(主設備數(shù)據(jù)輸出)、SCLK(時鐘)和CS/SS(片選):
MISO( Master Input Slave Output):主設備數(shù)據(jù)輸入,從設備數(shù)據(jù)輸出;
MOSI(Master Output Slave Input):主設備數(shù)據(jù)輸出,從設備數(shù)據(jù)輸入;
SCLK(Serial Clock):時鐘信號,由主設備產(chǎn)生;
CS/SS(Chip Select/Slave Select):從設備使能信號,由主設備控制,一主多從時,CS/SS是從芯片是否被主芯片選中的控制信號,只有片選信號為預先規(guī)定的使能信號時(高電位或低電位),主芯片對此從芯片的操作才有效。
1.1電路模式
采用一主多從的模式、同步,全雙工
所有SPI設備的SCK、MOSI、MISO分別連在一起
主機另外引出多條SS控制線,分別接到各從機的SS引腳
輸出引腳配置為推挽輸出,輸入引腳配置為浮空或上拉輸入
推挽輸出:高低電平都有很強的驅動能力,使得SPI引腳信號的下降沿和上升沿非常迅速
(IIC因為要實現(xiàn)半雙工,經(jīng)常切換輸出輸入,IIC又要實現(xiàn)多主機的時鐘同步和總線仲裁,若使用推挽輸出任意電源短路)
SPI的MISO可能有沖突,一位內(nèi)主機是輸入,三個從機都是輸出,若三個從機始終是推挽輸出,勢必會導致沖突。
故SPI有個規(guī)定:
當從機的SS引腳為高電平時,即從機未被選中,其MISO引腳必須切換成高阻態(tài),高阻態(tài)相當于引腳斷開,不輸出任何電平,這樣可以防止一條線有多個輸出,導致電平?jīng)_突問題
SS為低電平時,MISO才允許變?yōu)橥仆燧敵觯ㄇ袚Q在從機中,不需要關注)
1.2通信原理
SPI主設備和從設備都有一個串行移位寄存器,主設備通過向它的SPI串行寄存器寫入一個字節(jié)來發(fā)起一次傳輸。
SPI數(shù)據(jù)通信的流程可以分為以下幾步:
1、主設備發(fā)起信號,將CS/SS拉低,啟動通信。
2、主設備通過發(fā)送時鐘信號,來告訴從設備進行寫數(shù)據(jù)或者讀數(shù)據(jù)操作(采集時機可能是時鐘信號的上升沿(從低到高)或下降沿(從高到低),因為SPI有四種模式,后面會講到),它將立即讀取數(shù)據(jù)線上的信號,這樣就得到了一位數(shù)據(jù)(1bit)。
3、主機(Master)將要發(fā)送的數(shù)據(jù)寫到發(fā)送數(shù)據(jù)緩存區(qū)(Menory),緩存區(qū)經(jīng)過移位寄存器(緩存長度不一定,看單片機配置),串行移位寄存器通過MOSI信號線將字節(jié)一位一位的移出去傳送給從機,同時MISO接口接收到的數(shù)據(jù)經(jīng)過移位寄存器一位一位的移到接收緩存區(qū)。
4、從機(Slave)也將自己的串行移位寄存器(緩存長度不一定,看單片機配置)中的內(nèi)容通過MISO信號線返回給主機。同時通過MOSI信號線接收主機發(fā)送的數(shù)據(jù),這樣,兩個移位寄存器中的內(nèi)容就被交換。
1.3SPI時序基本單元
1.3.1起始和終止
起始條件:SS從高電平切換到低電平
終止條件:SS從低電平切換到高電平
1.3.2交換字節(jié)
Mode0:CPOL=0,CPHA =0:當空閑態(tài)時,SCK處于低電平,數(shù)據(jù)采樣是在第1個邊沿,也就是SCK由低電平到高電平的跳變,所以數(shù)據(jù)采樣是在上升沿(準備數(shù)據(jù)),(發(fā)送數(shù)據(jù))數(shù)據(jù)發(fā)送是在下降沿。
Mode1:CPOL=0,CPHA=1:當空閑態(tài)時,SCK處于低電平,數(shù)據(jù)發(fā)送是在第2個邊沿,也就是SCK由低電平到高電平的跳變,所以數(shù)據(jù)采樣是在下降沿,數(shù)據(jù)發(fā)送是在上升沿。
Mode2:CPOL=1,CPHA=0:當空閑態(tài)時,SCK處于高電平,數(shù)據(jù)采集是在第1個邊沿,也就是SCK由高電平到低電平的跳變,所以數(shù)據(jù)采集是在下降沿,數(shù)據(jù)發(fā)送是在上升沿。
Mode3:CPOL=1,CPHA=1:當空閑態(tài)時,SCK處于高電平,數(shù)據(jù)發(fā)送是在第2個邊沿,也就是SCK由高電平到低電平的跳變,所以數(shù)據(jù)采集是在上升沿,數(shù)據(jù)發(fā)送是在下降沿。
二、W25Q64
2.1W25Q64簡介
W25Qxx系列是一種低成本、小型化、使用簡單的非易失性存儲器,常應用于數(shù)據(jù)存儲、字庫存儲、固件程序存儲等場景
存儲介質:Nor Flash(閃存)
時鐘頻率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
存儲容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
2.2W25Q64硬件電路
2.3W25Q64框圖
2.4Flash操作注意事項
寫入操作時:
寫入操作前,必須先進行寫使能
每個數(shù)據(jù)位只能由1改寫為0,不能由0改寫為1
寫入數(shù)據(jù)前必須先擦除,擦除后,所有數(shù)據(jù)位變?yōu)?
擦除必須按最小擦除單元進行
連續(xù)寫入多字節(jié)時,最多寫入一頁的數(shù)據(jù),超過頁尾位置的數(shù)據(jù),會回到頁首覆蓋寫入
寫入操作結束后,芯片進入忙狀態(tài),不響應新的讀寫操作
讀取操作時:
直接調用讀取時序,無需使能,無需額外操作,沒有頁的限制,讀取操作結束后不會進入忙狀態(tài),但不能在忙狀態(tài)時讀取
三、軟件SPI讀寫W25Q64
3.1接線圖
3.2程序代碼
MySPI.c
#include "stm32f10x.h" // Device header
void MySPI_W_CS(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);
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}
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);
MySPI_W_CS(1);
MySPI_W_SCK(0);
}
void MySPI_Start(void)
{
MySPI_W_CS(0);
}
void MySPI_Stop(void)
{
MySPI_W_CS(1);
}
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++)
{
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();
}
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID);
*MID = MySPI_SwapByte(0xFF);
*DID = MySPI_SwapByte(0xFF);
*DID <<= 8;
*DID |= MySPI_SwapByte(0xFF);
MySPI_Stop();
}
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
Timeout = 100000;
while ((MySPI_SwapByte(0xFF) & 0x01) == 0x01)
{
Timeout --;
if (Timeout == 0)
{
break;
}
}
MySPI_Stop();
}
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
W25Q64_WaitBusy();
}
void W25Q64_ChipErase(void)
{
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_CHIP_ERASE);
MySPI_Stop();
W25Q64_WaitBusy();
}
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint8_t i;
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i ++)
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop();
W25Q64_WaitBusy();
}
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint8_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(0xFF);
}
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_SectorErase(uint32_t Address);
void W25Q64_ChipErase(void);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint32_t Count);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);
#endif
W25Q64_Ins.h文章來源:http://www.zghlxwxcb.cn/news/detail-597341.html
#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
#endif
main.c文章來源地址http://www.zghlxwxcb.cn/news/detail-597341.html
#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)
{
}
}
到了這里,關于STM-32:SPI通信協(xié)議/W25Q64簡介—軟件SPI讀寫W25Q64的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!