Chinaunix首页 | 论坛 | 博客
  • 博客访问: 545377
  • 博文数量: 101
  • 博客积分: 1889
  • 博客等级: 上尉
  • 技术积分: 906
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-14 16:22
文章分类

全部博文(101)

文章存档

2012年(11)

2011年(19)

2010年(59)

2009年(12)

我的朋友

分类: LINUX

2010-05-27 17:14:48

mini2440 支持 sst39vf1601,实现saveenv。
http://blogimg.chinaunix.net/blog/upfile2/090927210004.tar
把board/100askxxx/makefile里的flash.o去掉
用上面的文件分别去替换你的config文件和cfi_flash.c文件,就可以了,本来想做个patch,但是我的diff不好用。有的时候saveenv不成功,索性直接把写超时设置为 (0),就ok了。就是红色字体部分。
韦东山 大神的 详解里对没有支持sst39vf1601 nor flash,所以总是bad crc,更不用说saveenv命令了。
下面使用cfi(common flash interface),实现对sst39vf1601的支持。

#define CFG_ENV_ADDR         (CFG_FLASH_BASE + 0X0F0000)
#define CFG_ENV_SIZE        0x10000    /* Total Size of Environment Sector */
配置是上面这样的,我用的是sst39vf1601,共32个block,每个block 32k word = 64k = 0x10000
也就是说env放在了 第15块上(逻辑上是第16),前三块是uboot的代码,第15块是存放环境参数的块。
这里的block = sector,下面说到的扇区等于这里的block。即 扇区 = block = 64k = 0x10000
    00000000 (RO) 00010000 (RO) 00020000 (RO) 00030000     00040000           
    00050000      00060000      00070000      00080000      00090000           
    000A0000      000B0000      000C0000      000D0000      000E0000           
    000F0000 (RO) 00100000      00110000      00120000      00130000           
    00140000      00150000      00160000      00170000      00180000           
    00190000      001A0000      001B0000      001C0000      001D0000           
    001E0000      001F0000
用kscope搜索saveenv字符串,找到这里来了。

#if defined(CFG_ENV_IS_IN_NVRAM) || defined(CFG_ENV_IS_IN_EEPROM) || \
    ((CONFIG_COMMANDS & (CFG_CMD_ENV|CFG_CMD_FLASH)) == \
      (CFG_CMD_ENV|CFG_CMD_FLASH)) || \
    ((CONFIG_COMMANDS & (CFG_CMD_ENV|CFG_CMD_NAND)) == \
      (CFG_CMD_ENV|CFG_CMD_NAND))
U_BOOT_CMD(
    saveenv, 1, 0, do_saveenv,
    "saveenv - save environment variables to persistent storage\n",
    NULL
);
#endif /* CFG_CMD_ENV */

/*上面就是这个函数,如下*/
int do_saveenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    extern char * env_name_spec;

    printf ("Saving Environment to %s...\n", env_name_spec);

    return (saveenv() ? 1 : 0);
}

//在env_flash.c中,有这样的字符。

#if defined(CFG_ENV_IS_IN_FLASH) /* Environment is in Flash */
char * env_name_spec = "Flash";
#endif /* CFG_ENV_IS_IN_FLASH */

#define PHYS_FLASH_1 0x00000000 /* Flash Bank #1 */
#define CFG_FLASH_BASE 0x00000000
#define CFG_MONITOR_BASE 0x00000000
#define CFG_ENV_ADDR 0x0F0000
#define CFG_ENV_SIZE 0x10000
#define CFG_ENV_OFFSET 0x0F0000
#define CFG_ENV_SECT_SIZE 0x10000


#define CFG_ENV_IS_IN_FLASH 1
#define CFG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */

//给 saveenv(),去掉宏之后

