作者:残心秀士
转自:http://www.52rd.com/Blog/Detail_RD.Blog_swordlife_23439.html
在项目中用到2M Byte容量的Flash芯片AT45DB161,用来存放字库文件,这两天在写AT45DB161的读取控制程序,在参考网上的一些控制代码的基础上,编写成功。现总结经验如下:
1. AT45DB161芯片内部有两个page buffer,每一个page buffer默认是528 Byte。而Datasheet上说这个Page buffer的大小可以设定为512Byte;我在实际使用的时候没有修改设定,只是对一个page写入512个Byte而已,剩下的不管;读取的时候,读取528个Byte,只取前面512 Byte,剩下的不理睬。
以下是AT45DB161的控制程序代码:
__________________________________________________
#define AT45DB_BUFFER_1_WRITE 0x84 /* 写入第一缓冲区 */
#define AT45DB_BUFFER_2_WRITE 0x87 /* 写入第二缓冲区 */
#define AT45DB_BUFFER_1_WRITE_FLASH 0x82 /* 写入第一缓冲区 */
#define AT45DB_BUFFER_2_WRITE_FLASH 0x85 /* 写入第二缓冲区 */
#define AT45DB_BUFFER_1_READ 0xD4 /* 读取第一缓冲区 */
#define AT45DB_BUFFER_2_READ 0xD6 /* 读取第二缓冲区 */
#define AT45DB_B1_TO_MM_PAGE_PROG_WITH_ERASE 0x83 /* 将第一缓冲区的数据写入主存储器(擦除模式)*/
#define AT45DB_B2_TO_MM_PAGE_PROG_WITH_ERASE 0x86 /* 将第二缓冲区的数据写入主存储器(擦除模式)*/
#define AT45DB_MM_PAGE_TO_B1_XFER 0x53 /* 将主存储器的指定页数据加载到第一缓冲区 */
#define AT45DB_MM_PAGE_TO_B2_XFER 0x55 /* 将主存储器的指定页数据加载到第二缓冲区 */
#define AT45DB_PAGE_ERASE 0x81 /* 页删除(每页512/528字节) */
#define AT45DB_SECTOR_ERASE 0x7C /* 扇区擦除(每扇区128K字节)*/
#define AT45DB_READ_STATE_REGISTER 0xD7 /* 读取状态寄存器 */
#define AT45DB_BLACK_ERASE 0x50 /* 块删除(每块4KByte)*/
#define AT45DB_MM_PAGE_READ 0xD2 /* 直接读主存储器的内存页*/
#define AT45DB_16HZ_Block_Number 64 //占用256K字节存储空间,那么要占用256/4=64个Block
typedef enum {FLASH_BUFFER1=0, FLASH_BUFFER2} FLASH_BUFFER_NUM;
#define FLASH_PAGE_COUNT 4096 // 0..4095
#define FLASH_PAGE_SIZE 512
#define FLASH_SIZE_KBYTE 2112
#define SPI_Data_Flash_HZ_SELECT() GPIO_ResetBits(GPIOC, GPIO_Pin_13) /* AT45db161 CS = L */
#define SPI_Data_Flash_HZ_DESELECT() GPIO_SetBits(GPIOC, GPIO_Pin_13) /* AT45db161 CS = H */
// set Page (12Bit) and Byte Address (10Bit)
#define ADR_P_B_1(page) (unsigned char)(page>>6) // 2 res.bits + 6 bits of 12-bit page-address
#define ADR_P_B_2(page, address) (unsigned char)(page<<2)|(address>>8) // 6 bit page-address and 2bit Byte-address
#define ADR_P_B_3(address) (unsigned char)address // 8 bit Byte-address
// set Byte Address (10Bit)
#define ADR_B_1(address) (unsigned char)(address>>8) // 6 don't care + Bit8,9 of address (2bit address)
#define ADR_B_2(address) (unsigned char)(address) // bit7 .. Bit0 of address (8bit address)
// set Page Address (12Bit)
#define ADR_P_1(page) (unsigned char)(page>>6) // 2 res.bits + 6 bits of 12-bit page-address
#define ADR_P_2(page) ((unsigned char)(page))<<2 // 6 bit of 12-bit page-address + 2 don't care
/************************************************************************
** 函数名称:void Data_FLASH_HZ_SPI_Config(void)
** 功能描述: 配置STM32F10*与Data Flash 汉字库相关的GPIO引脚 为SPI引脚
** 输 入:
** 输 出: 无
** 全局变量:
** 调用模块:
*****************************************/
void Data_FLASH_HZ_SPI_Config(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA|
RCC_APB2Periph_AFIO |
RCC_APB2Periph_SPI1,
ENABLE);
/* SCK, MISO and MOSI A5=CLK,A6=MISO,A7=MOSI*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* PC.13 作片选*/
GPIO_SetBits(GPIOC, GPIO_Pin_13);//预置为高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* SPI1 configuration */
SPI_Cmd(SPI1, DISABLE); //必须先禁能,才能改变MODE
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //两线全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //CPOL=0 时钟悬空低
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //CPHA=0 数据捕获第1个
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件NSS
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //4分频
// SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //16分频
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位在前
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC7
SPI_Init(SPI1, &SPI_InitStructure);
//SPI_SSOutputCmd(SPI1, ENABLE); //使能NSS脚可用
SPI_Cmd(SPI1, ENABLE);
}
/************************************************************************
** 函数名称:static u8 SPI_ReadWrite_Byte(u8 byte)
** 功能描述: 发送或者接收1个字节
** 输 入: byte 发送时候,byte传递为发送的数据字节, 接收的时候,则固定为0xff
** 输 出: SPI1->DR 发送时候,可以忽略, 接收的时候,则为接收数据
** 全局变量:
** 调用模块:
***********************************************************************/
static u8 SPI_ReadWrite_Byte(u8 byte)
{
/*等待发送寄存器空*/
while((SPI1->SR & SPI_I2S_FLAG_TXE)==RESET);
/*发送一个字节*/
SPI1->DR = byte;
/* 等待接收寄存器有效*/
while((SPI1->SR & SPI_I2S_FLAG_RXNE)==RESET);
return(SPI1->DR);
}
/*********************************************************************
// Description : Returns Flash Status Byte
// Input : none
// Output : unsigned char - status-Byte
// Indication : see "Status Register Read"
// **********************************************************************************
u8 Dataflash_read_status(void)
{
u8 data;
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash
SPI_ReadWrite_Byte(AT45DB_READ_STATE_REGISTER);
data = SPI_ReadWrite_Byte(0x00);
SPI_Data_Flash_HZ_DESELECT();
return data;
}
/**********************************
Data Flash 检测忙
**********************************/
#define AT45DB_DATAFLASH_BUSY_BIT 0x80
u8 SPI_Data_Flash_Busy_Detect(void)
{
u16 i=0;
if((Dataflash_read_status() & AT45DB_DATAFLASH_BUSY_BIT) == 0)
{
for(i=0;i<0xff;i++);
if((Dataflash_read_status() & AT45DB_DATAFLASH_BUSY_BIT) == 0)
return 1; //忙返回1
else
return 0;
}
else
{
return 0; //空闲返回0
}
}
/*************************************
** 函数名称: u8 Dataflash_read_flash2buffer(FLASH_BUFFER_NUM buffer_num, unsigned int page)
** 功能描述: 设定AT45DB161在读取的时候,将数据送往内部的1号Buffer或者 2号
** 输入 : buffer_num - number of buffer to transfer
** Buffer 1 - 0
** Buffer 2 - 1
** : page - page address in main memory
** 输出 : 1 - OK
** : 0 - read error
** 全局变量:
** 调用模块:
*************************************/
u8 Dataflash_read_flash2buffer(FLASH_BUFFER_NUM buffer_num, u16 page)
{
if(SPI_Data_Flash_Busy_Detect()==0) //==0 Data Flash空闲
{
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash if (buffer_num == FLASH_BUFFER1)
if (buffer_num == FLASH_BUFFER1)
SPI_ReadWrite_Byte(AT45DB_MM_PAGE_TO_B1_XFER);
else
SPI_ReadWrite_Byte(AT45DB_MM_PAGE_TO_B2_XFER); // 8-bit opcode
SPI_ReadWrite_Byte(ADR_P_1(page));
SPI_ReadWrite_Byte(ADR_P_2(page));
SPI_ReadWrite_Byte(0x00); // 8 don't care bits
SPI_Data_Flash_HZ_DESELECT();
return 1;
}
return 0;
}
/*************************************
** 函数名称: void DataFlashPageRead(u16 page,u8 *Data)
** 功能描述: 读取AT45DB161内部页数据
** 输入 :
** : page - page address in main memory
** : Data - Adresse im Buffer
** 输出 :
** 全局变量:
** 调用模块:
*************************************/
//void DataFlashPageRead(u16 page,u8 *Data)
void DataFlashPageRead(unsigned int page,unsigned char * pHeader)
{
/*
u16 i;
while(SPI_Data_Flash_Busy_Detect());
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash if (buffer_num == FLASH_BUFFER1)
SPI_ReadWrite_Byte(AT45DB_MM_PAGE_READ);
SPI_ReadWrite_Byte((u8)(page >> 6));
SPI_ReadWrite_Byte((u8)(page << 2));
SPI_ReadWrite_Byte(0x00);//3个字节
for (i = 0;i < 528; i++)
{
Data[i] = SPI_ReadWrite_Byte(0x00);
}
SPI_Data_Flash_HZ_DESELECT();
*/
unsigned int i=0;
while(SPI_Data_Flash_Busy_Detect());
SPI_Data_Flash_HZ_SELECT();
SPI_ReadWrite_Byte(AT45DB_MM_PAGE_TO_B1_XFER);
SPI_ReadWrite_Byte((unsigned char)(page >> 6));
SPI_ReadWrite_Byte((unsigned char)(page << 2));
SPI_ReadWrite_Byte(0x00);
SPI_Data_Flash_HZ_DESELECT();
while(SPI_Data_Flash_Busy_Detect());
SPI_Data_Flash_HZ_SELECT();
SPI_ReadWrite_Byte(AT45DB_BUFFER_1_READ);
SPI_ReadWrite_Byte(0x00);
SPI_ReadWrite_Byte(0x00);
SPI_ReadWrite_Byte(0x00);
SPI_ReadWrite_Byte(0x00);
for (i=0; i<512; i++)
{
*pHeader++ = SPI_ReadWrite_Byte(0x00);
}
SPI_Data_Flash_HZ_DESELECT();
}
/*************************************
** 函数名称: void FlashPageWrite(u16 page,u8 *Data) //写一整页,页范围0-4095
** 功能描述: 将数据写入AT45DB161内部页存储空间
** 输入 :
** : page - page address in main memory
** : Data - Adresse im Buffer
** 输出 :
** 全局变量:
** 调用模块:
*************************************/
void FlashPageWrite(u16 page,u8 *Data) //写一整页,页范围0-4095
{
u16 i;
while(SPI_Data_Flash_Busy_Detect());
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash if (buffer_num == FLASH_BUFFER1)
SPI_ReadWrite_Byte(AT45DB_BUFFER_2_WRITE);
SPI_ReadWrite_Byte(0x00);
SPI_ReadWrite_Byte(0x00);
SPI_ReadWrite_Byte(0x00);
for (i = 0;i < 512; i++)
{
SPI_ReadWrite_Byte(Data[i]);
}
SPI_Data_Flash_HZ_DESELECT();
while(SPI_Data_Flash_Busy_Detect());
if ( page < 4096)
{
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash if (buffer_num == FLASH_BUFFER1)
SPI_ReadWrite_Byte(AT45DB_B2_TO_MM_PAGE_PROG_WITH_ERASE);
SPI_ReadWrite_Byte((u8)(page>>6));
SPI_ReadWrite_Byte((u8)(page<<2));
SPI_ReadWrite_Byte(0x00);
while(SPI_Data_Flash_Busy_Detect());
SPI_Data_Flash_HZ_DESELECT(); }
}
/*************************************
** 函数名称:u8 Dataflash_read_buffer(u8* data, u16 address, u16 size, FLASH_BUFFER_NUM buffer_num)
** 功能描述: 连续读AT45DB161内部缓冲区1或者缓冲区2的数据
** 输 入: : data - Pointer to buffer inside of SRAM from Microcontroller
** : address - Adresse im Buffer
** : size - count of Byte
** : buffer_num - Buffer 1 - 0
** : Buffer 2 - 1
** 输出 : 1 - OK
** : 0 - read error
** 全局变量:
** 调用模块:
*************************************/
u8 Dataflash_read_buffer(u8* data, u16 address, u16 size, FLASH_BUFFER_NUM buffer_num)
{
u16 i;
if(SPI_Data_Flash_Busy_Detect()==0) //==0 Data Flash空闲
{
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash if (buffer_num == FLASH_BUFFER1)
if (buffer_num == FLASH_BUFFER1)
{
SPI_ReadWrite_Byte(AT45DB_BUFFER_1_READ);
}
else
{
SPI_ReadWrite_Byte(AT45DB_BUFFER_2_READ);
}
// SPI_ReadWrite_Byte(ADR_P_1(address));
// SPI_ReadWrite_Byte(ADR_P_2(address));
SPI_ReadWrite_Byte(0x00);
SPI_ReadWrite_Byte(0x00);
SPI_ReadWrite_Byte(0x00); // 8 don't care
for (i=0; i<size; i++)
data[i] = SPI_ReadWrite_Byte(0x00);
SPI_Data_Flash_HZ_DESELECT();
return 1;
}
else //总线忙
{
return 0;
}
}
/*************************************
** 函数名称:u8 dataflash_write2flash(unsigned char* data, unsigned int page, unsigned int address, unsigned int size, FLASH_BUFFER_NUM buffer_num)
** 功能描述: 连续写数据到SPI Flash内部
** 输 入: : data - Pointer to buffer inside of SRAM from Microcontroller
** : address - Adresse im Buffer
** : size - count of Byte
** : buffer_num - Buffer 1 - 0
** : Buffer 2 - 1
** 输出 : 1 - OK
** : 0 - read error
** 全局变量:
** 调用模块:
*************************************/
u8 Dataflash_write2flash(u8* data, u16 page, u16 address, u16 size, FLASH_BUFFER_NUM buffer_num)
{
u16 i;
if(SPI_Data_Flash_Busy_Detect()==0) //==0 Data Flash空闲
{
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash
if(buffer_num == FLASH_BUFFER1)
SPI_ReadWrite_Byte(AT45DB_BUFFER_1_WRITE );
else
SPI_ReadWrite_Byte(AT45DB_BUFFER_2_WRITE);
/* SPI_ReadWrite_Byte(ADR_P_B_1(page));
SPI_ReadWrite_Byte(ADR_P_B_2(page, address));
SPI_ReadWrite_Byte(ADR_P_B_3(address)); */
SPI_ReadWrite_Byte(ADR_P_1(page));
SPI_ReadWrite_Byte(ADR_P_2(page));
SPI_ReadWrite_Byte(0x00);
for (i=0; i<size; i++)
SPI_ReadWrite_Byte(data[i]);
SPI_Data_Flash_HZ_DESELECT();
return 1;
}
return 0;
}
/***********************************************************************
- 功能描述:从某一页的某个位置开始读取一定长度的数据,放入数据缓冲区
- 隶属模块:AT45DB161模块
- 函数属性:外部,供用户调用
- 参数说明:addr:扇区地址
start:扇区内的起始位置
len:要读取的数据长度
pbuf:指向数据缓冲中的指针
- 返回说明:无
- 注:无
***********************************************************************/
void AT45DB161_ReadBytes(unsigned int addr,unsigned int start,unsigned int len,unsigned char *pbuf)
{
unsigned int i;
while(SPI_Data_Flash_Busy_Detect()==1); //==0 Data Flash空闲
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash
SPI_ReadWrite_Byte(0xe8);
SPI_ReadWrite_Byte((unsigned char)(addr>>6));
SPI_ReadWrite_Byte((unsigned char)((addr<<2)|(start>>8)));
SPI_ReadWrite_Byte((unsigned char)start);
for(i=0;i<4;i++) SPI_ReadWrite_Byte(0x00);
for(i=0;i<len;i++) pbuf[i]=SPI_ReadWrite_Byte(0x00);
SPI_Data_Flash_HZ_DESELECT();
}
/************************************************************************
** 函数名称:void Dataflash_erase_page(u16 page)
** 功能描述: 删除对应的页 每页对应512Byte
** 输 入: 无
** 输 出: 无
** 全局变量:
** 调用模块:
***********************************************************************/
void Dataflash_erase_page(u16 page)
{
while(SPI_Data_Flash_Busy_Detect());
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash
SPI_ReadWrite_Byte(AT45DB_PAGE_ERASE);
SPI_ReadWrite_Byte(ADR_P_1(page));
SPI_ReadWrite_Byte(ADR_P_2(page));
SPI_ReadWrite_Byte(0x00); // 8 don't care bits
SPI_Data_Flash_HZ_DESELECT();
}
/************************************************************************
** 函数名称:void Dataflash_erase_block(u16 page)
** 功能描述: DataFlash AT45DB161 块删除,每一块对应4KByte
** 输 入: 无
** 输 出: 无
** 全局变量:
** 调用模块:
************************************************************************/
void Dataflash_erase_block(u16 page)
{
while(SPI_Data_Flash_Busy_Detect());
SPI_Data_Flash_HZ_SELECT(); //片选Dataflash
SPI_ReadWrite_Byte(AT45DB_BLACK_ERASE);
SPI_ReadWrite_Byte(ADR_P_1(page));
SPI_ReadWrite_Byte(ADR_P_2(page));
SPI_ReadWrite_Byte(0x00); // 8 don't care bits
SPI_Data_Flash_HZ_DESELECT();
}
_______________________________________________________
2. 写入字库文件的方法:
1)建立2个容量为512Byte的缓冲数组Buffer1、Buffer2。串口中断中,先将第一个缓冲数组Buffer1填满,填满了512Byte内容,则发送一个消息到负责写入数据的任务,同时将中断接收到的数据放入第二个缓冲数组Buffer2中;
2)在任务中将缓冲数组Buffer1里面数据作为1 page的内容写入制定的page address。(AT45DB161 1 Page 大小为512/528Byte)。将Buffer1中的内容清零。
3)Buffer2满了以后,同样发送一个对应的消息到负责写入数据的任务,同时将中断接收到的数据放入第1个缓冲数组Buffer1中;
4)在任务中将缓冲数组Buffer2里面数据作为1 page的内容写入制定的page address。将Buffer2中的内容清零。
5)按照上面的循环,同时指定写入的Page address是要依次递增的。
6)在烧录的过程中,设定一个定时器,两个缓冲Buffer1、Buffer2在中断服务程序中切换使用的时候,将定时器复位,重新开始计时;当上位机没有字库文件发送到串口的时候,(发送完毕了)超过一定时间定时器中断调用中断服务程序,在服务程序中将Buffer1或者Buffer2的数据写入指定的Page address。(Buffer1或者Buffer2内部的数据小于512Byte的时候,写入数据的任务是不会有写入动作的,所以要在定时器服务程序中进行)
特别注意:
1.在Buffer1、Buffer2没有使用互斥的信号量的情况下,写入数据的任务必须保证不被其他任务打断,否则有可能Buffer1中的数据还没有被完成写入的情况下,就会被串口接收中断程序改写,导致丢失数据。(最好是增加一个互斥信号量,但是会显得复杂些。上位机的通信没有串口握手信号的情况下,两个Buffer都满,并且写入任务还没有释放一个Buffer信号量的情况下,同样会丢失数据,导致字库文件出错。丢失数据的位置并不固定,有时候前5KByte都正常,后面就出错,有时候则1KByte正常,后面出错)
2. 我在项目中实际处理方式是,没有采用信号量互斥处理,而是当目标板进入了字库烧录状态,就将系统其他周期性的任务挂起,确保写入任务能够得到足够的时间运行。烧录、校验完成以后,再恢复其他任务(好在字库烧录状态只是很少情况下使用)
-----------------------------------------------
AT45DB161 字库寻址方式:
1. 参考了网上很多代码和文章,都说以下公式是汉字库寻址地址计算公式:
address=(94*(hz[0]-0xa0-1)+(hz[1]-0xa0-1))*HZ_BYTES;//得到偏移位置
但是我用这个公式死活不能得到正确的显示数据,将以上公式改为如下即可:
address=(94*(hz[0]-0xb0)+(hz[1]-0xa0-1))*HZ_BYTES;//得到地址
具体原因不明,没有仔细研究汉字库的区别。是参考www.daxia.com/bbs论坛中DX32开发板在SST25FV08 Data Flash字库寻址公式。
2. 在AT45DB161字库读取的时候采用了整page读取之后再取对应数据的方法。程序代码如下:
hz是汉字的区位码。(0xXXXX,16bit)
u8 c1,c2;
u32 addr;
u16 HZ_page;
u16 HZ_offset;
u8 HZ_buffer[528];
u16 _HZ16_Buf[16];//汉字字库缓冲
u16 _HZ16_Buf2[32];
c1=(u8)hz; //获取区码
c2=hz>>8; //获取位码
addr=(HZ_DOT16_START_ADDR*512)+((c1-0xb0)*94+(c2-0xa1))*32; // ((区码-1)*94+(位码-1))*32
HZ_offset =addr%512;
HZ_page =addr/512;
DataFlashPageRead(HZ_page,&HZ_buffer[0]);
for(i=0,j=0;i<16;i++,j=j+2)
{
_HZ16_Buf[i]=HZ_buffer[HZ_offset+1+j ];
_HZ16_Buf[i]=_HZ16_Buf[i]<<8;
_HZ16_Buf[i]=_HZ16_Buf[i]|HZ_buffer[HZ_offset+j];
}
在AT45DB161中存储的点阵数据低8bit在前,高8bit在后。所以读取出来的时候要先将高8bit数据放到显示缓冲数组_HZ16_Buf[i]的高8bit;否则显示出来的字符有点异样。
或者用此AT45DB161读取函数也可,这个函数读取指定位置的字符,不是整页整页的读取。(注意_HZ16_Buf2这里要强制转换成为(u8*)类型,强制转换以后,在AT45DB161函数执行过程中,自动会将第一个SPI操作读取的8bit数据放入_HZ16_Buf2内部的低8bit;将第二个SPI操作读取到的8bit数据放入_HZ16_Buf2内部的高8bit,省去了转换的麻烦。
AT45DB161_ReadBytes(HZ_page,HZ_offset,32,(u8*)_HZ16_Buf2);
----------
以上是对AT45DB161烧录、读取16*16类型字库文件实际编程中的一点经验。