首页 > 代码库 > 开发经验分享(一)

开发经验分享(一)

开发经验分享系列文章主要记录工作实际项目中遇到的问题和解决办法,希望能对大家有参考意义。

一、芯片的地址分配和变量地址的指定

芯片的存储区很小,所以要合理利用存储区,在进行地址空间的分配时就需要一定的技巧。

 

在进行开发时,一定要做好地址的划分。

比如CODE区的0x0000~0x8000作为COS区,接下来的0x8000~0x10000作为文件系统区,依次类推……

 

在定义变量的时候,也要注意定义在了什么位置,占用的空间有多大。

比如,我们在XRAM区定义变量和数组:

xdata char temp1 _at_ 0x30;

xdata char key[8] _at_0x400;

xdata unsigned temp3[8] _at_ 0x460;

xdata unsigned int temp2 _at_ 0x40;

上面这种定义方式好吗?

不好,看上去非常乱,改一下:

xdata char temp1 _at_ 0x30;

xdata unsigned int temp2 _at_ 0x40;

xdata char key[8] _at_0x400;

xdata unsigned temp3[8] _at_ 0x460;

看上去是好了一点,我们做张XRAM区的使用图,红色是使用过的,蓝色是未使用的:

技术分享

上面的地址分配方式和定义方式有两个主要问题:

a).地址空间利用不连续,碎片化。变量如果多了,我们不知道两个变量之间的剩余多大的空间,就不敢使用这两个变量之间的空间来存放新的变量。

b).定义变量都指定了实际地址。如果因为某种原因,我想将XRAM区的0x00~0x50用作其他用途,这时我就要手动更改temp1和temp2地址,只有两个好像不太麻烦,如果我在0x00~0x50定义了40个变量,改起来就太麻烦了。

我们要改掉上面两个问题,采用下面的方式:

#define XRAMBASE 0x0000

xdata char temp1 _at_ XRAMBASE+0x10;

xdata unsigned int temp2 _at_ XRAMBASE+0x11;

xdata char key[8] _at_0x18;

xdata unsigned temp3[8] _at_ 0x20;

技术分享

上面的定义方式将所有的变量都紧凑排列,有效的利用了XRAM空间;

地址的指定采用XRAMBASE进行偏移,如果想将XRAM的前0x50空出来,只需改动一处#define XRAMBASE 0x50。

二、变量命名

1.在定义变量时,一定要做到见名知意,这样在读写代码时可以很容易回想起变量的用途。

比如见到 int Key[16],就知道是用来存储秘钥key的。

2.命名风格要一致。

int tempNum1 = 0;

int TempNum2 =0;

int tempnum3 = 0;

看上去不但乱,而且在使用的时候还要记得不要写错字母,分散注意力。

int TempNum1 = 0;

int TempNum2 = 0;

int TempNum3 =0;

这样看上去是不是清楚多了,而且使用的时候只要记得是大驼峰命名法就不会写错了。

 

三、代码分层

所有的代码都是有一定层次的,比如嵌入式开发,一个简单的系统可以简单的分为驱动层,OS层和应用层,如下图:

技术分享

这有点像封装的概念,把数据和方法封装在一个函数或者类中,只要保证提供给外界的接口不变,可以随意改动函数或者类的实现方式。

为什么要强调分层呢?有两个主要方面:

1.逻辑清楚,各层都负责各层的功能。

2.减小代码的修改量和各层之间的耦合性。

将代码分层之后,如果要添加功能,我们只需修改应用层的代码就可以了,不必改动下面两层。

嵌入式开发最常做的事情不是从头编写代码,可能是移植,将同样一套代码从一款芯片移植到另一款芯片上,在功能不变的情况下,只需修改驱动层代码。

四、注意全局变量

全局变量是我到目前为止工作中吃亏最多的地方,这个地方一定要引起重视。

1.全局变量与代码层的耦合性

在第三点重,我提到了代码分层,最好是将代码的改动限制在某一层之内,无论这层怎么改动,只要保证提供给其他两层的接口不变即可,即最大限度的降低各层之间的耦合性,其中尽量消除全局变量是一个很有效的措施。

但是,这是不可能的,因为某些特殊需要,或者其他原因,一定会存在贯穿三层的全局变量,而且这三层都会改变这个全局变量的值。这时候就要仔细跟踪这个变量,以保证这个变量的值从一个函数传到另一个或从一层传到另一层时值的正确性。

我将全局变量表示为图中的折线,将其与其他所有游离于其他三层的变量或者函数称为逻辑第四层。

技术分享