env_t *env_ptr = (env_t *)CFG_ENV_ADDR;
static env_t *flash_addr = (env_t *)CFG_ENV_ADDR;
#define PHYS_FLASH_1 0x00000000 /* Flash Bank #1 */
#define CFG_FLASH_BASE PHYS_FLASH_1
#define CFG_ENV_ADDR (CFG_FLASH_BASE + 0X0F0000)
#define CFG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */
int saveenv(void)
{
    int len, rc;
    ulong end_addr;
    ulong flash_sect_addr;
    uchar *env_buffer = (uchar *)env_ptr;
    int rcode = 0;

    flash_sect_addr = (ulong)flash_addr;
    len = CFG_ENV_SIZE;

    end_addr = flash_sect_addr + len - 1;

    if (flash_sect_protect (0, flash_sect_addr, end_addr))

    //首先取消保护
        return 1;

    puts ("Erasing Flash...");
    if (flash_sect_erase (flash_sect_addr, end_addr))

    //然后开始擦除

        return 1;

    puts ("Writing to Flash... ");
    rc = flash_write((char *)env_buffer, flash_sect_addr, len);

    //开始写进去

    if (rc != 0) {
        flash_perror (rc);
        rcode = 1;
    } else {
        puts ("done\n");
    }

    /* try to re-protect */
    (void) flash_sect_protect (1, flash_sect_addr, end_addr);

    //最后在保护起来

    return rcode;
}
/*逻辑很清晰
逐个分析下面的实现:
1.首先取消保护
2.然后开始擦除
3.开始写进去
*/

//1.首先取消保护,有个疑问,什么时候加的保护,都那些块加了保护呢?先不管。

int flash_sect_protect (0, flash_sect_addr, end_addr);
int flash_sect_protect (0, 0xf0000, 0x10000);
/*可以从函数原型分析出,0代表解锁,地址是从0xf0000-0x100000,如果成功将返回0。*/
int flash_sect_protect (int p, ulong addr_first, ulong addr_last)
{
    flash_info_t *info;
    ulong bank;
    int s_first[CFG_MAX_FLASH_BANKS], s_last[CFG_MAX_FLASH_BANKS];

    //这里CFG_MAX_FLASH_BANKS=1

    int protected, i;
    int planned;
    int rcode;
    /*下面计算出要撤销保护的扇区范围,和 扇区数量,成功返回0*/
    rcode = flash_fill_sect_ranges( addr_first, addr_last, s_first, s_last, &planned );
    protected = 0;

    if (planned && (rcode == 0)) {

    //这里说明flash_fill_sect_ranges成功的完成了任务

        for (bank=0,info=&flash_info[0]; bank < CFG_MAX_FLASH_BANKS; ++bank, ++info) {

    //遍历所有的块,显然是一次

            if (info->flash_id == FLASH_UNKNOWN) {
                continue;
            }
            if (s_first[bank]>=0 && s_first[bank]<=s_last[bank]) {
                protected += s_last[bank] - s_first[bank] + 1;
                for (i=s_first[bank]; i<=s_last[bank]; ++i) {
                    info->protect[i] = p;

    /*撤销保护的具体操作就是这里了,可见这是uboot在自己软件层次的保护方法---好像保护与不保护只是一个标志位的区别*/
                } 
    //到底是取消保护,还是加上保护,取决于p的取值,这里假设0,是解锁。
            }
        }

        printf ("%sProtected %d sectors\n",
            p ? "" : "Un-", protected);
    } else if (rcode == 0) {

      //到了这里,说明给的撤销保护的地址范围不正确
        puts ("Error: start and/or end address"
            " not on sector boundary\n");
        rcode = 1;
    }
    return rcode;
}

