SPI 简介
SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola首先在其 MC68HCXX 系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32F4 也有 SPI 接口。下面我们看看 SPI 的内部简明图(图 30.1.1).

SPI 接口一般使用 4 条线通信:
MISO 主设备数据输入,从设备数据输出。
MOSI 主设备数据输出,从设备数据输入。
SCLK 时钟信号,由主设备产生。
CS 从设备片选信号,由主设备控制。

从图中可以看出,主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器写入一个字节来发起一次传输。寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。
SPI 主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。
SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果 CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI 主模块和与之通信的外设备时钟相位和极性应该一致。
不同时钟相位下的总线数据传输时序如图 32.1.1 所示:

STM32F429 的 SPI 功能很强大,SPI 时钟最高可以到 45Mhz,支持 DMA,可以配置为 SPI协议或者 I2S 协议(支持全双工 I2S)。
本章,我们将使用 STM32F429 的 SPI 来读取外部 SPI FLASH 芯片(W25Q256),实现类似上节的功能。这里对 SPI 我们只简单介绍一下 SPI 的使用,STM32F429 的 SPI 详细介绍请参考《STM32F4xx 中文参考手册》第 721 页,27 节。然后我们再介绍下 SPI FLASH 芯片。
这节,我们使用 STM32F429 的 SPI5 的主模式,下面就来看看 SPI5 部分的设置步骤吧。SPI 相关的库函数和定义分布在文件 stm32f4xx_hal_spi.c 以及头文件 stm32f4xx_hal_spi.h 中。STM32 的主模式配置步骤如下:
1)配置相关引脚的复用功能,使能 SPI5 时钟。
我们要用 SPI5,第一步就要使能 SPI5 时钟和响应引脚时钟。其次要设置 SPI5 的相关引脚
为复用(AF5)输出,这样才会连接到 SPI5 上。这里我们使用的是 PF7、8、9 这 3 个(SCK.、
MISO、MOSI,CS 使用软件管理方式)
,所以设置这三个为复用 IO,复用功能为 AF5。
使能 SPI5 时钟的方法为:

__HAL_RCC_SPI5_CLK_ENABLE();

//使能 SPI5 时钟
复用 PF7,PF8,PF9 为 SPI5 引脚是通过 HAL_GPIO_Init 函数实现,代码如下:

GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
GPIO_Initure.Mode=GPIO_MODE_AF_PP;//复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP;//上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST;//快速
GPIO_Initure.Alternate=GPIO_AF5_SPI5;//复用为 SPI5
HAL_GPIO_Init(GPIOF,&GPIO_Initure);

2)初始化 SPI5,设置 SPI5 工作模式等。
这一步全部是通过 SPI5_CR1 来设置,我们设置 SPI5 为主机模式,设置数据格式为 8 位,然后通过 CPOL 和 CPHA 位来设置 SCK 时钟极性及采样方式。并设置 SPI5 的时钟频率(最大45Mhz),以及数据的格式(MSB 在前还是 LSB 在前)。在 HAL 库中初始化 SPI 的函数为:

HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);

下面我们来看看 SPI_HandleTypeDef 定义:

typedef struct __SPI_HandleTypeDef
{SPI_TypeDef     *Instance;//基地址
SPI_InitTypeDef Init;//初始化接哦固体
uint8_t         *pTxBuffPtr;//发送缓存
uint16_t        TxXferSize;//发送数据大小
uint16_t        TxXferCount;//还剩余多少个数据要发送
uint8_t         *pRxBuffPtr;//接收缓存
uint16_t        RxXferSize;//接收数据大小
uint16_t        RxXferCount;//还剩余多少个数据要接收
DMA_HandleTypeDef *hdmatx;//DMA 发送句柄
DMA_HandleTypeDef *hdmarx;//DMA 接收句柄
void            (*RxISR)(struct __SPI_HandleTypeDef * hspi);
void            (*TxISR)(struct __SPI_HandleTypeDef * hspi);
HAL_LockTypeDef   Lock;
__IO HAL_SPI_StateTypeDef   State;
__IO uint32_t               ErrorCode;
}SPI_HandleTypeDef;

