STM32学习笔记(八) SPI总线(操作外部flash)

摘要:
SPI作为基本的外设接口,在FLASH,EPPROM和一些数字通讯中,具有广泛的应用。其中CPOL:0空闲状态低电平1空闲时候高电平CPHA:0第一个边沿采样1第二个边沿采样2.工作原理图了解了SPI总线,下面就开始进入正题,通过SPI总线操作外部flash。了解了这些,就可以开始SPI_FLASH驱动硬件部分的编写了。

1. SPI总线简介

SPI全称串行外设接口,是一种高速,全双工,同步的外设总线;它工作在主从方式,常规需要至少4根线才能够正常工作。SPI作为基本的外设接口,在FLASH,EPPROM和一些数字通讯中,具有广泛的应用。SPI总线由四个接口构成:

CS 片选端,由主设备控制

MISO主设备输入,从设备输出

MOSI主设备输出,从设备输入

SCK 时钟信号

其中SCK仅能由主设备提供,且接收和发送和同时产生的,因此在主设备接收数据时也要先发送数据从而为从设备提供时钟;根据SPI时钟信号配置相关说明,SPI的时钟相位和极性由CPOL和CPHA两位控制共有四种不同的工作时序。

STM32学习笔记(八) SPI总线(操作外部flash)第1张

其中CPOL:0 空闲状态低电平 1 空闲时候高电平

CPHA: 0 第一个边沿采样 1 第二个边沿采样

2. 工作原理图

了解了SPI总线,下面就开始进入正题,通过SPI总线操作外部flash(W25X16)。首先确定开发板原理图对应的端口连接:

STM32学习笔记(八) SPI总线(操作外部flash)第2张

从上可以得出 CS:PB9 SCK:PA5

MISO:PA6 MOSI:PA7

不过因为开发板的资源有限,SD卡和外部flash共用SPI总线,因此在读取SPI FLASH之前要关闭SD卡的片选端,避免出现总线冲突。

了解了这些,就可以开始SPI_FLASH驱动硬件部分的编写了。

3. SPI硬件驱动

SPI端口配置比较简单,主要包含端口时钟启动,端口功能配置,初始化即可

GPIO_InitTypeDef  GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB                                             |RCC_APB2Periph_GPIOD |RCC_APB2Periph_AFIO, ENABLE);
/*SD_CS Disable PD11*/
GPIO_InitStructure.GPIO_Pin  =SD_CS_Pin;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_Out_PP;
GPIO_Init(SD_CS_Port, &GPIO_InitStructure);
GPIO_SetBits(SD_CS_Port, SD_CS_Pin);
/*SPI1_CS 端口配置*/
GPIO_InitStructure.GPIO_Pin  =SPI1_CS_Pin;
GPIO_Init(SPI1_CS_Port, &GPIO_InitStructure);
SPI1_CS_Disable();
/*SPI1_SCK 端口配置*/
GPIO_InitStructure.GPIO_Pin  =SPI1_SCK_Pin;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;
GPIO_Init(SPI1_SCK_Port, &GPIO_InitStructure);
/*SPI1_MISO 端口配置*/
GPIO_InitStructure.GPIO_Pin  =SPI1_MISO_Pin;
GPIO_Init(SPI1_MISO_Port, &GPIO_InitStructure);
/*SPI1_MOSI 端口配置*/
GPIO_InitStructure.GPIO_Pin  =SPI1_MOSI_Pin;
GPIO_Init(SPI1_MOSI_Port, &GPIO_InitStructure);

SPI功能配置主要包含上面我提到的主从设备,时钟相位和极性,发送数据长度和顺序(STM32本身集成功能,与SPI本身关系不大)等,具体配置如下:

SPI_InitTypeDef  SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 |RCC_APB2Periph_AFIO, ENABLE);
SPI_InitStructure.SPI_Direction =    SPI_Direction_2Lines_FullDuplex;    //SPI工作在双向双线模式
SPI_InitStructure.SPI_Mode      = SPI_Mode_Master;                       //CPU的SPI工作在主机模式
SPI_InitStructure.SPI_DataSize  = SPI_DataSize_8b;                       //SPI传输数据帧长度为8字节
SPI_InitStructure.SPI_CPOL      = SPI_CPOL_High;                         //空闲时SCK高电平    MODE3模式
SPI_InitStructure.SPI_CPHA      = SPI_CPHA_2Edge;                       //第二个下降沿接收/发送数据        
SPI_InitStructure.SPI_NSS       = SPI_NSS_Soft;                          //启用软件从设备管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;       //波特率为Pclk2/4
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                       //高位先发送
SPI_InitStructure.SPI_CRCPolynomial = 7;                                 //默认CRC校验多项式 x^2+x+1
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);                              

