一、SPI外設簡介
- STM32內部集成了硬件SPI收發電路,可以由硬件自動執行時鐘生成、數據收發等功能,減輕CPU的負擔
- 可配置8位/16位數據幀、高位先行/低位先行
- 時鐘頻率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
- 支持多主機模型、主或從操作
- 可精簡為半雙工/單工通信
- 支持DMA
- 兼容I2S協議
- STM32F103C8T6 硬件SPI資源:SPI1、SPI2
二、SPI框圖
1、2、3、4、5:主要是實現數據連續進入。
6、7、8、9、10:控制邏輯,寄存器位,控制什么部分,有什么效果等等
(3):移位寄存器,首先右邊的數據低位一位位的從MOSI移出去。MISO的數據一位位的移入到左邊的數據高位。顯然是一個低位先行的配置。由(5)來控制:0——高位先行;1——低位先行。
(1):MISO和MOSI交叉了,主要是進行主從模式引腳變換的,SPI作為主機時候,交叉就不用。作為從機時,就需要使用到交叉的這個路線(圖上MISO的箭頭反了)。
(2)、(4):分別為RDR和TDR。
三、傳輸模式
注:
連續傳輸和非連續傳輸的區別,主要體現在當TXE為1時,連續傳輸會把下一個數據放在TDR里面候著。而非連續傳輸時先等第一個時序結束,再把第二個放在DR里面。
四、代碼部分
注:SCK和MOSI是硬件外設控制的輸出信號,配置為復用推挽輸出
? ? ? ?MISO是硬件外設輸入信號,配置為上拉輸入
? ? ? ?CS(SS)是軟件控制的輸出信號,配置為通用推挽輸出
//寫DR數據寄存器
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
//讀DR數據寄存器
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
?MySPI.c
#include "stm32f10x.h" // Device header/*** 函 數:SPI寫SS引腳電平,SS仍由軟件模擬* 參 數:BitValue 協議層傳入的當前需要寫入SS的電平,范圍0~1* 返 回 值:無* 注意事項:此函數需要用戶實現內容,當BitValue為0時,需要置SS為低電平,當BitValue為1時,需要置SS為高電平*/
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根據BitValue,設置SS引腳的電平
}/*** 函 數:SPI初始化* 參 數:無* 返 回 值:無*/
void MySPI_Init(void)
{/*開啟時鐘*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //開啟GPIOA的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //開啟SPI1的時鐘/*GPIO初始化*/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); //將PA4引腳初始化為推挽輸出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); //將PA5和PA7引腳初始化為復用推挽輸出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); //將PA6引腳初始化為上拉輸入/*SPI初始化*/SPI_InitTypeDef SPI_InitStructure; //定義結構體變量SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //模式,選擇為SPI主模式SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //方向,選擇2線全雙工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; //SPI極性,選擇低極性SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //SPI相位,選擇第一個時鐘邊沿采樣,極性和相位決定選擇SPI模式0SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS,選擇由軟件控制SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC多項式,暫時用不到,給默認值7SPI_Init(SPI1, &SPI_InitStructure); //將結構體變量交給SPI_Init,配置SPI1/*SPI使能*/SPI_Cmd(SPI1, ENABLE); //使能SPI1,開始運行/*設置默認電平*/MySPI_W_SS(1); //SS默認高電平
}/*** 函 數:SPI起始* 參 數:無* 返 回 值:無*/
void MySPI_Start(void)
{MySPI_W_SS(0); //拉低SS,開始時序
}/*** 函 數:SPI終止* 參 數:無* 返 回 值:無*/
void MySPI_Stop(void)
{MySPI_W_SS(1); //拉高SS,終止時序
}/*** 函 數:SPI交換傳輸一個字節,使用SPI模式0* 參 數:ByteSend 要發送的一個字節* 返 回 值:接收的一個字節*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET); //等待發送數據寄存器空SPI_I2S_SendData(SPI1, ByteSend); //寫入數據到發送數據寄存器,開始產生時序while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET); //等待接收數據寄存器非空return SPI_I2S_ReceiveData(SPI1); //讀取接收到的數據并返回
}
W5Q64.c
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"/*** 函 數:W25Q64初始化* 參 數:無* 返 回 值:無*/
void W25Q64_Init(void)
{MySPI_Init(); //先初始化底層的SPI
}/*** 函 數:W25Q64讀取ID號* 參 數:MID 工廠ID,使用輸出參數的形式返回* 參 數:DID 設備ID,使用輸出參數的形式返回* 返 回 值:無*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{MySPI_Start(); //SPI起始MySPI_SwapByte(W25Q64_JEDEC_ID); //交換發送讀取ID的指令*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交換接收MID,通過輸出參數返回*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交換接收DID高8位*DID <<= 8; //高8位移到高位*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交換接收DID的低8位,通過輸出參數返回MySPI_Stop(); //SPI終止
}/*** 函 數:W25Q64寫使能* 參 數:無* 返 回 值:無*/
void W25Q64_WriteEnable(void)
{MySPI_Start(); //SPI起始MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交換發送寫使能的指令MySPI_Stop(); //SPI終止
}/*** 函 數:W25Q64等待忙* 參 數:無* 返 回 值:無*/
void W25Q64_WaitBusy(void)
{uint32_t Timeout;MySPI_Start(); //SPI起始MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //交換發送讀狀態寄存器1的指令Timeout = 100000; //給定超時計數時間while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //循環等待忙標志位{Timeout --; //等待時,計數值自減if (Timeout == 0) //自減到0后,等待超時{/*超時的錯誤處理代碼,可以添加到此處*/break; //跳出等待,不等了}}MySPI_Stop(); //SPI終止
}/*** 函 數:W25Q64頁編程* 參 數:Address 頁編程的起始地址,范圍:0x000000~0x7FFFFF* 參 數:DataArray 用于寫入數據的數組* 參 數:Count 要寫入數據的數量,范圍:0~256* 返 回 值:無* 注意事項:寫入的地址范圍不能跨頁*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{uint16_t i;W25Q64_WriteEnable(); //寫使能MySPI_Start(); //SPI起始MySPI_SwapByte(W25Q64_PAGE_PROGRAM); //交換發送頁編程的指令MySPI_SwapByte(Address >> 16); //交換發送地址23~16位MySPI_SwapByte(Address >> 8); //交換發送地址15~8位MySPI_SwapByte(Address); //交換發送地址7~0位for (i = 0; i < Count; i ++) //循環Count次{MySPI_SwapByte(DataArray[i]); //依次在起始地址后寫入數據}MySPI_Stop(); //SPI終止W25Q64_WaitBusy(); //等待忙
}/*** 函 數:W25Q64扇區擦除(4KB)* 參 數:Address 指定扇區的地址,范圍:0x000000~0x7FFFFF* 返 回 值:無*/
void W25Q64_SectorErase(uint32_t Address)
{W25Q64_WriteEnable(); //寫使能MySPI_Start(); //SPI起始MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //交換發送扇區擦除的指令MySPI_SwapByte(Address >> 16); //交換發送地址23~16位MySPI_SwapByte(Address >> 8); //交換發送地址15~8位MySPI_SwapByte(Address); //交換發送地址7~0位MySPI_Stop(); //SPI終止W25Q64_WaitBusy(); //等待忙
}/*** 函 數:W25Q64讀取數據* 參 數:Address 讀取數據的起始地址,范圍:0x000000~0x7FFFFF* 參 數:DataArray 用于接收讀取數據的數組,通過輸出參數返回* 參 數:Count 要讀取數據的數量,范圍:0~0x800000* 返 回 值:無*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{uint32_t i;MySPI_Start(); //SPI起始MySPI_SwapByte(W25Q64_READ_DATA); //交換發送讀取數據的指令MySPI_SwapByte(Address >> 16); //交換發送地址23~16位MySPI_SwapByte(Address >> 8); //交換發送地址15~8位MySPI_SwapByte(Address); //交換發送地址7~0位for (i = 0; i < Count; i ++) //循環Count次{DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //依次在起始地址后讀取數據}MySPI_Stop(); //SPI終止
}