static int
flash_fill_sect_ranges (ulong addr_first, ulong addr_last,
            int *s_first, int *s_last,
            int *s_count )
{
    flash_info_t *info;
    ulong bank;
    int rcode = 0;

    *s_count = 0;

    for (bank=0; bank < CFG_MAX_FLASH_BANKS; ++bank) {
        s_first[bank] = -1; /* first sector to erase */
        s_last [bank] = -1; /* last sector to erase */
    }

    for (bank=0,info=&flash_info[0]; //只遍历一次

         (bank < CFG_MAX_FLASH_BANKS) && (addr_first <= addr_last);
         ++bank, ++info) {
        ulong b_end;
        int sect;
        short s_end;

        if (info->flash_id == FLASH_UNKNOWN) {
            continue;
        }

        b_end = info->start[0] + info->size - 1;

      /* bank end addr 就是结束地址 */
        s_end = info->sector_count - 1;

     /* last sector 最后一个扇区数,这些内容在初始化是添好了。*/
        for (sect=0; sect < info->sector_count; ++sect) { 遍历这块nor的所有扇区
            ulong end; /* last address in current sect */

            end = (sect == s_end) ? b_end : info->start[sect + 1] - 1;

            if (addr_first > end)
                continue;
            if (addr_last < info->start[sect])
                continue;

            if (addr_first == info->start[sect]) {
                s_first[bank] = sect; //找到起始的那个扇区

            }
            if (addr_last == end) {
                s_last[bank] = sect;

      //和最后的那个扇区,可知对 addr_first和addr_last的要求比较苛刻。
            }
        }
        if (s_first[bank] >= 0) {
            if (s_last[bank] < 0) {
                if (addr_last > b_end) {
                    s_last[bank] = s_end;
                } else {
                    puts ("Error: end address"
                        " not on sector boundary\n");
                    rcode = 1;
                    break;
                }
            }
            if (s_last[bank] < s_first[bank]) {
                puts ("Error: end sector"
                    " precedes start sector\n");
                rcode = 1;
                break;
            }
            sect = s_last[bank]; //改到最后

            addr_first = (sect == s_end) ? b_end + 1: info->start[sect + 1];
            (*s_count) += s_last[bank] - s_first[bank] + 1;

     //计算出共几个扇区,指针传参。

        } else if (addr_first >= info->start[0] && addr_first < b_end) {
            puts ("Error: start address not on sector boundary\n");
            rcode = 1;
            break;
        } else if (s_last[bank] >= 0) {
            puts ("Error: cannot span across banks when they are"
                   " mapped in reverse order\n");
            rcode = 1;
            break;
        }
    }
    return rcode; //返回0

}

//现回顾下 flash_info_t 结构,然后在看上面的代码。
typedef struct {
    ulong size; /* total bank size in bytes */
    ushort sector_count; /* number of erase units */
    ulong flash_id; /* combined device & manufacturer code */
    ulong start[CFG_MAX_FLASH_SECT]; /* physical sector start addresses */
    uchar protect[CFG_MAX_FLASH_SECT];

/* sector protection status 这就保护的初始数值是多少,那里赋值的呢?*/
#ifdef CFG_FLASH_CFI
    uchar portwidth; /* the width of the port */
    uchar chipwidth; /* the width of the chip */

//下面是portwidth、chipwidth的定义
/* * Values for the width of the port*/
#define FLASH_CFI_8BIT 0x01
#define FLASH_CFI_16BIT 0x02
#define FLASH_CFI_32BIT 0x04
#define FLASH_CFI_64BIT 0x08
/*
 * Values for the width of the chip
 */

#define FLASH_CFI_BY8 0x01
#define FLASH_CFI_BY16 0x02
#define FLASH_CFI_BY32 0x04
#define FLASH_CFI_BY64 0x08

    ushort buffer_size; /* # of bytes in write buffer */
    ulong erase_blk_tout; /* maximum block erase timeout */
    ulong write_tout; /* maximum write timeout */
    ulong buffer_write_tout; /* maximum buffer write timeout */
    ushort vendor; /* the primary vendor id */
    ushort cmd_reset; /* Vendor specific reset command */
    ushort interface; /* used for x8/x16 adjustments */
    ushort legacy_unlock; /* support Intel legacy (un)locking */
#endif
} flash_info_t;
/*
2.然后开始擦除,成功返回0,猜想,有保护的,应该不能执行擦除操作。
这个可就不简单了,因为要深入到cfi借口的具体操作上。*/

int flash_sect_erase (0, flash_sect_addr, end_addr);
int flash_sect_erase (0, 0xf0000, 0x10000);

int flash_sect_erase (ulong addr_first, ulong addr_last)
{
    flash_info_t *info;
    ulong bank;
    int s_first[CFG_MAX_FLASH_BANKS], s_last[CFG_MAX_FLASH_BANKS];
    int erased = 0;
    int planned;
    int rcode = 0;

    rcode = flash_fill_sect_ranges (addr_first, addr_last,
                    s_first, s_last, &planned ); //代码复用了呵呵。


    if (planned && (rcode == 0)) { //成功到了这里

        for (bank=0,info=&flash_info[0];
             (bank < CFG_MAX_FLASH_BANKS) && (rcode == 0);
             ++bank, ++info) {
            if (s_first[bank]>=0) {
                erased += s_last[bank] - s_first[bank] + 1;

    //计算出要擦除的总扇区数量

                rcode = flash_erase (info, s_first[bank], s_last[bank]); //执行擦除操作

            }
        }
        printf ("Erased %d sectors\n", erased);
    } else if (rcode == 0) {
        puts ("Error: start and/or end address"
            " not on sector boundary\n");
        rcode = 1;
    }
    return rcode;
}
/*
主要的操作函数是这个,他的参数是擦除nor的flash_info结构和起始结束扇区数
成功返回0。我们跟进去。*/