该结构体和串口句柄结构体类似,同样有 6 个成员变量和 2 个 DMA_HandleTypeDef 指针类型变量。这几个参数的作用这里我们就不做过多讲解,大家如果对 HAL 库串口通信理解了,那么这些就很好理解。这里我们主要讲解第二个成员变量 Init,它是 SPI_InitTypeDef 结构体类型,该结构体定义如下:

typedef struct
{uint32_t Mode;// 模式:主(SPI_MODE_MASTER),从(SPI_MODE_SLAVE)uint32_t Direction; //方式: 只接受模式,单线双向通信数据模式,全双工uint32_t DataSize;//8 位还是 16 位帧格式选择项uint32_t CLKPolarity; //时钟极性uint32_t CLKPhase; //时钟相位uint32_t NSS;//SS 信号由硬件(NSS 管脚)还是软件控制uint32_t BaudRatePrescaler; //设置 SPI 波特率预分频值uint32_t FirstBit;//起始位是 MSB 还是 LSBuint32_t TIMode;//帧格式 SPI motorola 模式还是 TI 模式uint32_t CRCCalculation; //硬件 CRC 是否使能uint32_t CRCPolynomial; //CRC 多项式
}SPI_InitTypeDef;

该结构体个个成员变量的含义我们已经在成员变量后面注释了,请大家参考学习。SPI 初
始化实例代码如下:

SPI5_Handler.Instance=SPI5;//SP5
SPI5_Handler.Init.Mode=SPI_MODE_MASTER;//模式:主模式
SPI5_Handler.Init.Direction=SPI_DIRECTION_2LINES; //双线模式
SPI5_Handler.Init.DataSize=SPI_DATASIZE_8BIT;//发送接收 8 位帧结构
SPI5_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //串行同步时钟空闲状态为高电平
SPI5_Handler.Init.CLKPhase=SPI_PHASE_2EDGE;//第二个跳变沿数据被采样
SPI5_Handler.Init.NSS=SPI_NSS_SOFT;//NSS 信号由硬件管理
SPI5_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;
//定义波特率预分频的值:波特率预分频值为 256
SPI5_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //指定数据传输从 MSB 位开始
SPI5_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭 TI 模式
SPI5_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;/关闭硬件 CRC
SPI5_Handler.Init.CRCPolynomial=7; //CRC 值计算的多项式
HAL_SPI_Init(&SPI5_Handler);//初始化

同样,HAL 库也提供了 SPI 初始化 MSP 回调函数 HAL_SPI_MspInit,定义如下:

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi);

关于回调函数使用,这里我们就不做过多讲解。
3)使能 SPI1。
这一步通过 SPI5_CR1 的 bit6 来设置,以启动 SPI5,在启动之后,我们就可以开始 SPI 通
讯了。使能 SPI5 的方法为:

__HAL_SPI_ENABLE(&SPI5_Handler);//使能 SPI5

4)SPI 传输数据
通信接口当然需要有发送数据和接受数据的函数,HAL 库提供的发送数据函数原型为:

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData,uint16_t Size,uint32_t Timeout);

这个函数很好理解,往 SPIx 数据寄存器写入数据 Data,从而实现发送。
HAL 库提供的接受数据函数原型为:

HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData,
uint16_t Size, uint32_t Timeout);

这个函数也不难理解,从 SPIx 数据寄存器读出接受到的数据。
前面我们讲解了 SPI 通信的原理,因为 SPI 是全双工,发送一个字节的同时接受一个字节,
发送和接收同时完成,所以 HAL 也提供了一个发送接收统一函数:

HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData,
uint8_t *pRxData, uint16_t Size, uint32_t Timeout);