STM32因为SPI总线已经集成在CPU内部,因此配置起来十分简单,仅修改部分寄存器就可以实现对于SPI总线的配置,用于操作外部设备,不过涉及到外部设备的通讯并没有这么简单,这涉及读取和操作芯片的时序和指令,下面我以开发板上的W25X16外部flash为例,讲解SPI总线的实际运用。

4. SPI总线操作W25X16

外部flash的操作比较简单,总结起来仅读寄存器,写寄存器,读数据,写数据,擦除数据,读ID这6种工作模式,如W25X16指令表如下:

STM32学习笔记(八) SPI总线(操作外部flash)第3张

参照该表,程序中就可以有如下flash操作指令定义

STM32学习笔记(八) SPI总线(操作外部flash)第4张STM32学习笔记(八) SPI总线(操作外部flash)第5张
/*外部flash相关指令*/
#define Flash_WriteEnable            0x06 
#define Flash_WriteDisable            0x04 
#define Flash_ReadStatusReg            0x05 
#define Flash_WriteStatusReg        0x01 
#define Flash_ReadData                0x03 
#define Flash_FastReadData            0x0B 
#define Flash_FastReadDual            0x3B 
#define Flash_PageProgram            0x02 
#define Flash_BlockErase            0xD8 
#define Flash_SectorErase            0x20 
#define Flash_ChipErase                0xC7 
#define Flash_PowerDown            0xB9 
#define Flash_ReleasePowerDown            0xAB 
#define Flash_ManufactDeviceID            0x90 
#define Flash_JedecDeviceID            0x9F 
#define Flash_NoBusy                0xA5            
flash Instruction

可以看出外部flash主要包含擦除,读寄存器,写入寄存器,读数据,写数据,读ID这几种方式。

(1). flash单字节收发

根据上面SPI总线的说明,SPI的写入和读出是同时发生的,且时钟只能由主设备提供,因此SPI总线的收发由同一个函数完成。如下:

/*等待SPI发送数据寄存器为空时,发送1字节数据*/
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) ==RESET)
{
}
 SPI_I2S_SendData(SPI1, byte);
/*在发送数据同时,SPI_MISO引脚会读取管脚数据,等待读取寄存器非空*/
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) ==RESET)
{
}
return SPI_I2S_ReceiveData(SPI1);

当然,实际项目中在while循环内部需要添加超时时钟,避免因为可能出现的SPI硬件出错而导致整个系统停止的问题。

(2). flash擦除

外部flash的擦除主要包含sector(扇区)擦除,block(块)擦除,chip(整片)擦除。其中扇区擦除4kb, 块擦除64kb, flash的擦除按照芯片资料上要求,擦除需要3步:

1.写入允许(0x06)

2.擦除指令(0x20)

3. 写入擦除地址(24bit), 分三次发送

STM32学习笔记(八) SPI总线(操作外部flash)第6张

由时序可知,擦除片代码如下:

voidSPI_EraseSector(u32 SectorAddress)
{
    SectorAddress = (SectorAddress>>12)<<12; //确定擦除块的首地址
    SPI_Write_Enable();                      //允许写入
    SPI1_CS_Enable();
    SPI_SendWrite_Byte(Flash_SectorErase);   //写入擦除指令
    /*写入带擦除的扇区*/
    SPI_SendWrite_Byte((SectorAddress&0xFF0000)>>16);
    SPI_SendWrite_Byte((SectorAddress&0xFF00)>>8);
    SPI_SendWrite_Byte(SectorAddress&0xFF);
    SPI1_CS_Disable();
    SPI_WaitWriteEnd();
}

(3). 数据读取

数据读取包含3步,1.写入读取指令 2.写入待读取数据地址 3.读取flash内部数据。如此便完成外部flash的读取。

void SPI_Read(u8 *pBuffer,u32 ReadAddress,u16 ReadByteNum)
{
    SPI1_CS_Enable();
    SPI_SendWrite_Byte(Flash_ReadData);    //写入读取指令
    SPI_SendWrite_Byte(ReadAddress >> 16); //写入待读取数据地址
    SPI_SendWrite_Byte(ReadAddress >> 8);
    SPI_SendWrite_Byte(ReadAddress);
    /*读取数据*/
    while(ReadByteNum--)
    {
        *pBuffer =SPI_SendWrite_Byte(Flash_NoBusy);
        pBuffer++;
    }
    SPI1_CS_Disable();
}

(4)数据写入

