搜索
bottom↓
回复: 43

单片机编程之 - 程序模块化及可复用性(二)

  [复制链接]

出0入0汤圆

发表于 2014-5-15 10:44:57 | 显示全部楼层 |阅读模式
本帖最后由 electrlife 于 2014-5-15 10:47 编辑

单片机编程之 - 程序模块化及可复用性(二)

单片机编程之 - 程序模块化及可复用性(一)
http://www.amobbs.com/thread-5580954-1-1.html

关于这个贴子的原由请查看http://www.amobbs.com/thread-5580903-1-1.html
另外申明:技术只是实现产品的一种工具,因此你应该花更多的时间去关注产品的本身!


    终于开始写第二节了,如果你已经认真看完第一节,你应该多少对面象接口这种形式的程序方法有所感觉了,
此刻你应该已经试着把以前的各个传感器的驱动改成这种形式了!如果你还没有,那开始阅读这节之前,先
简单实践下吧!

   上一节中我们实现了两个模块,一个是软件IIC模块,另一个是EEPROM 24LC512模块,有了这两个模块,接下来
我们就开始学习如何使用这个EEPROM。在使用EEPROM之前,我把上一节的关于EEPROM面要考虑的问题再次复制过来:

1、EEPROM一般做什么使用
2、EEPROM的操作如何在多任务中使用
3、如何避免错误程序的多次误写(EEPROM有擦除次数的)
4、EEPROM如何考虑写时掉电,且如何识别错误并恢复
5、如何提高EEPROM的写效率,即那写的10MS延时

首先我们解决 1、EEPROM一般做什么使用?
我们知道EEPROM一般都是作为参数存储使用的,如传感器的校准系数、产品
生产日期、产品名称等等。那如何组织这些信息并进行管理呢?
我想最原始的方法应该主是类似如下这样:


  1. #define EEP_ADC_PARAM_OFFSET        0x100
  2. #define EEP_PRODUCT_TYPE_OFFSET     0x104
  3. #define EEP_PRODUCT_NAME_OFFSET     0x108

  4. int test_eeprom_param(void)
  5. {
  6.     uint32_t adc_param;

  7.     return dev_24lcxx_read(&dev_ee24lc512, EEP_ADC_PARAM_OFFSET, &adc_param, sizeof(adc_param));
  8. }
复制代码


如果你还在用以上方式,你千万不要说出来,因为太原始了,太暴力了!:-) !
这种方式的最大问题是,当你发现你先前定义的一个参数的字节设的太小时,
你需要更改所有参数的偏移量,对于我这样的懒人来说这是个巨大的工作量。
而这些工作其实可以让编译器来完成,如下所示:


  1. struct nvram_sysparam {
  2.     uint32_t    type;               /* 设备类型   */
  3.     uint32_t    rev;                /* 设备版本号 */
  4.     char        sn[32];             /* 设备序列号 */
  5.     char        name[32];           /* 设备名称   */

  6.     uint32_t    adc_param;
  7. };

  8. #define plat_offsetof(type, member)         ((unsigned long)(&((type *)0)->member))

  9. int test_eeprom_param(void)
  10. {
  11.     uint32_t adc_param;

  12.     return dev_24lcxx_read(&dev_ee24lc512, plat_offsetof(struct nvram_sysparam, adc_param),
  13.                          &adc_param, sizeof(adc_param));
  14. }
复制代码


有了上述的改变,我们可以省去了计算各个参数偏移量的工作,当增加新的参数或是改变老的参数
后,plat_offsetof宏这义会自动帮我们计算。但在这里我们发现还有一个小问题就是关于参数的字节
数,当调用dev_24lcxx_read函数时我们应当以EEPROM存储的这个参数的字节数为准,因此sizeof(adc_param)
这种做法不是推荐的,如果EEPROM里参数的字节数发生变化,那么你的应用程序用所有和这个参数的相关的操作
都得重新改过,这是我这种懒人地接受的,因此你需要下面的宏及使用方式:


  1. #define plat_offsetof(type, member)         ((unsigned long)(&((type *)0)->member))
  2. #define plat_paramsizeof(type, member)      (sizeof(((type *)0)->member))

  3. int test_eeprom_param(void)
  4. {
  5.     /* 注意这里需要对其进行赋值为0操作 */
  6.     uint32_t adc_param = 0;

  7.     return dev_24lcxx_read(&dev_ee24lc512, plat_offsetof(struct nvram_sysparam, adc_param),
  8.                          &adc_param, plat_paramsizeof(struct nvram_sysparam, adc_param));
  9. }
复制代码


     好了到这里我们已经可以很方便增加改变EEPROM中参数的大小了,这所有改变几乎不影响应用程序。
如果对于小型的MCU开发,或是资源紧张的MCU写到里或许已经可以很好的使用了,唯一的遗憾是对于
函数中    uint32_t adc_param = 0; 变量,我们在定义时需要初始化并定义此变量比实际EEPROM中
的字节数多,至少应该相等。这点需要特别注意!但是我们不应该只满足于此,上面两种方式都存在
一个共同的问题,就是可维护性!如果出现以下情景你该如何处理?

1、你的程序处于调试阶段,struct nvram_sysparam 结构的成员基本是每天都要增加
   当你辛苦用万用表调整的ADC系数,因为你的增加新参数导致其偏移改变,所以不得
   不重新使用万用表调整的ADC系数,当这种参数如果有几十个时,我相信你会崩溃!
2、你的程序已经运行在设备上,但当某人市场部提出增加新功能时,你不得不给老机器升级
   程序,但你发现新功能需要新的参数增加时,你傻眼了,以前的设备运行时的参数及数据都
   需要重新输入,这时候你还会崩溃的!
   
      有人看了以上问题,可能会说,这还不简单吗,我增加新的参数时只从struct nvram_sysparam最后
加入,那以上问题不是都解除了吗?的确这样确实是可以解决,但是这种方式是行不通的,至于原因
请住后看。

      上面说了那么多,其实都不是今天的主题,只是餐前开胃,接下来才是今天的正餐:
      
程序模块化及可复用性的原则三:应用模块接口尽量使用ASCII字符格式而非二进制流格式

为了大家能时刻记住前面说过的原则,我再次复制过来:

程序模块化及可复用性的原则一:面像接口编程
程序模块化及可复用性的原则二:硬件的抽象接口尽量通用简单

    对于 程序模块化及可复用性的原则三,真可谓是无边无际,往大了说可能我自己也都是门外汉,因此
我只能住小了说::-),对于这个原则我不想作过多的解释,因为我觉得如果你真要想感受到此原则的好处
确实是需要实际使用中体会!好了开始我们今天的正餐程序模块化及可复用性。

    我们知道在前面一节当中我们对两个设备进行了抽象,一个是IIC,一个EEPROM,细心的读者或许会发现
前的抽象都是针对具体的设备,也就是这种设备是看的到,摸得着的,它们都有很规范的操作时序及操作集合,
因此在进行抽象时并不是特别困难。接下来我们说下关于一些看不到摸不着的设备抽象:

    有了上面的EEPROM管理的基础,我们现在想想我们需要一个统一的EEPROM管理操作集合供上层应用代码,
那么如何把这些集合操作组织起来呢?下面就看下我的做法,当然可能别人还有更出色的做法!对于EEPROM
或是FRAM之类的这种非易失性的存储器我统称他们为NVRAM,对于NVRAM我们需思考给上层怎样的接口,或者
上层需要什么时候样的接口功能,为了程序的通用和模块化,因此我们需要一个完善的NVRM接口及功能,而
具体的功能如下:

