一.SPI介紹
SPI 是英語SerialPeripheral interface的縮寫,顧名思義就是串行外圍設備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在
EEPROM,FLASH,實時時鐘,AD轉換器,還有數字信號處理器和數字信號解碼器之間。SPI,是一種高速的,全雙工,同步的通信總線,并且在芯片的
管腳上只占用四根線,節約了芯片的管腳,同時為PCB的布局上節省空間,提供方便,正是出于這種簡單易用的特性,現在越來越多的芯片集成了這種
通信協議,STM32F4也有SPI接口。下面我們看看SPI的內部簡明圖
SPI接口一般使用4條線通信:
MISO 主設備數據輸入,從設備數據輸出。
MOSI 主設備數據輸出,從設備數據輸入。
SCLK時鐘信號,由主設備產生。
CS從設備片選信號,由主設備控制。
從圖中可以看出,主機和從機都有一個串行移位寄存器,主機通過向它的SPI串行寄存器
寫入一個字節來發起一次傳輸。寄存器通過MOSI信號線將字節傳送給從機,從機也將自己的移位寄存器中的內容通過MISO信號線返回給主機。這樣,兩個移位寄存器中的內容就被交換。外設的寫操作和讀操作是同步完成的。如果只進行寫操作,主機只需忽略接收到的字節;反之,若主機要讀取從機的一個字節,就必須發送一個空字節來引發從機的傳輸。
SPI主要特點有:可以同時發出和接收串行數據;可以當作主機或從機工作;提供頻率可
編程時鐘;發送結束中斷標志;寫沖突保護;總線競爭保護等。
SPI總線四種工作方式 SPI 模塊為了和外設進行數據交換,根據外設工作要求,其輸出串
行同步時鐘極性和相位可以進行配置,時鐘極性(CPOL)對傳輸協議沒有重大的影響。如果CPOL=0,串行同步時鐘的空閑狀態為低電平;如果CPOL=1,串行同步時鐘的空閑狀態為高電平。時 鐘 相 位(CPHA)能夠配置用于選擇兩種不同的傳輸協議之一進行數據傳輸。如果CPHA=0,在串行同步時鐘的第一個跳變沿(上升或下降)數據被采樣;如果CPHA=1,在串行同步時鐘的第二個跳變沿(上升或下降)數據被采樣。SPI主模塊和與之通信的外設備時鐘相位和極性應該一致。不同時鐘相位下的總線數據傳輸時序如圖
TM32F4的SPI功能很強大,SPI時鐘最高可以到37.5Mhz,支持DMA,可以配置為SPI
協議或者I2S協議(支持全雙工I2S)。
二.庫函數應用
SPI
相關的庫函數和定義分布在文件stm32f4xx_spi.c以及頭文件stm32f4xx_spi.h中。STM32的主模式配置步驟如下:
拿SPI1舉例
1) 配置相關引腳的復用功能,使能SPI1時鐘。
PB3、4、5這3個(SCK.、MISO、MOSI,CS使用軟件管理方式),所以設置這三個為復用IO,復用功能為AF5。
使能SPI1時鐘的方法為:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能SPI1時鐘
復用PB3,PB4,PB5為SPI1引腳的方法為:
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3復用為 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4復用為 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5復用為 SPI1
同時我們要設置相應的引腳模式為復用功能模式:
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//復用功能
2) 初始化SPI1,設置SPI1工作模式等。
這一步全部是通過SPI1_CR1來設置,我們設置SPI1為主機模式,設置數據格式為8位,然后通過CPOL和CPHA位來設置SCK時鐘極性及采樣方式。并設置SPI1的時鐘頻率(最大37.5Mhz),以及數據的格式(MSB在前還是LSB在前)。在庫函數中初始化SPI的函數為:
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
跟其他外設初始化一樣,第一個參數是SPI標號,這里我們是使用的SPI1。下面我們來看看第二個參數結構體類型SPI_InitTypeDef的定義:
typedefstruct
{
uint16_tSPI_Direction;
uint16_tSPI_Mode;
uint16_tSPI_DataSize;
uint16_tSPI_CPOL;
uint16_tSPI_CPHA;
uint16_tSPI_NSS;
uint16_tSPI_BaudRatePrescaler;
uint16_tSPI_FirstBit;
uint16_tSPI_CRCPolynomial;
}SPI_InitTypeDef;
結構體成員變量比較多,接下來我們簡單講解一下:
第一個參數SPI_Direction是用來設置SPI的通信方式,可以選擇為半雙工,全雙工,以及串行發和串行收方式,這里我們選擇全雙工模式
SPI_Direction_2Lines_FullDuplex。
第二個參數SPI_Mode用來設置SPI的主從模式,這里我們設置為主機模式SPI_Mode_Master,當然有需要你也可以選擇為從機模式SPI_Mode_Slave。
第三個參數SPI_DataSiz為8 位還是16 位幀格式選擇項,這里我們是8 位傳輸,選擇SPI_DataSize_8b。
第四個參數SPI_CPOL用來設置時鐘極性,我們設置串行同步時鐘的空閑狀態為高電平所以我們選擇SPI_CPOL_High。
第五個參數SPI_CPHA用來設置時鐘相位,也就是選擇在串行同步時鐘的第幾個跳變沿(上升或下降)數據被采樣,可以為第一個或者第二個條邊沿采集,這里我們選擇第二個跳變沿,所以選擇SPI_CPHA_2Edge
第六個參數SPI_NSS設置NSS信號由硬件(NSS管腳)還是軟件控制,這里我們通過軟件控制NSS關鍵,而不是硬件自動控制,所以選擇SPI_NSS_Soft。
第七個參數SPI_BaudRatePrescaler很關鍵,就是設置SPI波特率預分頻值也就是決定SPI的時鐘的參數,從2 分頻到256 分頻8 個可選值,初始化的時候我們選擇256 分頻值SPI_BaudRatePrescaler_256, 傳輸速度為84M/256=328.125KHz。
第八個參數SPI_FirstBit 設置數據傳輸順序是MSB位在前還是LSB位在前,,這里我們選擇SPI_FirstBit_MSB高位在前。
第九個參數SPI_CRCPolynomial是用來設置CRC校驗多項式,提高通信可靠性,大于1即可。
設置好上面9個參數,我們就可以初始化SPI外設了。初始化的范例格式為:
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //雙線雙向全雙工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // SPI發送接收8位幀結構
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步時鐘的空閑狀態為高電平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二個跳變沿數據被采樣
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信號由軟件控制
SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_256; //預分頻256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //數據傳輸從MSB位開始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值計算的多項式
SPI_Init(SPI2, &SPI_InitStructure); //根據指定的參數初始化外設SPIx寄存器
3) 使能SPI1。
這一步通過SPI1_CR1的bit6來設置,以啟動SPI1,在啟動之后,我們就可以開始SPI通訊了。庫函數使能SPI1的方法為:
SPI_Cmd(SPI1, ENABLE); //使能SPI1外設
4) SPI傳輸數據
通信接口當然需要有發送數據和接受數據的函數,固件庫提供的發送數據函數原型為:
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
這個函數很好理解,往SPIx數據寄存器寫入數據Data,從而實現發送。
固件庫提供的接受數據函數原型為:
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
這個函數也不難理解,從SPIx數據寄存器讀出接受到的數據。
5) 查看SPI傳輸狀態
在SPI傳輸過程中,我們經常要判斷數據是否傳輸完成,發送區是否為空等等狀態,這是通過函數SPI_I2S_GetFlagStatus實現的,這個函數很簡單就不詳細講解,判斷發送是否完成的方法是:
SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE);
三.庫函數應用源碼
voidSPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1時鐘
//GPIOFB3,4,5初始化設置
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5復用功能輸出
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF;//復用功能
GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽輸出
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);//PB3復用為 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4復用為 SPI1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5復用為 SPI1
//這里只針對SPI口初始化
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//復位SPI1
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止復位SPI1
SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工
SPI_InitStructure.SPI_Mode =SPI_Mode_Master; //設置SPI工作模式:設置為主SPI
SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b; //設置SPI的數據大小:SPI發送接收8位幀結構
SPI_InitStructure.SPI_CPOL =SPI_CPOL_High; //串行同步時鐘的空閑狀態為高電平
SPI_InitStructure.SPI_CPHA =SPI_CPHA_2Edge; //串行同步時鐘的第二個跳變沿(上升或下降)數據被采樣
SPI_InitStructure.SPI_NSS =SPI_NSS_Soft; //NSS信號由硬件(NSS管腳)還是軟件(使用SSI位)管理:內部NSS信號有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_256; //定義波特率預分頻的值:波特率預分頻值為256
SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB; //指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始
SPI_InitStructure.SPI_CRCPolynomial =7; //CRC值計算的多項式
SPI_Init(SPI1,&SPI_InitStructure); //根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器
SPI_Cmd(SPI1, ENABLE); //使能SPI外設
SPI1_ReadWriteByte(0xff);//啟動傳輸
}
//SPI1速度設置函數
//SPI速度=fAPB2/分頻系數
//@refSPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256
//fAPB2時鐘一般為84Mhz:
voidSPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判斷有效性
SPI1->CR1&=0XFFC7;//位3-5清零,用來設置波特率
SPI1->CR1|=SPI_BaudRatePrescaler; //設置SPI1速度
SPI_Cmd(SPI1,ENABLE); //使能SPI1
}
//SPI1 讀寫一個字節
//TxData:要寫入的字節
//返回值:讀取到的字節
u8SPI1_ReadWriteByte(u8 TxData)
{
while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET){}//等待發送區空
SPI_I2S_SendData(SPI1, TxData); //通過外設SPIx發送一個byte 數據
while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET){} //等待接收完一個byte
return SPI_I2S_ReceiveData(SPI1); //返回通過SPIx最近接收的數據
}
四.W25Q128介紹
W25Q128是華邦公司推出的大容量SPI FLASH產品,W25Q128的容量為128Mb,該系列還有W25Q80/16/32/64等。
W25Q128將16M的容量分為256個塊(Block),每個塊大小為64K字節,每個塊又分為16個扇區(Sector),每個扇區4K個字節。W25Q128的最小擦除單位為一個扇區,也就是每次必須擦除4K個字節。這樣我們需要給W25Q128開辟一個至少4K的緩存區,這樣對SRAM要求比較高,要求芯片必須有4K以上SRAM才能很好的操作。
W25Q128的擦寫周期多達10W次,具有20年的數據保存期限,支持電壓為2.7~3.6V,
W25Q128支持標準的SPI,還支持雙輸出/四輸出的SPI,最大SPI時鐘可以到80Mhz(雙輸出時相當于160Mhz,四輸出時相當于320M),更多的W25Q128的介紹,請參考W25Q128的DATASHEET。
五.SPI操作W25Q128
1. Read Manufacturer / Device ID(90h)
程序和時序圖一一對應
程序意思為:先片選,選中W25Q128,然后發送命令和address,然后再讀出ID,再取消片選
2. Sector Erase (20h)
對應的時序圖為
程序的意思是片選25Q128,然后發送命令和地址,然后再取消片選,等待擦除完成
3.Read Data (03h)
對應的時序圖為:
只介紹這三個,可以自行參照datasheet讀源碼,后續附上源碼
六.操作W25Q128源碼
W25qxx.h
#ifndef__W25QXX_H
#define__W25QXX_H
#include"sys.h"
//W25X系列/Q系列芯片列表
//W25Q80 ID 0XEF13
//W25Q16 ID 0XEF14
//W25Q32 ID 0XEF15
//W25Q64 ID 0XEF16
//W25Q128ID 0XEF17
#defineW25Q80 0XEF13
#defineW25Q16 0XEF14
#defineW25Q32 0XEF15
#defineW25Q64 0XEF16
#defineW25Q128 0XEF17
externu16 W25QXX_TYPE; //定義W25QXX芯片型號
#define W25QXX_CS PBout(14) //W25QXX的片選信號
//////////////////////////////////////////////////////////////////////////////////
//指令表
#defineW25X_WriteEnable 0x06
#defineW25X_WriteDisable 0x04
#defineW25X_ReadStatusReg 0x05
#defineW25X_WriteStatusReg 0x01
#defineW25X_ReadData 0x03
#defineW25X_FastReadData 0x0B
#defineW25X_FastReadDual 0x3B
#defineW25X_PageProgram 0x02
#defineW25X_BlockErase 0xD8
#defineW25X_SectorErase 0x20
#defineW25X_ChipErase 0xC7
#defineW25X_PowerDown 0xB9
#defineW25X_ReleasePowerDown 0xAB
#defineW25X_DeviceID 0xAB
#defineW25X_ManufactDeviceID 0x90
#defineW25X_JedecDeviceID 0x9F
voidW25QXX_Init(void);
u16 W25QXX_ReadID(void); //讀取FLASH ID
u8 W25QXX_ReadSR(void); //讀取狀態寄存器
voidW25QXX_Write_SR(u8 sr); //寫狀態寄存器
voidW25QXX_Write_Enable(void); //寫使能
voidW25QXX_Write_Disable(void); //寫保護
voidW25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
voidW25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead); //讀取flash
voidW25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//寫入flash
voidW25QXX_Erase_Chip(void); //整片擦除
voidW25QXX_Erase_Sector(u32 Dst_Addr); //扇區擦除
voidW25QXX_Wait_Busy(void); //等待空閑
voidW25QXX_PowerDown(void); //進入掉電模式
voidW25QXX_WAKEUP(void); //喚醒
#endif
W25qxx.c
#include"w25qxx.h"
#include"spi.h"
#include"delay.h"
#include"usart.h"
u16W25QXX_TYPE=W25Q128; //默認是W25Q128
//4Kbytes為一個Sector
//16個扇區為1個Block
//W25Q128
//容量為16M字節,共有128個Block,4096個Sector
//初始化SPI FLASH的IO口
voidW25QXX_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB時鐘
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);//使能GPIOG時鐘
//GPIOB14
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_14;//PB14
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;//輸出
GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽輸出
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_7;//PG7
GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化
GPIO_SetBits(GPIOG,GPIO_Pin_7);//PG7輸出1,防止NRF干擾SPI FLASH的通信
W25QXX_CS=1; //SPI FLASH不選中
SPI1_Init(); //初始化SPI
SPI1_SetSpeed(SPI_BaudRatePrescaler_4); //設置為21M時鐘,高速模式
W25QXX_TYPE=W25QXX_ReadID(); //讀取FLASH ID.
}
//讀取W25QXX的狀態寄存器
//BIT7 6 5 4 3 2 1 0
//SPR RV TBBP2 BP1 BP0 WEL BUSY
//SPR:默認0,狀態寄存器保護位,配合WP使用
//TB,BP2,BP1,BP0:FLASH區域寫保護設置
//WEL:寫使能鎖定
//BUSY:忙標記位(1,忙;0,空閑)
//默認:0x00
u8W25QXX_ReadSR(void)
{
u8 byte=0;
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_ReadStatusReg); //發送讀取狀態寄存器命令
byte=SPI1_ReadWriteByte(0Xff); //讀取一個字節
W25QXX_CS=1; //取消片選
return byte;
}
//寫W25QXX狀態寄存器
//只有SPR,TB,BP2,BP1,BP0(bit7,5,4,3,2)可以寫!!!
voidW25QXX_Write_SR(u8 sr)
{
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_WriteStatusReg); //發送寫取狀態寄存器命令
SPI1_ReadWriteByte(sr); //寫入一個字節
W25QXX_CS=1; //取消片選
}
//W25QXX寫使能
//將WEL置位
voidW25QXX_Write_Enable(void)
{
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_WriteEnable); //發送寫使能
W25QXX_CS=1; //取消片選
}
//W25QXX寫禁止
//將WEL清零
voidW25QXX_Write_Disable(void)
{
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_WriteDisable); //發送寫禁止指令
W25QXX_CS=1; //取消片選
}
//讀取芯片ID
//返回值如下:
//0XEF13,表示芯片型號為W25Q80
//0XEF14,表示芯片型號為W25Q16
//0XEF15,表示芯片型號為W25Q32
//0XEF16,表示芯片型號為W25Q64
//0XEF17,表示芯片型號為W25Q128
u16W25QXX_ReadID(void)
{
u16 Temp = 0;
W25QXX_CS=0;
SPI1_ReadWriteByte(0x90);//發送讀取ID命令
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
Temp|=SPI1_ReadWriteByte(0xFF)<<8;
Temp|=SPI1_ReadWriteByte(0xFF);
W25QXX_CS=1;
return Temp;
}
//讀取SPIFLASH
//在指定地址開始讀取指定長度的數據
//pBuffer:數據存儲區
//ReadAddr:開始讀取的地址(24bit)
//NumByteToRead:要讀取的字節數(最大65535)
voidW25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u16i;
W25QXX_CS=0; //使能器件
SPI1_ReadWriteByte(W25X_ReadData); //發送讀取命令
SPI1_ReadWriteByte((u8)((ReadAddr)>>16)); //發送24bit地址
SPI1_ReadWriteByte((u8)((ReadAddr)>>8));
SPI1_ReadWriteByte((u8)ReadAddr);
for(i=0;i { pBuffer[i]=SPI1_ReadWriteByte(0XFF); //循環讀數 } W25QXX_CS=1; } //SPI在一頁(0~65535)內寫入少于256個字節的數據 //在指定地址開始寫入最大256字節的數據 //pBuffer:數據存儲區 //WriteAddr:開始寫入的地址(24bit) //NumByteToWrite:要寫入的字節數(最大256),該數不應該超過該頁的剩余字節數!!! voidW25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u16i; W25QXX_Write_Enable(); //SET WEL W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_PageProgram); //發送寫頁命令 SPI1_ReadWriteByte((u8)((WriteAddr)>>16)); //發送24bit地址 SPI1_ReadWriteByte((u8)((WriteAddr)>>8)); SPI1_ReadWriteByte((u8)WriteAddr); for(i=0;i W25QXX_CS=1; //取消片選 W25QXX_Wait_Busy(); //等待寫入結束 } //無檢驗寫SPI FLASH //必須確保所寫的地址范圍內的數據全部為0XFF,否則在非0XFF處寫入的數據將失敗! //具有自動換頁功能 //在指定地址開始寫入指定長度的數據,但是要確保地址不越界! //pBuffer:數據存儲區 //WriteAddr:開始寫入的地址(24bit) //NumByteToWrite:要寫入的字節數(最大65535) //CHECKOK voidW25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u16 pageremain; pageremain=256-WriteAddr%256; //單頁剩余的字節數 if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256個字節 while(1) { W25QXX_Write_Page(pBuffer,WriteAddr,pageremain); if(NumByteToWrite==pageremain)break;//寫入結束了 else //NumByteToWrite>pageremain { pBuffer+=pageremain; WriteAddr+=pageremain; NumByteToWrite-=pageremain; //減去已經寫入了的字節數 if(NumByteToWrite>256)pageremain=256;//一次可以寫入256個字節 elsepageremain=NumByteToWrite; //不夠256個字節了 } }; } //寫SPIFLASH //在指定地址開始寫入指定長度的數據 //該函數帶擦除操作! //pBuffer:數據存儲區 //WriteAddr:開始寫入的地址(24bit) //NumByteToWrite:要寫入的字節數(最大65535) u8W25QXX_BUFFER[4096]; voidW25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite) { u32 secpos; u16 secoff; u16 secremain; u16i; u8 * W25QXX_BUF; W25QXX_BUF=W25QXX_BUFFER; secpos=WriteAddr/4096;//扇區地址 secoff=WriteAddr%4096;//在扇區內的偏移 secremain=4096-secoff;//扇區剩余空間大小 //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//測試用 if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096個字節 while(1) { W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//讀出整個扇區的內容 for(i=0;i { if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除 } if(i { W25QXX_Erase_Sector(secpos);//擦除這個扇區 for(i=0;i { W25QXX_BUF[i+secoff]=pBuffer[i]; } W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//寫入整個扇區 }elseW25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//寫已經擦除了的,直接寫入扇區剩余區間. if(NumByteToWrite==secremain)break;//寫入結束了 else//寫入未結束 { secpos++;//扇區地址增1 secoff=0;//偏移位置為0 pBuffer+=secremain; //指針偏移 WriteAddr+=secremain;//寫地址偏移 NumByteToWrite-=secremain; //字節數遞減 if(NumByteToWrite>4096)secremain=4096; //下一個扇區還是寫不完 elsesecremain=NumByteToWrite; //下一個扇區可以寫完了 } }; } //擦除整個芯片 //等待時間超長... voidW25QXX_Erase_Chip(void) { W25QXX_Write_Enable(); //SET WEL W25QXX_Wait_Busy(); W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_ChipErase); //發送片擦除命令 W25QXX_CS=1; //取消片選 W25QXX_Wait_Busy(); //等待芯片擦除結束 } //擦除一個扇區 //Dst_Addr:扇區地址 根據實際容量設置 //擦除一個山區的最少時間:150ms voidW25QXX_Erase_Sector(u32 Dst_Addr) { //監視falsh擦除情況,測試用 printf("fe:%x\r\n",Dst_Addr); Dst_Addr*=4096; W25QXX_Write_Enable(); //SET WEL W25QXX_Wait_Busy(); W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_SectorErase); //發送扇區擦除指令 SPI1_ReadWriteByte((u8)((Dst_Addr)>>16)); //發送24bit地址 SPI1_ReadWriteByte((u8)((Dst_Addr)>>8)); SPI1_ReadWriteByte((u8)Dst_Addr); W25QXX_CS=1; //取消片選 W25QXX_Wait_Busy(); //等待擦除完成 } //等待空閑 voidW25QXX_Wait_Busy(void) { while((W25QXX_ReadSR()&0x01)==0x01); // 等待BUSY位清空 } //進入掉電模式 void W25QXX_PowerDown(void) { W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_PowerDown); //發送掉電命令 W25QXX_CS=1; //取消片選 delay_us(3); //等待TPD } //喚醒 voidW25QXX_WAKEUP(void) { W25QXX_CS=0; //使能器件 SPI1_ReadWriteByte(W25X_ReleasePowerDown); // send W25X_PowerDown command 0xAB W25QXX_CS=1; //取消片選 delay_us(3); //等待TRES1 }
上一篇:【stm32f407】CAN總線
下一篇:【stm32f407】I2C實驗
推薦閱讀
史海拾趣
Galil Motion Control Inc. 的五個發展故事
故事一:技術創新引領者
Galil Motion Control Inc.,成立于1983年,位于美國加州硅谷中心,是電子行業中運動控制技術的先驅。公司成立初期,便致力于研發基于微處理器的精密運動控制器。據《DESIGNNEWS》雜志報道,Galil是全球第一家推出數字運動控制器的公司,這一技術創新在業界引起了巨大反響。此后,Galil不斷推陳出新,其產品已發展到第五代,采用RISC結構的DSP技術,結合最新控制理論及網絡技術,使得伺服更新速率和指令執行周期顯著提升,引領了運動控制領域的技術潮流。
故事二:全球市場的擴展
隨著技術的不斷成熟,Galil的產品逐漸在全球市場上占據了一席之地。目前,全球有超過500,000臺Galil的運動控制器在穩定運行,應用領域覆蓋了醫療、半導體、紡織、物料搬運、食品加工、機床、產業機械、航天、測試測量等多個行業。這一廣泛的應用不僅證明了Galil產品的可靠性和穩定性,也為其在全球市場的擴展奠定了堅實的基礎。
故事三:分布式運動控制器的誕生
在進入21世紀之初,Galil公司又開發出了DMC3425分布式運動控制器。這款控制器不僅提供了API函數支持高級應用開發,還引入了極其簡單易學的2字符命令集,使得應用編程變得如同書寫英文字母般簡單。這一創新不僅降低了用戶的學習成本,也極大地提高了系統的開發效率,進一步鞏固了Galil在運動控制領域的領先地位。
故事四:與光纖激光切割技術的結合
近年來,隨著激光切割技術的快速發展,Galil公司也緊跟這一趨勢,將運動控制技術與光纖激光切割技術相結合。通過設計以Galil控制卡為核心的四軸聯動伺服控制系統,實現了激光切割設備的高精度控制。這一結合不僅提升了激光切割設備的性能,也拓展了Galil運動控制技術的應用領域,為工業制造帶來了更多的可能性。
故事五:全球銷售與服務網絡的建立
為了更好地服務全球客戶,Galil公司在全球范圍內建立了完善的銷售和服務網絡。目前,Galil擁有眾多銷售代理商,遍布世界各地。這些代理商不僅負責產品的銷售工作,還為客戶提供專業的技術支持和售后服務。通過這一網絡,Galil能夠迅速響應客戶的需求,確保每位客戶都能獲得最佳的產品體驗和服務保障。這種以客戶為中心的服務理念,也為Galil贏得了良好的市場口碑和廣泛的客戶認可。
在追求經濟效益的同時,Esterline Power Systems也積極履行社會責任,注重綠色環保和可持續發展。公司采用環保材料和生產工藝,降低生產過程中的能源消耗和廢棄物排放。此外,公司還積極參與環保公益活動,推動綠色能源的發展和應用。這些舉措不僅提升了公司的社會形象,也為公司贏得了更多客戶的信任和支持。
DMS自創立之初,就致力于電子元器件的技術創新。公司投入大量研發資源,成功開發出一系列高性能、低成本的電子元器件,這些產品迅速在市場上獲得了認可。隨著技術的不斷進步,DMS不斷推出新產品,滿足了客戶日益增長的需求,逐漸在電子行業中樹立了技術領先的形象。
隨著業務的不斷發展,Desco Tools公司開始積極拓展全球市場。公司利用其在防靜電技術方面的優勢,成功打入歐美、亞洲等多個國家和地區的市場。通過與國際知名企業的合作,Desco Tools公司的產品和解決方案得到了廣泛應用,為全球電子行業的發展做出了重要貢獻。
面對日益激烈的市場競爭和不斷變化的市場需求,Electric Imp Inc.公司始終保持著敏銳的市場洞察力和持續的創新精神。公司不斷推出新產品、新技術,以滿足不同客戶的需求。同時,公司還加強了內部管理和團隊建設,提高了員工的專業素質和工作效率。這些努力使得公司能夠保持持續的發展態勢,并在電子行業中保持領先地位。
隨著第一款產品的成功,Electric Imp Inc.公司開始積極拓展市場。公司與多家知名廠商建立了合作關系,將物聯網設備管理平臺應用于智能家居、工業自動化、智慧城市等領域。同時,公司還加強了與高校、科研機構的合作,共同研發新技術、新產品。這些努力使得公司在物聯網領域的影響力逐漸擴大。
我一直有個疑惑,我們都知道內存中的數據是分段存儲的,但是我就一直不能理解,程序編譯之后,源程序中的變量名是否也編譯成為數據值了,而且這個變量的指針是由誰指向的,當變量名也被編譯為值的情況下,必然會導致內存的占用,是否就是說,從節約 ...… 查看全部問答∨ |
在eeworld里面有兩個月了,認識了好多朋友,也學到了很多,多謝eeworld的網友們,新的一年,繼續努力------- 宇帆嵌入式交流群1,氣氛很好,能給大家提供一個技術交流群,很高興,嘿嘿!現在正式推出宇帆嵌入式交流群2, 群號61273439,歡迎大家多 ...… 查看全部問答∨ |
void delay_5us(void) { TBCTL = TBSSEL_2 + TBCLR; /*SMCLK, SMCLK為8M, up mode*/ TBCCTL0 = CCIE; TBCCR0=39; /*8,000,000/200,000-1=CCRO。用來確定 PWM周期5us ...… 查看全部問答∨ |
|
國內電動汽車(電動客車)制造商使用的驅動電機主要有三款,1.直流有刷電機(串勵電機和他勵電機)。2.直流永磁無刷電機。3.交流感應電機。三者向比較:直流有刷電機,優點是扭矩大,驅動控制相對簡單,國內的控制器廠家也有不少,缺點是體積 ...… 查看全部問答∨ |
|
設計資源 培訓 開發板 精華推薦
- 文遠知行采用BlackBerry QNX系統,打造極致安全的ADAS解決方案
- IAR攜手極海半導體,高效開發全球首款基于Cortex-M52的G32R501實時控制MCU
- 中國發布HDMI和DisplayPort替代方案:GPMI接口來了
- 三星進軍 AI 機器人領域,Ballie 本周公開亮相
- 消息稱美光即日起針對存儲模組產品向美國客戶征收“關稅附加費”
- 迅為IMX6ULL開發板交叉編譯器的安裝和使用
- 迅為-i.MX6ULL 開發板-移植OpenCv3.4.1-搭建編譯環境
- BOE(京東方)董事長提議回購公司股份 堅定看好資本市場長期價值
- 英飛凌宣布收購Marvell的汽車以太網業務
- 強強聯合!兆易創新與納微半導體達成戰略合作
- 系列二:TI模擬芯片選型指南(參與過系列一活動的也可以參加這期喲)
- 3小時實戰+剖析:TI工程師手把手帶你全面入門MSP430(限量售$4.3開發套件,助你參與動手實驗)
- 答題贏獎:日圖科技低功耗測試系統介紹
- 有獎直播|英飛凌OBC解決方案深度詳解 報名中!
- 【歡樂新年精彩共享】優秀主題/回復活動第19期如期到來啦~~
- 讀故事寫評語嘍~ 踩過坑的你是否期待更貼心智能的客戶支持,KeysightCare邀您搶樓贏禮!
- PI 有獎直播:在無需DC-DC變換器的多路輸出電源設計中實現高效率和出色的輸出調整率
- 幫忙他人,成就自我——EEWORLD問答榜(10期)
- 平頭哥RVB2601開源應用方案征集來啊~100套板卡助陣,天貓精靈智能套裝禮品組等你抱走~