STM32给FPGA的外挂FLASH进行升级
前言:
一个复杂的嵌入式中,如果对某些实时性要求极高的情况下势必会使用到FPGA来保证,这里面牵扯到给FPGA的程序升级问题,一般采用负责逻辑处理的一个MCU来完成,实际就是对存储FPGA的这个flash进行读写操作。
一、电路方案设计
1、FLASH的(MOSI/MISO/CLK/CS)既要连接FPGA又要连接STM32,所以要设计一个单刀双置的开关。(注意:FLASH_D2和FLASH_D3是直接接入FPGA的,因为FPGA需要使用flash的四线模式)
二、软件写FLASH
1、mx25l.c
#include <stdio.h>
#include "mx25l.h"
/******************************************************
* @brief SPI写数据
* @param data 要写入mx25l的数据
* @retval 返回一个字节
******************************************************/
uint8_t SPI1_WriteByte(uint8_t data)
{
//等待发送完成
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
//发送数据
SPI_I2S_SendData(SPI1, data);
//等待接收完成
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
/******************************************************
* @brief SPI读数据
* @param 空
* @retval 返回读取到的字节
******************************************************/
uint8_t SPI1_ReadByte(void)
{
return(SPI1_WriteByte(0xff));
}
/********************************
* @brief mx25l的写使能
* @param None
* @retval None
******************************/
void Write_enable(void)
{
//CS=0, 选中芯片
FLASH_CS_L();
//MCU发送写使能命令0x06 SPI_Sendbyte
SPI1_WriteByte(0x06);
//CS=1;//释放芯片
FLASH_CS_H();
}
/********************************
* @brief mx25l的写失能
* @param None
* @retval None
******************************/
void Write_disable(void)
{
//CS=0, 选中芯片
FLASH_CS_L();
//MCU发送写失能命令0x04 SPI_Sendbyte
SPI1_WriteByte(0x04);
//CS=1;//释放芯片
FLASH_CS_H();
}
/****************************************
* @brief 读mx25l状态寄存器
* @param None
* @retval None
**************************************/
void Read_ststus(void)
{
u8 sta=0;//接收状态寄存器的值
//CS=0;//选中芯片
FLASH_CS_L();
//MCU发送读状态命令 0x05
SPI1_WriteByte(0x05);
do{
sta = SPI1_ReadByte();
}while((sta&(1<<0))==1);
//CS=1;//释放芯片
FLASH_CS_H();
}
#if 0
u8 SPI_Flash_ReadSR(void)
{
u8 byte=0;
FLASH_CS_L(); //使能器件
SPI1_WriteByte(0x05); //发送读取状态寄存器命令
byte=SPI1_ReadByte(); //读取一个字节
FLASH_CS_H(); //取消片选
return byte;
}
void SPI_Flash_Wait_Busy(void)
{
while ((SPI_Flash_ReadSR()&0x01)==0x01); // 等待WIP位清空
}
#endif
/*************************************************************************
* @brief 写mx25l状态寄存器QEbit,防止空板第一次下载程序FPGA无法启动现象
* @param None
* @retval None
************************************************************************/
void QEBit_Set(void)
{
u8 sta=0; // 接收状态寄存器的值
//写使能
Write_enable();
//CS=0;//选中芯片
FLASH_CS_L();
//MCU发送读状态命令 0x05
SPI1_WriteByte(0x05);
sta = SPI1_ReadByte();
//CS=1;//释放芯片
FLASH_CS_H();
sta |= 0x40;
//CS=0;//选中芯片
FLASH_CS_L();
//MCU发送写状态命令 0x01
SPI1_WriteByte(0x01);
SPI1_WriteByte(sta);
//CS=1;//释放芯片
FLASH_CS_H();
Write_disable();
}
/****************************************************
* @brief MCU 写数据到 mx25l
* @param addr 24位地址
* @param data 要写入的数据
* @retval None
* @attention 写数据之前一定要擦除芯片
**************************************************/
void Mx25l_WriteByte(uint32_t addr, uint8_t data)
{
//打开写使能
Write_enable();
//CS=0;//选中芯片
FLASH_CS_L();
//MCU发送页编程命令 0x02
SPI1_WriteByte(0x02);
//发送24位地址
SPI1_WriteByte((u8)(addr>>16));
SPI1_WriteByte((u8)(addr>>8));
SPI1_WriteByte((u8)(addr));
//MCU发送写入的数据
SPI1_WriteByte(data);
//CS=1;//释放芯片
FLASH_CS_H();
//读状态
Read_ststus();
}
/****************************************************
* @brief MCU页写数据到 mx25l
* @param pBuffer 要写的数组
* @param addr 24位地址
* @param Nb_bytes 要写入的数据长度
* @retval None
* @attention 写数据之前一定要擦除芯片
**************************************************/
void Mx25l_Write_Page(u8* pBuffer, u32 addr, u16 Nb_bytes)
{
u16 i;
Write_enable(); //SET WEL
FLASH_CS_L(); //使能器件
SPI1_WriteByte(0x02); //发送写页命令
SPI1_WriteByte((u8)(addr>>16)); //发送24bit地址
SPI1_WriteByte((u8)(addr>>8));
SPI1_WriteByte((u8)(addr));
for(i=0; i<Nb_bytes; i++)
{
SPI1_WriteByte(pBuffer[i]); //循环写数
}
FLASH_CS_H(); //取消片选
Read_ststus(); //等待写入结束
}
/****************************************************
* @brief MCU 无校验写数据到 mx25l
* @param pBuffer 要写的数组
* @param addr 24位地址
* @param Nb_bytes 要写入的数据长度
* @retval None
* @attention 写数据之前一定要擦除芯片
**************************************************/
void Mx25l_Write_NoCheck(u8* pBuffer,u32 addr,u16 Nb_bytes)
{
u16 PageRemain;
PageRemain = 256 - addr%256; //单页剩余的字节数
if(Nb_bytes <= PageRemain) { //不大于256个字节
Mx25l_Write_Page(pBuffer, addr, Nb_bytes);
}else {
Mx25l_Write_Page(pBuffer, addr, PageRemain);
addr += PageRemain;
Mx25l_Write_Page(pBuffer+PageRemain, addr, Nb_bytes-PageRemain);
}
}
/*******************************************************
* @brief MCU从 mx25l 读数据
* @param addr 24位地址
* @param data 存放读出的数据
* @param size 要读的字节长度(不要太大,否则RAM承受不了)
* @retval None
*****************************************************/
void Mx25l_Read_data(u32 addr, u8 *data, u32 size)
{
uint32_t i=0;//接收数据的循环变量
//CS=0;//选中芯片
FLASH_CS_L();
//MCU发送读数据命令 0x03
SPI1_WriteByte(0x03);
//MCU发送开始读的地址
SPI1_WriteByte((u8)(addr>>16));
SPI1_WriteByte((u8)(addr>>8));
SPI1_WriteByte((u8)(addr));
//MCU接收数据
for(i=0; i<size; i++)
{
data[i] = SPI1_ReadByte();
}
//CS=1;//释放芯片
FLASH_CS_H();
}
/******************************************************************
* @brief Sector Write
* @param nSector 第几个扇区
* @param pBuffer 数据
* @retval None
*****************************************************************/
void Mx25l_Write_Sector(uint32_t nSector, uint8_t* pBuffer)
{
int i,j;
//扇区号转为地址
nSector *= FLASH_SECTOR_SIZE;
//一个扇区需要几个页
for(j=0; j<FLASH_PAGES_PER_SECTOR; j++)
{
Write_enable(); //SET WEL
FLASH_CS_L();
SPI1_WriteByte(0x02);
SPI1_WriteByte((nSector >> 16) & 0xff);
SPI1_WriteByte((nSector >> 8) & 0xff);
SPI1_WriteByte(nSector & 0xff);
for(i=0; i<FLASH_PAGE_SIZE; i++)
{
SPI1_WriteByte(pBuffer[i]);
}
pBuffer += FLASH_PAGE_SIZE;
nSector += FLASH_PAGE_SIZE;
FLASH_CS_L();
Read_ststus();
}
}
/******************************************************************
* @brief Sector Read
* @param nSector 第几个扇区
* @param pBuffer 存放数据
* @retval None
*****************************************************************/
void Mx25l_Read_Sector(uint32_t nSector, u8* pBuffer)
{
uint16_t i;
//扇区号转为地址
nSector *= FLASH_SECTOR_SIZE;
FLASH_CS_L();
SPI1_WriteByte(0x03);
SPI1_WriteByte((u8)(nSector >> 16));
SPI1_WriteByte((u8)(nSector >> 8));
SPI1_WriteByte((u8)(nSector));
for(i=0;i<FLASH_SECTOR_SIZE;i++)
{
pBuffer[i] = SPI1_ReadByte();
//usb_printf("%02x, ", pBuffer[i]);
}
FLASH_CS_L();
Read_ststus();
}
/*******************************
* @brief 擦除整个芯片
* @param None
* @retval None
*****************************/
void Chip_erase(void)
{
//printf("chip erase starting...\r\n");
Write_enable();
//CS=0;//选中芯片
FLASH_CS_L();
//MCU发送芯片擦除命令 0x60
SPI1_WriteByte(0x60);
//CS=1;//释放芯片
FLASH_CS_H();
//读状态
Read_ststus();
//printf("chip erase complete...\r\n");
}
2、mx25l.h
#ifndef _MX25L_H_
#define _MX25L_H_
#include "stm32f10x.h"
#include "stm32f10x_spi.h"
#define FLASH_PAGE_SIZE 256
#define FLASH_SECTOR_SIZE 4096
#define FLASH_SECTOR_COUNT 4096
#define FLASH_BLOCK_SIZE 65536
#define FLASH_PAGES_PER_SECTOR FLASH_SECTOR_SIZE/FLASH_PAGE_SIZE
#define FLASH_CS_H() GPIO_SetBits(GPIOA, GPIO_Pin_4)
#define FLASH_CS_L() GPIO_ResetBits(GPIOA, GPIO_Pin_4)
uint8_t SPI1_WriteByte(uint8_t data);
uint8_t SPI1_ReadByte(void);
void Write_enable(void);
void Write_disable(void);
void Read_ststus(void);
void QEBit_Set(void);
void Mx25l_WriteByte(uint32_t addr,uint8_t data);
void Mx25l_Write_Page(u8* pBuffer, u32 addr, u16 Nb_bytes);
void Mx25l_Write_NoCheck(u8* pBuffer,u32 addr,u16 Nb_bytes);
void Mx25l_Read_data(uint32_t addr, uint8_t *data, uint32_t size);
void Mx25l_Write_Sector(uint32_t nSector, uint8_t* pBuffer);
void Mx25l_Read_Sector(uint32_t nSector, u8* pBuffer);
void Chip_erase(void);
#endif
三、解决第一次烧录后FPGA无法启动的问题
1、这个问题的原因是因为FPGA编译使用的flash是4线模式,而flash默认是1线模式,所以升级完之后调用QEBit_Set();配饰flash为4线模式。