1、允许拥有多的NVRAM,且这些NVRAM可以是不同或是相同类型
2、应用程序不需要知道是什么类型的NVRAM,应用程序都视一种设备NVRAM,统一管理
3、多个不同或是相同的NVRAM在应用层可合并成一个大容量的NVRAM使用
4、当然应用程序也可以把一个NVRAM设备分成多个区分并作为多个NVRAM使用
5、NVRAM设备具有错误识别及自动恢复能力,也即写时掉电的问题

    有了上面的要求,我们就有了目标,接下来就可以向着目标前进了!到这里似乎来原则三还没有什么
关系,别着急接下来你就可以看到了!

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

 楼主| 发表于 2014-5-15 10:45:24 | 显示全部楼层
本帖最后由 electrlife 于 2014-5-15 15:03 编辑

首先我们先设计出NVRAM给应用层的接口,注意设计接口的原则,我的设计如下所示

  1. int nvram_set(struct nvram_partition *partition, const char *name,
  2.               const char *buf, int size);

  3. int nvram_get(struct nvram_partition *partition, const char *name,
  4.               char *buf, int size);

  5. int nvram_set_ulong(struct nvram_partition *partition, const char *name,
  6.                     unsigned long value);

  7. int nvram_get_ulong(struct nvram_partition *partition, const char *name,
  8.                     unsigned long *value);
复制代码


     这里我对部分参数进行一下说明:
struct nvram_partition *partition 表示指向分区的指针,因为我们要求NVRAM是可以以分区的形式进行操作的,因此我们的函数
的基本结构应该是以分区作为对象的,这点需要理解。
const char *name 表示你需要读取的参数的名称,注意,这里使用了名称来操作NVRAM分区中的参数,这个是重点,也正是体现了
“程序模块化及可复用性的原则三:应用模块接口尽量使用ASCII字符格式而非二进制流格式”这条原则,有了这个名字,大家肯定都
会想到我们是需要把名称和对应的变量进行联系的,对的!
     后面的两个函数 nvram_set_ulong、nvram_get_ulong是为了方便操作而设立的,因为NVRAM中的参数一般的大小都不会超过4个字节
因此通过这两个函数可以很容易的设定NVRAM中各个变量的值。这里需要说明的是此函数应该自动依据NVRAM中变量的字节长度来截取
输入参数unsigned long value的值。有了这两个函数,你也可以根据实际上情况,增加nvram_set_float等方便操作的函数。好了,有了
上面的操作,下面该想分区对象了,想想为了满足上面的应用,分区对象应该需要具有哪些信息呢,现在给出我的设计如下:


  1. #define NVRAM_FLAGSINFO_VALID                   0xeaf5dca5

  2. #define NVRAM_PARAM_TYPE_UVALUE                 0
  3. #define NVRAM_PARAM_TYPE_SVALUE                 1
  4. #define NVRAM_PARAM_TYPE_STRING                 2

  5. #define NVRAM_PARAM_ATTR_SUPER                  0x00
  6. #define NVRAM_PARAM_ATTR_USER                   0x01

  7. struct nvram_hdr {
  8.     uint32_t flags;
  9.     uint32_t verify_crc;
  10.     uint32_t len;
  11.     uint32_t data[1];
  12. };

  13. struct nvram_param_info {
  14.     const char *name;
  15.     uint32_t    offset;
  16.     uint16_t    n_byte;
  17.     uint8_t     type;
  18.     uint8_t     attribute;
  19. };

  20. /* NVRAM分区信息 */
  21. struct nvram_partition {
  22.     char *name;                 /* 名称 */
  23.     struct nvram_chip *chip;    /* 分区属于哪个NVRAM设备 */
  24.     unsigned long offset;       /* 分区在NVRAM设备中的偏移量 */
  25.     unsigned long size;         /* 分区的大小 */

  26.     char *cache_image;
  27.     char *cache_addr;           /* 分区缓冲区首地址 */
  28.     unsigned long total_cache_size;/* 分区有效数据的长度,单位(字节)*/
  29.     struct nvram_param_info *tbl;/* 分区中变量名称与地址映射表 */
  30.     unsigned int tbl_size;

  31.     OS_MUTEX *locker;           /* 分区访问互斥锁 */
  32. };
复制代码


    关于这些结构体成员我就不再解释了,因为稍后你会看到代码,我想在源码里的使用你应该更容易理解!
但这里的一个成员我还是要说下:就是 struct nvram_chip *chip;    /* 分区属于哪个NVRAM设备 */
正如注释的那样,此变量是NVRAM与底层驱动的抽象,也即是和硬件相关联系的关键,有了个抽象我们就可以先不
用管硬件是什么器件了,就可以直接写上层的应用了,因此这个底层的抽象接口我们一定要设计好,其设计的原则是
什么呢还是那句老话:面象接口编程原则XXXXXXXXXX,这里就不再重复了!具体如下所示:

  1. struct nvram_chip {
  2.     char *name;
  3.     unsigned long size;
  4.     unsigned int  slave_addr;
  5.     unsigned int  page_size;
  6.     unsigned int  n_page;
  7.     void         *page_buf;

  8.     int (*write)(struct nvram_chip *thiz, unsigned long offset_addr,
  9.                     const char *buf, int size);

  10.     int (*read)(struct nvram_chip *thiz, unsigned long offset_addr,
  11.                     char *buf, int size);
  12. };
复制代码

    由于NVRAM一般是针对EEPROM这种设备也作用,因此对于底层的接口中对于EEPROM的属性部分的内容较多,如果你想在NOR或是其它
的一些存储设备上使用NVRAM,那这里你还需要加上这些设备所需要的特殊数据部分,而就目前而言我们不作更复杂的假设了,不然又得
绕进了。这里的NVRAM底层又重新抽象出了一种接口而没有直接使用struct dev_24lcxx的一个原因就是希望NVRAM更加的抽象而不紧紧针对
EEPROM,比如可能你的EEPROM设备是一个远程设备,即不要CPU板上,这个是有可能的,楼主的上一个项目就是如此,需要通过CAN总去设置
下位机的EEPROM,那么只要虚拟化一个struct nvram_chip即可,把write read等实现通过CAN总线发送即可。因此有了这个我们自己抽象
的接口,我们就能很方便的把我们的参数放到任何地方,:-)。

出0入0汤圆

 楼主| 发表于 2014-5-15 10:45:59 | 显示全部楼层
本帖最后由 electrlife 于 2014-5-16 10:28 编辑

有些网友提出来看起来比较费力,一部分原因可能是C语言还不够熟悉,当然我写的也比较粗,
如果因为编程语言不熟悉,就只能多看看书了,这里不可能对C语法的东西作说明,如果这样
那真的需要写的太多了,然而为了能更好帮助大家理解,对于以上的结构体我还是简单做下介绍:
struct nvram_hdr 是放在每个NVRAM分区的开始位置,也标识一个NVRAM设备

  1. struct nvram_hdr {
  2.     uint32_t flags;         /* 是一个标识符,表示EEPROM是否有效如果是一个已经初始化的EEPROM则其值应该是0xeaf5dca5 */
  3.     uint32_t verify_crc;    /* 对EEPROM中的所有数据进行CRC32处理,也是通过这个CRC来检查EEPROM中的数据是否正确,
  4.                                当然如果你的项目对EEPROM的数据的可靠性与完整性要求比较高,这里你也可以加上更复杂
  5.                                的校验算法,如MD5等 */
  6.     uint32_t len;           /* 表示整个NVRAM中有效数据长度 */
  7.     uint32_t data[1];       /* 暂时没有使用,只作为NVRAM中头部信息与用户数据的分隔 */
  8. };
复制代码


    在上面已经提到了,我们的接口函数使用字符串作为参数来对NVRAM中的变量进行访问,因此我们需要把字符串转化成对应的变量地址,
