1、学会 STM32 硬件 SPI
2、学会对 EN25Q64 进行读写操作
10.1 EN25Q64 简介
EN25Q64 是华邦公司推出的大容量 SPI FLASH 产品,EN25Q64 的容量为64M 比特,也便是说有 8M 字节。EN25Q64 将 8M 的容量分为 128 个块(Block),每个块大小为 64K 字节,每个块又分为 16 个扇区(Sector),每个扇区 4K 个字节。EN25Q64 的最少擦除单位为一个扇区,也便是每次必须擦除 4K 个字节。EN25Q64 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出时相称于 160Mhz,四输出时相称于 320M),更多的EN25Q64 的先容,请参考 EN25Q64 的 DATASHEET。
10.2 SPI 简介
从上面的简介我们知道,EN25Q64 是利用 SPI 来通信的。那什么是 SPI 呢?SPI 是英语 Serial Peripheral interface 的缩写,顾名思义便是串行外围设备接口,SPI 接口紧张用四根线进行通信:
1、MISO:主设备数据输入,从设备数据输出。
2、MOSI:主设备数据输出,从设备数据输入。
3、SCLK:时钟旗子暗记,由主设备产生。
4、CS:从设备片选旗子暗记,由主设备掌握。
而常日意义上,SPI 的通信只用三根线就可以了,一根时钟线、一根输出、一根输入。为了更好理解 SPI 的传输事理,我们来看一下 SPI 的内部构造:
从图上可以有知道,SPI 数据的传输过程实在是通过一个移位寄存器来完成的,主机将自己的移位寄存器的数据移出,同时从机的移位寄存器数据移入,同时将自己的数据移出。大略的来理解,就像将两个寄存器贴在一起,然后进行循环左移或者循环右移(SPI 的传输可以选择先发送高位还是先发送低位。),直到两个寄存器的数据交流为止。而时钟旗子暗记 SCLK 便是掌握传输速率的。STM32 内部是给我们供应了一个 SPI 的外设的,那么我们就可以利用单片机的内部的 SPI 来掌握 EN25Q64 了。
10.3 EN25Q64 的事理图
从上面的事理图,我们创造,实在 EN25Q64 是利用了 STM32 单片机的 SPI2的接口(我们开拓板上面利用的单片机是拥有两个 SPI 外设的。),而 EN25Q64的片选掌握端利用的是 PG13。
10.4 SPI 库函数先容
我们知道了 EN25Q64 的用场和通信办法,那么接下来我们就看下怎么对EN25Q64 进行读写呢?
1、打开时钟使能。我们要利用 GPIOB、GPIOG 和 SPI 以是要打开三个时钟。代码为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
2、配置 GPIO 口的模式。
这这里我们要打开 GPIOG 和 GPIOB 的时钟,然后将片选端 PG13 设置为推挽输出,然后设置 SPI 利用的 GPIO 口,将 PB13、PB14 和 PB15 设置为复用推挽输入。大家可能迷惑为什么 PB14 也设置为输出呢?它不是该当输入吗?在这里它是作为复用端口了,以是设置为复用推挽输出便是跟外设连接,要外设自己来决定输入输出了。那么我们要利用的库函数大家都很熟习了,就不详细先容了。代码为:
/ flash_cs 的 IO 口设置 PG13 /GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(GPIOG, &GPIO_InitStructure);/ SPI 的 IO 口设置 /GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);
3、设置 SPI 参数。
1) SPI_Init ()函数这个函数可以用来初始化 SPI 外设。它有两个参数:
第一个参数:选择你要设置的 SPI,我们要利用 SPI2 以是设置为:SPI2。
第二个参数:通报一个构造体,用于设置 SPI 的参数。它一共有 9个成员:
A)SPI_Direction:表示 SPI 的方向,我们要利用的是双线双向全双工,以是我们设置为:SPI_Direction_2Lines_FullDuplex。
B)SPI_Mode:表示 SPI 的模式,我们利用主机模式,以是设置为:SPI_Mode_Master。
C)SPI_DataSize:表示传输数据的长度,我们利用 8 位数据传输,以是设置为:SPI_DataSize_8b。
D)SPI_CPOL:表示在空闲的时候时钟线是高电平还是低电平。我们选择高:SPI_CPOL_High。
E)SPI_CPHA:选择采样延迟。我们选择从第二个时钟开始采样,以是设置为:SPI_CPHA_2Edge。
F)SPI_NSS:表示是否用硬件掌握片选端。我们的片选其余利用的PG13,以是不用。以是设置为软件掌握:SPI_NSS_Soft。
G)SPI_BaudRatePrescaler:表示时钟波特率的分频,用来设置 SPI的速率。我们设置为 256 分频:SPI_BaudRatePrescaler_256。
H)SPI_FirstBit:表示 SPI 的第一位,我们利用先发送高位,以是设置为:SPI_FirstBit_MSB。
I)SPI_CRCPolynomial:表示 CRC 效验的设置,我们利用 7 位 CRC,以是设置为:7。
以是代码为:
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//选择全双工 SPI 模式SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主机模式SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8 位 SPISPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //时钟悬空高电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //在第二个时钟采集数据SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//Nss 利用软件掌握/ 选择波特率预分频为 256 /SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//从最高位开始传输SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(SPI2, &SPI_InitStructure);
2)打开 SPI 的使能。我们可以利用 SPI_Cmd()函数。它有两个参数:
第一个参数选择要设置的 SPI,我们选择 SPI2。
第二个参数用来设置 SPI 的状态,我们选择 ANABLE。
4、利用 SPI 读写。
从上面 SPI 的简介我们知道,SPI 发送的同时,也吸收数据。以是我们利用 SPI 读写的时候,发送的同时也吸收。
1)SPI_I2S_SendData()函数。
这个函数用来发送数据的,它有两个参数:
第一个参数选择要利用的 SPI,我们选择 SPI2。
第二个参数用来通报我们要发送的 8 位数据。
2)SPI_I2S_GetFlagStatus()函数.这个函数用来检测 SPI 的状态,一样平常在发送和吸收之前都要检测 SPI的状态,发送时要检测 SPI 的发送缓冲器是否为空,吸收时检测吸收缓冲器是否为非空。这个函数有两个参数:
第一个参数选择要检测的 SPI,我们选择 SPI2。
第二个参数用来通报我们要检测的状态。检测发送缓冲器为空的时候设置为:SPI_I2S_FLAG_TXE;检测吸收缓冲器是否为非空的时候设置为:SPI_I2S_FLAG_RXNE。
同时它返回一个状态值,状态涌现返回:SET(非零);否者返回:RESET(0)。以是利用 SPI 读写我们可以写为:
uint8_t SPI2_WriteReadData(uint8_t dat){uint16_t i = 0;/ 当发送缓冲器空 /while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET){i++;if(i > 10000){eturn 0xFF;}}/ 发送数据 /SPI_I2S_SendData(SPI2, dat);/ 等待吸收缓冲器为非空 /while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) ==RESET);/ 将读取到的数值返回 /return SPI_I2S_ReceiveData(SPI2);}
10.5 EN25Q64 操作
我们在进行 EN25Q64 操作的时候,须要用到 EN24Q64 的命令。它的命令表
如下:
1. EN25Q64 读取 ID 操作
我们知道了,怎么跟 EN25Q64 通信之后,我们首先要对 EN25Q64 进行初始化,一样平常来说 EN25Q64 是不须要什么初始化的,不过我们为了确定芯片,我们先来读取一次芯片的 ID。从上面的命令表,我们知道读取芯片 ID 的命令是 90H,命令发送的第 1个字节是命令字节,然后第 2、3、4 字节是要么是伪字节要么是无用字节,在第 5 个字节返复临盆商 ID(即 M7-M0),第 6 个字节返回器件 ID(即ID7-ID0),我们利用的是 EN25Q64,生产商 ID 是 0x1C,器件 ID 是 0x16,合起来的 ID 便是 0x1C16 读取芯片的操作步骤如下:
1)把 CS 引脚拉低,选择片选。
2)把指令 90H 发送到芯片。
3)发送 3 个无用字节。
4)读取生产商 ID
5)读取器件 ID
6)把 CS 引脚拉高,取消片选。
代码我们可以写为:
uint16_t FLASH_ReadID(void){uint16_t id = 0;FLASH_CS_CLR; //打开片选SPI2_WriteReadData(0x90);//发送读取 ID 命令SPI2_WriteReadData(0x00);SPI2_WriteReadData(0x00);SPI2_WriteReadData(0x00);id |= SPI2_WriteReadData(0xFF) << 8; //读取 16 位 IDid |= SPI2_WriteReadData(0xFF);FLASH_CS_SET;//关闭片选return id;}
2. EN25Q64 读取数据命令表格中,EN25Q64 读取数据的命令是 0x03。第 1 个字节是命令字节,第 2、3、4 个字节是读取地址,从第 5 个字节开始返回数据,如果接着读,会返回下一个字节的数据。操作步骤如下:
1) 检测器件是否未处于忙状态在器件在实行“页编辑”“扇区擦除”“块区擦除”“芯片擦除”“写状态寄存器”指令的时候,除了“读状态寄存器”指令,其他指令都忽略。以是在实行写入之前我们要先查看器件是否处于忙状态。这个忙状态标志如下图:
它是掌握和状态寄存器的第 0 位。读状态寄存器的指令是 0x05,从命令表格中,我们可以看出第 1 个字节是命令字节,第 2 个字节返回状态寄存器,
以是读取状态寄存器的步骤为:
a)选择片选。
b)发送一个字节掌握指令 0x05。
c)读取一个字节状态寄存器。
d)取消片选。
程序实现为:
static uint8_t FLASH_CheckBusy(void){uint8_t statusValue;uint32_t timeCount = 0;do{timeCount++;if(timeCount > 0xEFFFFFFF)//等待超时{return 0xFF;}FLASH_CS_CLR; //使能器件SPI2_WriteReadData(EN25X_ReadStatusReg); //发送读取状态寄存器命令statusValue = SPI2_WriteReadData(0xFF); //读取一个字节FLASH_CS_SET; //取消片选}while((statusValue & 0x01) == 0x01); // 等待 BUSY 位清空return 0;}
2)选择片选。
3)发送读取命令 0x03。
4)发送三个字节的地址。
5)读取 n 个字节。
6)取消片选。
程序实现如下:
void FLASH_ReadData(uint8_t readBuff, uint32_t readAddr, uint16_treadByteNum){SPI2_SetSpeed(SPI_BaudRatePrescaler_2);FLASH_CS_CLR; //打开片选/ 写读取命令 /SPI2_WriteReadData(EN25X_ReadData);/ 发送 24 位读取地址 /SPI2_WriteReadData(readAddr >> 16);SPI2_WriteReadData(readAddr >> 8);SPI2_WriteReadData(readAddr);/ 读取数据 /while(readByteNum--){readBuff = SPI2_WriteReadData(0xFF);readBuff++;}FLASH_CS_SET; //关闭片选}
3. EN25Q64 写入数据。
对付 EN25Q64 的写入操作比较繁芜,它只能页编辑,也便是必须一次性写入 256 个字节的数据,而且在进行页编辑的时候,必须担保写入区域位都为 1,便是说如果有数据必须要擦除掉,以是一样平常每次写入都要先擦除数据才能写入。而写入数据的操作步骤为:
1)把稳要担保:要写入的区域,是空的,否则要先擦除。
2)检测器件是否处于忙状态。
3)开启写入使能。在命令表中,写使能命令是 0x06。每次实行编辑和擦除操作的时候,
都须要器件开启写使能。开启的操作为:
a)选择片选。
b)写入一个字节的写使能命令 0x06。
c)取消片选。
程序实现为:
static void FLASH_WriteEnable(void){FLASH_CS_CLR; //使能器件SPI2_WriteReadData(EN25X_WriteEnable); //发送写使能FLASH_CS_SET; //取消片选}
4)选择片选
5)发送 1 个字节的页编辑命令 0x02。
6)发送写入 3 个字节的写入地址。
7)写入 256 个字节。
8)取消片选。
例程中的程序实现如下:
static void FLASH_WritePage(uint8_t writeBuff, uint32_t writeAddr,uint16_t writeByteNum){uint16_t byteNum, i;byteNum = writeAddr % 256;byteNum = 256 - byteNum; //求出首页剩余地址if(writeByteNum <= byteNum)//当写入字节少于首页剩余地址{byteNum = writeByteNum;}/ 开始写入 /while(1){/ 写入数据 /FLASH_CheckBusy();//确定 FLASH 是否处于忙状态FLASH_WriteEnable(); //开启写使能FLASH_CS_CLR; //使能器件SPI2_WriteReadData(EN25X_PageProgram); //发送写命令SPI2_WriteReadData(writeAddr >> 16); //发送 24 位读取地址SPI2_WriteReadData(writeAddr >> 8);SPI2_WriteReadData(writeAddr); for(i=0; i<byteNum; i++) //循环写数{SPI2_WriteReadData(writeBuff);writeBuff++;}FLASH_CS_SET; //取消片选/ 判断是否写完 /if(writeByteNum == byteNum) //如果写入字节数即是剩余字节数表示写入完成{break;}else {//如果未写入完成writeAddr += byteNum;//写入地址偏移writeByteNum = writeByteNum - byteNum;//求出剩余字节数if(writeByteNum >= 256)//如果剩余字节数大于 256,那么一次写入一页{byteNum = 256;}else//如果剩余字节数小于 256,那么一次全部写完{byteNum = writeByteNum;}}}}
4. EN25Q64 擦除操作
由于 EN25Q64 的写入的时候都要担保写入位置都没有数据,以是每次写入数据的时候,总会伴随着擦除操作。器件擦除命令有三种,第一种是“块擦除”,一次擦除 64K 字节数据,即 256 页。第二种是“扇区擦除”,一次擦除 4K 字节数据,即:16 页。第三种是“芯片擦除”,一次性擦除整块芯片。我们这里就讲述“扇区擦除”,实在擦除操作办法都差不多,操作步骤为:
1)检测器件是否未处于忙状态。
2)开启写使能。
3)选择片选。
4)发送 1 个字节的“扇区擦除”命令。
5)发送 3 个字节的扇区擦除地址。
6)取消片选。
程序实现如下:
static void FLASH_SectorErase(uint32_t eraseAddr){FLASH_CheckBusy();//确定 FLASH 是否处于忙状态 FLASH_WriteEnable(); //开启写使能FLASH_CS_CLR; //使能器件SPI2_WriteReadData(EN25X_SectorErase); //发送命令SPI2_WriteReadData(eraseAddr >> 16); //发送 24 位地址SPI2_WriteReadData(eraseAddr >> 8);SPI2_WriteReadData(eraseAddr);FLASH_CS_SET; //取消片选}
10.6 例程主函数
const u8 TEXT_Buffer[19]={"FLASH SPI TEST OK!"};#define SIZE (19-1)int main(void){uint8_t buff[SIZE], showChar[SIZE + 1], j, keyValue, ledState = 0;uint32_t i;/ 初始化 / TFT_Init();KEY_Config(); FLASH_Init();LED_Config();/ 显示初始化 / TFT_ClearScreen(BLACK); while(FLASH_ReadID() != EN25Q64){GUI_Show12Char(75, 0, "FLASH Init ERROR!", RED, BLACK);} TFT_ClearScreen(BLACK); GUI_Show12Char(75, 0, "FLASH Init OK! ", RED, BLACK);GUI_Show12Char(0, 32, "KEY_UP: write KEY_DOWN:read", RED,BLACK);while(1){keyValue = KEY_Scan();/ 根据按键做出反应 /switch(keyValue) {case(KEY_UP): //按上键将"FLASH SPI test OK!"从 FLASH 地址 0 开始写入 FLASH_WriteData((u8)TEXT_Buffer, 0, SIZE); GUI_Show12Char(0, 48, "wirte OK!", RED,BLACK); break; case(KEY_DOWN): //按下键将从 FLASH 地址 0开始读取 18位数,将读取到的显示 FLASH_ReadData(buff, 0, SIZE);for(j=0; j<SIZE; j++) { showChar[j] = buff[j]; } GUI_Show12Char(0, 48, showChar, RED, BLACK); break; default: break; }/ LED 灯闪烁 / i++;if(i > 0x5FFFF) {i = 0;if(ledState == 0xFE){ ledState = 0xFF; }else{ledState = 0xFE; } LED_SetState(ledState);}}}
程序效果是,按 KEY_UP 写入“FLASH SPI TEST OK!”到 EN25Q64 中,KEY_DOWN,读取写入的数据,并显示到 LCD 彩屏上面。