SPI读写FRAM笔记

一、SPI协议概述

1.1 协议简介

SPI串行外设接口,在stm32中可以配置成基于三条线的全双工同步传输和基于双线的单工同步传输。由于是同步传输,所以需要一条时钟信号线,在时钟的上升沿或下降沿对信号进行采样,全双工需要一条输出线和一条输入线,若是有多个从机则还需要一条片选信号线。

SPI传输需要配置主从模式,通常MCU端为主模式,发送时钟信号,四条信号线定义如下:
MISO:Master input slave output 主机输入,从机输出(数据来自从机);
MOSI:Master output slave input 主机输出,从机输入(数据来自主机);
SCLK :Serial Clock 串行时钟信号,由主机产生发送给从机;
SS:Slave Select 片选信号,由主机发送,以控制与哪个从机通信,通常是低电平有效信号。

MISO也可以是SIMO,DOUT,DO,SDO或SO(在主机端);
MOSI也可以是SOMI,DIN,DI,SDI或SI(在主机端);
NSS也可以是CE,CS或SSEL;
SCLK也可以是SCK

1.2 通讯过程

SPI通讯过程可以形象的看作一条自行车的链条,上边的链条向右,下边的链条向左。
在这里插入图片描述
可以看到在主模式下,MCU需要先将片选信号拉低,通过时钟信号采样,然后将发送缓冲区的数据依次通过MOSI线发送给从机,同时接收从机自MISO线上的数据。

通过配置时钟极性和相位来设置是上升沿还是下降沿读写数据。这部分在SPI模式时配置,因此SPI也有四种模式。
在这里插入图片描述

在这里插入图片描述
一般外置SRAM使用模式3,四种模式中,模式0和3同为时钟上升沿采样传输,区别模式0还是模式3主要看时钟空闲时是高电平还是低电平。

多从机模式的连接有两种方式
一种是多条NSS片选信号线,和特定从机通讯时拉低,其余拉高。
另一种是菊花链的形式,主机输出–>从机1–>从机1输出–>从机2–>从机2输出…–>主机。

二、stm32配置SPI读写FRAM

2.1 FRAM简介

FRAM:铁电存储器,非易失性随机存储器,相比FLASH,无需擦除,写入更快,且几乎没有写入次数限制,可比EEPROM容量更大,使用SPI接口通讯。

2.3 SPI配置代码

2.3.1 接口定义:

/*********************************SPI接口定义******************************************/
#define FLASH_SPI                           SPI1
#define FLASH_SPI_CLK                       RCC_APB2Periph_SPI1
#define FLASH_SPI_CLK_INIT                  RCC_APB2PeriphClockCmd

#define FLASH_SPI_SCK_PIN                   GPIO_Pin_3                  
#define FLASH_SPI_SCK_GPIO_PORT             GPIOB                       
#define FLASH_SPI_SCK_GPIO_CLK              RCC_AHB1Periph_GPIOB
#define FLASH_SPI_SCK_PINSOURCE             GPIO_PinSource3
#define FLASH_SPI_SCK_AF                    GPIO_AF_SPI1

#define FLASH_SPI_MISO_PIN                  GPIO_Pin_4                
#define FLASH_SPI_MISO_GPIO_PORT            GPIOB                   
#define FLASH_SPI_MISO_GPIO_CLK             RCC_AHB1Periph_GPIOB
#define FLASH_SPI_MISO_PINSOURCE            GPIO_PinSource4
#define FLASH_SPI_MISO_AF                   GPIO_AF_SPI1

#define FLASH_SPI_MOSI_PIN                  GPIO_Pin_5                
#define FLASH_SPI_MOSI_GPIO_PORT            GPIOB                     
#define FLASH_SPI_MOSI_GPIO_CLK             RCC_AHB1Periph_GPIOB
#define FLASH_SPI_MOSI_PINSOURCE            GPIO_PinSource5
#define FLASH_SPI_MOSI_AF                   GPIO_AF_SPI1

#define FLASH_CS_PIN                        GPIO_Pin_4               
#define FLASH_CS_GPIO_PORT                  GPIOA                     
#define FLASH_CS_GPIO_CLK                   RCC_AHB1Periph_GPIOA

#define SPI_FLASH_CS_LOW()      {FLASH_CS_GPIO_PORT->BSRRH=FLASH_CS_PIN;}
#define SPI_FLASH_CS_HIGH()     {FLASH_CS_GPIO_PORT->BSRRL=FLASH_CS_PIN;}

