SPI 是什么?
SPI是串行外設(shè)接口(Serial Peripheral Interface)的縮寫,是一種高速的,全雙工,同步的通信總 線,并且在芯片的管腳上只占用四根線,節(jié)約了芯片的管腳,同時(shí)為PCB的布局上節(jié)省空間,提 供方便,正是出于這種簡(jiǎn)單易用的特性,越來(lái)越多的芯片集成了這種通信協(xié)議,比如 AT91RM9200 。
SPI 包含 4 條總線,SPI 總線包含 4 條總線,分別為SS、SCK、MOSI、MISO。它們的作用介紹如 下 :
- (1) MISO – Master Input Slave Output,主設(shè)備數(shù)據(jù)輸入,從設(shè)備數(shù)據(jù)輸出
- (2) MOSI – Master Output Slave Input,主設(shè)備數(shù)據(jù)輸出,從設(shè)備數(shù)據(jù)輸入
- (3) SCK – Serial Clock,時(shí)鐘信號(hào),由主 設(shè)備產(chǎn)生
- (4) CS – Chip Select,片選信號(hào),由主設(shè)備控制?
SPI 工作原理?
?SPI 工作模式
時(shí)鐘極性(CPOL): 沒(méi)有數(shù)據(jù)傳輸時(shí)時(shí)鐘線的空閑狀態(tài)電平
0:SCK在空閑狀態(tài)保持低電平 1:SCK在空閑狀態(tài)保持高 電平
時(shí)鐘相位(CPHA): 時(shí)鐘線在第幾個(gè)時(shí)鐘邊沿采樣數(shù)據(jù)
0:SCK的第一(奇數(shù))邊沿進(jìn)行數(shù)據(jù)位采樣,數(shù)據(jù)在第一個(gè)時(shí) 鐘邊沿被鎖存
1:SCK的第二(偶數(shù))邊沿進(jìn)行數(shù)據(jù)位采樣,數(shù)據(jù)在第二個(gè)時(shí)鐘邊沿被鎖存
模式 0 和模式 3 最常用。?
模式 0 時(shí)序圖:
模式 3 時(shí)序圖:
W25Q128 介紹?
什么是 W25Q128 ?
W25Q128 是華邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存儲(chǔ)空間為 128 Mbit,相當(dāng)于 16M 字節(jié)。 Flash 是常用的用于儲(chǔ)存數(shù)據(jù)的半導(dǎo)體器件,它具有容量大,可重復(fù)擦寫、按“扇區(qū)/塊”擦除、掉 電后數(shù)據(jù)可繼續(xù)保存的特性。
Flash 是有一個(gè)物理特性:只能寫 0 ,不能寫 1 ,寫 1 靠擦除。
W25Q128 存儲(chǔ)架構(gòu)?
一般按扇區(qū)(4k)進(jìn)行擦除。
可以按 章 -- 節(jié) -- 頁(yè) -- 字 進(jìn)行理解。?
?寫使能 (06H)
執(zhí)行頁(yè)寫,扇區(qū)擦除,塊擦除,片擦除,寫狀態(tài)寄存器等指令前,需要寫使能。
拉低CS片選 → 發(fā)送06H → 拉高CS片選
讀狀態(tài)寄存器(05H)
拉低CS片選 → 發(fā)送05H→ 返回SR1的值 → 拉高CS片選
讀時(shí)序(03H)
拉低CS片選 → 發(fā)送03H→ 發(fā)送24位地址 → 讀取數(shù)據(jù)(1~n) → 拉高CS片選
頁(yè)寫時(shí)序 (02H)
頁(yè)寫命令最多可以向FLASH傳輸256個(gè)字節(jié)的數(shù)據(jù)。
拉低CS片選 → 發(fā)送02H→ 發(fā)送24位地址 → 發(fā)送數(shù)據(jù)(1~n) → 拉高CS片選
扇區(qū)擦除時(shí)序(20H)
寫入數(shù)據(jù)前,檢查內(nèi)存空間是否全部都是 0XFF ,不滿足需擦除。 拉
低CS片選 → 發(fā)送20H→ 發(fā)送24位地址 → 拉高CS片選
W25Q128 狀態(tài)寄存器 W25Q128 一共有 3 個(gè)狀態(tài)寄存器,它們的作用是跟蹤芯片的狀態(tài)。 其中,狀態(tài)寄存器 1 較為常用。
?W25Q128 常見(jiàn)操作流程
以下流程省略了拉低/拉高片選信號(hào)CS
?
寫操作?
?
實(shí)驗(yàn):使用 SPI 通訊讀寫 W25Q128 模塊
硬件接線 :VCC -- 3.3V CS -- PA4 CLK -- PA5 DO -- PA6 DI -- PA7
cubeMX配置
w25q128_write_nocheck流程圖?
代碼實(shí)現(xiàn)?
main.c文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-558835.html
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_SPI1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
w25q128_init();
/* 寫入測(cè)試數(shù)據(jù) */
sprintf((char *)datatemp, "liangxu shuai");
w25q128_write(datatemp, FLASH_WriteAddress, TEXT_SIZE);
printf("數(shù)據(jù)寫入完成!\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 */
}
W25Q128.c文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-558835.html
#include "w25q128.h"
#include "spi.h"
#include "stdio.h"
/**
* @brief 初始化W25Q128
* @param 無(wú)
* @retval 無(wú)
*/
void w25q128_init(void)
{
uint16_t flash_type;
spi1_read_write_byte(0xFF); /* 清除DR的作用 */
W25Q128_CS(1);
flash_type = w25q128_read_id(); /* 讀取FLASH ID. */
if (flash_type == W25Q128)
printf("檢測(cè)到W25Q128芯片\r\n");
}
/**
* @brief 等待空閑
* @param 無(wú)
* @retval 無(wú)
*/
static void w25q128_wait_busy(void)
{
while ((w25q128_rd_sr1() & 0x01) == 0x01); /* 等待BUSY位清空 */
}
/**
* @brief 讀取W25Q128的狀態(tài)寄存器1的值
* @param 無(wú)
* @retval 狀態(tài)寄存器值
*/
uint8_t w25q128_rd_sr1(void)
{
uint8_t rec_data = 0;
W25Q128_CS(0);
spi1_read_write_byte(FLASH_ReadStatusReg1); /* 讀狀態(tài)寄存器1 */
rec_data = spi1_read_write_byte(0xFF);
W25Q128_CS(1);
return rec_data;
}
/**
* @brief W25Q128寫使能
* @note 將S1寄存器的WEL置位
* @param 無(wú)
* @retval 無(wú)
*/
void w25q128_write_enable(void)
{
W25Q128_CS(0);
spi1_read_write_byte(FLASH_WriteEnable); /* 發(fā)送寫使能 */
W25Q128_CS(1);
}
/**
* @brief W25Q128發(fā)送地址
* @param address : 要發(fā)送的地址
* @retval 無(wú)
*/
static void w25q128_send_address(uint32_t address)
{
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 地址 */
}
/**
* @brief 擦除整個(gè)芯片
* @note 等待時(shí)間超長(zhǎng)...
* @param 無(wú)
* @retval 無(wú)
*/
void w25q128_erase_chip(void)
{
w25q128_write_enable(); /* 寫使能 */
w25q128_wait_busy(); /* 等待空閑 */
W25Q128_CS(0);
spi1_read_write_byte(FLASH_ChipErase); /* 發(fā)送讀寄存器命令 */
W25Q128_CS(1);
w25q128_wait_busy(); /* 等待芯片擦除結(jié)束 */
}
/**
* @brief 擦除一個(gè)扇區(qū)
* @note 注意,這里是扇區(qū)地址,不是字節(jié)地址!!
* 擦除一個(gè)扇區(qū)的最少時(shí)間:150ms
*
* @param saddr : 扇區(qū)地址 根據(jù)實(shí)際容量設(shè)置
* @retval 無(wú)
*/
void w25q128_erase_sector(uint32_t saddr)
{
//printf("fe:%x\r\n", saddr); /* 監(jiān)視falsh擦除情況,測(cè)試用 */
saddr *= 4096;
w25q128_write_enable(); /* 寫使能 */
w25q128_wait_busy(); /* 等待空閑 */
W25Q128_CS(0);
spi1_read_write_byte(FLASH_SectorErase); /* 發(fā)送寫頁(yè)命令 */
w25q128_send_address(saddr); /* 發(fā)送地址 */
W25Q128_CS(1);
w25q128_wait_busy(); /* 等待扇區(qū)擦除完成 */
}
/**
* @brief 讀取芯片ID
* @param 無(wú)
* @retval FLASH芯片ID
* @note 芯片ID列表見(jiàn): w25q128.h, 芯片列表部分
*/
uint16_t w25q128_read_id(void)
{
uint16_t deviceid;
W25Q128_CS(0);
spi1_read_write_byte(FLASH_ManufactDeviceID); /* 發(fā)送讀 ID 命令 */
spi1_read_write_byte(0); /* 寫入一個(gè)字節(jié) */
spi1_read_write_byte(0);
spi1_read_write_byte(0);
deviceid = spi1_read_write_byte(0xFF) << 8; /* 讀取高8位字節(jié) */
deviceid |= spi1_read_write_byte(0xFF); /* 讀取低8位字節(jié) */
W25Q128_CS(1);
return deviceid;
}
/**
* @brief 讀取SPI FLASH
* @note 在指定地址開(kāi)始讀取指定長(zhǎng)度的數(shù)據(jù)
* @param pbuf : 數(shù)據(jù)存儲(chǔ)區(qū)
* @param addr : 開(kāi)始讀取的地址(最大32bit)
* @param datalen : 要讀取的字節(jié)數(shù)(最大65535)
* @retval 無(wú)
*/
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ā)送讀取命令 */
w25q128_send_address(addr); /* 發(fā)送地址 */
for(i=0;i<datalen;i++)
{
pbuf[i] = spi1_read_write_byte(0XFF); /* 循環(huán)讀取 */
}
W25Q128_CS(1);
}
/**
* @brief SPI在一頁(yè)(0~65535)內(nèi)寫入少于256個(gè)字節(jié)的數(shù)據(jù)
* @note 在指定地址開(kāi)始寫入最大256字節(jié)的數(shù)據(jù)
* @param pbuf : 數(shù)據(jù)存儲(chǔ)區(qū)
* @param addr : 開(kāi)始寫入的地址(最大32bit)
* @param datalen : 要寫入的字節(jié)數(shù)(最大256),該數(shù)不應(yīng)該超過(guò)該頁(yè)的剩余字節(jié)數(shù)!!!
* @retval 無(wú)
*/
static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
uint16_t i;
w25q128_write_enable(); /* 寫使能 */
W25Q128_CS(0);
spi1_read_write_byte(FLASH_PageProgram); /* 發(fā)送寫頁(yè)命令 */
w25q128_send_address(addr); /* 發(fā)送地址 */
for(i=0;i<datalen;i++)
{
spi1_read_write_byte(pbuf[i]); /* 循環(huán)寫入 */
}
W25Q128_CS(1);
w25q128_wait_busy(); /* 等待寫入結(jié)束 */
}
/**
* @brief 無(wú)檢驗(yàn)寫SPI FLASH
* @note 必須確保所寫的地址范圍內(nèi)的數(shù)據(jù)全部為0XFF,否則在非0XFF處寫入的數(shù)據(jù)將失敗!
* 具有自動(dòng)換頁(yè)功能
* 在指定地址開(kāi)始寫入指定長(zhǎng)度的數(shù)據(jù),但是要確保地址不越界!
*
* @param pbuf : 數(shù)據(jù)存儲(chǔ)區(qū)
* @param addr : 開(kāi)始寫入的地址(最大32bit)
* @param datalen : 要寫入的字節(jié)數(shù)(最大65535)
* @retval 無(wú)
*/
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) /* 不大于256個(gè)字節(jié) */
{
pageremain = datalen;
}
while (1)
{
/* 當(dāng)寫入字節(jié)比頁(yè)內(nèi)剩余地址還少的時(shí)候, 一次性寫完
* 當(dāng)寫入直接比頁(yè)內(nèi)剩余地址還多的時(shí)候, 先寫完整個(gè)頁(yè)內(nèi)剩余地址, 然后根據(jù)剩余長(zhǎng)度進(jìn)行不同處理
*/
w25q128_write_page(pbuf, addr, pageremain);
if (datalen == pageremain) /* 寫入結(jié)束了 */
{
break;
}
else /* datalen > pageremain */
{
pbuf += pageremain; /* pbuf指針地址偏移,前面已經(jīng)寫了pageremain字節(jié) */
addr += pageremain; /* 寫地址偏移,前面已經(jīng)寫了pageremain字節(jié) */
datalen -= pageremain; /* 寫入總長(zhǎng)度減去已經(jīng)寫入了的字節(jié)數(shù) */
if (datalen > 256) /* 剩余數(shù)據(jù)還大于一頁(yè),可以一次寫一頁(yè) */
{
pageremain = 256; /* 一次可以寫入256個(gè)字節(jié) */
}
else /* 剩余數(shù)據(jù)小于一頁(yè),可以一次寫完 */
{
pageremain = datalen; /* 不夠256個(gè)字節(jié)了 */
}
}
}
}
/**
* @brief 寫SPI FLASH
* @note 在指定地址開(kāi)始寫入指定長(zhǎng)度的數(shù)據(jù) , 該函數(shù)帶擦除操作!
* SPI FLASH 一般是: 256個(gè)字節(jié)為一個(gè)Page, 4Kbytes為一個(gè)Sector, 16個(gè)扇區(qū)為1個(gè)Block
* 擦除的最小單位為Sector.
*
* @param pbuf : 數(shù)據(jù)存儲(chǔ)區(qū)
* @param addr : 開(kāi)始寫入的地址(最大32bit)
* @param datalen : 要寫入的字節(jié)數(shù)(最大65535)
* @retval 無(wú)
*/
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; /* 在扇區(qū)內(nèi)的偏移 */
secremain = 4096 - secoff; /* 扇區(qū)剩余空間大小 */
//printf("ad:%X,nb:%X\r\n", addr, datalen); /* 測(cè)試用 */
if (datalen <= secremain)
{
secremain = datalen; /* 不大于4096個(gè)字節(jié) */
}
while (1)
{
w25q128_read(w25q128_buf, secpos * 4096, 4096); /* 讀出整個(gè)扇區(qū)的內(nèi)容 */
for (i = 0; i < secremain; i++) /* 校驗(yàn)數(shù)據(jù) */
{
if (w25q128_buf[secoff + i] != 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); /* 寫入整個(gè)扇區(qū) */
}
else /* 寫已經(jīng)擦除了的,直接寫入扇區(qū)剩余區(qū)間. */
{
w25q128_write_nocheck(pbuf, addr, secremain); /* 直接寫扇區(qū) */
}
if (datalen == secremain)
{
break; /* 寫入結(jié)束了 */
}
else /* 寫入未結(jié)束 */
{
secpos++; /* 扇區(qū)地址增1 */
secoff = 0; /* 偏移位置為0 */
pbuf += secremain; /* 指針偏移 */
addr += secremain; /* 寫地址偏移 */
datalen -= secremain; /* 字節(jié)數(shù)遞減 */
if (datalen > 4096)
{
secremain = 4096; /* 下一個(gè)扇區(qū)還是寫不完 */
}
else
{
secremain = datalen;/* 下一個(gè)扇區(qū)可以寫完了 */
}
}
}
}
到了這里,關(guān)于stm32(SPI讀寫W25Q18)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!