int flash_erase (info, 15, 15);
int flash_erase (flash_info_t * info, int s_first, int s_last)
{
    int rcode = 0;
    int prot;
    flash_sect_t sect;

    if (info->flash_id != FLASH_MAN_CFI) {
        puts ("Can't erase unknown flash type - aborted\n");
        return 1;
    }
    if ((s_first < 0) || (s_first > s_last)) {
        puts ("- no sectors to erase\n");
        return 1;
    }

    prot = 0;
    for (sect = s_first; sect <= s_last; ++sect) {

    //这里计算出即将要擦出的扇区中有保护的扇区数
        if (info->protect[sect]) {
            prot++;
        }
    }
    if (prot) { 
    /*保护的不擦除*/
        printf ("- Warning: %d protected sectors will not be erased!\n", prot);
    } else {
        putc ('\n');
    }

    for (sect = s_first; sect <= s_last; sect++) {
        if (info->protect[sect] == 0) { /* not protected */
            switch (info->vendor) {
            case CFI_CMDSET_INTEL_STANDARD:
            case CFI_CMDSET_INTEL_EXTENDED:
                flash_write_cmd (info, sect, 0, FLASH_CMD_CLEAR_STATUS);
                flash_write_cmd (info, sect, 0, FLASH_CMD_BLOCK_ERASE);
                flash_write_cmd (info, sect, 0, FLASH_CMD_ERASE_CONFIRM);
                break;
            case CFI_CMDSET_AMD_STANDARD:
            case CFI_CMDSET_AMD_EXTENDED:
                flash_unlock_seq (info, sect);
                flash_write_cmd (info, sect, AMD_ADDR_ERASE_START,
                            AMD_CMD_ERASE_START);
                flash_unlock_seq (info, sect);
                flash_write_cmd (info, sect, 0, AMD_CMD_ERASE_SECTOR);
                break;
            case CFI_SST_1601_LZD:

     /*上面两个是intel和amd的nor flash擦出方法,这里是我加的sst39vf1601的擦出方法*/
                flash_write_cmd (info, 0, 0x5555, 0xaa);
                flash_write_cmd (info, 0, 0x2aaa, 0x55);
                flash_write_cmd (info, 0, 0x5555,0x80);
                flash_write_cmd (info, 0, 0x5555,0xaa);
                flash_write_cmd (info, 0, 0x2aaa, 0x55);
                flash_write_cmd (info, sect, 0, 0x50);

    //确定要擦除的扇区
                break;
           default:
                debug ("Unkown flash vendor %d\n",
                       info->vendor);
                break;
            }
   //擦除要花费一段时间,这里进行检查擦除是否ok。在下面
            if (flash_full_status_check(info, sect, info->erase_blk_tout, "erase")) {
                rcode = 1;
            } else
                putc ('.');
        }
    }
    puts (" done\n");
    return rcode;
}

//先看看下面这个函数的行为。
typedef unsigned long flash_sect_t;
/*它是把cmd这个命令写到一个地址处,这个地址是经过精心安排的,让我们看看这个地址是怎么来的 ^_^*/
static void flash_write_cmd (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd)

//下面函数用到的数据结构
typedef union {
    volatile unsigned char *cp;
    volatile unsigned short *wp;
    volatile unsigned long *lp;
    volatile unsigned long long *llp;
} cfiptr_t;

typedef union {
    unsigned char c;
    unsigned short w;
    unsigned long l;
    unsigned long long ll;
} cfiword_t;

#define FLASH_CFI_8BIT 0x01
#define FLASH_CFI_16BIT 0x02
//我的是16bit数据宽度的,就是每个地址产生16bit数据,就是portwidth的值
#define FLASH_CFI_32BIT 0x04
#define FLASH_CFI_64BIT 0x08