void SPI_FRAM_Init(void)
{
  SPI_InitTypeDef SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;

  /* 使能 FLASH_SPI 及GPIO 时钟 */
  /*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO,
       SPI_FLASH_SPI_MISO_GPIO,SPI_FLASH_SPI_SCK_GPIO 时钟使能 */
  RCC_AHB1PeriphClockCmd(FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MISO_GPIO_CLK | FLASH_SPI_MOSI_GPIO_CLK | FLASH_CS_GPIO_CLK, ENABLE);

  /*!< SPI_FLASH_SPI 时钟使能 */
  FLASH_SPI_CLK_INIT(FLASH_SPI_CLK, ENABLE);

  // 设置引脚复用
  GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT, FLASH_SPI_SCK_PINSOURCE, FLASH_SPI_SCK_AF);
  GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT, FLASH_SPI_MISO_PINSOURCE, FLASH_SPI_MISO_AF);
  GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT, FLASH_SPI_MOSI_PINSOURCE, FLASH_SPI_MOSI_AF);

  /*!< 配置 SPI_FLASH_SPI 引脚: SCK */
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

  GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);

  /*!< 配置 SPI_FLASH_SPI 引脚: MISO */
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
  GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);

  /*!< 配置 SPI_FLASH_SPI 引脚: MOSI */
  GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
  GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);

  /*!< 配置 SPI_FLASH_SPI 引脚: CS */
  GPIO_InitStructure.GPIO_Pin = FLASH_CS_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_Init(FLASH_CS_GPIO_PORT, &GPIO_InitStructure);

  /* 停止信号 FLASH: CS引脚高电平*/
  SPI_FLASH_CS_HIGH();

  /* FLASH_SPI 模式配置 */
  // FLASH芯片 支持SPI模式0及模式3,据此设置CPOL CPHA
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  SPI_InitStructure.SPI_CRCPolynomial = 7;
  SPI_Init(FLASH_SPI, &SPI_InitStructure);

  /* 使能 FLASH_SPI  */
  SPI_Cmd(FLASH_SPI, ENABLE);
}

2.3.2 协议定义:

//接口函数-FM25CL64 片选使能
#define BSP_FM25CL64_NSS_EN		GPIO_ResetBits(GPIOA, GPIO_Pin_4)

//接口函数-FM25CL64 片选失能
#define BSP_FM25CL64_NSS_DN		GPIO_SetBits(GPIOA, GPIO_Pin_4)
/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT         ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
/* Private define ------------------------------------------------------------*/
/*命令定义-开头*******************************/
#define FM25CL64_WREN				0x06
#define FM25CL64_WRDI				0x04
#define FM25CL64_RDSR				0x05
#define FM25CL64_WRSR				0x01
#define FM25CL64_READ				0x03
#define FM25CL64_WRITE			    0x02
#define FM25CL64_PROTECT 		    0x0c
#define FM25CL64_UNPROTECT	        0x00
/*命令定义-结束*******************************/

/**
 * @brief  使用SPI发送一个字节的数据
 * @param  byte:要发送的数据
 * @retval 返回接收到的数据
 */
static __IO uint32_t SPITimeout = SPIT_LONG_TIMEOUT;
u8 SPI_FLASH_SendByte(u8 byte)
{
	SPITimeout = SPIT_FLAG_TIMEOUT;

	/* 等待发送缓冲区为空,TXE事件 */
	while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET)
	{
		if ((SPITimeout--) == 0)
			return 0;
	}

	/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
	SPI_I2S_SendData(FLASH_SPI, byte);

	SPITimeout = SPIT_FLAG_TIMEOUT;

	/* 等待接收缓冲区非空,RXNE事件 */
	while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET)
	{
		if ((SPITimeout--) == 0)
			return 1;
	}

	/* 读取数据寄存器,获取接收缓冲区数据 */
	return SPI_I2S_ReceiveData(FLASH_SPI);
}
/*
 ************************************************************
 *  名称:	BSP_FM25CL64_ReadState()
 *  功能:	读铁电状态寄存器
 *	输入:  无
 *	输出:  无
 ************************************************************
 */
u8 BSP_FM25CL64_ReadState(void)
{
	BSP_FM25CL64_NSS_EN;
	SPI_FLASH_SendByte(FM25CL64_RDSR);
	u8 output = SPI_FLASH_SendByte(0);
	BSP_FM25CL64_NSS_DN;

	return output;
}

/*
 ************************************************************
 *  名称:	BSP_FM25CL64_ReadState()
 *  功能:	铁电写使能
 *	输入:  无
 *	输出:  无
 ************************************************************
 */
void BSP_FM25CL64_WriteEnable(void)
{
	BSP_FM25CL64_NSS_EN;
	SPI_FLASH_SendByte(FM25CL64_WREN);
	BSP_FM25CL64_NSS_DN;
}

/*
 ************************************************************
 *  名称:	BSP_FM25CL64_WriteState()
 *  功能:	写铁电状态寄存器
 *	输入:  无
 *	输出:  无
 ************************************************************
 */
void BSP_FM25CL64_WriteState(u8 data)
{
	BSP_FM25CL64_WriteEnable();

	BSP_FM25CL64_NSS_EN;
	SPI_FLASH_SendByte(FM25CL64_WRSR);
	SPI_FLASH_SendByte(data);
	BSP_FM25CL64_NSS_DN;
}

