一、简介
最近在调试STM32F103C8驱动墨水屏的实验,在使用过程中,需要使用大内存的RAM需要,由于C8T6的RAM空间只有20KB,而墨水屏的需要的内存为800*480*2/8=93.75KB。
在网上查了相关的方案,都是使用STM32F103ZE外扩IS62WV51216这种并口SRAM的,使用IS62WV51216需要使用多引脚的STM32F103Zx系列,对于我这种使用背景,不需要上Zx系列,想查找下有没有串口的SRAM方案,但是并没有找到。
网上有成熟的TM32F103Cx系列外扩W25Qxx芯片的方案,但是这是FLASH,不是RAM,也不是我想要的方案。
所以干脆自己做个方案,找到了一块乐鑫的外扩RAM芯片,ESP-PSRAM64H。
查看其引脚分布,W25Qxx和PSRAM64H的引脚基本一致,都是SPI接口的,并且都支持QSPI。
所以直接使用【WeAct Studio】家的BluePill板子,其自带了W25Qxx接口,但是未焊接,所以直接买了一块,准备焊接PSRAM64进行验证测试。
使用的STM32库是标准库,为了保证速度,使用的是硬件SPI。
二、使用
2.1 电路图
STM32F103C8核心板W25Qxx电路图
ESP-PSRAM64的芯片接口
2.2 代码
2.2.1 SPI
使用的是SPI1的接口,为了保证传输速度,使用硬件SPI而不是软件SPI,所以需要先完成SPI的初始化。相关代码:
#include "spi.h"
void SPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );//PORTA时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE );//SPI1时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PA5/6/7复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA
GPIO_SetBits(GPIOA, GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7); //PA5/6/7上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI1, ENABLE); //使能SPI外设
SPI1_ReadWriteByte(0xff);//启动传输
// SPI1_SetSpeed(SPI_BaudRatePrescaler_2);//设置为18M时钟,高速模式
}
//SPI 速度设置函数
//SpeedSet:
//SPI_BaudRatePrescaler_2 2分频
//SPI_BaudRatePrescaler_8 8分频
//SPI_BaudRatePrescaler_16 16分频
//SPI_BaudRatePrescaler_256 256分频
void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
SPI1->CR1&=0XFFC7;
SPI1->CR1|=SPI_BaudRatePrescaler; //设置SPI1速度
SPI_Cmd(SPI1, ENABLE);
}
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI1_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据
}
2.2.2 PSRAM64
psram64.c文件
#include "psram64.h"
#include "spi.h"
#include "usart.h"
#include "delay.h"
/*
CS<--->PA4
SCLK<--->PA5
MISO<--->PA6
MOSI<--->PA7
*/
u8 PSRAM64_ReadID(void)
{
u8 MFID,KGD,EID[6],i;
PSRAM64_CS=0;
SPI1_ReadWriteByte(0x9F);//发送读取ID命令
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
SPI1_ReadWriteByte(0x00);
MFID = SPI1_ReadWriteByte(0xFF);
KGD = SPI1_ReadWriteByte(0xFF);
for(i=0; i<6; i++)
EID[i] = SPI1_ReadWriteByte(0xFF);
PSRAM64_CS=1;
printf("MDIF=0x%02X\r\n", MFID);
printf("KGD=0x%02X\r\n", KGD);
printf("EID=0x%02X%02X%02X%02X%02X%02X\r\n",EID[0],EID[1],EID[2],EID[3],EID[4],EID[5]);
return KGD;
}
void PSRAM64_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u16 i;
PSRAM64_CS=0;
SPI1_ReadWriteByte(PSRAM64_ReadData); //写读数据命令
SPI1_ReadWriteByte((u8)((ReadAddr)>>16)); //写读数据地址
SPI1_ReadWriteByte((u8)((ReadAddr)>>8));
SPI1_ReadWriteByte((u8)ReadAddr);
for(i=0;i<NumByteToRead;i++) //读数据
{
pBuffer[i]=SPI1_ReadWriteByte(0XFF);
}
PSRAM64_CS=1;
}
void PSRAM64_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToRead)
{
u16 i;
PSRAM64_CS=0;
SPI1_ReadWriteByte(PSRAM64_WriteData); //写写数据命令
SPI1_ReadWriteByte((u8)((WriteAddr)>>16)); //写写数据地址
SPI1_ReadWriteByte((u8)((WriteAddr)>>8));
SPI1_ReadWriteByte((u8)WriteAddr);
for(i=0;i<NumByteToRead;i++) //写数据
{
SPI1_ReadWriteByte(pBuffer[i]);
}
PSRAM64_CS=1;
}
void PSRAM64_DataReset(u32 WriteAddr,u16 NumByteToRead)
{
u16 i;
PSRAM64_CS=0;
SPI1_ReadWriteByte(PSRAM64_WriteData); //写写数据命令
SPI1_ReadWriteByte((u8)((WriteAddr)>>16)); //写写数据地址
SPI1_ReadWriteByte((u8)((WriteAddr)>>8));
SPI1_ReadWriteByte((u8)WriteAddr);
for(i=0;i<NumByteToRead;i++) //复位数据,写0x00
{
SPI1_ReadWriteByte(0x00);
}
PSRAM64_CS=1;
}
void PSRAM64_Reset(void)
{
PSRAM64_CS=0;
SPI1_ReadWriteByte(PSRAM64_RESET_ENABLE); //复位使能
SPI1_ReadWriteByte(PSRAM64_RESET_CMD); //复位
PSRAM64_CS=1;
}
u8 PSRAM64_Test(void)
{
u32 addr=0,data,ret=0;
for(addr=0; addr<PSRAM64_SIZE; addr+=4)
{
PSRAM64_Write((u8*)&addr, addr, 4);
if((addr&0xFFFF)==0)
printf("write %d/%d\r\n", addr>>16, PSRAM64_SIZE>>16);
}
for(addr=0; addr<PSRAM64_SIZE; addr+=4)
{
PSRAM64_Read((u8*)&data, addr, 4);
if(data!=addr)
{
printf("read error, addr(0x%08X),data(0x%08X)\r\n", addr, data);
ret++;
}
if((addr&0xFFFF)==0)
printf("read %d/%d\r\n", addr>>16, PSRAM64_SIZE>>16);
}
if(ret==0)
printf("PSRAM64 测试成功\r\n");
else
printf("PSRAM64 测试失败,ret(%d)\r\n",ret);
return ret;
}
void PSRAM64_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );//PORTA时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // PA4 推挽
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_4);
PSRAM64_CS=1; //SPI FLASH不选中
SPI1_Init();
delay_ms(10);
PSRAM64_Reset();
PSRAM64_ReadID();
PSRAM64_Test();
}
实现了对PSRAM64的测试,按照4字节为单位,对整块芯片的内存进行写,再对写入的数据进行读,对比写入的数据和读出的数据是否一致。
psram64.h文件
#ifndef __PSRAM64_H__
#define __PSRAM64_H__
#include "sys.h"
//容量:64Mb=8MB
#define PSRAM64_SIZE 8388608//8*1024*1024
#define PSRAM64_ReadData 0x03
#define PSRAM64_WriteData 0x02
#define PSRAM64_RESET_ENABLE 0x66
#define PSRAM64_RESET_CMD 0x99
#define PSRAM64_CS PAout(4) //PSRAM64的片选信号
u8 PSRAM64_ReadID(void);
void PSRAM64_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);
void PSRAM64_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToRead);
void PSRAM64_DataReset(u32 WriteAddr,u16 NumByteToRead);
void PSRAM64_Init(void);
#endif
三、测试结果
3.1 读取ID及写数据
3.2 读数据
四、后记
- PSRAM64也是支持QSPI的,但是由于目前对QSPI不太熟悉,并且F103也不支持QSPI,后续准备使用QSPI完成对此芯片的读取。
- 对于数据手册中的多种读取方式并没有深入了解,不太明白相关概念,后续有机会再继续研究。比如32字节/1K字节突发wrap模式,不太明白是什么意思。也不太清楚低频率和高频率的读是什么区别。
- 对于其他外扩串口RAM芯片,比如LY68L6400、APS6404L,也需要完成相关功能的验证。