针对富士通的MB85RS2M驱动程序
软件模拟SPI
首先是初始化GPIO引脚,因为是软件模拟,所以GPIO不做要求,任意引脚即可。
#define BSP_MB85RS2M
#include "bsp_mb85rs2m.h"
/*
MX_SPI1_Init
------------------
Des:SPI初始化
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
void MX_SPI1_Init(void)
{
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
/**SPI1 GPIO Configuration
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PA7 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = LL_GPIO_PIN_5|LL_GPIO_PIN_7;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LL_GPIO_PIN_4;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LL_GPIO_PIN_6;
GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
编写SPI的读字节程序
/*
SpiReadByte
------------------
Des:SPI读字节函数
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
static uint8_t SpiReadByte(void)
{
uint8_t u8Index, u8Rdata = 0;
FM_CLR_CLK;
for(u8Index=0; u8Index<8; u8Index++)
{
if(FM_STA_MISO)
{
u8Rdata |= 0x80 >> u8Index;
}
FM_SET_CLK;
FM_CLR_CLK;
}
return u8Rdata;
}
编写SPI写字节函数
/*
SpiWriteByte
------------------
Des:SPI写字节函数
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
void SpiWriteByte(uint8_t u8Wdata)
{
uint8_t u8Index;
FM_CLR_CLK;
for(u8Index = 0; u8Index < 8; u8Index++)
{
if(u8Wdata & (0x80 >> u8Index))
{
FM_SET_MOSI;
}
else
{
FM_CLR_MOSI;
}
FM_SET_CLK;
FM_CLR_CLK;
}
}
下面是读写内存的函数,是重点
写一条数据到FRAM指定地址
首先看FRAM的数据手册,数据手册上说需要先对WEL寄存器进行操作,在写数据之前,首先要发送写使能的命令;
可以看到,写数据前先要发送解锁命令后才能进行常规操作,在解锁之后发送写使能命令,然后发送要写入的地址和要写入的数据,所以程序如下:
/*
FM_Write_Datas
------------------
Des:Write memory data,在例程中时钟为18432000Hz条件下,写200字节的时间为6ms
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
uint8_t FM_Write_Datas(uint32_t u32Addr, uint8_t *pu8Buf, uint32_t u32PLen)
{
uint32_t u32Index;
if(u32PLen == 0)
{
return 0;
}
if((u32Addr + u32PLen) > (MB85RS2M_ADDR_MAX + 1))
{
return 0;
}
/*发送解锁命令*/
FM_CLR_CS;
SpiWriteByte(MB85RS256A_WREN_INST);
FM_SET_CS;
/*发送写使能命令*/
FM_CLR_CS;
SpiWriteByte(MB85RS256A_WRITE_INST);
SpiWriteByte((u32Addr & 0x3FFFF) >> 16);
SpiWriteByte((u32Addr & 0xFFFF) >> 8);
SpiWriteByte(u32Addr & 0xFF);
for(u32Index=0; u32Index < u32PLen; u32Index++)
{
SpiWriteByte(pu8Buf[u32Index]);
}
FM_SET_CS;
return 1;
}
程序解释
u32Addr为要写入的地址,pu8Buf为写入数据的指针,u32PLen为要写入数据的长度,第一个if用于长度判断,第二个if用于地址溢出判断,防止写入地址出错。然后发送解锁指令,解锁指令为0x06,为什么要先拉高片选再拉低发送写命令,是因为手册的时序图不允许连续发送命令,在一个命令发送后需要对片选进行拉高。写入命令发送后就要发送地址,因为2M的FRAM地址为24位,高位在前,所以将地址分为三个字节进行发送,发送之后就是循环发送要写入的数据,最后将片选拉高,完成写操作。
读一条数据到FRAM指定地址
读取的操作比写少了一步解锁,基本原理相同,所以不做解释,下面附上代码
/*
FM_Read_Datas
------------------
Des:Read memory data
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
uint8_t FM_Read_Datas(uint32_t u32Addr, uint8_t *pu8Buf, uint32_t u32PLen)
{
uint32_t u32Index;
if(u32PLen == 0)
{
return 0;
}
if((u32Addr + u32PLen) > (MB85RS2M_ADDR_MAX + 1))
{
return 0;
}
FM_CLR_CS;
SpiWriteByte(MB85RS256A_READ_INST);
SpiWriteByte((u32Addr & 0x3FFFF) >> 16);
SpiWriteByte((u32Addr & 0xFFFF) >> 8);
SpiWriteByte(u32Addr & 0xFF);
for(u32Index=0; u32Index < u32PLen; u32Index++)
{
pu8Buf[u32Index] = SpiReadByte();
}
FM_SET_CS;
return 1;
}
最后是读芯片的ID
/*
FM_Read_DeviceID
------------------
Des:Read device ID
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
void FM_Read_DeviceID(uint8_t *pID)
{
uint8_t u8Index;
FM_CLR_CS;
SpiWriteByte(MB85RS256A_RDID_INST);
for(u8Index=0; u8Index < 9; u8Index++)
{
pID[u8Index] = SpiReadByte();
}
FM_SET_CS;
}
最后是读写的实际时序图
开启硬件SPI
第一步开始初始化引脚,GPIO初始化采用LL库,SPI采用寄存器操作,方便移植,对应不同的库函数,只需要更改相应的GPIO初始化以及使能SPI时钟即可。
/*
MX_SPI1_Init
------------------
Des:SPI初始化
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
void MX_SPI1_Init(void)
{
/* Peripheral clock enable */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
/**SPI1 GPIO Configuration
PA5 ------> SPI1_SCK
PA6 ------> SPI1_MISO
PA7 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = LL_GPIO_PIN_5|LL_GPIO_PIN_7;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LL_GPIO_PIN_6;
GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LL_GPIO_PIN_4;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*SPI主机模式*/
SPI1->CR1|=1<<2;
/*NSS引脚软件模式*/
SPI1->CR1|=1<<9;
SPI1->CR1|=1<<8;
/*使能SPI*/
SPI1->CR1|=1<<6;
}
开启硬件SPI只有四条指令,因查阅寄存器后均可使用默认值所以未做修改.
根据SPI的寄存器,我们仅使能SPI为主设备,软件管理NSS引脚,开启SPI,时钟极性和时钟相位都选择0,这点也是依据手册来的。记得将MISO,MOSI,.CLK引脚设置为复用功能,片选为推挽输出。
下面是读写一条数据,因为SPI设置为全双工,所以在写之前要查询发送是否为空,如果为空则发送数据,并且读引脚,读完引脚要检查读取引脚是否为空,如果为空则进行下一步,切记写之后要读引脚,否则时序会错,具体原因应该是SPI的发送寄存器和接收寄存器共用一个寄存器DR,所以在发送之后要对引脚进行读取,手册是这样说的:
虽然手册上说是有俩缓冲区,但是对应的是一个寄存器,在操作的过程中我发现如果用软件模拟不存在问题,因为没有使用这个寄存器,但是在硬件SPI中,对这个寄存器写之后需要进行读取,否则SPI_SR寄存器中的RXNE位将处于空状态,会引发程序不进行数据等待而直接将片选拉高。
附上读写一条SPI指令程序
/*
SpiWriteByte
------------------
Des: 读写一条SPI数据
NOTE:
REF:
------------------
Author:zx
Date:2023年05月19日
*/
uint8_t SpiReadWriteByte(uint8_t u8TxData)
{
uint16_t u16Count = 0;
while((SPI1->SR&1<<1) == 0)
{
u16Count++;
if(u16Count >= 0xFFFE)
{
return 0;
}
}
SPI1->DR = u8TxData;
u16Count = 0;
while((SPI1->SR&1<<0) == 0)
{
u16Count++;
if(u16Count >= 0xFFFE)
{
return 0;
}
}
return SPI1->DR;
}
下面是写一条数据到FRAM
/*
FM_Write_Datas
------------------
Des: Write memory data
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
uint8_t FM_Write_Datas(uint32_t u32Addr, uint8_t *pu8Buf, uint32_t u32PLen)
{
uint32_t u32LoopData;
if(u32PLen == 0)
{
return 0;
}
if((u32Addr + u32PLen) > (MB85RS2M_ADDR_MAX + 1))
{
return 0;
}
FM_CLR_CS;
SpiReadWriteByte(MB85RS256A_WREN_INST);
FM_SET_CS;
FM_CLR_CS;
/*写命令*/
SpiReadWriteByte(MB85RS256A_WRITE_INST);
SpiReadWriteByte((u32Addr & 0x3FFFF) >> 16);
SpiReadWriteByte((u32Addr & 0xFFFF) >> 8);
SpiReadWriteByte(u32Addr & 0xFF);
for(u32LoopData=0; u32LoopData < u32PLen; u32LoopData++)
{
SpiReadWriteByte(pu8Buf[u32LoopData]);
}
FM_SET_CS;
return 1;
}
程序的具体逻辑参考软件模拟的,是一样的
下面是读一条FRAM中的数据程序
/*
FM_Read_Datas
------------------
Des:Read memory data
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
uint8_t FM_Read_Datas(uint32_t u32Addr, uint8_t *pu8Buf, uint32_t u32PLen)
{
uint32_t u32LoopData;
if(u32PLen == 0)
{
return 0;
}
if((u32Addr + u32PLen) > (MB85RS2M_ADDR_MAX + 1))
{
return 0;
}
FM_CLR_CS;
/*读命令*/
SpiReadWriteByte(MB85RS256A_READ_INST);
SpiReadWriteByte((u32Addr & 0x3FFFF) >> 16);
SpiReadWriteByte((u32Addr & 0xFFFF) >> 8);
SpiReadWriteByte(u32Addr & 0xFF);
for(u32LoopData=0; u32LoopData < u32PLen; u32LoopData++)
{
pu8Buf[u32LoopData] = SpiReadWriteByte(0xFF);
}
FM_SET_CS;
return 1;
}
最后是读取ID
/*
FM_Read_DeviceID
------------------
Des:读取设备ID
NOTE:
REF:
------------------
Author:zx
Date:2023年05月11日
*/
void FM_Read_DeviceID(uint8_t *pID)
{
uint8_t u8LoopData;
FM_CLR_CS;
SpiReadWriteByte(MB85RS256A_RDID_INST);
for(u8LoopData=0; u8LoopData < 9; u8LoopData++)
{
pID[u8LoopData] = SpiReadWriteByte(0xFF);
}
FM_SET_CS;
}
最后附上.h文件
//
// bsp_mb85rs2m.h
// 描述: 2mFram驱动程序
//
// 履历:
// -------------------------------
// 【1】创建模块 zx 2023年05月19日
// -------------------------------
#ifndef __HH_BSP_MB85RS2M_H_HH__
#define __HH_BSP_MB85RS2M_H_HH__
#ifdef BSP_MB85RS2M
#define BSP_MB85RS2M_EX
#else
#define BSP_MB85RS2M_EX extern
#endif
#include "include.h"
/*MB85RS256A寄存器定义*/
/*设置写使能锁存器*/
#define MB85RS256A_WREN_INST (0x06)
/*写禁止*/
#define MB85RS256A_WRDI_INST (0x04)
/*读状态寄存器*/
#define MB85RS256A_RDSR_INST (0x05)
/*写状态寄存器*/
#define MB85RS256A_WRSR_INST (0x01)
/*读存储器数据*/
#define MB85RS256A_READ_INST (0x03)
/*写存储器数据*/
#define MB85RS256A_WRITE_INST (0x02)
#define MB85RS256A_STATUS_REG (0x0)
#define MB85RS256A_INIT_STATE (0x09)
/*读器件ID*/
#define MB85RS256A_RDID_INST (0x9F)
#define FM_SET_CLK LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_5)
#define FM_CLR_CLK LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_5)
#define FM_SET_MOSI LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_7)
#define FM_CLR_MOSI LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_7)
#define FM_STA_MISO LL_GPIO_ReadInputPort(GPIOA) & 0x0040
#define FM_SET_CS LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_4)
#define FM_CLR_CS LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_4)
void MX_SPI1_Init(void);
void FM_Read_DeviceID(uint8_t *pID);
uint8_t SpiReadWriteByte(uint8_t u8TxData);
uint8_t FM_Write_Datas(uint32_t u32Addr, uint8_t *pu8Buf, uint32_t u32PLen);
uint8_t FM_Read_Datas(uint32_t u32Addr, uint8_t *pu8Buf, uint32_t u32PLen);
#endif
在最后的最后没有附时序图,是因为SPI挂载在APB2上,APB2的时钟是72MHZ,在SPI寄存器配置速度的时候是/2,所以速度是36MHz,我的逻辑分析仪最大支持24MHz,读不出来- - 所以没图。配置为36MHz也是看手册的,手册上说器件最大支持40MHz,所以36MHz是稳稳可以跑起来的。