NOR Flash编程实现识别擦、写、读地址
/*
*硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
*软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
*参考资料:《嵌入式Linux应用开发完全手册》韦东山,开发版原理图,MX29LV160DBTI-70G(NOR FLASH) datasheet,MX29LV800BBTC datasheet
*/
目录
一、基础知识点
1、可视字符与不可视字符
可视字符 | 不可视字符 | |
---|---|---|
范围 | ASCII字符集中的95个可打印字符(0x20-0x7E) | ASCII字符集中的33个控制字符(0x00-0x1F,0x7F) |
用途 | 显示在输出设备上 | 向计算机发出一些特殊指令 |
具体介绍可点击这里查看。
二、实现功能
功能:读取和编写某个地址信息与擦除某个扇区
三、编程原理
继续在上一篇博客的nor_flash.c文件上编写
1、功能:读某个指定地址
编程思路:通过串口中输入操作地址,打印地址信息时固定其长度位64字节,输出格式为:一行16个字节,共4行,每行先打印ASCII码数值,最后打印ASCII码字符。
1.1 获取操作的地址
unsigned int addr;
volatile unsigned char *p;
/* 获取地址 */
printf("Enter the address to read: ");
addr = get_uint();
p = (volatile unsigned char *)addr;
1.2 打印地址信息
在打印字符时,需判断字符是否可视,具体介绍可看本篇基础知识点
/* 各变量对应含义
* 用到的标志位
* 存储输入的地址
* 存储1字节地址的信息,存储16字节的信息
* 存储地址,以指针形式
*/
int i,j;
unsigned int addr;
unsigned char c,str[16];
volatile unsigned char *p;
/* 打印地址信息
* 1、固定长度64字节
* 2、按照市面上的标准以16个字节为一行,前为数值,后为字符
* 3、分辨字符是否可视
*/
printf("Data: \n\r");
for(i=0;i<4;i++)
{
for(j=0;j<16;j++)
{
c = *p++;
str[j] = c;
printf("%02x ",c);
}
for(j=0;j<16;j++)
{
if((str[j] < 0x20) || (str[j] > 0x7E))
printf(".");
else
printf("%c",str[j]);
}
printf("\n\r");
}
}
2、功能:擦除nor flash某个指定扇区
编程思路:通过串口中输入操作扇区地址,根据手册上的指令执行擦除命令,最后通过查手册可知:可通过判断Q6位是否在变化,可知擦除是否完毕,不变则完,变则在擦除。
2.1 获取操作的扇区地址
unsigned int addr;
volatile unsigned char *p;
/* 获取地址 */
printf("Enter the address of sector to erase: ");
addr = get_uint();
p = (volatile unsigned char *)addr;
2.2 按照手册执行擦除命令
查MX29LV800BT/BB datasheet第10页可知:
nor_cmd(0x555,0xaa); //解锁
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0x80);
printf("erasing ...\n\r");
nor_cmd(0x555,0xaa); //erase
nor_cmd(0x2aa,0x55);
nor_cmd(addr>>1,0x30);
补充说明:为什么这里的addr>>1?
解释:举例说明,当我们输入想操作的扇区地址addr为0x100,由于在nor_cmd()函数中由于Nor Flash和S3c2440的数据线相差一位,函数编写里面已经addr<<1,实际到Nor Flash操作时地址变为0x200,所以addr>>1。
2.3 判断Q6位是否擦除完毕
查MX29LV800BT/BB datasheet第16页可知:
功能说明:用户可以通过查询第I位切换指示是否有自动程序或擦除算法正在进行或完成。
使用方法:执行擦除命令后,通过先后两次读取操作地址的第I位,判断两次的电平是否相同,若相同则命令执行完毕,否则命令未执行完毕。
编写判断函数:
void wait_readly(unsigned int addr)
{
unsigned char val,flag;
/* 先后两次读取操作地址*/
val = nor_data(addr>>1);
flag = nor_data(addr>>1);
/* 提取第6位进行判断 */
while((val&(1<<6))!= (val&(1<<6)))
{
val =flag;
flag = nor_data(addr>>1);;
}
}
wait_readly(addr);
3、功能:编写某个指定地址
某个地址写入值时的条件:
①、先擦除地址信息,后才可写入。
②、此时地址信息为0xff,可直接写入。
编程思路:通过串口输入操作的地址和信息,后根据手册上的指令执行烧写命令,最后通过查手册可知:可通过判断Q6位是否在变化,可知烧写是否完毕,不变则完,变则在烧写。
3.1 获取地址
unsigned int addr;
/* 获取地址 */
printf("Enter the address of sector to write: ");
addr = get_uint();
3.2 获取写入信息
unsigned char str[100];
/* 获取写入信息 */
printf("Enter the address to write: ");
gets(str);
3.3 按照手册执行写命令
int i = 0;
/* 执行写命令
* 因为芯片上为16位数据线,所以传输数据时应该一次写16位的数据,提高利用率
*/
printf("writing ...\n\r");
while( str[i] && str[i+1] )
{
val = str[i]+(str[i+1]<<8);
nor_cmd(0x555,0xaa); //解锁
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0xa0);
nor_cmd(addr>>1,val); //proram
i += 2;
addr += 2;
}
val = str[i];
nor_cmd(0x555,0xaa); //解锁
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0xa0);
nor_cmd(addr>>1,val); //proram
3.4 判断Q6位是否写完毕
wait_readly(addr);
四、编程文件
1、完整nor flash.c文件
#include "my_printf.h"
#include "string_utils.h"
#define NOR_FLASH_BASE 0 //nor--cs0,base addr :0
/* 构建一个函数,功能:往地址里面写东西 */
void nor_write_addr(unsigned int base,unsigned int offset,unsigned int val )
{
volatile unsigned short *p = (volatile unsigned short *)(base + (offset<<1));
*p = val;
}
/* 封装nor_write_addr */
void nor_cmd(unsigned int offset,unsigned int cmd)
{
nor_write_addr(NOR_FLASH_BASE,offset,cmd);
}
/* 构建一个函数,功能:读取地址中的信息 */
unsigned int nor_read_addr(unsigned int base,unsigned int offset)
{
volatile unsigned short *p = (volatile unsigned short *)(base + (offset<<1));
return *p;
}
/* 封装nor_read_addr */
unsigned int nor_data(unsigned int offset)
{
return nor_read_addr(NOR_FLASH_BASE,offset);
}
void wait_readly(unsigned int addr)
{
unsigned char val,flag;
val = nor_data(addr>>1);
flag = nor_data(addr>>1);
while((val&(1<<6))!= (val&(1<<6)))
{
val =flag;
flag = nor_data(addr>>1);;
}
}
/* 识别nor flash */
void do_scan_nor_flash(void)
{
/* 各变量对应含义
* 用到的标志位
* 设备容量大小
* 厂家ID,设备ID
* region的数量、第一个region的信息地址
* blocks的数量、block的地址、block的大小
*/
char str[4];
int i,j,flag;
int device_size;
int vendor,device;
int regions,regions_info_base;
int blocks,block_addr,block_size;
/* 打印设备ID
* 1、先解锁
* 2、后根据信息输入对应的nor_cmd指令
* 3、读取数据后要reset
*/
nor_cmd(0x555,0xaa);
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0x90);
vendor = nor_data(0);
device = nor_data(1);
nor_cmd(0,0xf0);
/*
* 1、首先进入cfi模式
* 2、对获取信息的不同,执行nor_cmd进行写操作
* 3、执行nor_data,读出对应地址的信息
*/
nor_cmd(0x55,0x98);
str[0] = nor_data(0x10);
str[1] = nor_data(0x11);
str[2] = nor_data(0x12);
str[3] = '\0';
printf("\n\r");
printf("str = %s\n\r",str);
/* 打印容量 */
device_size = 1<<(nor_data(0x27)); //2的nor_data(0x27)次方
printf("Vendor ID = 0x%x, Device ID = 0x%x, Nor Flash Size = 0x%x, %dM\n\r", vendor, device, device_size, (device_size/(1024*1024)));
/* 打印各个扇区的起始地址 */
/* 名词解释:
* erase block region : 里面含有1个或多个block, 它们的大小一样
* 一个nor flash含有1个或多个region
* 一个region含有1个或多个block(扇区)
* Erase block region information:
* 前2字节+1 : 表示该region有多少个block
* 后2字节*256 : 表示block的大小
*/
regions = nor_data(0x2C);
regions_info_base = 0x2d;
block_addr = 0;
flag = 0;
printf("\n\r");
printf("Block/Sector Start Address:\n\r");
for(i = 0;i<regions;i++)
{
blocks = nor_data(regions_info_base)+(nor_data(regions_info_base+1)<<8)+1; //计算该region中blocks的个数
block_size = 256*(nor_data(regions_info_base+2)+(nor_data(regions_info_base+3)<<8)); //计算每个block的大小
regions_info_base +=4; //使得进入下次循环时地址为下一个region的信息地址
for(j=0;j<blocks;j++)
{
/* 打印每个block的起始地址 */
printHex(block_addr);
putchar(' ');
flag++;
block_addr +=block_size;
if (flag % 5 == 0)
printf("\n\r");
}
}
printf("\n\r");
/* 退出CFI模式 */
nor_cmd(0, 0xf0);
}
/* 擦除nor flash某个扇区
* 1、获取扇区地址
* 2、按照手册执行擦除命令
* 3、判断Q6位是否擦除完毕
*/
void do_erase_nor_flash(void)
{
/* 各变量对应含义
* 存储输入的地址
* 存储地址,以指针形式
*/
unsigned int addr;
volatile unsigned char *p;
/* 获取地址 */
printf("Enter the address of sector to erase: ");
addr = get_uint();
p = (volatile unsigned char *)addr;
nor_cmd(0x555,0xaa); //解锁
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0x80);
printf("erasing ...\n\r");
nor_cmd(0x555,0xaa); //erase
nor_cmd(0x2aa,0x55);
nor_cmd(addr>>1,0x30);
wait_readly(addr);
printf("\n\r");
}
/* 编写某个地址
* 1、获取地址
* 2、获取写入信息
* 3、按照手册执行写命令
* 4、判断Q6位是否写完毕
*/
void do_write_nor_flash(void)
{
/* 各变量对应含义
* 用到的标志位
* 存储输入的地址,存储构建的16位信息
* 存储写入的信息
*/
int i;
unsigned int addr,val;
unsigned char str[100];
i = 0;
/* 获取地址 */
printf("Enter the address of sector to write: ");
addr = get_uint();
/* 获取写入信息 */
printf("Enter the address to write: ");
gets(str);
/* 执行写命令
* 因为芯片上为16位数据线,所以传输数据时应该一次写16位的数据,提高利用率
*/
printf("writing ...\n\r");
while( str[i] && str[i+1] )
{
val = str[i]+(str[i+1]<<8);
nor_cmd(0x555,0xaa); //解锁
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0xa0);
nor_cmd(addr>>1,val); //proram
i += 2;
addr += 2;
}
val = str[i];
nor_cmd(0x555,0xaa); //解锁
nor_cmd(0x2aa,0x55);
nor_cmd(0x555,0xa0);
nor_cmd(addr>>1,val); //proram
wait_readly(addr);
printf("\n\r");
}
/* 读某个地址
* 1、获取地址
* 2、打印地址信息
*/
void do_read_nor_flash(void)
{
/* 各变量对应含义
* 用到的标志位
* 存储输入的地址
* 存储1字节地址的信息,存储16字节的信息
* 存储地址,以指针形式
*/
int i,j;
unsigned int addr;
unsigned char c,str[16];
volatile unsigned char *p;
/* 获取地址 */
printf("Enter the address to read: ");
addr = get_uint();
p = (volatile unsigned char *)addr;
/* 打印地址信息
* 1、固定长度64字节
* 2、按照市面上的标准以16个字节为一行,前为数值,后为字符
* 3、分辨字符是否可视
*/
printf("Data: \n\r");
for(i=0;i<4;i++)
{
for(j=0;j<16;j++)
{
c = *p++;
str[j] = c;
printf("%02x ",c);
}
for(j=0;j<16;j++)
{
if((str[j] < 0x20) || (str[j] > 0x7E))
printf(".");
else
printf("%c",str[j]);
}
printf("\n\r");
}
}
void nor_flash_test(void)
{
char c;
while(1)
{
/* 打印菜单,供选择测试内容 */
printf("[S] Scan nor flash\n\r");
printf("[E] Erase nor flash\n\r");
printf("[W] Write nor flash\n\r");
printf("[R] Read nor flash\n\r");
printf("[Q] Quit nor flash\n\r");
printf("Enter Selection: ");
c = getchar();
printf("%c\n\r",c);
/* 测试内容:
* 1、识别nor flash
* 2、擦除nor flash某个扇区
* 3、编写某个地址
* 4、读某个地址
*/
switch (c)
{
case 'Q':
case 'q':
return;
break;
case 'S':
case 's':
do_scan_nor_flash();
break;
case 'W':
case 'w':
do_write_nor_flash();
break;
case 'R':
case 'r':
do_read_nor_flash();
break;
case 'E':
case 'e':
do_erase_nor_flash();
break;
default:
break;
}
}
}
五、运行结果截图
1、读取某个指定地址
对比读取0地址的运行截图与反汇编代码可知:功能实现成功。
2、擦除某个指定扇区
可以看出,执行擦除命令之后,扇区起始地址为0x100000的信息为0xff,擦除成功。
3、编写某个指定地址
从图中可以知道abcdef成功写入到起始地址为0x100000的扇区中。