而下面的这个结构体struct nvram_param_info的作用就是把变量的地址与其名称进行映射。这种通过字符串查找来寻变量地址的方式
需要一定量的查找与比较,因此变量的名称应当尽量短,当然你也可以通过更先进的查找技术来解决这个问题,比如HASH查找等。

  1. struct nvram_param_info {
  2.     const char *name;       /* 变量的名字,是一个字符串 */
  3.     uint32_t    offset;     /* 此变量在NVRAM设备的偏移量,即此变量的具体地址 */
  4.     uint16_t    n_byte;     /* 此变量所占用的字节数,即变量的大小 */
  5.     uint8_t     type;       /* 变量的类型,目前只支持有符号整形,无符号整形,
  6.                                字符串,当然你也可以根据实际情况增加自己的类型及处理函数 */
  7.     uint8_t     attribute;  /* 此变量的属性,主要的作用是,你可以为你的NVRAM中的变量设置不同的属性加以区分
  8.                                一般我会把变量分为两种属性,一个是用户变量, 一个是系统变量,它们的属性不同,则当
  9.                                用户需要恢复出厂时,则可以根据此属性来决定此变量是否需要被恢复,当然你也可以添加其它
  10.                                的属性,比如写一次属性,只读属性等等 */
  11. };
复制代码


     有了这个结构体struct nvram_param_info,我们就可以把相应的变量与其名称对应起来了,那是如何做的呢?

  1. struct nvram_sysparam {
  2.     struct nvram_hdr hdr;

  3.     uint32_t    type;               /* 设备类型   */
  4.     uint32_t    rev;                /* 设备版本号 */
  5.     char        sn[32];             /* 设备序列号 */
  6.     char        name[32];           /* 设备名称   */

  7.     /* 以下是用户定义数据类型 */
  8.     uint32_t    reserve32_1;
  9.     uint16_t    reserve16_1;
  10.     uint8_t     reserve08_1;
  11. };
复制代码

     有了这个结构体,那我们下一步就是要把此结构体中的变量地址和其名称映射起来,这里说明下,一般我会直接
使用变量名作为其名称,那我们就需要定义如下数组:
static const struct nvram_param_info param_system_map_tbl[] = {
    {.name = "type", .offset = plat_offsetof(struct nvram_sysparam, type), ...},
    .......
};
     实在不好意思,写到这儿我就不想写了,太麻烦了,因此作为懒人的我肯定不会手动去一个一个去设置了。
