一、SPI外設簡介
-
STM32內部集成了硬件SPI收發電路,可以由硬件自動執行時鐘生成、數據收發等功能,減輕CPU的負擔【硬件電路自動生成時序】
-
可配置
8位
/16位數據幀、高位先行
/低位先行 -
時鐘頻率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)【SPI1是APB2的外設,PCLK=72MHz;SPI2是APB1的外設,PCLK=36MHz】
-
支持多主機模型、主或從操作
-
可精簡為半雙工/單工通信【使用SPI就是為了發揮全雙工的優勢】
-
支持DMA
-
兼容I2S協議【音頻協議,將數字信號轉化為模擬信號】
-
STM32F103C8T6 硬件SPI資源:SPI1、SPI2
二、SPI框圖
- LSBFIRST:設置低位先行還是高位先行
- stm32的SPI外設也可以設置為從機模式,此時MOSI和MISO的線路就會交叉(如圖所示)
- 接收緩沖器(TDR)和發送緩沖區(RDR)占用同一個地址(DR)
- 移位寄存器和數據寄存器配合可以實現數據連續傳輸,要判斷狀態寄存器的TXE和RXNE
- SPI在發送的同時也接收數據,這點和半雙工不一樣,所以會有兩個緩沖區,移位寄存器可以共用【在外設電路設計上:USART是全雙工異步外設,所以移位寄存器和數據寄存器都不是共用的,I2C是半雙工同步外設,所以移位寄存器和數據寄存器是共用的】
- 發送緩沖區為空時,TXE置1,接收緩沖區非空時,RXNE置1
三、SPI基本結構
- SS引腳采用普通的GPIO口模擬即可
四、主模式全雙工連續傳輸
- 當數據從發送緩沖區移動到移位寄存器時,就會產生時序
- 當三個數據都發送完畢時,BSY標志才清空
- 數據從接收緩沖區讀出后要軟件置RXNE標志位為0
連續傳輸的硬件設計比較復雜,需要軟件配合
五、非連續傳輸【常用】
- 相比于連續傳輸,TXE為1(發送緩沖區為空)的時候不急著把數據寫入TDR,而是等到一個字節時序結束才將數據放到TDR
- 第一個字節時序結束也表示了接收完成,此時RXNE標志位為1,將數據讀出
交換一個字節的四步編程:
- 等待TXE為1
- 寫入發送的數據到TDR
- 等待RXNE為1
- 讀出RDR接收到的數據
缺點:傳輸頻率越高,字節與字節之間的時間間隙會越大,如圖所示
六、軟件/硬件波形對比
邊沿和電平期間,都可以作為數據變化的時刻
軟件波形一般在電平期間,硬件的波形一般會緊貼邊沿
七、硬件I2C讀寫MPU6050
電路設計
關鍵代碼
MySPI.c
#include "stm32f10x.h" // Device header//ss引腳采用軟件模擬即可
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}void MySPI_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//通用推挽輸出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//由外設控制,設置為復用推挽輸出GPIO_InitStructure.GPIO_Pin = 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);//完成SPI外設的配置,配置SPI_InitStructure結構體SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主機SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//雙線全雙工SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//8位數據幀SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//高位先行SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//128分頻SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//模式0SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//模式0SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC校驗SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);//使能外設MySPI_W_SS(1);
}void MySPI_Start(void)
{MySPI_W_SS(0);
}void MySPI_Stop(void)
{MySPI_W_SS(1);
}//基于spi外設交換一個字節的四個步驟:
/*
- 等待TXE為1
- 寫入發送的數據到TDR
- 等待RXNE為1
- 讀出RDR接收到的數據
*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);SPI_I2S_SendData(SPI1, ByteSend);//自動生成時序波形,寫入操作會自動將TXE清除while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);return SPI_I2S_ReceiveData(SPI1);//讀取操作會自動將RXNE清除
}
MySPI.h
#ifndef __MYSPI_H
#define __MYSPI_Hvoid 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時序初始化
}//根據指令集寫功能函數void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{MySPI_Start();MySPI_SwapByte(W25Q64_JEDEC_ID);//不同時序調用得到的返回值是不同的*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則表示不忙,程序返回,否則會阻塞
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();
}
//按頁寫入
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{uint16_t i;W25Q64_WriteEnable();//寫入操作前,必須先進行寫使能MySPI_Start();MySPI_SwapByte(W25Q64_PAGE_PROGRAM);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);//Count不能定義為uint8_t ,因為最大是255字節,而不是256字節for (i = 0; i < Count; i ++){MySPI_SwapByte(DataArray[i]);}MySPI_Stop();W25Q64_WaitBusy();
}
//扇區擦除
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_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);//置換有用數據}MySPI_Stop();
}
W25Q64.h
#ifndef __W25Q64_H
#define __W25Q64_Hvoid 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){}
}
參考視頻:江科大自化協