STM32学习笔记(八) SPI总线(操作外部flash)
1. SPI总线简介
SPI全称串行外设接口,是一种高速,全双工,同步的外设总线;它工作在主从方式,常规需要至少4根线才能够正常工作。SPI作为基本的外设接口,在FLASH,EPPROM和一些数字通讯中,具有广泛的应用。SPI总线由四个接口构成:
CS :片选端,由主设备控制
MISO:主设备输入,从设备输出
MOSI:主设备输出,从设备输入
SCK :时钟信号
其中SCK仅能由主设备提供,且接收和发送和同时产生的,因此在主设备接收数据时也要先发送数据从而为从设备提供时钟;根据SPI时钟信号配置相关说明,SPI的时钟相位和极性由CPOL和CPHA两位控制共有四种不同的工作时序。
其中CPOL:0 空闲状态低电平 1 空闲时候高电平
CPHA: 0 第一个边沿采样 1 第二个边沿采样
2. 工作原理图
了解了SPI总线,下面就开始进入正题,通过SPI总线操作外部flash(W25X16)。首先确定开发板原理图对应的端口连接:
从上可以得出 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 = ; //默认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指令表如下:
参照该表,程序中就可以有如下flash操作指令定义
/*外部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), 分三次发送
由时序可知,擦除片代码如下:
void SPI_EraseSector(u32 SectorAddress)
{
SectorAddress = (SectorAddress>>)<<; //确定擦除块的首地址
SPI_Write_Enable(); //允许写入 SPI1_CS_Enable();
SPI_SendWrite_Byte(Flash_SectorErase); //写入擦除指令 /*写入带擦除的扇区*/
SPI_SendWrite_Byte((SectorAddress&0xFF0000)>>);
SPI_SendWrite_Byte((SectorAddress&0xFF00)>>);
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 >> ); //写入待读取数据地址
SPI_SendWrite_Byte(ReadAddress >> );
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 >> ); //写入存储地址
SPI_SendWrite_Byte(PageAddress >> );
SPI_SendWrite_Byte(PageAddress); if(WriteByteNum > )
{
WriteByteNum = ;
printf("\n\r 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 << )|(temp_m << )|temp_l;
return ID_Temp;
}
上面便是SPI总线基本操作了,具体可参考代码:
http://files.cnblogs.com/files/zc110747/6.SPI-Flash.7z
根据代码中设计以及通过串口输出如下图,可以判断成功实现了SPI-flash的读,写和擦除的工作。
STM32学习笔记(八) SPI总线(操作外部flash)的更多相关文章
- Python学习笔记八:文件操作(续),文件编码与解码,函数,递归,函数式编程介绍,高阶函数
文件操作(续) 获得文件句柄位置,f.tell(),从0开始,按字符数计数 f.read(5),读取5个字符 返回文件句柄到某位置,f.seek(0) 文件在编辑过程中改变编码,f.detech() ...
- 【canvas学习笔记八】像素操作
ImageData对象 ImageData对象包含了一个区域内的canvas的像素信息.它包含以下可读属性: width canvas的宽度,单位是像素. height canvas的高度,单位是像素 ...
- stm32学习笔记——外部中断的使用
stm32学习笔记——外部中断的使用 基本概念 stm32中,每一个GPIO都可以触发一个外部中断,但是,GPIO的中断是以组为一个单位的,同组间的外部中断同一时间只能使用一个.比如说,PA0,PB0 ...
- STM32学习笔记(四)——串口控制LED(中断方式)
目录: 一.时钟使能,包括GPIO的时钟和串口的时钟使能 二.设置引脚复用映射 三.GPIO的初始化配置,注意要设置为复用模式 四.串口参数初始化配置 五.中断分组和中断优先级配置 六.设置串口中断类 ...
- STM32学习笔记——OLED屏
STM32学习笔记--OLED屏 OLED屏的特点: 1. 模块有单色和双色可选,单色为纯蓝色,双色为黄蓝双色(本人选用双色): 2. 显示尺寸为0.96寸 3. 分辨率为128*64 4. ...
- STM32学习笔记——点亮LED
STM32学习笔记——点亮LED 本人学习STM32是直接通过操作stm32的寄存器,使用的开发板是野火ISO-V2版本: 先简单的介绍一下stm32的GPIO: stm32的GPIO有多种模式: 1 ...
- Redis学习笔记八:集群模式
作者:Grey 原文地址:Redis学习笔记八:集群模式 前面提到的Redis学习笔记七:主从复制和哨兵只能解决Redis的单点压力大和单点故障问题,接下来要讲的Redis Cluster模式,主要是 ...
- stm32学习笔记----双串口同时打开时的printf()问题
stm32学习笔记----双串口同时打开时的printf()问题 最近因为要使用串口2外接PN532芯片实现通信,另一方面,要使用串口1来将一些提示信息输出到上位机,于是重定义了printf(),使其 ...
- RX学习笔记:JavaScript数组操作
RX学习笔记:JavaScript数组操作 2016-07-03 增删元素 unshift() 在数组开关添加元素 array.unshift("value"); array.un ...
随机推荐
- Cocoapods配置
这真是蛋疼的东西,配置了几次,每次都不同,每次都折腾半天.这一段时间应该不会变了,记录下来. 一 换源 看了教程都说官方源https://rubygems.org/不能访问,我特意点了一下,发现能访问 ...
- 转贴: 更改Outlook2013数据文件的位置
转自: 老田博客 近日体验了一下微软OFFICE 2013 说实话 除了与skydriver深度整合实现云同步文档外 其他的功能对我这样的『Light User』实在是大材小用 wps足够了 在使用过 ...
- Python高级特性(1):Iterators、Generators和itertools(参考)
对数学家来说,Python这门语言有着很多吸引他们的地方.举几个例子:对于tuple.lists以及sets等容器的支持,使用与传统数学类 似的符号标记方式,还有列表推导式这样与数学中集合推导式和集的 ...
- windows下的socket网络编程(入门级)
windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了,这次因为需要做一个跨平台的网络程序,就先 ...
- git http\https\git免密设置记住用户名和密码的方法
设置记住密码(默认15分钟): git config --global credential.helper cache如果想自己设置时间,可以这样做: git config credential.he ...
- svn的差异查看器和合并工具换成BCompare.exe
svn的差异查看器和合并工具换成BCompare.exe
- RDIFramework.NET ━ .NET快速信息化系统开发框架 V2.8 版本━新增企业通(内部简易聊天工具)
RDIFramework.NET ━ .NET快速信息化系统开发框架 V2.8 版本 新增企业通(内部简易聊天工具) RDIFramework.NET,基于.NET的快速信息化系统开发.整合框架,给用 ...
- <<卸甲笔记>>-Oracle线下迁移到PPAS
迁移原则 1.尽量保持Oracle与PPAS一致,这会使得日后应用程序迁移更为简单 2.迁移前检查PPAS中是否有同名帐号及同名的Schema a)如果有,建议考虑删除或改名 b)如果没有,先手工建立 ...
- STL之容器(1)
STL容器类的模板 容器部分主要由头文件<vector>,<list>,<deque>,<set>,<map>,<stack>和 ...
- IO/ACM中来自浮点数的陷阱(收集向)
OI/ACM中经常要用到小数来解决问题(概率.计算几何等),但是小数在计算机中的存储方式是浮点数而不是我们在作数学运算中的数,有精度的限制. 以下以GUN C++为准,其他语言(或编译器)也差不了多少 ...