目錄
一、SPI是什么
二、SPI物理架構(gòu)
三、SPI工作原理
四、SPI工作模式
五、SPI相關(guān)寄存器介紹
六、SPI用到的結(jié)構(gòu)體與函數(shù)
1.結(jié)構(gòu)體
2.函數(shù)
七、W25Q128芯片
1.W25Q128介紹
2.W25Q128存儲(chǔ)架構(gòu)
3.W25Q128常用指令
4.W25Q128狀態(tài)寄存器
5.W25Q128常見(jiàn)操作流程
八、實(shí)驗(yàn)(使用SPI通訊讀寫(xiě)W25Q128模塊)
1.接線
2.配置
3.代碼
1.main.c文件
2.w25q128.c文件(向工程添加w25q128.c文件)
3.w25q128.h文件(向工程添加w25q128.h文件)
4.spi.c文件編寫(xiě)
5.spi.h文件編寫(xiě)
九、STM32工程添加.c和.h文件
一、SPI是什么
????????SPI是串行外設(shè)接口(Serial Peripheral Interface)的縮寫(xiě),是一種高速的,全雙工,同步的通信總線,并且在芯片的管腳上只占用四根線,節(jié)約了芯片的管腳,同時(shí)為PCB的布局上節(jié)省空間,提供方便,正是出于這種簡(jiǎn)單易用的特性,越來(lái)越多的芯片集成了這種通信協(xié)議,比如 AT91RM9200 。
二、SPI物理架構(gòu)
????????SPI總線包含四條總線:分別為MOSI、MISO、SCK、NSS(CS)。
(1)MISO:主設(shè)備輸入/從設(shè)備輸出引腳。該引腳在從模式下發(fā)送數(shù)據(jù),在主模式下接收數(shù)據(jù)。
(2)MOSI:主設(shè)備輸出/從設(shè)備輸入引腳。該引腳在主模式下發(fā)送數(shù)據(jù),在從模式下接收數(shù)據(jù)。
(3)SCK:時(shí)鐘信號(hào),可以使主從設(shè)備同步輸入輸出。
(4)NSS(CS):?由主設(shè)備控制,用來(lái)選擇指定的從設(shè)備進(jìn)行通信。(當(dāng)主設(shè)備想要讀/寫(xiě)從設(shè)備時(shí),首先拉低從設(shè)備對(duì)應(yīng)的NSS線)。
三、SPI工作原理
????????1.SPI主從模式
?????????SPI分為主、從兩種模式,一個(gè)SPI通訊系統(tǒng)需要包含一個(gè)(且只能是一個(gè))主設(shè)備,一個(gè)或多個(gè)從設(shè)備。提供時(shí)鐘的為主設(shè)備(Master),接收時(shí)鐘的設(shè)備為從設(shè)備(Slave),SPI接口的讀寫(xiě)操作,都是由主設(shè)備發(fā)起。當(dāng)存在多個(gè)從設(shè)備時(shí),主設(shè)備通過(guò)從設(shè)備各自的片選信號(hào)(NSS)來(lái)選擇從設(shè)備。
????????2.SPI主、從設(shè)備通訊接線
????????????????一個(gè)主設(shè)備和一個(gè)從設(shè)備
? ??? ???????????一個(gè)主設(shè)備和多個(gè)從設(shè)備????????
? ? ? ? 3.SPI數(shù)據(jù)傳輸
????????SPI主設(shè)備和從設(shè)備都有一個(gè)移位寄存器,主機(jī)可以通過(guò)向它的移位寄存器寫(xiě)入數(shù)據(jù)來(lái)發(fā)起一次SPI通訊,主設(shè)備的7移到從設(shè)備的0上,而從設(shè)備的7移到主設(shè)備的0上。
1.主設(shè)備拉低對(duì)應(yīng)從設(shè)備的NSS信號(hào)線。(選擇從設(shè)備進(jìn)行通信)
2.主設(shè)備發(fā)送時(shí)鐘信號(hào),從設(shè)備接收時(shí)鐘信號(hào)。(告訴從設(shè)備開(kāi)始進(jìn)行SPI通訊)
3.數(shù)據(jù)交換
????????主設(shè)備(Master)將要發(fā)送的數(shù)據(jù)傳輸?shù)桨l(fā)送緩存區(qū)(Menory),當(dāng)從設(shè)備收到主設(shè)備發(fā)送的時(shí)鐘信號(hào),并且在MOSI引腳上出現(xiàn)第一個(gè)數(shù)據(jù)位時(shí),發(fā)送過(guò)程開(kāi)始。余下的位被裝進(jìn)移位寄存器,通過(guò)MOSI信號(hào)線將字節(jié)一位一位的發(fā)送給從設(shè)備。同時(shí)主設(shè)備通過(guò)MISO引腳將數(shù)據(jù)一位一位的接收到移位寄存器,當(dāng)數(shù)據(jù)接收完成時(shí),將數(shù)據(jù)傳輸?shù)浇邮站彌_區(qū)。
????????從設(shè)備同理,將自己發(fā)送緩沖區(qū)的數(shù)據(jù)通過(guò)移位寄存器和MISO一位一位發(fā)送給主設(shè)備,同時(shí)通過(guò)MOSI引腳將數(shù)據(jù)一位一位的接收到移位寄存器,當(dāng)數(shù)據(jù)接收完成時(shí),將數(shù)據(jù)傳輸?shù)浇邮站彌_區(qū)?
????????SPI只有主模式和從模式之分,沒(méi)有讀和寫(xiě)的說(shuō)法,數(shù)據(jù)的寫(xiě)操作和讀操作是同步完成的。
? ? ? ? ? ? ? ? ? ? ? i.如果只進(jìn)行寫(xiě)操作,主機(jī)只需忽略接收到的字節(jié)。? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ii.如果只進(jìn)行讀操作,只需發(fā)送一個(gè)空字節(jié)來(lái)獲取SPI通訊的一個(gè)字節(jié)。
四、SPI工作模式
1.時(shí)鐘極性(CPOL)
????????控制在沒(méi)有數(shù)據(jù)傳輸時(shí)時(shí)鐘線的空閑狀態(tài)電平。? ? ? ?
-
0:SCK在空閑狀態(tài)保持低電平。
-
1:SCK在空閑狀態(tài)保持高電平。
2.時(shí)鐘相位(CPHA)
????????時(shí)鐘線在第幾個(gè)時(shí)鐘邊沿采樣數(shù)據(jù)。
-
0:SCK的第一個(gè)(奇數(shù))邊沿進(jìn)行數(shù)據(jù)位采樣,數(shù)據(jù)在第一個(gè)時(shí)鐘邊沿被鎖存。
-
1:SCK的第二個(gè)(偶數(shù))邊沿進(jìn)行數(shù)據(jù)位采樣,數(shù)據(jù)在第二個(gè)時(shí)鐘邊沿被鎖存。
3.SPI模式時(shí)序圖
? ? ? ? 模式0(常用)(CPOL = 0,CPHA = 0)
? ? ? ? ? ? ?空閑時(shí)SCK時(shí)鐘為低電平,采樣時(shí)刻為第一個(gè)邊沿即上升沿。如圖所示,黃線進(jìn)行采樣
? ? ? ? ?模式1(CPOL = 0,CPHA = 1)
? ? ? ? ? ? ?空閑時(shí)SCK時(shí)鐘為低電平,采樣時(shí)刻為第二個(gè)邊沿即下降沿。如圖所示,黃線進(jìn)行采樣。
? ? ? ? ?模式2(CPOL = 1,CPHA = 0)
? ? ? ? ? ? ?空閑時(shí)SCK時(shí)鐘為高電平,采樣時(shí)刻為第一個(gè)邊沿即下降沿。如圖所示,黃線進(jìn)行采樣。
????????模式3(常用)(CPOL = 1,CPHA = 1)
? ? ? ? ? ??空閑時(shí)SCK時(shí)鐘為高電平,采樣時(shí)刻為第二個(gè)邊沿即上升沿。如圖所示,黃線進(jìn)行采樣。
五、SPI相關(guān)寄存器介紹
六、SPI用到的結(jié)構(gòu)體與函數(shù)
1.結(jié)構(gòu)體
(句柄結(jié)構(gòu)體)SPI_HandleTypeDef
typedef struct __SPI_HandleTypeDef
{
SPI_TypeDef *Instance; /* SPIx */
SPI_InitTypeDef Init; /* SPI初始化結(jié)構(gòu)體:通信參數(shù) */
} SPI_HandleTypeDef;
(初始化結(jié)構(gòu)體)SPI_InitTypeDefSPI
typedef struct
{
uint32_t Mode; /* SPI模式(主機(jī)模式。從機(jī)模式) */
uint32_t Direction; /* 工作方式(全雙工方式、半雙工、只讀、只寫(xiě)) */
uint32_t DataSize; /* 數(shù)據(jù)格式(8bit、16bit) */
uint32_t CLKPolarity; /* 時(shí)鐘極性(CPOL) */
uint32_t CLKPhase; /* 時(shí)鐘相位(CPHA) */
uint32_t NSS; /* SS控制方式(軟件) */
uint32_t BaudRatePrescaler; /* SPI波特率預(yù)分頻值 */
uint32_t FirstBit; /* 數(shù)據(jù)傳輸順序(MSB、LSB) */
uint32_t TIMode; /* 數(shù)據(jù)幀格式(Motorola、TI)*/
uint32_t CRCCalculation; /* 設(shè)置硬件CRC檢驗(yàn) */
uint32_t CRCPolynomial; /* 設(shè)置CRC檢驗(yàn)多項(xiàng)式 */
} SPI_InitTypeDef;
2.函數(shù)
__HAL_RCC_SPI1_CLK_ENABLE()
使能SPI時(shí)鐘。(使用STM32CubeMX會(huì)自動(dòng)配置)
HAL_SPI_Init()
初始化SPI。(使用STM32CubeMX會(huì)自動(dòng)配置)
HAL_SPI_MspInit()
初始化SPI相關(guān)引腳。(使用STM32CubeMX會(huì)自動(dòng)配置)
HAL_SPI_Transmit()? (SPI發(fā)送數(shù)據(jù))
原型:
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)參數(shù):
SPI_HandleTypeDef *hspi:SPI句柄
uint8_t *pData:發(fā)送數(shù)據(jù)的存儲(chǔ)地址
uint16_t Size:發(fā)送的數(shù)據(jù)量大小
uint32_t Timeout:超時(shí)時(shí)間實(shí)例:
uint8_t uint8_t data = 56;HAL_SPI_TransmitReceive(&hspi1, &data, 1, 1000);
HAL_SPI_Receive()? (SPI接收數(shù)據(jù))
原型:
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)參數(shù):
SPI_HandleTypeDef *hspi:SPI句柄
uint8_t *pData:接收數(shù)據(jù)的存儲(chǔ)地址
uint16_t Size:接收的數(shù)據(jù)量大小
uint32_t Timeout:超時(shí)時(shí)間實(shí)例:
uint8_t uint8_t data;HAL_SPI_TransmitReceive(&hspi1, &data, 1, 1000);
HAL_SPI_TransmitReceive()? (SPI發(fā)送接收數(shù)據(jù))
原型:
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,uint32_t Timeout)參數(shù):
SPI_HandleTypeDef *hspi:SPI句柄
uint8_t *pTxData:發(fā)送數(shù)據(jù)的存儲(chǔ)地址
uint8_t *pRxData:接收數(shù)據(jù)的存儲(chǔ)地址
uint16_t Size:發(fā)送和接收的數(shù)據(jù)量大小
uint32_t Timeout:超時(shí)時(shí)間實(shí)例:
uint8_t spi1_read_write_byte(uint8_t data)
{
uint8_t rec_data = 0;HAL_SPI_TransmitReceive(&hspi1, &data, &rec_data, 1, 1000);
return rec_data;
}
__HAL_SPI_ENABLE()? ?(使能SPI外設(shè))
__HAL_SPI_DISABLE()? ? (失能SPI外設(shè))
七、W25Q128芯片
1.W25Q128介紹
-
W25Q128是華邦公司推出的一款SPI接口的NOR FIash芯片,其存儲(chǔ)空間為128 Mbit,相當(dāng)于16M字節(jié)。
-
Flash 是常用的用于儲(chǔ)存數(shù)據(jù)的半導(dǎo)體器件,它具有容量大,可重復(fù)擦寫(xiě)、按”扇區(qū)/塊”擦除、掉電后數(shù)據(jù)可繼續(xù)保存的特性。
-
Flash 是有一個(gè)物理特性:只能寫(xiě)0,不能寫(xiě)1,靠擦除來(lái)寫(xiě)1。
-
支持SPI模式1。
-
數(shù)據(jù)格式:8bit,MSB。
2.W25Q128存儲(chǔ)架構(gòu)
-
一個(gè)W25Q128 = 256個(gè)塊 = 256 * 16個(gè)扇區(qū) = 256 * 16 *16個(gè)頁(yè) = 256 * 16 * 16 * 256個(gè)字節(jié),即16777216字節(jié),約16M字節(jié),即尋址范圍為0x00 ~ 0xFFFFFF。
-
16777216 -1 = 0xFFFFFF。
-
對(duì)Flash擦除時(shí)一般按扇區(qū)(4K = 4096字節(jié))來(lái)進(jìn)行擦除。
3.W25Q128常用指令
????????W25Q128 全部指令非常多,但常用的如下幾個(gè)指令:
1.寫(xiě)使能(0x06)
-
寫(xiě)使能指令將狀態(tài)寄存器中的WEL位設(shè)置為1。
-
必須在每個(gè)頁(yè)寫(xiě)、扇區(qū)擦除、塊擦除、芯片擦除和寫(xiě)狀態(tài)寄存器指令之前進(jìn)行寫(xiě)使能。
-
操作:拉低CS片選->發(fā)送指令0x06 ->拉高CS片選。
2.讀SR1(0x05)
-
讀取狀態(tài)寄存器指令允許讀取8位狀態(tài)寄存器的值。
-
操作:拉低CS片選 ->發(fā)送指令0x05 ->定義一個(gè)uint8_t數(shù)據(jù)接收SR1的返回值 ->拉高CS片選?
3.讀數(shù)據(jù)(0x03)
-
讀取數(shù)據(jù)指令允許從存儲(chǔ)器順序讀取一個(gè)或多個(gè)數(shù)據(jù)字節(jié)。
-
操作:拉低CS片選 -> 發(fā)送指令0x03 -> 發(fā)送24位地址 -> 讀取數(shù)據(jù) -> 拉高CS片選
4.頁(yè)寫(xiě)(0x02)
-
頁(yè)寫(xiě)指令允許在指定地址寫(xiě)入小于256字節(jié)的指定長(zhǎng)度的數(shù)據(jù),在非0xFF處寫(xiě)入的數(shù)據(jù)會(huì)失敗。
-
操作:寫(xiě)使能 -> 拉低CS片選 -> 發(fā)送指令0x02 -> 發(fā)送24位地址 -> 寫(xiě)入數(shù)據(jù) -> 拉高CS片選 -> 等待寫(xiě)入結(jié)束(即判斷狀態(tài)寄存器的BUSY位是否置0
5.扇區(qū)擦除時(shí)序(0x20)
????????寫(xiě)入數(shù)據(jù)前,檢查內(nèi)存空間是否全部都是 0XFF ,不滿(mǎn)足需擦除。
????????拉低CS片選 → 發(fā)送20H→ 發(fā)送24位地址 → 拉高CS片選
6.芯片擦除(0xC7)
-
芯片擦除指令將W25Q128的所有數(shù)據(jù)都擦除為0xFF。
-
操作:寫(xiě)使能 -> 等待空閑(即判斷狀態(tài)寄存器的BUSY位是否置0) -> 拉低CS片選 -> 發(fā)送指令0xC7 -> 拉高CS片選 -> 等待芯片擦除完成(即判斷狀態(tài)寄存器的BUSY位是否置0)
7.讀取W25Q128的芯片ID(0x90)
-
讀取制造商/設(shè)備ID指令。
-
操作:拉低片選信號(hào) -> 發(fā)送24位地址,地址為0xFFFFFF -> 定義一個(gè)uint16_t數(shù)據(jù)接收芯片ID -> 拉高片選信號(hào)
4.W25Q128狀態(tài)寄存器
????????W25Q128一共有3個(gè)狀態(tài)寄存器,它們的作用時(shí)跟蹤芯片的狀態(tài)。其中,狀態(tài)寄存器1比較常用。
? ? ? ? ?1.BUSY位(指示當(dāng)前的狀態(tài))????????
????????BUSY是狀態(tài)寄存器中的只讀位,當(dāng)設(shè)備執(zhí)行頁(yè)程序、四頁(yè)程序、扇區(qū)擦除、塊擦除、芯片擦除、寫(xiě)狀態(tài)寄存器或擦除/程序安全寄存器指令時(shí),將其設(shè)置為1狀態(tài)。 在此期間,器件將忽略除讀取狀態(tài)寄存器和擦除/程序掛起指令之外的其他指令。 當(dāng)編程、擦除或?qū)懭霠顟B(tài)/安全寄存器指令完成時(shí),忙位將被清除為0狀態(tài),表示設(shè)備已準(zhǔn)備好接受進(jìn)一步的指令。
0:空閑
1:忙
? ? ? ? 2.WEL位(寫(xiě)使能鎖定)
????????WEL是狀態(tài)寄存器(S1)中的只讀位,在執(zhí)行寫(xiě)使能指令后被設(shè)置為1。 當(dāng)設(shè)備被禁止寫(xiě)入時(shí),WEL狀態(tài)位被清除為0。 在上電時(shí)或在下列任何指令之后發(fā)生寫(xiě)禁用狀態(tài):寫(xiě)禁用、頁(yè)程序、四頁(yè)程序、扇區(qū)擦除、塊擦除、芯片擦除、寫(xiě)狀態(tài)寄存器、擦除安全寄存器和程序安全寄存器。
1:可以操作頁(yè)、扇區(qū)、塊
0:禁止寫(xiě)入
?
5.W25Q128常見(jiàn)操作流程
寫(xiě)操作:
八、實(shí)驗(yàn)(使用SPI通訊讀寫(xiě)W25Q128模塊)
1.接線
????????W25Q128與STM32F103C8T6板子接線,在STM32F103C8T6的產(chǎn)品手冊(cè)中找到板子上的SPI1的接口。
????????PA4作為SPI1的NSS,PA5作為SPI1的CLK,PA6作為SPI1的DO(MISO),PA7作為SPI1的DI(MOSI)。
2.配置
? ? ? ? 1.SYS
? ? ? ? 2.?RCC
?
?????????3.SPI1
?????????4.SUART1
? ? ? ? ? 5.使用MicroLIB庫(kù)
3.代碼
1.main.c文件
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#include "w25q128.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define TEXT_SIZE 16
#define FLASH_WriteAddress 0x000000 //數(shù)據(jù)寫(xiě)入w25q128的地址,地址范圍為0x000000 ~ 0xFFFFFF
#define FLASH_ReadAddress FLASH_WriteAddress
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//重寫(xiě)stdio.h文件中的prinft()里的fputc()函數(shù)
int fputc(int my_data,FILE *p)
{
unsigned char temp = my_data;
//改寫(xiě)后,使用printf()函數(shù)會(huì)將數(shù)據(jù)通過(guò)串口一發(fā)送出去
HAL_UART_Transmit(&huart1,&temp,1,0xffff); //0xfffff為最大超時(shí)時(shí)間
return my_data;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t datatemp[TEXT_SIZE];
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
/* USER CODE BEGIN 2 */
/* w25q128初始化 */
w25q128_init();
/* 寫(xiě)入測(cè)試數(shù)據(jù) */
sprintf((char *)datatemp, "hello jiangxiao");
w25q128_write(datatemp, FLASH_WriteAddress, TEXT_SIZE);
printf("數(shù)據(jù)寫(xiě)入完成!\r\n");
/* 讀出測(cè)試數(shù)據(jù) */
memset(datatemp, 0, TEXT_SIZE);
w25q128_read(datatemp, FLASH_ReadAddress, TEXT_SIZE);
printf("讀出數(shù)據(jù):%s\r\n", datatemp);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
2.w25q128.c文件(向工程添加w25q128.c文件)
#include "w25q128.h"
#include "spi.h"
#include "stdio.h"
//w25q128初始化
void w25q128_init(void)
{
uint16_t flash_type;
spi1_read_write_byte(0xFF); /* 清除DR(數(shù)據(jù)寄存器),寫(xiě)入一個(gè)0xFF */
W25Q128_CS(1); //拉高片選信號(hào)不進(jìn)行SPI通信
flash_type = w25q128_read_id(); /* 讀取FLASH ID. */
if (flash_type == W25Q128){
printf("檢測(cè)到W25Q128芯片\r\n");
}
}
//等待W25Q128空閑
static void w25q128_wait_busy(void)
{
while ((w25q128_rd_sr1() & 0x01) == 1); /* 等待狀態(tài)寄存器的BUSY位清空 */
}
//讀取狀態(tài)寄存器的值
uint8_t w25q128_rd_sr1(void)
{
uint8_t rec_data = 0;
W25Q128_CS(0);
spi1_read_write_byte(FLASH_ReadStatusReg1); // 寫(xiě)入指令0x05:讀狀態(tài)寄存器1
rec_data = spi1_read_write_byte(0xFF); //獲取狀態(tài)寄存器1的值
W25Q128_CS(1);
return rec_data;
}
//W25Q128寫(xiě)使能,即置位WEL為1
void w25q128_write_enable(void)
{
W25Q128_CS(0);
spi1_read_write_byte(FLASH_WriteEnable); /* 發(fā)送指令0x06:寫(xiě)使能 */
W25Q128_CS(1);
}
//發(fā)送24位地址
static void w25q128_send_address(uint32_t address) /*address:地址范圍0~16777215字節(jié),即尋址范圍為0x00 ~ 0xFFFFFF */
{
spi1_read_write_byte((uint8_t)((address)>>16)); /* 發(fā)送 bit23 ~ bit16 地址 */
spi1_read_write_byte((uint8_t)((address)>>8)); /* 發(fā)送 bit15 ~ bit8 地址 */
spi1_read_write_byte((uint8_t)address); /* 發(fā)送 bit7 ~ bit0 地址 */
}
//擦除整個(gè)芯片
void w25q128_erase_chip(void)
{
w25q128_write_enable(); /* 寫(xiě)使能 */
w25q128_wait_busy(); /* 等待空閑 */
W25Q128_CS(0);
spi1_read_write_byte(FLASH_ChipErase); /* 發(fā)送指令0xC7:擦除整個(gè)芯片 */
W25Q128_CS(1);
w25q128_wait_busy(); /* 等待芯片擦除結(jié)束 */
}
//擦除一個(gè)扇區(qū)
void w25q128_erase_sector(uint32_t saddr) /* saddr:該參數(shù)是第幾個(gè)扇區(qū) */
{
saddr *= 4096; /* 一個(gè)扇區(qū)大小為4096字節(jié) */
w25q128_write_enable(); /* 寫(xiě)使能 */
w25q128_wait_busy(); /* 等待空閑 */
W25Q128_CS(0);
spi1_read_write_byte(FLASH_SectorErase); /* 發(fā)送指令0x20:擦除指定扇區(qū) */
w25q128_send_address(saddr); /* 發(fā)送擦除的扇區(qū)地址 */
W25Q128_CS(1);
w25q128_wait_busy(); /* 等待扇區(qū)擦除完成 */
}
//讀取w25q128芯片ID
uint16_t w25q128_read_id(void)
{
uint16_t deviceid;
W25Q128_CS(0); //拉低片選信號(hào)進(jìn)行SPI通信
spi1_read_write_byte(FLASH_ManufactDeviceID); /* 發(fā)送讀取 ID 命令 */
/* 發(fā)送3個(gè)0 */
/*
spi1_read_write_byte(0);
spi1_read_write_byte(0);
spi1_read_write_byte(0);
*/
w25q128_send_address(0x000000);
deviceid = spi1_read_write_byte(0xFF) << 8; /* 讀取高8位字節(jié) */
deviceid |= spi1_read_write_byte(0xFF); /* 讀取低8位字節(jié) */
W25Q128_CS(1);
return deviceid;
}
/*
讀取W25Q128的FLASH,在指定地址開(kāi)始讀取指定長(zhǎng)度的數(shù)據(jù)
pubf:需要讀取的數(shù)據(jù)
addr:指定的地址
datalen:指定的數(shù)據(jù)大小
*/
void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t i;
W25Q128_CS(0);
spi1_read_write_byte(FLASH_ReadData); /* 發(fā)送指令0x03:讀取數(shù)據(jù) */
w25q128_send_address(addr); /* 發(fā)送需要讀取的數(shù)據(jù)地址 */
for(i=0;i<datalen;i++)
{
pbuf[i] = spi1_read_write_byte(0XFF); /* 循環(huán)讀取 */
}
W25Q128_CS(1);
}
/*
單頁(yè)寫(xiě),在指定地址寫(xiě)入小于256字節(jié)的指定長(zhǎng)度的數(shù)據(jù),在非0xFF處寫(xiě)入的數(shù)據(jù)會(huì)失敗
pubf:需要寫(xiě)入的數(shù)據(jù)
addr:指定的地址
datalen:指定的數(shù)據(jù)大小
*/
static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t i;
w25q128_write_enable(); /* 寫(xiě)使能 */
W25Q128_CS(0);
spi1_read_write_byte(FLASH_PageProgram); /* 發(fā)送命令0x02:頁(yè)寫(xiě) */
w25q128_send_address(addr); /* 發(fā)送寫(xiě)入的頁(yè)地址 */
for(i=0;i<datalen;i++)
{
spi1_read_write_byte(pbuf[i]); /* 循環(huán)寫(xiě)入 */
}
W25Q128_CS(1);
w25q128_wait_busy(); /* 等待寫(xiě)入結(jié)束 */
}
/*
多頁(yè)寫(xiě),在指定地址寫(xiě)入指定長(zhǎng)度的數(shù)據(jù),在非0xFF處寫(xiě)入的數(shù)據(jù)會(huì)失敗
pubf:需要寫(xiě)入的數(shù)據(jù)
addr:指定的地址
datalen:指定的數(shù)據(jù)大小
*/
static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t pageremain;
pageremain = 256 - addr % 256; /* 獲取指定地址那頁(yè)的剩余字節(jié)數(shù) */
if (datalen <= pageremain) /* 指定地址那頁(yè)的剩余字節(jié)數(shù)能裝下指定數(shù)據(jù)大小 */
{
pageremain = datalen;
}
while (1)
{
/* 當(dāng)指定地址那頁(yè)的剩余字節(jié)數(shù)能裝下指定數(shù)據(jù)大小時(shí),一次性寫(xiě)完 */
/* 當(dāng)指定數(shù)據(jù)大小比指定地址那頁(yè)的剩余字節(jié)數(shù)要大時(shí), 先寫(xiě)完指定地址那頁(yè)的剩余字節(jié), 然后根據(jù)剩余數(shù)據(jù)大小進(jìn)行不同處理 */
w25q128_write_page(pbuf, addr, pageremain); //頁(yè)寫(xiě)
if (datalen == pageremain) /* 寫(xiě)入結(jié)束了 */
{
break;
}
else /* datalen > pageremain */
{
pbuf += pageremain; /* pbuf指針地址偏移,前面已經(jīng)寫(xiě)了pageremain字節(jié) */
addr += pageremain; /* 寫(xiě)地址偏移,前面已經(jīng)寫(xiě)了pageremain字節(jié) */
datalen -= pageremain; /* 寫(xiě)入總長(zhǎng)度減去已經(jīng)寫(xiě)入了的字節(jié)數(shù) */
if (datalen > 256) /* 剩余數(shù)據(jù)大小還大于一頁(yè) */
{
pageremain= 256; /* 一次寫(xiě)入256個(gè)字節(jié),即一次寫(xiě)一頁(yè) */
}
else /* 剩余數(shù)據(jù)大小小于一頁(yè) */
{
pageremain= datalen; /* 一次性寫(xiě)完 */
}
}
}
}
/*
//寫(xiě)入W25Q128的FLASH,在指定地址開(kāi)寫(xiě)入取指定長(zhǎng)度的數(shù)據(jù)
pubf:需要寫(xiě)入的數(shù)據(jù)
addr:指定的地址
datalen:指定的數(shù)據(jù)大小
*/
uint8_t g_w25q128_buf[4096]; /* 扇區(qū)緩存 */
void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t *w25q128_buf;
w25q128_buf = g_w25q128_buf;
secpos = addr / 4096; /* 獲取指定地址在哪片扇區(qū) */
secoff = addr % 4096; /* 指定數(shù)據(jù)在在扇區(qū)內(nèi)的偏移數(shù)據(jù)大小 */
secremain = 4096 - secoff; /* 扇區(qū)剩余字節(jié)數(shù) */
if (datalen <= secremain) /* 指定地址那片扇區(qū)的剩余字節(jié)數(shù)能裝下指定數(shù)據(jù)大小 */
{
secremain = datalen;
}
while (1)
{
w25q128_read(w25q128_buf, secpos * 4096, 4096); /* 讀出指定地址那片扇區(qū)的全部?jī)?nèi)容 */
for (i = 0; i < secremain; i++) /* 校驗(yàn)數(shù)據(jù),防止數(shù)據(jù)出現(xiàn)非0xFF */
{
if (w25q128_buf[secoff + i] != 0xFF) //扇區(qū)數(shù)據(jù)有一個(gè)數(shù)據(jù)不是0xFF
{
break; /* 需要擦除, 直接退出for循環(huán) */
}
}
if (i < secremain) /* 需要擦除 */
{
w25q128_erase_sector(secpos); /* 擦除這個(gè)扇區(qū) */
for (i = 0; i < secremain; i++) /* 復(fù)制 */
{
w25q128_buf[i + secoff] = pbuf[i];
}
w25q128_write_nocheck(w25q128_buf, secpos * 4096, 4096); /* 寫(xiě)入整個(gè)扇區(qū) */
}
else /* 寫(xiě)已經(jīng)擦除了的,直接寫(xiě)入扇區(qū)剩余區(qū)間. */
{
w25q128_write_nocheck(pbuf, addr, secremain); /* 直接寫(xiě)扇區(qū) */
}
if (datalen == secremain)
{
break; /* 寫(xiě)入結(jié)束了 */
}
else /* 寫(xiě)入未結(jié)束 */
{
secpos++; /* 扇區(qū)地址增1,新的一個(gè)扇區(qū) */
secoff = 0; /* 偏移位置為0 */
pbuf += secremain; /* 指針偏移 */
addr += secremain; /* 寫(xiě)地址偏移 */
datalen -= secremain; /* 字節(jié)數(shù)遞減 */
if (datalen > 4096)
{
secremain = 4096; /* 一次寫(xiě)入一個(gè)扇區(qū) */
}
else
{
secremain = datalen;/* 一次性寫(xiě)完 */
}
}
}
}
3.w25q128.h文件(向工程添加w25q128.h文件)
#include "stdint.h"
/* W25Q128片選引腳定義 */
#define W25Q128_CS_GPIO_PORT GPIOA
#define W25Q128_CS_GPIO_PIN GPIO_PIN_4
/* W25Q128片選信號(hào) */
#define W25Q128_CS(x) do{ x ? \
HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT, W25Q128_CS_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT, W25Q128_CS_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
/* FLASH芯片列表 */
#define W25Q128 0XEF17 /* W25Q128 芯片ID */
/* 指令表 */
#define FLASH_WriteEnable 0x06
#define FLASH_ReadStatusReg1 0x05
#define FLASH_ReadData 0x03
#define FLASH_PageProgram 0x02
#define FLASH_SectorErase 0x20
#define FLASH_ChipErase 0xC7
#define FLASH_ManufactDeviceID 0x90
/* 靜態(tài)函數(shù) */
static void w25q128_wait_busy(void); //等待W25Q128空閑
static void w25q128_send_address(uint32_t address); //發(fā)送24位地址
static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen); //單頁(yè)寫(xiě),在指定地址寫(xiě)入小于256字節(jié)的指定長(zhǎng)度的數(shù)據(jù),在非0xFF處寫(xiě)入的數(shù)據(jù)會(huì)失敗
static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen); //多頁(yè)寫(xiě),在指定地址寫(xiě)入指定長(zhǎng)度的數(shù)據(jù),在非0xFF處寫(xiě)入的數(shù)據(jù)會(huì)失敗
/* 普通函數(shù) */
void w25q128_init(void); //w25q128初始化
uint16_t w25q128_read_id(void); //讀取w25q128芯片ID
void w25q128_write_enable(void); //W25Q128寫(xiě)使能,即置位WEL為1
uint8_t w25q128_rd_sr1(void); //讀取狀態(tài)寄存器的值
void w25q128_erase_chip(void); //擦除整個(gè)芯片
void w25q128_erase_sector(uint32_t saddr); //擦除一個(gè)扇區(qū)
void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen); //讀取W25Q128的FLASH,在指定地址開(kāi)始讀取指定長(zhǎng)度的數(shù)據(jù)
void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen); //寫(xiě)入W25Q128的FLASH,在指定地址開(kāi)寫(xiě)入取指定長(zhǎng)度的數(shù)據(jù)
4.spi.c文件編寫(xiě)
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file spi.c
* @brief This file provides code for the configuration
* of the SPI instances.
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "spi.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
SPI_HandleTypeDef hspi1; /* SPI句柄 */
/* SPI1 init function */
void MX_SPI1_Init(void)
{
/* USER CODE BEGIN SPI1_Init 0 */
/* USER CODE END SPI1_Init 0 */
/* USER CODE BEGIN SPI1_Init 1 */
/* USER CODE END SPI1_Init 1 */
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER; /* 設(shè)置SPI模式主機(jī)模式 */
hspi1.Init.Direction = SPI_DIRECTION_2LINES; /* 設(shè)置SPI工作方式:全雙工 */
hspi1.Init.DataSize = SPI_DATASIZE_8BIT; /* 設(shè)置數(shù)據(jù)格式:8bit */
/* SPI模式1 */
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; /* 設(shè)置時(shí)鐘極性:CPOL = 0 */
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; /* 設(shè)置時(shí)鐘相位:CPHA = 1 */
hspi1.Init.NSS = SPI_NSS_SOFT; /* 設(shè)置片選方式:軟件NSS */
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; /* 設(shè)置SPI時(shí)鐘波特率分頻:256分頻 */
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 設(shè)置數(shù)據(jù)大小端:MSB */
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; /* 設(shè)置數(shù)據(jù)幀格式:Motolora */
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 設(shè)置CRC校驗(yàn):關(guān)閉CRC檢驗(yàn) */
hspi1.Init.CRCPolynomial = 10; /* 設(shè)置CRC校驗(yàn)多項(xiàng)式:1~65535 */
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SPI1_Init 2 */
/* USER CODE END SPI1_Init 2 */
}
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(spiHandle->Instance==SPI1)
{
/* USER CODE BEGIN SPI1_MspInit 0 */
/* USER CODE END SPI1_MspInit 0 */
/* SPI1 clock enable */
__HAL_RCC_SPI1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**SPI1 GPIO Configuration
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PA7 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN SPI1_MspInit 1 */
/* USER CODE END SPI1_MspInit 1 */
}
}
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{
if(spiHandle->Instance==SPI1)
{
/* USER CODE BEGIN SPI1_MspDeInit 0 */
/* USER CODE END SPI1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_SPI1_CLK_DISABLE();
/**SPI1 GPIO Configuration
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PA7 ------> SPI1_MOSI
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);
/* USER CODE BEGIN SPI1_MspDeInit 1 */
/* USER CODE END SPI1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/*通過(guò)SPI1同時(shí)讀寫(xiě)一個(gè)字節(jié)數(shù)據(jù)
主機(jī)只向從機(jī)進(jìn)行寫(xiě)操作,調(diào)用此函數(shù)時(shí)忽略返回值
主機(jī)只向從機(jī)進(jìn)行讀操作,調(diào)用此函數(shù)時(shí)隨便傳入一個(gè)字符,盡量是0xFF
*/
uint8_t spi1_read_write_byte(uint8_t data)
{
uint8_t rec_data = 0;
HAL_SPI_TransmitReceive(&hspi1, &data, &rec_data, 1, 1000); //spi讀寫(xiě)數(shù)據(jù)函數(shù),參數(shù)2存放用來(lái)發(fā)送的數(shù)據(jù),參數(shù)3存放用來(lái)接收的數(shù)據(jù)
return rec_data;
}
/* USER CODE END 1 */
5.spi.h文件編寫(xiě)
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file spi.h
* @brief This file contains all the function prototypes for
* the spi.c file
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __SPI_H__
#define __SPI_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
extern SPI_HandleTypeDef hspi1;
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MX_SPI1_Init(void);
/* USER CODE BEGIN Prototypes */
uint8_t spi1_read_write_byte(uint8_t data);
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* __SPI_H__ */
九、STM32工程添加.c和.h文件
????????1.在創(chuàng)建好的STM32工程中找到Core的文件夾
? ? ? ? ?2.向文件夾里添加新的xxx.c文件或xxx.h文件
? ? ? ? 3.在keil5中導(dǎo)入工程后,將這兩個(gè)文件添加到工程列表中
?
? ? ? ? 4.添加頭文件
????????只需在需要添加頭文件的c文件中寫(xiě)上需添加的頭文件再編譯就能自動(dòng)添加
? ? ? ? 例如下圖文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-628647.html
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-628647.html
到了這里,關(guān)于STM32—SPI詳解入門(mén)(使用SPI通訊讀寫(xiě)W25Q128模塊)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!