该函数发送一个字节的同时负责接收一个字节。
5)设置 SPI 传输速度
SPI 初始化结构体 SPI_InitTypeDef 有一个成员变量是 BaudRatePrescaler,该成员变量用来设置 SPI 的预分频系数,从而决定了 SPI 的传输速度。但是 HAL 库并没有提供单独的 SPI 分频系数修改函数,如果我们需要在程序中不时的修改速度,那么我们就要通过设置 SPI 的 CR1 寄存器来修改,具体实现方法请参考后面软件设计小节相关函数。
SPI5 的使用就介绍到这里,接下来介绍一下 W25Q128。W25Q128 是华邦公司推出的大容量 SPI FLASH 产品, W25Q128 的容量为 128Mb,该系列还有 W25Q80/16/32/64 等。 ALIENTEK所选择的 W25Q128 容量为 128Mb,也就是 16M 字节。
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。
硬件设计
本章实验功能简介:开机的时候先检测 W25Q256 是否存在,然后在主循环里面检测两个
按键,其中 1 个按键(KEY1)用来执行写入 W25Q256 的操作,另外一个按键(KEY0)用来
执行读出操作,在 LCD 模块上显示相关信息。同时用 DS0 提示程序正在运行。
所要用到的硬件资源如下:

  1. 指示灯 DS0
  2. KEY0 和 KEY1 按键
  3. LCD 模块
  4. SPI
  5. W25Q256
    这里只介绍 W25Q256 与 STM32F429 的连接,板上的 W25Q256 是直接连在 STM32F429
    的 SPI5 上的,连接关系如图 32.2.1 所示:

    软件设计
    打开我们光盘的 SPI 实验工程,可以看到我们加入了 spi.c,flash.c 文件以及头文件 spi.h 和
    flash.h,同时引入了库函数文件 stm32f4xx_hal_spi.c 文件以及头文件 stm32f4xx_hal_spi.h。
    打开 spi.c 文件,看到如下代码:
SPI_HandleTypeDef SPI5_Handler; //SPI 句柄
//以下是 SPI 模块的初始化代码,配置成主机模式
//SPI 口初始化
//这里针是对 SPI5 的初始化
void SPI5_Init(void)
{SPI5_Handler.Instance=SPI5;//SP5SPI5_Handler.Init.Mode=SPI_MODE_MASTER; //设置 SPI 工作模式,设置为主模式SPI5_Handler.Init.Direction=SPI_DIRECTION_2LINES; // SPI 设置为双线模式SPI5_Handler.Init.DataSize=SPI_DATASIZE_8BIT; // PI 发送接收 8 位帧结构SPI5_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //同步时钟空闲状态为高电平SPI5_Handler.Init.CLKPhase=SPI_PHASE_2EDGE;//同步时钟第 2 个跳变沿数据被采样SPI5_Handler.Init.NSS=SPI_NSS_SOFT; //NSS 信号由硬件(NSS 管脚)控制SPI5_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;//定义波特率预分频的值:波特率预分频值为 256SPI5_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB;//指定数据传输从 MSB 位开始SPI5_Handler.Init.TIMode=SPI_TIMODE_DISABLE;//关闭 TI 模式SPI5_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE; //关闭 CRCSPI5_Handler.Init.CRCPolynomial=7; //CRC 值计算的多项式HAL_SPI_Init(&SPI5_Handler);//初始化__HAL_SPI_ENABLE(&SPI5_Handler); //使能 SPI5SPI5_ReadWriteByte(0Xff); //启动传输}//SPI5 底层驱动,时钟使能,引脚配置//此函数会被 HAL_SPI_Init()调用//hspi:SPI 句柄void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi){GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_GPIOF_CLK_ENABLE();__HAL_RCC_SPI5_CLK_ENABLE();519//使能 GPIOF 时钟//使能 SPI5 时钟GPIO_Initure.Pin=GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9; //PF7,8,9GPIO_Initure.Mode=GPIO_MODE_AF_PP;//复用推挽输出GPIO_Initure.Pull=GPIO_PULLUP;//上拉GPIO_Initure.Speed=GPIO_SPEED_FAST;//快速GPIO_Initure.Alternate=GPIO_AF5_SPI5;//复用为 SPI5HAL_GPIO_Init(GPIOF,&GPIO_Initure);}//SPI 速度设置函数//SPI 速度=fAPB1/分频系数//@ref SPI_BaudRate_Prescaler:SPI_BAUDRATEPRESCALER_2~//SPI_BAUDRATEPRESCALER_2 256//fAPB1 时钟一般为 45Mhz:void SPI5_SetSpeed(u8 SPI_BaudRatePrescaler){assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性__HAL_SPI_DISABLE(&SPI5_Handler); //关闭 SPISPI5_Handler.Instance->CR1&=0XFFC7; //位 3-5 清零,用来设置波特率SPI5_Handler.Instance->CR1|=SPI_BaudRatePrescaler;//设置 SPI 速度__HAL_SPI_ENABLE(&SPI5_Handler); //使能 SPI}//SPI5 读写一个字节//TxData:要写入的字节返回值:读取到的字节u8 SPI5_ReadWriteByte(u8 TxData){u8 Rxdata;HAL_SPI_TransmitReceive(&SPI5_Handler,&TxData,&Rxdata,1, 1000);return Rxdata; //返回收到的数据
}

