百为STM32开发板教程之九——SPI FLASH
实验目的:把字库写到SPI FLASH中,然后在程序里利用SPI FLASH中的字库进行显示
主要内容:
一、了解SPI通信协议
二、了解STM32的SPI接口及编程
三、了解SPI FLASH的工作原理及读写方法
四、了解如何把字库写到SPI FLASH及把字库读出显示
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
一、SPI通信协议
SPI总线规定了4个逻辑信号:
(1)MOSI – SPI总线主机输出/ 从机输入(Master Output/Slave Input)
(2)MISO – SPI总线主机输入/ 从机输出(Master Input/Slave Output)
(3)SCLK – 时钟信号,由主设备产生
(4)CS – 从设备使能信号,由主设备控制(Chip select)
其中SCLK的极性和相位是可设置的:
CPOL表示时钟极性,即空闲状态时的电平,低电平(0)或高电平(1) CPOH表示数据采样的时钟沿,第一个沿(0)或第二个沿(1)
CPOL和CPOH组成了SPI的四种工作模式:(0) CPOL = 0 CPOH = 0 (1) CPOL = 0 CPOH = 1 (2) CPOL = 1, CPOH = 0 (3) CPOL = 1, CPOH = 1
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
二、STM32的SPI接口及编程
STM32F103ZET6有3个SPI,分别为SPI1、SPI2和SPI3。
其中SP2和I2S2,SPI3和I2S3共用管脚,采用SPI功能还是I2S功能是由SPI_I2S_CFGR寄存器的第11位I2SMOD来决定的。
SPI_I2S配置寄存器(SPI_I2S_CFGR)
其中I2SMODE位为1时表示选择I2S模式,为0时表示选择SPI模式。
在我们百为STM32开发板上,是采用STM32的SPI1连接SPI FLASH的
IO对应关系为:
STM32 M25P80
PB2(CS)---------------------------------- S(片选信号)
PA5(SPI1_SCK)------------------------- C(串行时钟)
PA6(SPI1_MISO)------------------------ Q(串行数据输出)
PA7(SPI1_MOSI)------------------------ D(串行数据输入)
程序中要SPI,首先要调用下面的函数进行初始化
void SPI_FLASH_Init(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* 打开SPI1和GPIO时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIO_CS, ENABLE);
/* 配置SPI1管脚: SCK, MISO and MOSI */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 配置SPI FLASH 片选IO */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_CS;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIO_CS, &GPIO_InitStructure);
/* 初始化SPI FLASH片选为高电平 */
SPI_FLASH_CS_HIGH();
/* SPI1参数配置 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI为全双工通信
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为SPI主模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置为8位数据格式
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //设置为SPI工作模式4,即CPOL = 1,
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //CPOH = 1
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //设置NSS为软件管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; //设置SPI波特率为fpclk/4
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //设置数据高位在先
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
/* 使能SPI1 */
SPI_Cmd(SPI1, ENABLE);
}
三、了解SPI FLASH的工作原理及读写方法
1、简介
M25P80是一个8Mbit(1Mbitx8)的串行FLASH,具有高级的写保护机制,可以通过SPI兼容的总线进行读写。
M25P80一共有16个扇区,每个扇区有256个页,每个页有256 byte。
通过页写入指令,可以一次写入1-256 byte数据。
通过扇区擦除指令,可以以扇区为单位进行擦除。
2、M25P80的SPI工作模式
M25P80支持两种SPI模式:(0) CPOL = 0 CPOH = 0 (3) CPOL = 1, CPOH = 1
3、操作指令
(1)写使能指令(0x06)
部分指令如页写入指令,扇区擦除指令,批量擦除指令操作之前都需要发送写使能指令。
写使能指令时序:
(2)页写入指令(0x02)
页写入需要两个指令,一个是写使能指令(1个字节),另一个是页写入指令(4个字节以上)。
(3)扇区擦除指令(0xD8)
在页写入之前,我们必须擦除相应的扇区。扇区擦除也需要两个指令,一个是写使能指令(1个字节),另一个是扇区擦除指令(4个字节)。
扇区擦除指令时序:
(4)批量擦除指令(0xC7)
批量擦除是指擦除整个芯片内容。批量擦除需要两个指令,一个是写使能指令(1个字节),另一个是批量擦除指令(1个字节)
批量擦除指令时序:
(5)读ID指令(0x9F)
发送读ID指令时数据输出序列:
读ID指令时序:
(6)读数据指令(0x03)
写入读数据指令(1个字节)和地址(3个字节),可以读回1个字节以上的数据。
读数据指令时序:
(7)读状态指令(0x05)
状态寄存器格式:
读状态寄存器指令时序:
读SPI FLASH ID程序:
u32 SPI_FLASH_ReadID(void)
{
u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
/* 设置片选为低电平,选中SPI FLASH*/
SPI_FLASH_CS_LOW();
/* 发送读ID指令0x9F */
SPI_FLASH_SendByte(0x9F);
/* 从SPI FLASH读出第一个字节(0x20) */
Temp0 = SPI_FLASH_SendByte(Dummy_Byte);
/* 从SPI FLASH读出第二个字节(0x20) */
Temp1 = SPI_FLASH_SendByte(Dummy_Byte);
/* 从SPI FLASH读出第三个字节(0x14) */
Temp2 = SPI_FLASH_SendByte(Dummy_Byte);
/* 设置片选为高电平,取消选择SPI FLASH */
SPI_FLASH_CS_HIGH();
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
return Temp;
}
SPI FLASH扇区擦除函数:
void SPI_FLASH_SectorErase(u32 SectorAddr)
{
/* 发送写使能指令 */
SPI_FLASH_WriteEnable();
/* 扇区擦除 */
/* 设置片选为低电平,选中SPI FLASH */
SPI_FLASH_CS_LOW();
/* 发送扇区擦除指令 */
SPI_FLASH_SendByte(SE);
/* 发送扇区地址a23~a16 */
SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
/* 发送扇区地址a15~a8 */
SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
/* 发送扇区地址a7~a0 */
SPI_FLASH_SendByte(SectorAddr & 0xFF);
/* 设置片选为高电平,取消选择SPI FLASH */
SPI_FLASH_CS_HIGH();
/* 等待写操作完成 */
SPI_FLASH_WaitForWriteEnd();
}
写入数据到SPI FLASH,SPI FLASH只提供了页写入指令对SPI FLASH进行写操作,一次最多能写256 Byte数据,所以如果超过256 Byte,我们要多次调用页写入指令。
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
Addr = WriteAddr % SPI_FLASH_PageSize; //检测地址WriteAddr是否是页(256 Byte)对齐的
count = SPI_FLASH_PageSize - Addr; //Addr 是最后不足一页的地址偏移,count是补齐一页的地址长度
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize; //计算出总页数
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize; //计算出最后不足一页的数据长度
if (Addr == 0) /* 判断地址WriteAddr是否是页对齐的*/
{
if (NumOfPage == 0) /* 要写入的字节数NumByteToWrite 小于 页大小SPI_FLASH_PageSize,则只调用一次页写入指令即可,否则要调用多次*/
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else /* 要写入的字节数NumByteToWrite 小于 页大小SPI_FLASH_PageSize */
{
while (NumOfPage--) //写入NumOfPage个页
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize; //地址指向下一页
pBuffer += SPI_FLASH_PageSize; //pBuffer指针指向要写入下一页的数据
}
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle); //写入最后不足一页的字节数
}
}
else /* 地址WriteAddr不是页对齐的 */
{
if (NumOfPage == 0) /* 要写入的字节数NumByteToWrite 小于页大小SPI_FLASH_PageSize */
{
if (NumOfSingle > count) /* 要写入的数据超出了当前地址WriteAddr到页尾的距离,(NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */
{
temp = NumOfSingle - count; //超出部分写到下一页,temp表示写到下一页的字节数
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); //写当前页
WriteAddr += count; //地址指向下一页
pBuffer += count; //pBuffer指针指向要写入下一页的数据
SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp); //写下一页
}
else
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite); //要写入的数据没有超出当前地址WriteAddr到页尾的距离,直接写入当前页
}
}
else /* 要写入的字节数NumByteToWrite 大于页大小SPI_FLASH_PageSize */
{
NumByteToWrite -= count; //NumByteToWrite -count表示补齐当前页地址长度后,剩余的数据长度
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize; //剩余数据长度所占的页数
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize; //剩余数据长度最后不足一页的数据长度
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); //补齐当前页
WriteAddr += count; //地址指向下一页
pBuffer += count; //pBuffer指针指向要写入下一页的数据
while (NumOfPage--) //写入NumOfPage个页
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize; //地址指向下一页
pBuffer += SPI_FLASH_PageSize; //pBuffer指针指向要写入下一页的数据
}
if (NumOfSingle != 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle); //写入最后不足一页的字节数
}
}
}
}
SPI FLASH页写入函数:
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
/* 发送写使能指令 */
SPI_FLASH_WriteEnable();
/* 片选设为低电平,选中SPI FLASH */
SPI_FLASH_CS_LOW();
/* 发送页写入指令 */
SPI_FLASH_SendByte(WRITE);
/* 发送地址a23~a16 */
SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
/* 发送地址a15~a8 */
SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
/* 发送地址a7~a0 */
SPI_FLASH_SendByte(WriteAddr & 0xFF);
/* 循环写入NumByteToWrite个字节数据 */
while (NumByteToWrite--)
{
/* 发送一字节数据 */
SPI_FLASH_SendByte(*pBuffer);
/* pBuffer指向下一个要写入的数据 */
pBuffer++;
}
/* 片选设为高电平,取消选中SPI FLASH*/
SPI_FLASH_CS_HIGH();
/* 等待写操作完成 */
SPI_FLASH_WaitForWriteEnd();
}
从SPI FLASH读取数据,发送读指令后,可以读取无限个数据:
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
/* 片选设为低电平,选中SPI FLASH */
SPI_FLASH_CS_LOW();
/* 发送读数据指令 */
SPI_FLASH_SendByte(READ);
/* 发送地址a23~a16 */
SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
/* 发送地址a15~a8 */
SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);
/* 发送地址a7~a0 */
SPI_FLASH_SendByte(ReadAddr & 0xFF);
while (NumByteToRead--) /* 读取NumByteToRead个字节数据 */
{
/* 从SPI FLASH读一字节数据*/
*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
/* pBuffer指针指向读出数据被存放到的下一个位置 */
pBuffer++;
}
/* 设置片选为高电平,取消选择SPI FLASH */
SPI_FLASH_CS_HIGH();
}
四、了解如何把字库写到SPI FLASH及把字库读出显示
把hzk16.c文件里的字库数据写到SPI FLASH里,首先要擦除要写入的扇区
相关代码:
/* main.c */
sectorCnt = sizeof(GB2312_16x16_Table)/SPI_FLASH_SectorSize; //计算字库数据需要写入的扇区数
if(sizeof(GB2312_16x16_Table)%SPI_FLASH_SectorSize != 0)
sectorCnt++; //如果不满一个扇区,擦除一个扇区
for(i = 0; i < sectorCnt; i++ ) //擦除sectorCnt个扇区
{
SPI_FLASH_SectorErase(i*SPI_FLASH_SectorSize); //擦除一个扇区
}
SPI_FLASH_BufferWrite((u8*)GB2312_16x16_Table, 0, sizeof(GB2312_16x16_Table)); //把字库数据写入到SPI FLASH,从地址0开始写。
/* stm3210e_eval_lcd.c */
void LCD_DisplayChineseChar(uint16_t Line, uint16_t Column, uint8_t *Char_GB)
{
uint32_t offset;
uint16_t byteOfDots;
uint8_t pData[24*24];
byteOfDots = LCD_Currentfonts->Height*(LCD_Currentfonts->Width/8);
offset = 94 * (*Char_GB - 0xA1) * byteOfDots + (*(Char_GB+1) - 0xA1) * byteOfDots; //计算当前要显示的字符的点阵数据在SPI FLASH中的偏移地址
SPI_FLASH_BufferRead(pData, offset, byteOfDots); //从SPI FLASH中读取字库点阵数据,存放在pData数组中
// LCD_DrawChineseChar(Line, Column, &LCD_Currentfonts->table[offset]);
LCD_DrawChineseChar(Line, Column, (uint16_t*)pData); //调用函数把点阵数据画到LCD上
}
|
|