数据写入包含4步,1.写入允许 2.写入数据写入指令 3.写入数据存储地址 4.写入数据。如此便完成外部flash的写入。

void SPI_PageWrite(u8 *pBuffer, u32 PageAddress,u16 WriteByteNum)
{
    SPI_Write_Enable();                        //写入允许
    SPI1_CS_Enable();
    SPI_SendWrite_Byte(Flash_PageProgram);    //写入存储指令
    SPI_SendWrite_Byte(PageAddress >> 16);    //写入存储地址
    SPI_SendWrite_Byte(PageAddress >> 8);
    SPI_SendWrite_Byte(PageAddress);
    if(WriteByteNum > 256)                                 
    {
        WriteByteNum = 256;
        printf("Err: SPI_PageWrite too large!");
    }
    /*写入数据*/
    while(WriteByteNum--)
    {
        SPI_SendWrite_Byte(*pBuffer);
        pBuffer++;
    }
  SPI1_CS_Disable();
    SPI_WaitWriteEnd();
}

(5)ID读取

ID读取比较简单,主要用来测试硬件是否成功,具体代码如下

u32 SPI_ReadID(void)
{
    u32 ID_Temp,temp_h,temp_m,temp_l;
    SPI1_CS_Enable();
    SPI_SendWrite_Byte(Flash_JedecDeviceID);
    temp_h =SPI_SendWrite_Byte(Flash_NoBusy);
    temp_m =SPI_SendWrite_Byte(Flash_NoBusy);
    temp_l =SPI_SendWrite_Byte(Flash_NoBusy);
    SPI1_CS_Disable();
    ID_Temp = (temp_h << 16)|(temp_m << 8)|temp_l;
    returnID_Temp;
}    

上面便是SPI总线基本操作了,具体可参考代码:

http://files.cnblogs.com/files/zc110747/6.SPI-Flash.7z

根据代码中设计以及通过串口输出如下图,可以判断成功实现了SPI-flash的读,写和擦除的工作。

STM32学习笔记(八) SPI总线(操作外部flash)第7张

免责声明:文章转载自《STM32学习笔记(八) SPI总线(操作外部flash)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇在CYGWIN下编译和运行软件Bundler ,以及PMVS,CMVS的编译与使用HBase 学习(一) Python操作Hbase下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

树莓派wiringPi经常使用的函数介绍

 1.void pinMode (int pin, int mode) ; 这个函数式设置pin脚的输入和输出模式以及PWM的输入和输出模式。在wiringPi中仅仅有 pin 1 (BCM_GPIO 18)是支持PWM的输出的。 2.void digitalWrite (int pin, int value) ; 这个函数式用来设置pin脚的高低电...

stm32的DFU使用方法

stm32的dfu看上去是个很高级的东西,似乎可以通过USB给内部flash、外部spi flash、外部nor等东西刷写数据、把数据读出来,但是用了一下感觉确实有点麻烦。 先不管原理是怎样的,使用方法是这样: 1、先下载这个Dfuse,然后安装。 2、用Jlink之类的东西把这个hex的bootloader刷进stm32的内部flash 3、改成usb直...

【STM32H7教程】第72章 STM32H7的SPI总线基础知识和HAL库API

完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第72章       STM32H7的SPI总线基础知识和HAL库API 本章节为大家讲解SPI(Serial peripheral interface)总线的基础知识和对应的HAL库API。 72.1 初学者重要提示 7...

STM32 USB HID BarCodeReader不兼容问题的解决

STM32USB HID class的一部分 BarCodeScanner(条码枪)不兼容的解决 硬件构成 STM32F479-EVAL 评价板型号为FFTAA10AP条码枪 现象 最近用CubeMX生成的USB库做条形码枪的USB驱动,用的是HID协议。有的条形码枪,用标准的USB库一次就能成功。但是有的型号的条码枪貌似和标准USB库流程有所出入,执行后...

STM32 ADC基础与多通道采样

12位ADC是一种逐次逼近型模拟数字数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源。ADC的输入时钟不得超过14MHZ,它是由PCLK2经分频产生。如果被ADC转换的模拟电压低于低阀值或高于高阀值,AWD模拟看门狗状态位被设置。 ADC通常要与DMA一起使用 这里只是简单的用库配置ADC 不断扫描来实现ADC的应用。 配置DMA: voi...

树莓派上传数据错误一例

首先是源码: 1 #-*- utf-8 -*- 2 #env !/usr/bin/python 3 4 importRPi.GPIO as GPIO 5 importtime 6 importjson 7 importdatetime 8 importrequests 9 10 requests.adapters.DEFAULT_RETRIES = 5...