此部分代码主要初始化 SPI,这里我们选择的是 SPI5,所以在 SPI5_Init 函数里面,其相关的操作都是针对 SPI5 的,其初始化主要是通过函数 HAL_SPI_Init 来实现的,初始化之后同时开启 SPI5。在初始化之后,我们就可以开始使用 SPI5 了,这里特别注意,SPI 初始化函数的最后有一个启动传输,这句话最大的作用就是维持 MOSI 为高电平,而且这句话也不是必须的,可以去掉。
在 SPI5_Init 函数里面,我们把 SPI5 的频率设置成了最低(90Mhz,256 分频),而在外部我们可以随时通过函数 SPI5_SetSpeed 来设置 SPI5 的速度。函数 SPI5_ReadWriteByte 则主要通过调用 HAL 库函数 HAL_SPI_TransmitReceive 来实现数据的发送和接收。
接下来我们来看看 w25qxx.c 文件内容。由于篇幅所限,详细代码,这里就不贴出了。我们仅介绍几个重要的函数,首先是 W25QXX_Read 函数,该函数用于从 W25Q128 的指定地址读出指定长度的数据。其代码如下:

//读取 SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大 65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{u16 i;W25QXX_CS=0;//使能器件SPI5_ReadWriteByte(W25X_ReadData); //发送读取命令if(W25QXX_TYPE==W25Q256) //如果是 W25Q256 地址为 4 字节的,要发送最高 8 位{SPI5_ReadWriteByte((u8)((ReadAddr)>>24));}SPI5_ReadWriteByte((u8)((ReadAddr)>>16)); //发送 24bit 地址SPI5_ReadWriteByte((u8)((ReadAddr)>>8));SPI5_ReadWriteByte((u8)ReadAddr);for(i=0;i<NumByteToRead;i++){pBuffer[i]=SPI5_ReadWriteByte(0XFF);//循环读数}W25QXX_CS=1;
}

由于 W25Q256 支持以任意地址(但是不能超过 W25Q256 的地址范围)开始读取数据,所以,这个代码相对来说就比较简单了,在发送 32 位地址(25Q256 及以上型号有 32 位地址,其他型号只有 24 位地址)之后,程序就可以开始循环读数据了,其地址会自动增加的,不过要注意,不能读的数据超过了 W25Q256 的地址范围哦!否则读出来的数据,就不是你想要的数据了。
有读的函数,当然就有写的函数了,接下来,我们介绍 W25QXX_Write 这个函数,该函数的作用与W25QXX_Flash_Read 的作用类似,不过是用来写数据到 W25Q256 里面的,代码如下:

//写 SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大 65535)
u8 W25QXX_BUFFER[4096];
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{u32 secpos;u16 secoff,secremain,i;u8 * W25QXX_BUF;W25QXX_BUF=W25QXX_BUFFER;secpos=WriteAddr/4096;//扇区地址secoff=WriteAddr%4096;//在扇区内的偏移secremain=4096-secoff;//扇区剩余空间大小if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于 4096 个字节while(1){W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容for(i=0;i<secremain;i++)//校验数据{if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除}if(i<secremain)//需要擦除{W25QXX_Erase_Sector(secpos);//擦除这个扇区for(i=0;i<secremain;i++)//复制{W25QXX_BUF[i+secoff]=pBuffer[i];}W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.if(NumByteToWrite==secremain)break;//写入结束了else//写入未结束{secpos++;//扇区地址增 1secoff=0;//偏移位置为 0pBuffer+=secremain; //指针偏移WriteAddr+=secremain;//写地址偏移NumByteToWrite-=secremain;//字节数递减if(NumByteToWrite>4096)secremain=4096; //下一个扇区还是写不完else secremain=NumByteToWrite;//下一个扇区可以写完了}};
}