/* * Write a proper sized command to the correct address */
static void flash_write_cmd (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd)
{
    volatile cfiptr_t addr;
    cfiword_t cword;

    addr.cp = flash_make_addr (info, sect, offset);
//这个函数产生一个地址,根据sect和offset计算出,在下面
    flash_make_cmd (info, cmd, &cword);
//----addr.wp、cword.w就和这里有关,仔细研究
    switch (info->portwidth) {
    case FLASH_CFI_8BIT:
        debug ("fwc addr %p cmd %x %x 8bit x %d bit\n", addr.cp, cmd,
               cword.c, info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
        *addr.cp = cword.c;
        break;
    case FLASH_CFI_16BIT:
        debug ("fwc addr %p cmd %x %4.4x 16bit x %d bit\n", addr.wp,
               cmd, cword.w,
               info->chipwidth << CFI_FLASH_SHIFT_WIDTH);//2<<3 = 16
   *addr.wp = cword.w;

//这里就是写命令的过程。怎么去执行呢?直接赋值就可以么?

        break;
    case FLASH_CFI_32BIT:
        debug ("fwc addr %p cmd %x %8.8lx 32bit x %d bit\n", addr.lp,
               cmd, cword.l,
               info->chipwidth << CFI_FLASH_SHIFT_WIDTH);
        *addr.lp = cword.l;
        break;
    case FLASH_CFI_64BIT:
        *addr.llp = cword.ll;
        break;
    }
}

inline uchar *flash_make_addr (flash_info_t * info, flash_sect_t sect, uint offset)
{
    return ((uchar *) (info->start[sect] + (offset * info->portwidth)));
}
/*
上面就是计算地址的过程,这个地址根据sect和offset计算出确切的地址,比如我们要访问0x5555这个地址,可以是sect= 0,offset = 0x5555
然后把offset左移一位,这个地址就是nor flash想要的地址,从nor的角度看到的是0x5555这个地址,从cpu来说,它输出的地址是 0x5555<<1。
如果要访问第n块的第0x5555这个地址,那么从cpu的角度输出的地址是 n*0x10000+0x5555<<1-----不明白*/

//下面是产生命令的过程
static void flash_make_cmd (flash_info_t * info, uchar cmd, void *cmdbuf)
{
    int i;
    uchar *cp = (uchar *) cmdbuf;
   //这里传递一个变量的指针

    for (i = info->portwidth; i > 0; i--)
        *cp++ = (i & (info->chipwidth - 1)) ? '\0' : cmd;
//直接修改了cp指向的地址的值,其实就是联合体cfiword_t cword的值,因此不需要返回
//小端模式,在最低的那个byte放上命令字节,很妙
}
/*
地址和命令字节就是这样产生的。回去看erase。
下面的是检查nor的状态的函数,也就相当于检查擦除操作成功与否的函数,擦除成功要返回0
成功后还要复位一下,使nor进入到read mode
他的第三个参数是最大的等待时间,毫秒为单位,最大为32毫秒*/

static int flash_full_status_check(info, 15, info->erase_blk_tout, "erase")
static int flash_full_status_check (flash_info_t * info, flash_sect_t sector,ulong tout, char *prompt)
{
    int retcode;
    retcode = flash_status_check (info, sector, tout, prompt);
    switch (info->vendor) {
    case CFI_CMDSET_INTEL_EXTENDED:
    case CFI_CMDSET_INTEL_STANDARD:
        if ((retcode == ERR_OK)
            && !flash_isequal (info, sector, 0, FLASH_STATUS_DONE)) {
            retcode = ERR_INVAL;
            printf ("Flash %s error at address %lx\n", prompt,
                info->start[sector]);
            if (flash_isset (info, sector, 0, FLASH_STATUS_ECLBS | FLASH_STATUS_PSLBS)) {
                puts ("Command Sequence Error.\n");
            } else if (flash_isset (info, sector, 0, FLASH_STATUS_ECLBS)) {
                puts ("Block Erase Error.\n");
                retcode = ERR_NOT_ERASED;
            } else if (flash_isset (info, sector, 0, FLASH_STATUS_PSLBS)) {
                puts ("Locking Error\n");
            }
            if (flash_isset (info, sector, 0, FLASH_STATUS_DPS)) {
                puts ("Block locked.\n");
                retcode = ERR_PROTECTED;
            }
            if (flash_isset (info, sector, 0, FLASH_STATUS_VPENS))
                puts ("Vpp Low Error.\n");
        }
        flash_write_cmd (info, sector, 0, info->cmd_reset);
        break;
    default: //我添加的复位命令,为sst39vf1601

        flash_write_cmd (info, 0, 0, info->cmd_reset);
        break;
    }
    return retcode;
}
//它把参数全部传递给了 flash_status_check 函数,寄希望于他能够返回0。

static int flash_status_check (flash_info_t * info, flash_sect_t sector,
                   ulong tout, char *prompt)
{
    ulong start;
    tout *= CFG_HZ/1000;

    /* Wait for command completion */
    start = get_timer (0);
    while (flash_is_busy (info, sector)) {
        if (get_timer (start) > tout) {
            printf ("Flash %s timeout at address %lx data %lx\n",
                prompt, info->start[sector],
                flash_read_long (info, sector, 0));
            flash_write_cmd (info, sector, 0, info->cmd_reset);
            return ERR_TIMOUT;
        }
        udelay (1); /* also triggers watchdog */
    }
    return ERR_OK;
}

/* the PWM TImer 4 uses a counter of 15625 for 10 ms, so we need */
/* it to wrap 100 times (total 1562500) to get 1 sec. */
#define CFG_HZ 1562500
#define ERR_OK 0
/*显然希望flash_is_busy (info, sector) 在时间到期之前返回0
这个函数完成的任务是查询flash忙不忙,不忙返回0*/

flash_is_busy (info, sector)
static int flash_is_busy (flash_info_t * info, flash_sect_t sect)
{
    int retval;

    switch (info->vendor) {
    case CFI_CMDSET_INTEL_STANDARD:
    case CFI_CMDSET_INTEL_EXTENDED:
        retval = !flash_isset (info, sect, 0, FLASH_STATUS_DONE);
        break;
    case CFI_CMDSET_AMD_STANDARD:
    case CFI_CMDSET_AMD_EXTENDED:
        retval = flash_toggle (info, sect, 0, AMD_STATUS_TOGGLE);
        break;
    default:
       // 我添加的 for sst39vf1601,这个函数的功能见下面

        retval = !flash_isset (info, sect, 0, FLASH_STATUS_DONE); 
       //经过这里的取反后,返回0
       // retval = 0;
    }
    debug ("flash_is_busy: %d\n", retval);
    return retval;
}
//下面的这个函数测试FLASH_STATUS_DONE(flash 状态完成标志),返回这个标志的数值
flash_isset (info, sect, 0, FLASH_STATUS_DONE)
static int flash_isset (flash_info_t * info, flash_sect_t sect, uint offset, uchar cmd)
{
    cfiptr_t cptr;
    cfiword_t cword;
    int retval;

    cptr.cp = flash_make_addr (info, sect, offset); //这里无所谓

    flash_make_cmd (info, cmd, &cword);
    switch (info->portwidth) {
    case FLASH_CFI_8BIT:
        retval = ((cptr.cp[0] & cword.c) == cword.c);
        break;
    case FLASH_CFI_16BIT:
        retval = ((cptr.wp[0] & cword.w) == cword.w);
//从地址处取得一个16bit数,与要求的poll date相比较,不忙返回真???

//目前就定位到这里,retval返回0,读出的值与写入的值不相等。
        break;
    case FLASH_CFI_32BIT:
        retval = ((cptr.lp[0] & cword.l) == cword.l);
        break;
    case FLASH_CFI_64BIT:
        retval = ((cptr.llp[0] & cword.ll) == cword.ll);
        break;
    default:
        retval = 0;
        break;
    }
    return retval;
}
/*
到了这里,我们的程序,已经擦除了要求的扇区了。^_^
返回int saveenv(void)*/
/* 3.开始写进去
写成功,返回0,这个(char *)0xxxxx 是什么呢?下面告诉你,反正是个内存地址而不是0xf0000 */

rc = flash_write((char *)0xxxxx, 0xf0000, 0x10000); 开始写进去
env_t *env_ptr = (env_t *)CFG_ENV_ADDR = 0xf0000;
static env_t *flash_addr = (env_t *)CFG_ENV_ADDR = 0xf0000;

阅读(3627) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~