/*
 ************************************************************
 *  名称:	BSP_FM25CL64_ProAll()
 *  功能:	铁电保护全部
 *	输入:  无
 *	输出:  无
 ************************************************************
 */
void BSP_FM25CL64_ProAll(void)
{
	BSP_FM25CL64_WriteState(FM25CL64_PROTECT);
}

/*
 ************************************************************
 *  名称:	BSP_FM25CL64_UProAll()
 *  功能:	铁电去掉保护
 *	输入:  无
 *	输出:  无
 ************************************************************
 */
void BSP_FM25CL64_UProAll(void)
{
	BSP_FM25CL64_WriteState(FM25CL64_UNPROTECT);
}

/*
 ************************************************************
 *  名称:	BSP_FM25CL64_SingleRead()
 *  功能:	铁电读一个字节
 *	输入:  address-地址
 *	输出:  无
 ************************************************************
 */
u8 BSP_FM25CL64_SingleRead(u32 address)
{
	u8 output;

	BSP_FM25CL64_NSS_EN;
	SPI_FLASH_SendByte(FM25CL64_READ);
	SPI_FLASH_SendByte(((address & 0x030000) >> 16));
	SPI_FLASH_SendByte(((address & 0x00ff00) >> 8));
	SPI_FLASH_SendByte((address & 0x0000ff));
	output = SPI_FLASH_SendByte(0);
	BSP_FM25CL64_NSS_DN;

	return output;
}

/*
 ************************************************************
 *  名称:	BSP_FM25CL64_MultipleRead()
 *  功能:	铁电读多个字节
 *	输入:  address-地址  num-个数  pointer-读出数据存放的地址指针
 *	输出:  无
 ************************************************************
 */
void BSP_FM25CL64_MultipleRead(u32 address, u16 num, u8 *pointer)
{
	BSP_FM25CL64_NSS_EN;

	SPI_FLASH_SendByte(FM25CL64_READ);
	SPI_FLASH_SendByte(((address & 0x030000) >> 16));
	SPI_FLASH_SendByte(((address & 0x00ff00) >> 8));
	SPI_FLASH_SendByte((address & 0x0000ff));

	for (u16 i = 0; i < num; i++)
	{
		*(pointer + i) = SPI_FLASH_SendByte(0);
	}

	BSP_FM25CL64_NSS_DN;
}

/*
 ************************************************************
 *  名称:	BSP_FM25CL64_SingleWrite()
 *  功能:	铁电写单个字节(写入之前先取消写保护!)
 *	输入:  address-地址  valve-写入数据数值
 *	输出:  无
 ************************************************************
 */
u8 BSP_FM25CL64_SingleWrite(u32 address, u8 valve)
{
	BSP_FM25CL64_WriteEnable();

	BSP_FM25CL64_NSS_EN;
	SPI_FLASH_SendByte(FM25CL64_WRITE);
	SPI_FLASH_SendByte(((address & 0x030000) >> 16));
	SPI_FLASH_SendByte(((address & 0x00ff00) >> 8));
	SPI_FLASH_SendByte((address & 0x0000ff));
	u8 output = SPI_FLASH_SendByte(valve);
	BSP_FM25CL64_NSS_DN;

	return output;
}

/*
 ************************************************************
 *  名称:	BSP_FM25CL64_MultipleWrite()
 *  功能:	铁电写多个字节(写入之前先取消写保护!)
 *	输入:  address-地址  num-个数  pointer-写入数据存放的地址指针
 *	输出:  无
 ************************************************************
 */
void BSP_FM25CL64_MultipleWrite(u32 address, u16 num, u8 *pointer)
{
	BSP_FM25CL64_WriteEnable();

	BSP_FM25CL64_NSS_EN;
	SPI_FLASH_SendByte(FM25CL64_WRITE);
	SPI_FLASH_SendByte(((address & 0x030000) >> 16));
	SPI_FLASH_SendByte(((address & 0x00ff00) >> 8));
	SPI_FLASH_SendByte((address & 0x0000ff));
	for (u16 i = 0; i < num; i++)
	{
		SPI_FLASH_SendByte(pointer[i]);
	}
	BSP_FM25CL64_NSS_DN;
}

三、问题记录

由于一开始使用的8k字节的FRAM,后来根据需要换成了2M的FRAM,移植代码后发现接收乱码,经过数据手册对比发现是通讯数据地址位字节数不一致导致。
在这里插入图片描述
在这里插入图片描述
如上图,2M的地址位为18bit,表示从0到3FFFFh位地址,地址范围刚好是256 * 1024,256K * 8=2Mbit。
因此程序发送和接收修改为相应大小位,再按照高位在前,8位8位的发送数据,数据传输无误。

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值