该函数可以在 W25Q256 的任意地址开始写入任意长度(必须不超过 W25Q256 的容量)的数据。我们这里简单介绍一下思路:先获得首地址(WriteAddr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。这里我们还定义了一个 W25QXX_BUFFER 的全局变量,用于擦除时缓存扇区内的数据。
其他的代码就比较简单了,我们这里不介绍了。对于头文件 w25qxx.h,这里面就定义了一些与 W25Q128 操作相关的命令和函数(部分省略了),这些命令在 W25Q128 的数据手册上都有详细的介绍,感兴趣的读者可以参考该数据手册。
最后,我们看看 main 函数,代码如下:

//要写入到 W25Q16 的字符串数组
const u8 TEXT_Buffer[]={"Apollo STM32F4 SPI TEST"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{u8 key;u16 i=0;u8 datatemp[SIZE];u32 FLASH_SIZE;HAL_Init();//初始化 HAL 库Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz...//此处省略部分代码W25QXX_Init();//W25QXX 初始化while(W25QXX_ReadID()!=W25Q256)//检测不到 W25Q256{LCD_ShowString(30,150,200,16,16,"W25Q256 Check Failed!");delay_ms(500);LCD_ShowString(30,150,200,16,16,"Please Check!");delay_ms(500);LED0=!LED0;//DS0 闪烁}LCD_ShowString(30,150,200,16,16,"W25Q256 Ready!");FLASH_SIZE=32*1024*1024; //FLASH 大小为 32M 字节POINT_COLOR=BLUE;//设置字体为蓝色while(1){key=KEY_Scan(0);if(key==KEY1_PRES)//KEY1 按下,写入 W25Q128{LCD_Fill(0,170,239,319,WHITE);//清除半屏LCD_ShowString(30,170,200,16,16,"Start Write W25Q256....");W25QXX_Write((u8*)TEXT_Buffer,FLASH_SIZE-100,SIZE);//从倒数第 100 个地址处开始,写入 SIZE 长度的数据LCD_ShowString(30,170,200,16,16,"W25Q256 Write Finished!");//提示传送完成}if(key==KEY0_PRES)//KEY0 按下,读取字符串并显示{LCD_ShowString(30,170,200,16,16,"Start Read W25Q256.... ");W25QXX_Read(datatemp,FLASH_SIZE-100,SIZE);//从倒数第 100 个地址处开始,读出 SIZE 个字节LCD_ShowString(30,170,200,16,16,"The Data Readed Is: "); //提示传送完成LCD_ShowString(30,190,200,16,16,datatemp); //显示读到的字符串}i++;delay_ms(10);if(i==20){LED0=!LED0;//提示系统正在运行i=0;}}
}


伴随 DS0 的不停闪烁,提示程序在运行。程序在开机的时候会检测 W25Q256 是否存在,如果不存在则会在 LCD 模块上显示错误信息,同时 DS0 慢闪。大家可以通过跳线帽把 PF7 和PF8 短接就可以看到报错了。

STM32F4-SPI相关推荐

  1. cubemx 读卡器_STM32CubeMX+Keil实现 STM32F4 SDcard+SPI Flash读卡器

    STM32CubeMX+Keil实现 STM32F4 SDcard+SPI Flash读卡器 STM32CubeMX+Keil实现 STM32F4 SDcard+SPI Flash读卡器 总体流程 S ...

  2. STM32F4 使用SPI读取气压计MS5611的数据并转化为大气压强

    ms5611是同时支持I2C和SPI通信协议的气压计芯片.已经很普遍的被用在飞行器控制板上作为高度传感器.开发过stm32的朋友都知道它的硬件I2C是由bug的,一般使用的都是自己编写的软件I2C通信 ...

  3. STM32F407 SPI SD卡驱动的验证

    试验原因 网上靠谱的STM32F4的SPI方式SD卡驱动不多. 包括ST官方板子,都不用SPI_SD的驱动方式,而是用SDIO. 如果没有官方资料加持,出问题时真头疼, 相当于一个人在玩. 自己板子上 ...

  4. 【干货】老外的GitHub整理的stm32f4驱动库

    昨晚在github发现了一个老外自己整理的封装库,有很多,比如一些通讯啊啥的,一些显示屏驱动等等 目录 STM32F4 Libraries and projects Here are listed a ...

  5. arduinopn532模块_NFC开发板/nfc芯片标签/PN532开发板/RFID读卡器/NFC模块/Arduino

    带40 kB ROM和1 kB RAM的80C51微控制器内核 高度集成的模拟电路,解调和译码响应 输出缓冲驱动器通过非常少量的外部无源器件连接天线 集成了RF场检测器 集成了数据模式检测器 支持IS ...

  6. PN532开发指南(uart)

    开发环境 STM32F103C8T6   STM32f103c8t6最小系统板(typec接口) STM32CUBEMX PN532模块  一.NFC简介 NFC(Near Field Communi ...

  7. STM32四轴飞行器

    小马哥STM32四轴飞行器 原理图解析 小马哥 DragonFly四轴 软件开发 13 STM32 SPI总线通讯 SPI 总线协议简介 SPI 物理层 SPI 协议层 SPI 通信时序 STM32硬 ...

  8. STM32F4与STM32L4,SPI DMA HAL 关闭片选 时机探讨

    STM32F4与STM32L4,SPI DMA HAL 关闭片选 时机探讨 我使用STM32F407,标准库 + SPI + DMA 通信,发送接收数据. 当我们配置好SPI,DMA发送模式后,首先开 ...

  9. STM32F4 SPI DMA

    文章目录 STM32F4 SPI DMA 自己整理(存储器到外设模式) SPI结构体 SPI引脚编号 SPI配置 DMA结构体 DMA请求映射 DMA传输模式 SPI 发送DMA配置 DMA发送中断服 ...

最新文章

  1. 修改tomcat6.0.25日志默认路径
  2. 在一个sql分组查询中使用多个聚集函数
  3. [uEnv.txt]在uEnv.txt文件中使用if语句实现Image/dtb文件切换
  4. 逆clarke变换_是clarke变换还是clark
  5. Oracle RAC集群体系结构
  6. python求解微分方程组_python – SymPy / SciPy:求解具有不同变量的常微分方程组...
  7. php决策管理,报表管理与数据分析:为系统未来发展规划提供决策依据,有效避免IT管理与投资的盲目??...
  8. Sql Server 2005资源瓶颈监控(二)
  9. 拓端tecdat|在R语言中使用概率分布:dnorm,pnorm,qnorm和rnorm
  10. Windows命令行解决8080端口被占用
  11. springbus类是做什么用的_SpringCloud-Bus组件的使用
  12. 破局模块总结 -- 宁向东的清华管理学课总结
  13. 文件传输协议FTP/TFTP/SSH/SCP——应用层
  14. 给猜字游戏增加难度设置
  15. 商业银行房贷业务节后骤然下降
  16. stm32时钟初始化过程浅析
  17. 栈和队列、堆、堆栈的区别?
  18. base+ball=games
  19. 1983. 【普及组模拟赛】手机(mobile)
  20. LED流水灯多种点灯方式代码0基础讲解

热门文章

  1. php中文分词的实现方案
  2. QQ2009通讯协议分析(一)
  3. d3.js v3版本实现-树状图
  4. 【Linux】CentOS7 无法打开终端
  5. 地表最强之Android开机Logo动态替换(附100余款车商原厂高清logo)
  6. linux kernel 文件夹,Linux kernel 文件夹说明
  7. GBase8a基于GDB调试之三——保存文档
  8. Chronic/iPhone Dev Team联合发布iOS 5.0.1完美越狱
  9. 智慧旅游大数据背景下的应用现状及发展前景
  10. Premiere Pro CS6软件安装教程