SPI-Flash访问
中科昊芯DSP_RISC-V笔记(3)_SPI-Flash访问
●1.功能:实现SPI_FIash数据访问,要求:8位数据传输,无延迟,增强FIFO模式,不使用中断
●写入数据为upper_100[128]={0x55,0x55,0x55,0x98,Q×56,0x78,0x45,0x34},
读出数据upper_128[128]与写入数据一致
●2.SPI模块讲解:(1)参数功能讲解,结合手册P49-51
●(2)收发逻辑
●(3)接收/发送FIFO
●(4)主机发送模式
●(5)中断触发
●(6)波特率选择与时钟模式
●3.如何配置SPI:(1)SPI的FIFO配置,(2)SPI初始化配置
●4.结合Flash说明书对W25Q64Flash原理讲解,J104的1-2短接
●5.详细讲解程序(1)SPI配置(2)Flash按手册编程实现收发功能(3)主函数执行调用步骤与逻辑
●6.调试:读写成功,D401持续闪灯一持续读数,D400常亮一读出与发送的数据一致
同步串行通信接口(SPI)模块
HXS320F2803X 器件包括 2 个四引脚的同步串行通信接口(SPI)模块。SPI 是一个高速、 同步串行 I/O 端口,此端口可在设定的位传输速率上将一个设定长度(1 至 16 位)的串行 比特流移入和移出器件。
SPI-A 寄存器
SPI收发逻辑
●包含移位及传输缓冲器,FIFO及状态控制逻辑等部件组成,移位寄存器SPIDAT在时钟作用下完成数据移入移出,实现数据传输.传输缓冲器SPITXBUF/SPIRXBUF负责在合适的时刻向SPIDAT中加载需要发送的数据,或读取SPIDAT中已接收完成的数据,并触发响应的状态及中断控制信号等.FIFO部分在模块使能FIFO功能时提供了发送的数据源及接收的目的,同时产生中断及状态控制信号等.状态控制逻辑通过状态机控制发送和接收状态.
●收发原理:环形总线结构,由SPISTE、SPICLK、MISO、MOSI构成,其时序主要是在SPICLK的控制下,两个双向移位寄存器进行数据交换。SPIDAT寄存器为16位寄存器,由于发送数据字长度可以在1~16位之间配置,因而在数据长度小于16位时,数据按照左对齐的方式存入SPIDAT中,即MSB对应SPIDAT的位15。数据传输的过程中,MSB经过MOSI/MISO发送到对方SPIDAT的LSB中,当传输完成后,数据字按照右对齐的方式存放在SPIDAT中,即LSB对应SPIDAT的位O。
●非FIFO模式下,SPITXBUF发送缓冲寄存器接收来自于CPU的待发送数据。当SPITXBUF被写入数据时,若此时没有SPI传输发生,SPITXBUF被写入的数据将加载至SPIDAT,从而发起SPI传输;若SPI传输正在进行,SPITXBUF保留数据并设置BUF_FULL标志位,当SPI传输完成时,数据加载至SPIDAT开始SPI传输,同时清除标志位。FIFO模式下,SPITXBUF的数据来源于相应的发送FIFO,在FIFO控制逻辑作用下依次触发SPIDAT的加载实现SPI传输。
●SPI传输完成时,SPIDAT接收到的内容会加载到接收缓冲器SPIRXBUF中。非FIFO模式下,SPIRXBUF中的数据需要CPU读取。当CPU未及时读取数据,会产生溢出事件,可以触发相应中断。FIFO模式下,读取SPITXBUF数据会直接读取RXFIFO的内容,使RXFIFO指针减1。
接收/发送FIFO
4x16位大小,FIFO模式使能时,对SPITXBUF寄存器写入发送数据会导致数据写入TXFIFO,从而使得FIFO指针增加。写入TXFIFO的数据在延迟控制下按照一定的频率写入SPITXBUF,启动SPI传输,FIFO指针减少。使能FIFO模式下,SPI接收到数据,将数据存入接收FIFO中,导致FIFO指针增加。读取SPIRXBUF寄存器会实际上读取RXFIFO中的数据,使得FIFO指针减少。当与FIFO相关的中断使能时,RX/TXFIFO中的数据现存数目满足设定值范围时,将触发相应的中断,故需要的FIFO深度为4,宽度为16位,需要输出当前FIFO的状态信号如full,empty以及当前存储的数据数目num。读写设置为无延迟。
主机发送模式
包括6个状态:
●a)idle:系统初始化时,状态保持在idle状态,当sPIDAT中写入数据时即datful_flag有效,进入到start状态准备产生SPI时钟。
●b)start:根据不同的时钟相位模式选择开始传输的时机,进入到发送数据状态transmit。
●c)transmit:发送数据向外发出,并计算已发送的比特数。SPI时钟的合适边沿进入到接收状态。
●d)receive:接收来自从设备的数据,根据当前已发送的数据判断是否传输完成。如果还有未发送完成的数据则在合适的时刻回到transmit,否则进入end状态结束传输。
●e)end:给出一些标志位及结束信号结束SPISTE,之后进入delay状态。
●f)delay:等待半个SPI时钟周期,保持SPISTE无效一段时间再结束回到初始状态。
中断触发
●非FIFO模式下:
●(1)传输完成触发中断:INT_FLAG表示SPI模块已经完成了发送或接收的最后一位,同时已经做好再次被访问的准备。标志位置位,接收到的字符会被放在接收缓冲器SPIRXBUF中,若SPIINTENA置位,标志位将触发SPI中断;
●(2)接收溢出标志触发中断:OVERRUN_FLAG指示了一次发送或接收行为的完成,同时上一次接收的字符还未被从接收缓冲器SPIRXBUF中读取。标志位会触发一个SPI中断,相应OVERRUNINTENA标志位被置位,此时标志位已被清除。
●FIFO模式下:
●SPI中断产生依赖于相应的匹配事件。
●(1)当RXFFST大于等于RXFFIL时,接收FIFO的中断标志RXFFINT将会被置位,如果接收FIFO中断被使能(RXFFIENA=1),那么此时SPIRXINT中断触发
●(2)TXFFST小于等于TXFFIL时,发送FIFO中断标志位TXFFINT将会被置位,如果发送FIFO中断被使能(TXFFIENA=1),那么此时SPITXINT中断触发
波特率选择与时钟模式
●(1)波特率选择:支持125种不同波特率,根据不同的主从模式,SPICLK会接收外部的时钟信号或由本模块向外提供SPI时钟。
●从模式中,SPI时钟由SPICLK引脚从外部接收时钟源,同时时钟速率最快不能超过LSPCLK/4。
●主模式,SPI时钟由SPI模块产生并由SPICLK引脚输出,最大不超过LSPCLK/4。
●(2)时钟模式:由极性选择(SPICCR.CLKPOLARITY)和相位选择(SPICTL.CLK_PHASE)控制:CLKPOLARITY选择有效边缘,上升沿/下降沿;CLK PHASE选择是否半时钟周期延迟。
●a)上升沿无延迟O0:SPICLK低有效。SPI数据传输发生在SPICLK的上升沿前半个SPICLK周期处,数据接收发
生在SPICLK的下降沿。
●b)上升沿有延迟O1:SPICLK低有效。SPI数据传输发生在SPICLK的上升沿前半个SPICLK周期处,数据接收发
生在SPICLK的上升沿。
●c)下降沿无延迟10:SPICLK高有效。SPI数据传输发生在SPICLK的下降沿,数据接收发生在SPICLK的上升沿。
●d)下降沿有延迟11:SPICLK高有效。SPI数据传输发生在SPICLK的上升沿,数据接收发生在SPICLK的下降沿。
配置SPI
●(1)SPI的FIFO配置
● a)复位:上电进入到普通SPI模式,FIFO模式被禁止SPIFFTX.SPIRST=1
● b)改变模式:增强FIFO模式选择,通过SPIFFTX.SPIFENA=1配置
● c)激活寄存器:SPIFFTX.TXFFST=1SPIFFRX.RXFFST=1
● d)中断:两种中断:发送/接收FIFO中断接收错误/接收FIFO溢出中断TXFFIENA/RXFFIENA
● e)缓冲:4字FIFO SPIFFRX.RXFFIL
● f)延迟发送SPIFFCT.alI
●(2)SPI初始化配置
SPI初始化配置
●SPISWRESET=O,进入复位状态
●按需配置SPI:
●(1)传输数据长度SPICHAR
●(2)时钟模式选择
●(3)主从模式选择
●(4)波特率配置
●(5)清除SPI标志
●(6)3线模式
●(7)FIFO模式配置
●(8)酌情配置中断
● 设置SPISWRESET=1,释放SPI模块
● 对于FIash访问还需配置SPI优先级SPIPRI.FREE=1自由运行,
● SPIPRI.CS_PRIORITY=O片选处于最高优先级
电路图部分
W25Q16DV(华邦)简介
储存空间限制在256,16位的一个数据空间。工作电源在2.7V至3.6V。
页面擦除可分为16组(4KB扇区擦除)、128组(32KB块擦除)、256组(64KB块擦除)或整个芯片(芯片擦除)。W25Q16DV有512个可擦扇区和32个可擦块。较小的4KB扇区为需要数据和参数存储的应用程序提供了更大的灵活性。
Flash按手册编程实现收发功能
●(1)读FIash.ID ●(8)写使能:
●(2)读Flash.SReg状态 ●(9)擦除芯片
●(3)写使能: ●(10)延时等待4个周期
●(4)读出写寄存器状态: ●(11)读数据
●(5)读Flash.SReg状态; ●(12)写使能
●(6)延时等待; ●(13)写入数据
●(7)读Flash.SReg状态: ●(14)延时等待
●(15)读出数据
代码部分
引脚初始化 - GPIO16、GPIO17、GPIO18为SPI功能,GPIO19为片选引脚
GPAQSEL2 选择为011的原因时 控制MISO的信号同步的速率,比系统时钟速度远比系统时钟速度要慢很多,所以我们选择他的异步 就是(011)
void SPI_IOinit(void)
{
EALLOW;
/*将 GPIO19 引脚配置为:通用输入/输出*/
GpioCtrlRegs.GPAMUX2.bit.GPIO19 = 0;
/*将 GPIO19引脚配置为输出*/
GpioCtrlRegs.GPADIR.bit.GPIO19 = 1;
/*//使能内部上拉*/
GpioCtrlRegs.GPAPUD.bit.GPIO16 = 0;
GpioCtrlRegs.GPAPUD.bit.GPIO17 = 0;
GpioCtrlRegs.GPAPUD.bit.GPIO18 = 0;
/*异步*/
GpioCtrlRegs.GPAQSEL2.bit.GPIO16 = 3;
GpioCtrlRegs.GPAQSEL2.bit.GPIO17 = 3;
GpioCtrlRegs.GPAQSEL2.bit.GPIO18 = 3;
/*SPISIMOA-SPI-A 从机输入,主机输出 (I/O)*/
GpioCtrlRegs.GPAMUX2.bit.GPIO16 = 1;
GpioCtrlRegs.GPAMUX2.bit.GPIO17 = 1;
GpioCtrlRegs.GPAMUX2.bit.GPIO18 = 1;
EDIS;
}
SPIFFTX 类型定义
// SPI Individual Register Bit Definitions:
//
// SPI FIFO Transmit register bit definitions:
struct SPIFFTX_BITS { // bit description
Uint32 TXFFIL:5; // 4:0 Interrupt level
Uint32 TXFFIENA:1; // 5 Interrupt enable
Uint32 TXFFINTCLR:1; // 6 Clear INT flag ///?? 0
Uint32 TXFFINT:1; // 7 INT flag
Uint32 TXFFST:5; // 12:8 FIFO status
Uint32 TXFIFO:1; // 13 FIFO reset
Uint32 SPIFFENA:1; // 14 Enhancement enable
Uint32 SPIRST:1; // 15 Reset SPI
Uint32 rsvd:16; // 31:16 reserved
};
初始化SPI的FIFO寄存器
void SPI_fifo_init(void)
{
SpiaRegs.SPIFFTX.bit.SPIRST = 1;//1h (R/W) = SPI FIFO 可以恢复发送或接收。对 SPI 寄存 器位没有影响。
SpiaRegs.SPIFFTX.bit.SPIFFENA = 1; //使能SPIFIFO增强型功能。
SpiaRegs.SPIFFTX.bit.TXFFST = 1; //发送 FIFO 有 1 个字。
SpiaRegs.SPIFFTX.bit.TXFFINTCLR = 1; //写 1 以清除 SPIFFTX[TXFFINT]标志。
SpiaRegs.SPIFFTX.bit.TXFFIENA = 0; //TX FIFO 中断使能
SpiaRegs.SPIFFRX.bit.RXFFOVFCLR = 1; //给此位写 1 清除 SPIFFRX[RXFFOVF]。
SpiaRegs.SPIFFRX.bit.RXFFINTCLR = 1; //写 1 以清除 SPIFFRX[RXFFINT]标志。
SpiaRegs.SPIFFRX.bit.RXFFST = 1; //接收 FIFO 有一个字
SpiaRegs.SPIFFRX.bit.RXFFIENA = 0; //接收 FIFO 中断基于 RXFFIL 匹配(大于或 等于)的将被启用
SpiaRegs.SPIFFRX.bit.RXFFIL = 4; //=一个 RX FIFO 中断请求产生时,有 4 个字 在接收缓冲区
SpiaRegs.SPIFFCT.all = 0; //上一个字传输完成后,TX FIFO 缓冲区 中的下一个字立刻被传输到 SPITXBUF。
}
初始化SPI的控制寄存器
void spi_init(void)
{
GpioDataRegs.GPASET.bit.GPIO19 = 1; //GPIO19置1 从W25Q16DV时序图中看出
SpiaRegs.SPICCR.bit.SPISWRESET = 0; //清除接收端溢出标志 位(SPISTS.7)、SPI INT 标志位(SPISTS.6)
SpiaRegs.SPICCR.bit.SPICHAR = 7; //字符长度控制位7h (R/W) = 8-bit word
SpiaRegs.SPICCR.bit.CLKPOLARITY = 1; //数据在下降边输出,上升边输入。
SpiaRegs.SPICTL.bit.CLK_PHASE = 0; //正常的 SPI 时钟方案,取决于 时钟极性位(SPICCR.6)
SpiaRegs.SPICTL.bit.MASTER_SLAVE = 1; //设置为主机
SpiaRegs.SPIBRR = 2; //SPI 波特率= LSPCLK / 4。
SpiaRegs.SPICCR.bit.SPISWRESET = 1; //准备发生或接收下一字
SpiaRegs.SPIPRI.bit.FREE = 1; //自由运行,无论挂起或何时发生挂起,继续 SPI 操作。
SpiaRegs.SPIPRI.bit.STEINV = 0; //SPISTEn 活跃低(正常)
}
查询ID信息
输出制造商的ID信息;储存型号的ID信息;还有容量的ID信息;三个ID信息需要定义三个变量。
/******************************************************************
*发送一个字节。
******************************************************************/
uint8_t Send_Byte(uint16 a)
{
uint16 rdata;
SpiaRegs.SPICTL.bit.TALK = 1; //传输路径选择发送
SpiaRegs.SPITXBUF = (a << 8) & 0xff00; //左对齐命令
while (SpiaRegs.SPIFFRX.bit.RXFFST == 0) //是否完成了一个接收的传输,等待引脚激活
{
}
rdata = SpiaRegs.SPIRXBUF;
return rdata;
}
/******************************************************************
*接收一个字节。
******************************************************************/
uint16 Get_Byte(void)
{
uint8_t rdata;
SpiaRegs.SPICTL.bit.TALK = 0; //传输路径选择接收
SpiaRegs.SPITXBUF = DummyData //Flash芯片储存字节 为0xA5 表示可以存储165个字节
;
while (SpiaRegs.SPIFFRX.bit.RXFFST == 0)
{
}
rdata = SpiaRegs.SPIRXBUF;
return rdata;
}
/******************************************************************
*函数名:unsigned long Jedec_ID_Read(void)
*参 数:无
*返回值:W25Q16 ID参数
*作 用:*《W25Q16BV.pdf》
*P17 W25Q16 ID参数
*P20 ID 指令表格与读取指令的通信格式
******************************************************************/
unsigned long Jedec_ID_Read(void)
{
unsigned long temp1, temp2, temp3;
temp1 = 0;
temp2 = 0;
temp3 = 0;
/*GPIO19置0*/
GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
Send_Byte(0x9F);//查询ID信息的指令。
temp1 = Get_Byte();
temp2 = Get_Byte();
temp3 = Get_Byte();
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
/* 数据在SPIRXBUF中是右对齐存储的*/
temp1 = ((temp1 << 16) | (temp2 << 8) | (temp3)) & 0x00FFFFFF;
return temp1;
}
读取Flash状态寄存
/******************************************************************
*函数名:uint16 Read_Status_2Reg(void)
*参 数:无
*返回值:状态寄存器1和状态寄存器2中的值
*作 用:读状态寄存器1和状态寄存器2的值
******************************************************************/
uint16 Read_Status_2Reg(void)
{
uint16 wbyte1, wbyte2;
/*GPIO19置0*/
GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
/*读状态寄存器1指令,返回Status Register1*/
Send_Byte(0x05);
wbyte1 = Get_Byte();
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
/*GPIO19置0*/
GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
/*READ_STATUS_REG_IST 读状态寄存器2指令,返回Status Register2*/
Send_Byte(0x35);
wbyte2 = Get_Byte();
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
wbyte2 <<= 6;
wbyte2 &= 0xff00;
wbyte2 += wbyte1 & 0xff;
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
return wbyte2;
}
写使能
void WREN(void)
{
/*GPIO19置0*/
GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
Send_Byte(0x06);
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
}
写入Flash
void WrSReg(uint16 setReg)
{
/*GPIO19置0*/
GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
Send_Byte(0x01);
Send_Byte(setReg & 0x00ff);//传低8位。
Send_Byte((setReg & 0x0300) >> 8);//传高8位,Flash是从0x0300开始的
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
}
等待延时
unsigned char Read_Status_Register(void)
{
uint16 byte;
/*GPIO19置0*/
GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
Send_Byte(0x05);
byte = Get_Byte();
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
return byte;
}
void Wait_Busy(void)
{
uint32 waitcnt;
unsigned char Wbusy;
Wbusy = Read_Status_Register();
waitcnt = 600000;
while ((Wbusy & 0x01) == 0x01)
{
Wbusy = Read_Status_2Reg();
if (waitcnt == 0)
{
WREN();
WrSReg(0x0000);
SPIFlash.SReg = Read_Status_2Reg();
}
else
waitcnt--;
}
}
擦除
void Chip_Erase(void)
{
/*GPIO19置0*/
GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
Send_Byte(0x60);
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
}
读取数据
void ReadData(unsigned char Dst, unsigned char *Rxbuf, unsigned long len)
{
unsigned long i = 0;
/*GPIO19置0*/
GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
Send_Byte(0x03);
Send_Byte((Dst & 0xFFFFFF) << 16);
Send_Byte((Dst & 0xFFFF) << 8);
Send_Byte(Dst & 0xff);
for (i = 0; i < len; i++)
{
*(Rxbuf++) = Get_Byte();
}
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
}
写入数据
跟读取数据时序差不多。
void PageProgram(unsigned char Dst, unsigned char *byte, unsigned char len)
{
unsigned char i = 0;
/*GPIO19置0*/
GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
Send_Byte(0x02);
Send_Byte((Dst & 0xFFFFFF) << 16);
Send_Byte((Dst & 0xFFFF) << 8);
Send_Byte(Dst & 0xff);
for (i = 0; i < len; i++)
{
Send_Byte(*(byte++));
}
/*GPIO19置1*/
GpioDataRegs.GPASET.bit.GPIO19 = 1;
}
主函数部分
#include "dsc_config.h"
#include <syscalls.h>
#include "IQmathLib.h"
#include "spi.h"
extern void InitLED(void);
uint8_t upper_100[128] = { 0x55, 0x55, 0x55, 0x98, 0x56, 0x78, 0x45, 0x34 };
uint16 k = 0;
bool_t status = true;
int main(void)
{
/*初始化系统控制:PLL,WatchDog,使能外设时钟*/
InitSysCtrl();
/*初始化LED*/
InitLED();
/*初始化GPIO为SPI引脚功能*/
SPI_IOinit();
/*初始化SPI的FIFO寄存器*/
SPI_fifo_init();
/*初始化SPI的控制寄存器*/
spi_init();
/*读取ID信息*/
SPIFlash.Jedec_ID = Jedec_ID_Read();
/*读取flash状态*/
SPIFlash.SReg = Read_Status_2Reg();
/*flash写使能*/
WREN();
/*写状态*/
WrSReg(0x0000);
/*读取flash状态*/
SPIFlash.SReg = Read_Status_2Reg();
/*等待延时*/
Wait_Busy();
/*读取flash状态*/
SPIFlash.SReg = Read_Status_2Reg();
/*写使能*/
WREN();
/*擦除*/
Chip_Erase();
Wait_Busy();//等待4个周期。
Wait_Busy();
Wait_Busy();
Wait_Busy();
/*读取数据*/
ReadData(0, upper_128, 128);
/*写使能*/
WREN();
/*写入*/
PageProgram(0, upper_100, 128);
Wait_Busy();
/*读取数据*/
ReadData(0, upper_128, 128);
for (k = 0; k < 128; k++)
{
/*判断数组内的元素是否相同*/
if (upper_128[k] != upper_100[k])
{
status = false;
}
}
if (status == true)
{
GpioDataRegs.GPACLEAR.bit.GPIO6 = 1;
}
else
{
GpioDataRegs.GPASET.bit.GPIO7 = 1;
}
while (1)
{
}
return 0;
}