于是就有了下面的宏定义:

  1. #define NVRAM_EXPORT_PARAM_UVALUE(type, param, attr)  \
  2.     {#param, plat_offsetof(type, param), sizeof(((type *)0)->param), NVRAM_PARAM_TYPE_UVALUE, attr}
  3. #define NVRAM_EXPORT_PARAM_SVALUE(type, param, attr)  \
  4.     {#param, plat_offsetof(type, param), sizeof(((type *)0)->param), NVRAM_PARAM_TYPE_SVALUE, attr}
  5. #define NVRAM_EXPORT_PARAM_STRING(type, param, attr)  \
  6.     {#param, plat_offsetof(type, param), sizeof(((type *)0)->param), NVRAM_PARAM_TYPE_STRING, attr}
复制代码

     有了上面的宏定义那我们刚才的初始化就相对来说就比较容易了:

  1. static const struct nvram_param_info param_system_map_tbl[] = {
  2.     NVRAM_EXPORT_PARAM_UVALUE(struct nvram_sysparam, type               ,NVRAM_PARAM_ATTR_SUPER),
  3.     NVRAM_EXPORT_PARAM_UVALUE(struct nvram_sysparam, rev                ,NVRAM_PARAM_ATTR_SUPER),
  4.     NVRAM_EXPORT_PARAM_STRING(struct nvram_sysparam, sn                 ,NVRAM_PARAM_ATTR_SUPER),
  5.     NVRAM_EXPORT_PARAM_STRING(struct nvram_sysparam, name               ,NVRAM_PARAM_ATTR_SUPER),

  6.     NVRAM_EXPORT_PARAM_UVALUE(struct nvram_sysparam, reserve32_1        ,NVRAM_PARAM_ATTR_SUPER),
  7.     NVRAM_EXPORT_PARAM_UVALUE(struct nvram_sysparam, reserve16_1        ,NVRAM_PARAM_ATTR_SUPER),
  8.     NVRAM_EXPORT_PARAM_UVALUE(struct nvram_sysparam, reserve08_1        ,NVRAM_PARAM_ATTR_SUPER),
  9. };
复制代码

     当然这也不是最省心的方案,还有一种就是利用编译器及链接器对段(SECTION)控制来处理,但这种方法需要
对程序的编译链接具有一定的了解,而本文主要目的是程序的模块化及可复用性,因此这种方法这里就不提及了!
通过以上的展示大家应该明白一点上述的结构及成员的作用了吧,
     接下来我们就该该说NVRAM的驱动接口 struct nvram_chip 了,还记得前面我们已经实现了一个抽象的设备了吧
对的,我们已经有了一个EEPROM的抽象设备,现在是时个使用它了,我们把此设备引用过来而且我们还需要实现两个
struct nvram_chip的函数,read 与 write,具体的代码如下所示:

  1. extern const struct dev_24lcxx dev_ee24lc512;

  2. static OS_MUTEX ee24lc512_lock;

  3. static int eep_24lc512_write(struct nvram_chip *thiz, unsigned long offset_addr, const char *buf, int size)
  4. {
  5.     OS_ERR os_err;
  6.     int r;

  7.     if (!thiz) {
  8.         return -1;
  9.     }

  10.     OSMutexPend(&ee24lc512_lock, 0, OS_OPT_PEND_BLOCKING, 0, &os_err);

  11.     r = dev_24lcxx_write(&dev_ee24lc512, offset_addr, buf, size);

  12.     OSMutexPost(&ee24lc512_lock, OS_OPT_POST_NONE, &os_err);

  13.     return r;
  14. }

  15. static int eep_24lc512_read(struct nvram_chip *thiz, unsigned long offset_addr, char *buf, int size)
  16. {
  17.     int r;
  18.     OS_ERR os_err;

  19.     if (!thiz) {
  20.         return -1;
  21.     }

  22.     OSMutexPend(&ee24lc512_lock, 0, OS_OPT_PEND_BLOCKING, 0, &os_err);

  23.     r = dev_24lcxx_read(&dev_ee24lc512, offset_addr, buf, size);

  24.     OSMutexPost(&ee24lc512_lock, OS_OPT_POST_NONE, &os_err);

  25.     return r;
  26. }
复制代码


    有了上面的两个操作函数,接下来我们就可以定义struct nvram_chip这个抽象的设备了,具体如下所示:

  1. #define EE_ADDR             0x00
  2. #define EE_CMD_RD           0xA1
  3. #define EE_CMD_WR           0xA0
  4. #define EE_PAGESIZE         128
  5. #define EE_PAGENUM          512

  6. const struct nvram_chip nvram_ee24lc512 = {
  7.     .name       = "ee24lc512",
  8.     .size       = EE_PAGESIZE * EE_PAGENUM,
  9.     .slave_addr = EE_ADDR,
  10.     .page_size  = EE_PAGESIZE,
  11.     .n_page     = EE_PAGENUM,
  12.     .page_buf   = 0,
  13.     .write      = &eep_24lc512_write,
  14.     .read       = &eep_24lc512_read,
  15. };
复制代码

      从上面的代码中我们可以看出,如果哪天你的板子把EEPROM换成FRAM了,或是其它的存储介质,那么,你只需要重新
构建一个struct nvram_chip并实现其驱动,上层的所有逻辑不用更改,看到了吧,这就是面像接口的魅力。现在万事俱备只欠
东风了,我们东风是什么呢,就是我们最终要构建的NVRAM设备!

出0入0汤圆

 楼主| 发表于 2014-5-15 10:46:20 | 显示全部楼层
占位占位占位占位占位占位占位占位占位

出0入0汤圆

发表于 2014-5-15 11:50:02 | 显示全部楼层
好东西,顶楼主

出0入0汤圆

发表于 2014-5-15 12:08:30 | 显示全部楼层
支持楼主,长知识了

出0入0汤圆

发表于 2014-5-15 12:13:29 来自手机 | 显示全部楼层
顶楼主   

出0入0汤圆

发表于 2014-5-15 12:30:26 | 显示全部楼层
顶楼主,写得好

出0入0汤圆

发表于 2014-5-15 12:33:31 | 显示全部楼层
先顶再看.

出100入101汤圆

发表于 2014-5-15 12:37:14 | 显示全部楼层
有时间再学习!

出0入0汤圆

发表于 2014-5-15 12:52:09 | 显示全部楼层
学习,学习

出0入0汤圆

发表于 2014-5-15 13:15:11 来自手机 | 显示全部楼层
这样写很久了,楼主值得一顶啊!
让大家什么叫模块化编程

出0入0汤圆

发表于 2014-5-15 13:37:59 | 显示全部楼层
赶上这么好的文章,要细读了。

出0入0汤圆

发表于 2014-5-15 13:40:15 | 显示全部楼层
这个观念很好,利于积累和提高。

出0入0汤圆

 楼主| 发表于 2014-5-15 15:06:10 | 显示全部楼层
更新较慢,大家见谅啊!好久没打这么多字了!

出10入0汤圆

发表于 2014-5-15 15:08:01 | 显示全部楼层
搬凳子,学习

出0入0汤圆

发表于 2014-5-15 23:04:42 来自手机 | 显示全部楼层
学习学习 手机看着好累呀

出0入0汤圆

 楼主| 发表于 2014-5-16 10:35:09 | 显示全部楼层
昨天有点事耽搁了,今天继续啊!

出0入17汤圆

发表于 2014-5-16 11:06:42 来自手机 | 显示全部楼层
如果国人有十分之一电工都像楼主这么无私,何愁中国硬软件赶不上美国

出0入0汤圆

发表于 2014-5-16 11:51:39 | 显示全部楼层
本帖最后由 fengyun_524 于 2014-5-16 14:22 编辑

好帖子,关于设计模型和设计思想我也看了很多书,面很广很大,感觉也是略懂,要真正用到实际的产品看法上,还是很难。。

出0入0汤圆

发表于 2014-5-16 13:06:14 | 显示全部楼层
radar_12345 发表于 2014-5-16 11:06
如果国人有十分之一电工都像楼主这么无私,何愁中国硬软件赶不上美国

其实主要不是电工的问题。

出0入0汤圆

发表于 2014-5-16 21:21:55 | 显示全部楼层
/// xxx.h

struct nvram_sysparam {
    uint32_t    type;               /* 设备类型   */
    uint32_t    rev;                /* 设备版本号 */
    char        sn[32];             /* 设备序列号 */
    char        name[32];           /* 设备名称   */

    uint32_t    adc_param;
};
static const struct nvram_sysparam *NVRAM_Data = 0;

/// xxx.c
#include "xxx.h"
dev_24lcxx_read(&dev_ee24lc512, NVRAM_Data->adc_param, &adc_param, sizeof(adc_param));

这样写可读性更加高一些。

出0入0汤圆

发表于 2014-5-16 21:23:29 | 显示全部楼层
eye 发表于 2014-5-16 21:21
/// xxx.h

struct nvram_sysparam {

尤其在支持自动补全的编辑器里面。

出0入0汤圆

发表于 2014-5-16 23:37:54 | 显示全部楼层
支持楼主

出0入0汤圆

发表于 2014-5-17 09:52:16 来自手机 | 显示全部楼层
这种方式太好了,支持楼主,跟楼主学习

出0入0汤圆

 楼主| 发表于 2014-5-17 10:52:54 | 显示全部楼层
      接下来就开始构建我们的NVRAM设备,对于NVRAM设备我们一般是为EEPROM作为存储介质的,而项目中EEPROM的第一次上电
总是空的,所以我们需要一个默认的参数列表,因此我们定义一个默认的参数列表放在程序代码里,当发现是一个新的或是无效的
EEPROM时就使用默认的参数来进行初始化。

  1. static const struct nvram_sysparam nvram_sysparam_default = {
  2.     .hdr                = {NVRAM_FLAGSINFO_VALID, 0, 0, 0},
  3.     .type               = 0,
  4.     .rev                = 0,
  5.     .sn                 = "N/A",
  6.     .name               = "N/A",
  7.     /* 以下是用户定义数据类型 */
  8.     .reserve32_1        = 0,
  9.     .reserve16_1        = 0,
  10.     .reserve08_1        = 0,
  11. };
复制代码

      我们的NVRAM设备需要在多任务环境下使用因此,我们需要进行临界段处理,即进行共享资源互斥,这里使用互斥信号,
而且为了快速对NVRAM进行处理,我们需要定义一个缓冲区来加速应用程序的访问。

  1. extern const struct nvram_chip nvram_ee24lc512;

  2. static OS_MUTEX sysparam_locker;
  3. static struct nvram_sysparam sysparam_cache;

  4. const struct nvram_partition sysparam_partition = {
  5.     .name               = "sysparam_partition",                 /* 抽象设备的名称 */
  6.     .chip               = (struct nvram_chip *)&nvram_ee24lc512,/* 底层驱动指针 */
  7.     .offset             = 0,                                    /* 该分区位于NVRAM_CHIP设备的偏移量,因为一个NVRAM_CHIP可以
  8.                                                                    拥有多个分区 */
  9.     .size               = 1024*16,                              /* 该分区的大小 */

  10.     .cache_image        = (void *)&nvram_sysparam_default,
  11.     .cache_addr         = (void *)&sysparam_cache,
  12.     .total_cache_size   = sizeof(sysparam_cache),
  13.     .tbl                = (struct nvram_param_info *)&param_system_map_tbl[0],  /* 把NVRAM设备与对应的名称映射表进行绑定 */
  14.     .tbl_size           = plat_countof(param_system_map_tbl),

  15.     .locker             = &sysparam_locker,
  16. };
复制代码


       到此我们的NVRAM设备已经抽象出来了,下面就是实现具体的接口函数了,这里我给出我实现这些接口函数的代码如下:

  1. #define PARTITION_FLAGS_INVALID             1
  2. #define PARTITION_CRC_VERIFY_FAILED         2
  3. #define PARTITION_USE_DEFAULT_IMAGE         3
  4. #define PARTITION_USE_BACKUP_IMAGE          4
  5. #define PARTITION_CHIP_RW_ERR               5

  6. #define get_partition_hdr(p)\
  7.     ((struct nvram_hdr *)p->cache_addr)

  8. #define get_chip_offset(p, offs)\
  9.     (unsigned long)(p->offset + offs)

  10. #define get_chip_bak_offset(p, offs)\
  11.     (unsigned long)(p->offset + (p->size >> 2) + offs)

  12. /**
  13. * @brief  None.
  14. *
  15. * @param  partition
  16. * @return
  17. * @note   None.
  18. */
  19. static int init_partition_locker(struct nvram_partition *partition)
  20. {
  21.     OS_ERR os_err;

  22.     OSMutexCreate(partition->locker, "partition locker", &os_err);

  23.     if (os_err != OS_ERR_NONE) {
  24.         kprintf("error:%u nvram partition create locker failed\n", os_err);
  25.         return -1;
  26.     }

  27.     return 0;
  28. }

  29. static void lock_partition(struct nvram_partition *partition)
  30. {
  31.     OS_ERR os_err;

  32.     OSMutexPend(partition->locker, 0, OS_OPT_PEND_BLOCKING, 0, &os_err);
  33. }

  34. static void unlock_partition(struct nvram_partition *partition)
  35. {
  36.     OS_ERR os_err;

  37.     OSMutexPost(partition->locker, OS_OPT_POST_NONE, &os_err);
  38. }

  39. /**
  40. * @brief  None.
  41. *
  42. * @param  partition
  43. * @return
  44. * @note   None.
  45. */
  46. static int chk_partition_info(struct nvram_partition *partition)
  47. {
  48.     struct nvram_chip *chip = partition->chip;

  49.     if (!partition->name) {
  50.         return -1;
  51.     }

  52.     if (!partition->chip) {
  53.         return -1;
  54.     }

  55.     if (!partition->cache_image) {
  56.         return -1;
  57.     }

  58.     if (!partition->cache_addr) {
  59.         return -1;
  60.     }

  61.     if (!partition->cache_image) {
  62.         return -1;
  63.     }

  64.     if (!partition->tbl) {
  65.         return -1;
  66.     }

  67.     if (!partition->locker) {
  68.         return -1;
  69.     }

  70.     if (partition->total_cache_size < 1) {
  71.         return -1;
  72.     }

  73.     if (partition->size < (partition->total_cache_size * 2)) {
  74.         return -1;
  75.     }

  76.     if (chip->size < partition->size) {
  77.         return -1;
  78.     }

  79.     if ((partition->offset + partition->size) > chip->size) {
  80.         return -1;
  81.     }

  82.     return 0;
  83. }

  84. /**
  85. * @brief  计算CRC32.
  86. *
  87. * @param  partition
  88. * @return
  89. * @note   None.
  90. */
  91. static uint32_t calc_partition_cache_crc(struct nvram_partition *partition)
  92. {
  93.     uint32_t verify;
  94.     unsigned long verify_size;
  95.     struct nvram_hdr *hdr;

  96.     hdr         = get_partition_hdr(partition);
  97.     verify_size = ((unsigned char *)partition->cache_addr +
  98.                    partition->total_cache_size) - (unsigned char *)hdr->data;

  99.     // 计算参数区的crc32其中包括hdr区的数据data[1]内容
  100.     verify = 0;
  101.     verify = crc32(verify, (const unsigned char *)hdr->data, verify_size);

  102.     return verify;
  103. }

  104. /**
  105. * @brief  复制分区中默认的IMAGE数据到用户CACHE中供应用程序使用.
  106. *
  107. * @param  partition
  108. * @return None.
  109. * @note   None.
  110. */
  111. static void copy_default_image(struct nvram_partition *partition)
  112. {
  113.     struct nvram_hdr *hdr;
  114.     hdr = (struct nvram_hdr *)partition->cache_addr;

  115.     memcpy(partition->cache_addr, partition->cache_image,
  116.            partition->total_cache_size);

  117.     hdr->flags      = NVRAM_FLAGSINFO_VALID;
  118.     hdr->verify_crc = calc_partition_cache_crc(partition);
  119. }

  120. /**
  121. * @brief  从EEPROM中某个区域取了一定数量的数据,此时判断此EEPROM区域是否是有
  122. *         效的区域,如果是有效区域则查看是CRC校验是否正确.
  123. *
  124. * @param  partition
  125. * @param  force_offset
  126. * @param  verify_crc
  127. * @return -1: 表示读取EEPROM出错.
  128. *          1:表示是一个新的EEPROM区域
  129. *          2:表示是一个有效的EEPROM区域,但是CRC校验出错
  130. * @note   None.
  131. */
  132. static int chk_partition_and_verify(struct nvram_partition *partition,
  133.                                     unsigned long force_offset, uint32_t *verify_crc)
  134. {
  135.     uint32_t verify;
  136.     struct nvram_chip *chip;
  137.     struct nvram_hdr *hdr;

  138.     chip = partition->chip;
  139.     hdr  = get_partition_hdr(partition);

  140.     // 读取chip对应的此分区并进行有效性标识检查
  141.     if (chip->read(chip, partition->offset + force_offset, partition->cache_addr,
  142.                    partition->total_cache_size) != 0) {
  143.         return -1;
  144.     }

  145.     // 如果flags不是一个有效则表示是一个新的EEPROM
  146.     if (hdr->flags != NVRAM_FLAGSINFO_VALID) {
  147.         kprintf("find a new chip partition mapping, %s-->%s offset:%u size:%u\n",
  148.                 chip->name, partition->name, partition->offset, partition->size);

  149.         // 返回表示chip对应的此分区是一个新的分区
  150.         return PARTITION_FLAGS_INVALID;
  151.     } else {
  152.         // 对CRC再次检验
  153.         verify = calc_partition_cache_crc(partition);

  154.         *verify_crc = verify;

  155.         if (verify != hdr->verify_crc) {
  156.             // 出现这种原因主要可能是SysParaInfo结构中成员数量有所变化,如程序更改时
  157.             kprintf("%s verify crc failed, verify:%u, hdr->verify_crc:%u",
  158.                     partition->name, verify, hdr->verify_crc);

  159.             return PARTITION_CRC_VERIFY_FAILED;
  160.         } else {
  161.             return 0;
  162.         }
  163.     }
  164. }

  165. /**
  166. * @brief  None.
  167. *
  168. * @param  partition
  169. * @param  force_offset
  170. * @return
  171. * @note   None.
  172. */
  173. static int program_partitions(struct nvram_partition *partition,
  174.                               unsigned long force_offset)
  175. {
  176.     int r;
  177.     struct nvram_chip *chip;

  178.     chip = partition->chip;

  179.     r = chip->write(chip, partition->offset + force_offset, partition->cache_addr,
  180.                     partition->total_cache_size);

  181.     r = (r == 0) ? 0 : -1;

  182.     return r;
  183. }

  184. /**
  185. * @brief  None.
  186. *
  187. * @param  partition
  188. * @return
  189. * @note   None.
  190. */
  191. static int init_partitions_cache(struct nvram_partition *partition)
  192. {
  193.     uint32_t verify_crc1, verify_crc_bak;
  194.     int r1, r_bak;
  195.     unsigned long force_offset;

  196.     /* --------------------------- 从原始位置读取参数 */
  197.     /* 读取参数区有效性标识 */
  198.     force_offset = partition->size >> 1;

  199.     r_bak = chk_partition_and_verify(partition, force_offset, &verify_crc_bak);
  200.     r1    = chk_partition_and_verify(partition, 0, &verify_crc1);

  201.     // 如果两个位置有一个读取失败则返回
  202.     if ((r1 == -1) || (r_bak == -1)) {
  203.         copy_default_image(partition);

  204.         return -1;
  205.     }

  206.     /*
  207.      * 如果首要位置读取成功,则需要判定备份位置是否正确,如果不正确则恢复备份位置
  208.      * 到首要位置状态
  209.      */
  210.     if (r1 == 0) {
  211.         /* 原始数据区读取有效且数据效验正确 */
  212.         if ((r_bak) || (verify_crc1 != verify_crc_bak)) {
  213.             r1 = program_partitions(partition, force_offset);
  214.         }

  215.         return r1;
  216.     } else if (r1 == PARTITION_CRC_VERIFY_FAILED) {
  217.         /* 原始数据区读取有效但数据校验错误 */

  218.     } else if (r1 == PARTITION_FLAGS_INVALID) {
  219.         /* 原始数据区读取有效但是一个新的或是空的区域 */
  220.     }

  221.     /* ---------- 程序到此则表明原始的EEPROM区域无法校验通过或是一个新的EEPROM */

  222.     /* 如果加载出错则需要再次从备份区域加载参数 */
  223.     r_bak = chk_partition_and_verify(partition, force_offset, &verify_crc_bak);

  224.     /* 判断备份位置参数是否正确 */
  225.     if (r_bak == 0) {
  226.         /* 到此说明最原始的EEPROM区域已无效因此需要更新原始EEPROM区域 */
  227.         r1 = program_partitions(partition, 0);

  228.         return ((r1 == 0) ? PARTITION_USE_BACKUP_IMAGE : -1);
  229.     } else if (r_bak == -1) {
  230.         kprintf("%s use the default partition image\n", partition->name);
  231.         copy_default_image(partition);
  232.     } else if ((r_bak == PARTITION_CRC_VERIFY_FAILED) ||
  233.                (r_bak == PARTITION_FLAGS_INVALID)) {

  234.         kprintf("%s use the default partition image\n", partition->name);

  235.         /*
  236.          * 如果备份区域也是一个无效的区域或是一个无法校验通过的区域,则此时需要
  237.          * 使用默认的参数来保证程序的运行
  238.          */
  239.         copy_default_image(partition);

  240.         r_bak = program_partitions(partition, force_offset);
  241.         r1    = program_partitions(partition, 0);

  242.         if ((r_bak == 0) && (r1 == 0)) {
  243.             return PARTITION_USE_DEFAULT_IMAGE;
  244.         }
  245.     }

  246.     return -1;
  247. }

  248. /**
  249. * @brief  None.
  250. *
  251. * @param  partition
  252. * @param  offset_addr
  253. * @param  buf
  254. * @param  n_bytes
  255. * @return None.
  256. * @note   None.
  257. */
  258. static int nvram_raw_write(struct nvram_partition *partition, unsigned long offset_addr,
  259.                            const char *buf, unsigned long n_bytes)
  260. {
  261.     struct nvram_chip *chip;
  262.     struct nvram_hdr *hdr;
  263.     uint32_t verify;
  264.     unsigned long chip_offset;

  265.     memcpy(partition->cache_addr + offset_addr, buf, n_bytes);

  266.     // 对CRC再次检验
  267.     verify = calc_partition_cache_crc(partition);

  268.     chip   = partition->chip;
  269.     hdr    = get_partition_hdr(partition);

  270.     hdr->verify_crc = verify;

  271.     // 写EEPROM原始位置
  272.     chip_offset = get_chip_offset(partition, offset_addr);
  273.     if (chip->write(chip, chip_offset, buf, n_bytes) < 0) {
  274.         return -1;
  275.     }

  276.     chip_offset = get_chip_offset(partition, plat_offsetof(struct nvram_hdr, verify_crc));
  277.     if (chip->write(chip, chip_offset, (const char *)&verify, sizeof(verify)) < 0) {
  278.         return -1;
  279.     }

  280.     // 更新备份位置
  281.     chip_offset = get_chip_bak_offset(partition, offset_addr);
  282.     if (chip->write(chip, chip_offset, buf, n_bytes) < 0) {
  283.         return -1;
  284.     }

  285.     chip_offset = get_chip_bak_offset(partition, plat_offsetof(struct nvram_hdr, verify_crc));
  286.     if (chip->write(chip, chip_offset, (const char *)&verify, sizeof(verify)) < 0) {
  287.         return -1;
  288.     }

  289.     return 0;
  290. }

  291. /**
  292. * @brief  None.
  293. *
  294. * @param  partition
  295. * @param  offset_addr
  296. * @param  buf
  297. * @param  n_bytes
  298. * @return
  299. * @note   None.
  300. */
  301. static int nvram_raw_read(struct nvram_partition *partition,
  302.                           unsigned long offset_addr, char *buf,
  303.                           unsigned long n_bytes)
  304. {
  305.     memcpy(buf, partition->cache_addr + offset_addr, n_bytes);

  306.     return 0;
  307. }

  308. /**
  309. * @brief  None.
  310. *
  311. * @param  partition
  312. * @param  name
  313. * @return
  314. * @note   None.
  315. */
  316. static struct nvram_param_info *get_param_info(
  317.                 struct nvram_partition *partition, const char *name) {
  318.     struct nvram_param_info *tbl;

  319.     tbl = partition->tbl;

  320.     for (unsigned int i = 0; i < partition->tbl_size; i++) {
  321.         if (strcmp(name, tbl->name) == 0)
  322.             return tbl;

  323.         tbl++;
  324.     }

  325.     return NULL;
  326. }

  327. /**
  328. * @brief  None.
  329. *
  330. * @param  part_tbl
  331. * @param  counter
  332. * @return
  333. * @note   None.
  334. */
  335. int nvram_init_partitions(struct nvram_partition *part_tbl, unsigned int counter)
  336. {
  337.     struct nvram_partition *partition;

  338.     if (!part_tbl) {
  339.         return -1;
  340.     }

  341.     if (counter < 1) {
  342.         return -1;
  343.     }

  344.     for (unsigned int i = 0; i < counter; i++) {
  345.         partition = &part_tbl[i];

  346.         if (chk_partition_info(partition) < 0) {
  347.             return -1;
  348.         }

  349.         if (init_partition_locker(partition)) {
  350.             return -1;
  351.         }

  352.         switch (init_partitions_cache(partition)) {
  353.         case 0:
  354.             break;
  355.         case PARTITION_USE_DEFAULT_IMAGE:
  356.             break;
  357.         case PARTITION_USE_BACKUP_IMAGE:
  358.             break;
  359.         default:
  360.             break;
  361.         }
  362.     }

  363.     return 0;
  364. }

  365. /**
  366. * @brief  None.
  367. *
  368. * @param  partition
  369. * @param  name
  370. * @param  buf
  371. * @param  size
  372. * @return
  373. * @note   None.
  374. */
  375. int nvram_set(struct nvram_partition *partition, const char *name, const char *buf, int size)
  376. {
  377.     int r = -1;

  378.     uint32_t param_value;
  379.     int32_t  param_value_signed;

  380.     struct nvram_param_info *param_info;

  381.     if (!partition) {
  382.         return -1;
  383.     }

  384.     if (!name) {
  385.         return -1;
  386.     }

  387.     if (!buf) {
  388.         return -1;
  389.     }

  390.     if (size < 0) {
  391.         return -1;
  392.     }

  393.     if (size == 0) {
  394.         return 0;
  395.     }

  396.     param_info = get_param_info(partition, name);

  397.     if (param_info == NULL) {
  398.         return -1;
  399.     }

  400.     lock_partition(partition);

  401.     if ((param_info->type == NVRAM_PARAM_TYPE_UVALUE) ||
  402.             (param_info->type == NVRAM_PARAM_TYPE_SVALUE)) {
  403.         switch (param_info->n_byte) {
  404.         case 1:
  405.         case 2:
  406.         case 4:
  407.             param_value = 0;
  408.             param_value_signed = 0;
  409.             if (param_info->type == NVRAM_PARAM_TYPE_UVALUE) {
  410.                 param_value = (uint32_t)strtoul(buf, 0, 0);
  411.                 r = nvram_raw_write(partition, param_info->offset,
  412.                                     (char *)&param_value, param_info->n_byte);
  413.             } else {
  414.                 param_value_signed = (int32_t)strtol(buf, 0, 0);
  415.                 r = nvram_raw_write(partition, param_info->offset,
  416.                                     (char *)&param_value_signed, param_info->n_byte);
  417.             }
  418.             break;

  419.         default:
  420.             break;
  421.         }
  422.     } else if (param_info->type == NVRAM_PARAM_TYPE_STRING) {

  423.         unsigned int value_len = DEF_MIN(size, param_info->n_byte);

  424.         r = nvram_raw_write(partition, param_info->offset, buf, value_len);
  425.     }

  426.     unlock_partition(partition);

  427.     return r;
  428. }

  429. /**
  430. * @brief  None.
  431. *
  432. * @param  partition
  433. * @param  name
  434. * @param  buf
  435. * @param  size
  436. * @return
  437. * @note   None.
  438. */
  439. int nvram_get(struct nvram_partition *partition, const char *name, char *buf, int size)
  440. {
  441.     int r = -1;

  442.     uint32_t param_value;
  443.     int32_t  param_value_signed;

  444.     struct nvram_param_info *param_info;

  445.     if (!partition) {
  446.         return -1;
  447.     }

  448.     if (!name) {
  449.         return -1;
  450.     }

  451.     if (!buf) {
  452.         return -1;
  453.     }

  454.     if (size < 0) {
  455.         return -1;
  456.     }

  457.     if (size == 0) {
  458.         return 0;
  459.     }

  460.     param_info = get_param_info(partition, name);

  461.     if (param_info == NULL) {
  462.         return -1;
  463.     }

  464.     lock_partition(partition);

  465.     if ((param_info->type == NVRAM_PARAM_TYPE_UVALUE) ||
  466.             (param_info->type == NVRAM_PARAM_TYPE_SVALUE)) {
  467.         switch (param_info->n_byte) {
  468.         case 1:
  469.         case 2:
  470.         case 4:
  471.             param_value = 0;
  472.             param_value_signed = 0;
  473.             if (param_info->type == NVRAM_PARAM_TYPE_UVALUE) {
  474.                 r = nvram_raw_read(partition, param_info->offset,
  475.                                    (char *)&param_value, param_info->n_byte);

  476.                 ksnprintf(buf, size, "%u", param_value);
  477.             } else {
  478.                 r = nvram_raw_read(partition, param_info->offset,
  479.                                    (char *)&param_value_signed, param_info->n_byte);

  480.                 ksnprintf(buf, size, "%s", param_value_signed);
  481.             }
  482.             break;

  483.         default:
  484.             break;
  485.         }
  486.     } else if (param_info->type == NVRAM_PARAM_TYPE_STRING) {

  487.         unsigned int value_len = DEF_MIN(size, param_info->n_byte);

  488.         r = nvram_raw_read(partition, param_info->offset, buf, value_len);
  489.     }

  490.     unlock_partition(partition);

  491.     return r;
  492. }

  493. /**
  494. * @brief  None.
  495. *
  496. * @param  partition
  497. * @param  name
  498. * @param  value
  499. * @return
  500. * @note   None.
  501. */
  502. int nvram_set_ulong(struct nvram_partition *partition, const char *name, unsigned long value)
  503. {
  504.     struct nvram_param_info *param_info;
  505.     int r = -1;

  506.     if (!partition) {
  507.         return -1;
  508.     }

  509.     if (!name) {
  510.         return -1;
  511.     }

  512.     param_info = get_param_info(partition, name);

  513.     if (param_info == NULL) {
  514.         return -1;
  515.     }

  516.     lock_partition(partition);

  517.     if ((param_info->type == NVRAM_PARAM_TYPE_UVALUE) ||
  518.             (param_info->type == NVRAM_PARAM_TYPE_SVALUE)) {
  519.         switch (param_info->n_byte) {
  520.         case 1:
  521.         case 2:
  522.         case 4:
  523.             r = nvram_raw_write(partition, param_info->offset,
  524.                                 (char *)&value, param_info->n_byte);
  525.             break;

  526.         default:
  527.             break;
  528.         }
  529.     }

  530.     unlock_partition(partition);

  531.     return r;
  532. }

  533. /**
  534. * @brief  None.
  535. *
  536. * @param  partition
  537. * @param  name
  538. * @param  value
  539. * @return
  540. * @note   None.
  541. */
  542. int nvram_get_ulong(struct nvram_partition *partition,
  543.                     const char *name, unsigned long *value)
  544. {
  545.     int r = -1;

  546.     unsigned long param_value;

  547.     struct nvram_param_info *param_info;

  548.     if (!partition) {
  549.         return -1;
  550.     }

  551.     if (!name) {
  552.         return -1;
  553.     }

  554.     if (!value) {
  555.         return -1;
  556.     }

  557.     param_info = get_param_info(partition, name);

  558.     if (param_info == NULL) {
  559.         return -1;
  560.     }

  561.     lock_partition(partition);

  562.     if ((param_info->type == NVRAM_PARAM_TYPE_UVALUE) ||
  563.             (param_info->type == NVRAM_PARAM_TYPE_SVALUE)) {
  564.         switch (param_info->n_byte) {
  565.         case 1:
  566.         case 2:
  567.         case 4:
  568.             param_value = 0;

  569.             r = nvram_raw_read(partition, param_info->offset,
  570.                                (char *)&param_value, param_info->n_byte);

  571.             if (r == 0) {
  572.                 *value = (unsigned long)param_value;
  573.             }
  574.             break;

  575.         default:
  576.             break;
  577.         }
  578.     }

  579.     unlock_partition(partition);

  580.     return r;
  581. }
复制代码

出0入0汤圆

 楼主| 发表于 2014-5-17 11:03:03 | 显示全部楼层
eye 发表于 2014-5-16 21:21
/// xxx.h

struct nvram_sysparam {

嗯确实是一个好方法,但是我的本意不是要使用这种方式来访问struct nvram_sysparam里的变量,
而是通过下面的int nvram_set(struct nvram_partition *partition, const char *name, const char *buf, int size);函数接口
来访问struct nvram_sysparam里的变量,而变量的大小与偏移量nvram_set函数处理,用户只关心参数的名字即可,也不需
要关心类型等,全部转化成ASCII流的方式。这样在效率上有所下降,但是通用性和可利用性比较好些!

出0入296汤圆

发表于 2014-5-17 11:47:43 | 显示全部楼层
electrlife 发表于 2014-5-17 11:03
嗯确实是一个好方法,但是我的本意不是要使用这种方式来访问struct nvram_sysparam里的变量,
而是通过下 ...

用ASC不是一个好方法,使用它只是你们团队的历史遗留问题。我这么说,就说明
他具有争议性,有争议性的内容请标明具有争议性,但不可作为原则直接提出,会
有误导经验不足的程序员的可能。
你这一篇着重介绍的是存储器抽象层(MAL),写得很好,但我发现你的接口都有
一个违反嵌入式系统初衷的“倾向”——通用。嵌入式系统一定要专用,反映到接口
设计上也必须专用,以满足裁剪,成本和功能的平衡。嵌入式系统的确可以使用通用
计算机系统的知识,但必须要做一定的吸收和转化。具体来说,就是嵌入式系统不可
追求通用,只能追求在“严格定义的应用环境,功能需求下”,在不增加或只增加少量
成本下“尽可能多的获得通用性”。所以,这里的通用是限制范围内的最大化。

做出通用不难,但要做出“好用量刚好”的嵌入式接口,就一定要在讨论前明确的声明
应用环境,需求之类。比如,你这里提出的接口,你要明确的说出其应用的平台大约
是什么级别的MCU,拥有多大的资源(flash,sram)这么设计接口是为了满足怎样
的应用需求。

嵌入式系统,是面向应用,高度裁剪的“专用计算机系统” 。请大家从一开始就意识到
这里的专用。然后再学会定义范围后的讨论。很实在的,没有范围,总有人站出来
指出你的资源,效率有问题,不明真相的看客也容易被吓到。

出0入0汤圆

发表于 2014-5-17 14:01:36 | 显示全部楼层
Dioxide louzhu,xuexi le

出0入0汤圆

 楼主| 发表于 2014-5-17 15:33:35 | 显示全部楼层
本帖最后由 electrlife 于 2014-5-17 15:38 编辑
Gorgon_Meducer 发表于 2014-5-17 11:47
用ASC不是一个好方法,使用它只是你们团队的历史遗留问题。我这么说,就说明
他具有争议性,有争议性的内 ...


     呵呵,班竹说的确实严谨很多!这里的通用是针对非易失性存储器的接口提出的,在平时的项目中非易失性存储器使用比较多
也是实际需要我去实现一种统一的管理方式,这里之所以使用的从字符串流到到BIN流转换也是经过折中的一种方案,
后续的文章会提到关于后续程序的升级的问题,如果升级程序涉及到EEPROM中参数的更新与变化,那原始EEPROM中一些
参数数据必须能够保存与恢复,而如果新的程序中struct nvram_sysparam的内容出现变化,那么将无法通过BIN流的形式进行
恢复与管理。其实ASCII的流的方式就我个人来说,我觉得确实是可以简化你的程序设计并提高通用性,但确实性能方面有所
损失。这里我给出一个项目中我遇到的一个真实的ASCII的案例(这个案例本应该在上述文章中给出的):
    在我去年的一个项目中需要对设备每次运行的一些关键数据进行记录,以便后期查阅,我想这种历史记录的功能一般大家也
会用到吧,当初我的做法就是如下所示,假设结构体中是需要记录的数据
struct history_data {
        int data1;
        int data2;
        int data3;
        int data4;
};
一般都是直接eep_write(..., sizeof(struct history_data));也即直接存储为BIN格式的,但当某天领导告诉我需要为机器增加一个新的数据进行保存,
其实这对新程序实现很简单,但是更新程序后,以前的记录就全部丢失了,因为你不能再通过eep_read(..., sizeof(struct history_data))读取了,
而你如果存储时是以ASCII的方式,那么这个问题就很好处理了,比如你可以存储"data1=1\0data2=2\0data3=3\0data4=4\0\0"。不管你的struct
结构如何改变,你都可以通过ASCII的方式解析得到具的数据。
     因此我把ASCII作为一个通用性原则提出,因为实际的项目中类似上面的案例很多!当然你确实是需要去评估你的MCU资源与实际项目需求!

      对于EEPROM的管理,其实还有另一种更加通用实现方式,但需要一定量的内存与复杂的算法,如果感兴趣可以去看下u-boot的非易失性存储器的管理方式,
这种是完全ASCII的方式,其EEPROM中的参数的数量与大小可以在程序运行时动态增加与减少,这种方式我也实现了,现无奈正如版主所说,
它所需的内存与算法不是一般单片机所能接受的,如果只是为了管理几个参数那就不合适了!


出10入0汤圆

发表于 2014-5-17 17:12:59 | 显示全部楼层
第一篇觉得很不错。第二篇就有些太过于个人应用了。并不适合大多数人的思路。其实简单点讲,要用到如些复杂的存储应用,就应该上FS了。而不能再继续搞这个祼体了。

出0入296汤圆

发表于 2014-5-17 23:36:26 | 显示全部楼层
electrlife 发表于 2014-5-17 15:33
呵呵,班竹说的确实严谨很多!这里的通用是针对非易失性存储器的接口提出的,在平时的项目中非易失 ...

你举的例子实际上是面向对象数据处理,用扩展替代修改的典型例子。
一般情况下,如果预见到数据类型会发生扩展,应该在结构体第一个位置
留下一个位置,比如ID,或者是接口说明符之类的来表征当前数据由哪个
版本的程序来处理。这样,利用多态技术(使用类型解析和分发),让正确
的代码来处理这段数据。一旦数据被解析后,下次就统一用新的数据格式来
保存了。从面向对象技术来说,这种方式更灵活,更专注,扩展性也更强。

字符串的确更灵活,所以,大型系统往往使用XML来存储数据。但,那是
资源丰富环境下的玩法。我猜你经常处理的平台是ARM9以上的。少数M3以上的
环境也可以这么玩。但怎么说呢,看你的平台了。如果你的平台效应很强,
从平台继承一些通用的做法不失为快速开发的捷径。但如果是我,我一定会
选择在平台间增加一个转换适配来处理通用到专用的过度。把更多的通用性
放在资源丰富的一边,这是诀窍,也是指导原则。

出0入0汤圆

发表于 2014-5-19 11:36:00 | 显示全部楼层
再提一个可以讨论,也是我遇到的实际问题。

使用一个struct来组织EEPROM数据:
     好处是地址由编译器自动分配了,限制只在末尾添加数据项可以做到升级兼容。
     不好的地方就是各个模块用到的数据都耦合到一起了。

另外一个方案就是把数据都定义到同一个section,让连接器来分配地址。
类似于

  1. #pragma section="EEPROM"
  2. static const char SN[16] = "SN:0000001";
复制代码

实际SN的EEPROM地址只需要相对__section_begin("EEPROM")偏移就好了。
这个方案:
     好处是地址由编译器自动分配,低耦合。
     不好就是升级以后向下兼容麻烦,不好做数据继承。

最终我们用的还是第一个方案,牺牲了优雅吧。

出0入10汤圆

发表于 2014-5-22 11:46:22 | 显示全部楼层
目前水平还是看不懂

出0入76汤圆

发表于 2014-5-22 23:35:43 | 显示全部楼层
eye 发表于 2014-5-19 11:36
再提一个可以讨论,也是我遇到的实际问题。

使用一个struct来组织EEPROM数据:

呵呵, 我平时也是这么做的,使用struct来组织EEPROM的数据。

我是有一个专门负责参数的模块: 对于参数的维护比较方便。
1) 它负责来整合其它模块使用的存储参数;
2) 完成参数的合法性检查,校验等;
3) 默认的配置参数, 用于初始化EEPROM, 以及参数检验失败(出错)后的恢复的默认数据。(可以确保设备的正常运行)

出0入0汤圆

发表于 2014-5-23 09:33:58 | 显示全部楼层
非常有用,谢谢楼主

出0入4汤圆

发表于 2014-6-6 10:28:58 | 显示全部楼层
分析得很透彻,很有感触

出60入0汤圆

发表于 2014-6-18 08:47:39 来自手机 | 显示全部楼层
看了两三遍,还是没明白当中的机制,再多看几遍试试

出0入0汤圆

发表于 2014-8-28 08:27:11 | 显示全部楼层
本帖最后由 黄晨0410 于 2014-8-28 08:28 编辑

我目前的水平,看不懂  大家都说好,但我去看不懂

出0入0汤圆

发表于 2014-8-28 08:33:19 | 显示全部楼层
electrlife 发表于 2014-5-17 15:33
呵呵,班竹说的确实严谨很多!这里的通用是针对非易失性存储器的接口提出的,在平时的项目中非易失 ...

为什么说用 ASCII 的方法就可以解决 当history_data结构体内增加元素后,仍可用  eep_read(....,sizeof(struc history_data))读取  呢 ???

出0入0汤圆

发表于 2014-8-29 20:51:09 | 显示全部楼层
electrlife 发表于 2014-5-17 15:33
呵呵,班竹说的确实严谨很多!这里的通用是针对非易失性存储器的接口提出的,在平时的项目中非易失 ...

很荣幸能看到这么好的帖子。
请问楼主你以前的门题是如何解决的呢?现在应该有比较好的感悟了吧,希望能分亨一下。
http://www.amobbs.com/thread-5479799-1-1.html

出0入0汤圆

发表于 2016-5-13 14:29:35 | 显示全部楼层
mark,感谢楼主!!!!!!!!!

出0入0汤圆

发表于 2016-6-15 18:00:23 | 显示全部楼层
好帖,解答了不少以前的疑惑。谢谢楼主

出0入0汤圆

发表于 2016-12-2 10:31:16 | 显示全部楼层
一直在受用中~经典~
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-25 23:18

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表