逻辑第四层与其他三层交叉(没画出那个效果。。。),表示相互影响。

技术分享

逻辑第四层是一个十分复杂的概念,我会在其他文章里详述。

2.全局变量初始化

C语言中,如果定义了一个全局变量,一定要在定义时或主程序开始处(第一次使用前)进行初始化。

 

刚刚进入公司接受培训的时候,总监讲了一个案例,因为一个全局变量没有进行初始化导致南京公交卡闪卡,导致公司损失700多万元。

当时也注意到了全局变量初始化的重要性,但是,认识是一方面,直到自己踩到这个坑里吃了亏,才会记得深刻。

前几天的项目就是因为一个全局变量没有初始化,导致交付日期晚了两天。

 

简单叙述如下(因为涉及到公司的某些技术,不可能叙述的特别详细,我尽量让大家看懂):

在进行一种接触卡的开发时,断电测试出现了第三态。

断电设计是这样的:如果在使用卡进行消费的过程中,突然发生断电,那么卡里的金额保持不变,即消费不成功(另一种方式是扣除卡里的需要消费的金额,即消费成功)。

问题描述:

第一个版本完成后,进行断电测试,发现消费时断电之后的卡里的金额(采用二进制表现形式)一半是新值,一半是旧值,即所谓的第三态。

问题分析:

上面的表现形式说明读写的时候发生了跨页。

出现第三态可能有以下三种情况:

a).交易时跨页写发生错误,第一页新值写入正确,第二页新值没有写入。

b).旧数据备份时发生错误,只备份了第二页的数据。

c).旧数据恢复的时候发生错误,没有将旧数据恢复过去。

查找问题:

做了5条私有指令,读出了第一页和第二页的原数据,读出页备份区和事务备份区的数据(涉及公司的设计方案,不便详写,但不影响理解问题),读出旧数据恢复之后第一页和第二页的数据。

发现是旧数据备份时正确的,但是在恢复旧数据的过程中出现了问题。

解决问题:

所使用的卡的Flash芯片的特性是必须先擦后写;XRAM区上电后状态不确定,定义在XRAM区的值也不确定。

 

因为Flash的先擦后写的特性,这个版本做了一个优化,为了提高性能,在XRAM区定义了一个Flag变量来对控制位于Flash区的备份页BackUpPage的擦除操作。

当Flag为0时,对备份页进行擦除后写入新数据,并将Flag置为1;如果写时Flag不为0,则直接写。详细过程如图:

技术分享

将区域1的新数据写入区域2前,先将区域2的要被覆盖的所有旧数据备份到备份页BackUpPage(箭头①),然后在将区域2的当前这一页的旧数据将区域2的旧数据备份到备份页BackUpPage(箭头②),将区域1的数据写入区域2(先擦后写,箭头③),这时如果发生断电,区域1的数据没有成功写入(比如刚擦除完成还没有写入),在上电时就要将备份区2的旧数据恢复到区域2(箭头⑤④),这要通过备份页BackUpPage进行。

注意,备份页BackUpPage备份的是指针所指的当前页的一页,如果被覆盖的旧数据跨页了,就要先后备份两页;备份页2只备份被覆盖的旧数据,这些旧数据可能跨页。

在图中红线圈出来的过程外,还有一次对备份页BackUpPage的擦除操作,会将Flag置为0。

Flash的特性是先擦除后写,XRAM区变量的特性是上电后状态不确定,Flag定义在XRAM区,如果不进行初始化,其值不确定。

假如断电时区域1的数据没有写入成功区域2,而且区域2的数据被破坏了,就要将备份页2的数据恢复到区域2,这要借助备份页BackUpPage(先将备份页2的数据写到备份页BackUpPage,在写到区域2,如果跨页了,要写两次,第一次是上一页被覆盖的旧数据,第二次是下一页被覆盖的旧数据。备份页BackUpPage要先擦后写)。

如图,一上电时,Flag的值不确定,如果Flag不为0,就不会对备份页BackUpPage也进行擦除,那么上一页的旧数据就不会成功写入备份页PageBackUpPage,导致恢复失败,但在红线圈出来的过程外的那次对备份页BackUpPage的擦除操作,会使第二次写成功。这就造成了数据恢复时上一页旧数据没有恢复成功,保留了新值,而下一页旧数据恢复成功,出现第三态。

 

其实,整个过程远比上面叙述的复杂的多(涉及公司利益,只能简写),而且出现问题时定位也很难。

但是,如果养成了初始化全局变量的习惯,完全可以避免上述问题。

 

这篇到此为止,其他经验会在后续文章中继续分享。

开发经验分享(一)