CCS调试总结:
1. 在调试fat32/16时有这样一个结构体
struct fs_dir_ent
{
char filename[260];
unsigned char is_dir;
UINT32 cluster;
UINT32 size;
};
在下面这个函数中调用了这个结构体变量
int fatfs_list_directory_next(struct fatfs *fs, struct fs_dir_list_status *dirls, struct fs_dir_ent *entry)
{
…
strncpy(entry->filename, LongFilename, FATFS_MAX_LONG_FILENAME-1);
if (fatfs_entry_is_dir(directoryEntry))
entry->is_dir = 1;
else
entry->is_dir = 0;
entry->size = directoryEntry->FileSize;
entry->cluster = (directoryEntry->FstClusHI<<16) | directoryEntry->FstClusLO;
…
}
其中entry 为指向 struct fs_dir_ent 的指针,发现entry->filename可以成功赋值,而后面的is_dir,size,cluster都不能成功赋值。
用汇编查看后发现其实结构体的赋值都是以第一个变量的地址为起始地址,后面的变量都是根据起始地址的偏移来查询,例如如果filename第一个变量的地址存放在AR3里,则is_dir的地址为AR3 + 260,调试的时候发现AR3为0xffe5,则0xffe5 + 260已经溢出,又从0x0000开始加偏移,当然不能正常寻址到is_dir。
为了测试,我把结构体改为如下形式:
struct fs_dir_ent
{
unsigned char is_dir;
UINT32 cluster;
UINT32 size;
char filename[260];
};
果然is_dir,cluster, size可以成功赋值,当然这只是调试,真正可不能这么修改。
2. 不要小看CMD文件和-heap,-stack,-sysstack
在调试时,我发现CMD文件和-heap,-stack,-sysstack对程序有着非常隐蔽的影响。
在调试程序时,如果程序比较小,可能不会出现问题,但如果程序庞大了,那么要十分注意上面所提到的东西。
如果我们不可以配置heap,stack,sysstack的话,编译器会默认给我们分配一段空间,大概为500W,1000W,通常会出现3个warning,告诉你没分配,我们经常忽略,但是如果程序庞大了,我们一定要小心,因为这些空间的大小,编译链接时编译器不会告诉我们是否足够,在程序运行中也不会知道,因为系统也不会告诉你堆栈是否溢出。heap是堆的意思,主要用来分配malloc,calloc的空间,在调试时,如果发现malloc返回0x0000,则堆空间不够,我们就要扩大。
比较方便的方法是我们先把这几个空间设的足够大,如果空间不够,我们逐渐缩小,看看程序是否正常运行。
CMD文件中内存分配如果不够大多会在链接时提示错误,但我在调试时,经常发现非常离奇的错误,比如函数形参获得值不对,for循环进入死循环等离奇的现象,在调整CMD内存分配大小后,就能够解决,我也没有发现什么规律,但是如果程序莫名其妙的执行不正常,首先怀疑CMD文件,比如.bss,.cinit。。。分配的大小是否足够大,注意一旦printf执行不正常,适当扩大.cio空间一般可以解决,这些错误一般很难发现。
3. 注意不要任意选择优化,我在选择file(-o3)优化选项时,发现字符串赋值不正常。
4. rts55.lib和csl5509.lib一起用,而rts55x和csl5509x一起用,rts.lib要包含stdlib.h文件,malloc,calloc之类的函数要用到这个运行库,如果不用到DSP的外设或者寄存器的话,可以不用包含csl.lib,如果用到PLL,EMIF,TIMER之类的外设时,必须包含csl.lib。
5. 内存分配问题
(1)避免因为数据对齐而引起的空洞
编译器把所有long型的数据以偶地址存储,当声明一个结构体时包含多种数据类型时,应把long型数据放在最前面,这样编译器自动会以偶地址存储,防止结构体内产生空洞。
Example:
/* Not recommended */
typedef struct abc{
int a;
long b;
int c;
} ABC;
/* Recommended */
typedef struct abc{
long a;
int b,c;
} ABC;
(2)局部和全局变量声明
局部声明的变量(在C函数里)被编译器放到堆栈中,而全局变量和局部静态变量被编译器默认放到.bss段中。C boot 程序将C55XX DSP 以CPL模式运行,CPL模式使能以堆栈地址偏移的方式,而使DP地址偏移模式无效。编译器通过绝对地址方式访问全局变量,而全地址模式将被解码成为指令的一部分,将会造成更大的代码空间,因此会使程序变慢。鉴于我们可以利用堆栈寻址的优点,所以CPL模式更适合声明局部变量。
如果程序需要用到全局变量,更好的方式是声明一个局部变量,然后将它赋以合适的值。
Example:
extern int Xflag;
int function(void)
{
int lflag = Xflag;
.
x = lflag ? lflag & 0xfffe : lflag;
.
.
return x;
}
(3)在MEMORY MAP分配代码和数据
编译器能够产生下面任意的段:
l 在DARAM分配.stack和.systack:因为当一个函数call/return发生时,.stack和.systack经常同时访问RAM,如果分配到同一个SARAM block,则会发生内存冲突,增加额外的周期。
l .stack和.systack段的起始地址被用来分别初始化SP(data stack pointer)和SSP(system stack pointer),因为这两个寄存器共享SPH(data page pointer)寄存器,所以这些段必须分配到同一个64KW内存页内。
l 分配.bss和.stack到一个DARAM中或者独立的SARAM中。如果不这样做,很可能在同一个指令中,既有全局变量(分配到.bss中),又有局部变量(分配到.stack中),造成访问冲突。
l 当以小内存模式编译时,要把所有的数据段(.data, .bss, .stack, .sysmem, .sysstack, .cio, .const)放到头64KW内存页中,不然会发生赋值异常的现象。
6. Pragma伪指令
(1)CODE_SECTION Pragma
CODE_SECTION pragma为symbol分配一段名为section name的段
在C中的语法为:
#pragma CODE_SECTION(symbol,”section name”)[;]
如果你想链接一段程序和.text section分开,那么CODE_SECTION pragma就是有用的。
e.g.
(a) C source file
#pragma CODE_SECTION(funcA,“codeA”)
int funcA(int a)
{
int i;
return (i = a);
}
(b) Assembly source file
.sect “codeA”
.global _funcA
;********************************************************
;* FUNCTION NAME: _funcA *
;********************************************************
_funcA:
RET
(2)DATA_SECTION Pragma
DATA_SECTION pragma为symbol分配一段名为section name的段
在C中的语法为:
#pragma DATA_SECTION(symbol,”section name”)[;]
如果你想链接一段程序和.bss section分开,那么DATA_SECTION pragma就是有用的。
e.g.
(a) C source file
#pragma DATA_SECTION(bufferB, ”my_sect”)
char bufferA[512];
char bufferB[512];
(b) Assembly source file
.global _bufferA
.bss _bufferA,512,0,0
.global _bufferB
_bufferB: .usect ”my_sect”,512,0,0
7. CSL Macros
在初始化时,我们经常用到CSL Macros,下面总结一下常用的CSL Macros:
PER_REG_RMK( fieldval_15, . . . Fieldval_0) | _RMK macors以位赋值初始化寄存器 |
PER_RGET(REG#) | 返回外设寄存器值 |
PER_RSET(REG#,regval) | 写外设寄存器 |
PER_FMK(REG, FIELD,fieldval) | 可以赋一个寄存器的几位,而_RMK必须赋值所有位,可以用_FMK取或来初始化寄存器。 |
PER_FGET(REG#,FIELD) | 返回寄存器的指定位的值 |
PER_FSET(REG#, FIELD,fieldval) | 写外设寄存器的指定位 |
PER_RGETH(handle, REG) | 返回与handle关联的寄存器值 |
PER_RSETH(handle, REG, regval) | 写与handle关联的寄存器值 |
PER_FGETH(handle, REG, FIELD) | 返回与handle关联的寄存器的指定位的值 |
PER_FSETH(handle, REG, FIELD, fieldval) | 写与handle关联的寄存器的指定位的值 |