十年专注单片机方案开发的方案公司英锐恩,分享C语言的点滴。英锐恩现提供服务产品涉及主控芯片:8位单片机、16位单片机、32位单片机及各类运算放大器等。
由于ICD2是在线仿真所以回占用部分芯片资源,但连接器并不知道什么地址已经被ICD2占用,如果碰巧分配了被ICD2占用的资源,回浪费大家的调试时间,那么如何让连接器知道什么地址不能分配呢?
请参考以下方法
适用条件:在MPLAB IDE 环境集成C编译器 + ICD2 调试程序
1.使用 HITECH C + ICD2 PROJECT->BUILD OPTIONS->PROJECT->PICC Global 选择compile for ICD 以下这段话表达的意思也不很贴切:光在中断入口和出口处为了保护和恢复这些中间临时变量就需要大量的开销,严重影响中断服务的效率。
其实,在PICC中,对中断中使用的临时变量,为了防止重入或覆盖,干脆就是中断专用。
在我们的PICC编程中,临时变量(或者说局部变量),都有其生存期,在生存期结束后,可被覆盖。但中断函数中使用到的临时变量却与一般概念上的临时变量有很大的区别。中断函数中使用到的临时变量和中断函数中不使用到的临时变量不能占用同一地址。换言之,中断和非中断的临时变量地址不得重叠。看一下下面的例程:(16F877A) #include "pic.h"
unsigned char i,j,k;
void fn1(void) { unsigned char s1[40]; s1[0]=1; }
void interrupt SAMPLE (void) { unsigned char s2[50]; s2[0]=1; fn1();
}
void main(void) {
}
说明:i,j,k-----三个全局变量 s1[40]----40个中断调用使用的临时变量 s2[50]----50个中断中使用的临时变量 保护W、STATUS和PCLATH用去三个内存变量 从上面程序中可以看出,BANK0资源已经耗尽。50+40+3+3=96。由于中断使用了93个RAM,这样中断以外就没有可以使用的临时变量了。也就是说中断使用的临时变量将不会被重入。
再看一下下面的例子: #include "pic.h"
unsigned char i,j,k;
void fn1(void) { unsigned char s1[20]; s1[0]=1; }
void fn2(void) { unsigned char s3[20]; s3[0]=1; }
void interrupt SAMPLE (void) { unsigned char s2[50]; s2[0]=1; fn1();
}
void main(void) { fn2(); } 说明:i,j,k-----三个全局变量 s1[20]----20个中断调用使用的临时变量 s2[50]----50个中断中使用的临时变量 s3[20]----20个非中断使用的临时变量 保护W、STATUS和PCLATH用去三个内存变量 从上面的例子中也可以看出,中断使用的临时变量和中断外使用的临时变量不可重叠。 eyuge2兄所说的PICC自动保护现场,其实就是PICC对中断中使用的临时变量实施“禁入”。也就是,中断内可实施中断内临时变量覆盖;中断外的临时变量也可进行覆盖;但中断内外之间的临时变量不可相互覆盖。 当然为了防止重入,中断中使用的函数不可在中断外被调用。(PICC可自动实施重入检查。)
大家来看一下具体的例子,用PIC16F887A编译下列程序。
第一次,中断处理函数什么也不做。 #include "pic.h" unsigned int m,n; unsigned char i,j,k,l; void interrupt SAMPLE (void) {
}
void main(void) {
}
因为不进行操作,中断函数不需要进行现场保护,编译后结果是直接返回。
0004 0009 retfie
第二次, 中断处理函数增加一条对全局变量的操作。 void interrupt SAMPLE (void) { i++; } 编译结果是: 0004 00F0 movwf saved_w 0005 0803 movf 3,w 0006 0183 clrf 3 0007 00A8 movwf saved_status 0008 0183 clrf 3 0009 0AA0 incf _i 000A 0828 movf saved_status,w 000B 0083 movwf 3 000C 0EF0 swapf saved_w 000D 0E70 swapf saved_w,w 000E 0009 retfie
从结果看,只保护W和STATUS,不对全局变量进行现场保护。
最后来看一个中断处理函数中调用一个函数fn()的例子。在fn()函数中涉及全局变量和局部变量。
void fn(void) { char temp; temp=i+j; i=temp++; } void interrupt SAMPLE (void) { fn(); }
0004 int_entry 0004 00F0 movwf saved_w 0005 0803 movf 3,w 0006 0183 clrf 3 0007 00A9 movwf saved_status 0008 080A movf 10,w 0009 00AA movwf saved_pclath 000A 018A clrf 10 000B 120A ISR BCF PCLATH,0X4 000C 118A BCFPCLATH,0X3 000D 27F9 CALL fn 000E 120A BCF PCLATH,0X4 000F 118A BCF PCLATH,0X3 0010 082A movf saved_pclath,w 0011 008A movwf 10 0012 int_restore 0012 0829 movf saved_status,w 0013 0083 movwf 3 0014 0EF0 swapf saved_w 0015 0E70 swapf saved_w,w 0016 0009 retfie
在第三个例子中,由于涉及到跨页的问题,中断函数为了能正确返回中断发生处地址,对PCLATH进行保护。
从以上我们可以看出,无论是在中断处理中使用的变量,还是在中断处理中调用其它函数而使用的变量,中断函数都不会对其实行自动保护。
当然中断前的地址入栈是属于硬件功能,不需要软件中作出相应操作。 PICC与指针 1:指针四要素(这部分主要从网上搜索到的,还不错):指针的类型指针所指向的类型指针的值或者叫指针所指向的内存区指针本身所占据的内存区 1 指针的类型。 从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就 是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的 类型: (1)int *ptr; //指针的类型是int * (2)char *ptr; //指针的类型是char * (3)int **ptr; //指针的类型是 int ** (4)int (*ptr)[3]; //指针的类型是 int(*)[3] (5)int *(*ptr)[4]; //指针的类型是 int *(*)[4] 怎么样?找出指针的类型的方法是不是很简单?
1 .2指针所指向的类型。 当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译 器将把那片内存区里的内容当做什么来看待。 从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符 *去掉,剩下的就是指针所指向的类型。例如: (1)int *ptr; //指针所指向的类型是int (2)char *ptr; //指针所指向的的类型是char (3)int **ptr; //指针所指向的的类型是 int * (4)int (*ptr)[3]; //指针所指向的的类型是 int()[3] (5)int *(*ptr)[4]; //指针所指向的的类型是 int *()[4] 在指针的算术运算中,指针所指向的类型有很大的作用。 指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越 来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的 类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。
1.3 指针的值,或者叫指针所指向的内存区或地址。 指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是 一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为 32位程序里内存地址全都是32位长。 指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相 当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块 内存区域,就相当于说该指针的值是这块内存区域的首地址。 指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中 ,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区 是不存在的,或者说是无意义的。 以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指向的 类型是什么?该指针指向了哪里? 1.4 指针本身所占据的内存区。 指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道 了。在32位平台里,指针本身占据了4个字节的长度。 指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。(注释:在picc18里,指针占用了两个字节)
下面就picc18列举个实例看下面程序
int data[10]={1,2,4,5,6,7,8}; int lenth1,lenth2,lenth3; int *ptr1,*ptr2; main() { ptr1=&data[0]; ptr2=&data[1]; lenth1=ptr2-ptr1; lenth2=(int)ptr2-(int)ptr1; lenth3=*ptr2-*ptr1; } 在watch窗口可以看到lenth1 为1,而lenth2为2,可以这样解释:在lenth1=ptr2-ptr1;这条语句中两个INT类型指针变量相减得一个(这在c中是允许的)非指针的数,这个数的代表如下:如果这两个指针指向的内型为INT型,那么这个数代表两个指针之间相隔多少个INT型变量,显然在以上程序中,data[0],和data[1]之间相隔了1个INT型变量而在 lenth2=(int)ptr2-(int)ptr1;实际上是求data[1]和data[0]之间占用多少个内存空间,注意(int)ptr是ptr的值(不是ptr所指向的数的值) lenth3=*ptr2-*ptr1;相信稍微懂c的人都知道是在求ptr所指向的两个值之间的差,和lenth3=data[1]-data[0]等效大家不防试试lenth4=(char*)ptr2-(char*)ptr1;看看等于多少,
2: 指针函数和函数指针有什么区别
2.1,这两个概念都是简称,指针函数是指带指针的函数,即本质是一个函数。我们知道函数都又返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。其定义格式如下所示:
返回类型标识符 *返回名称(形式参数表) { 函数体 } 返回类型可以是任何基本类型和复合类型。返回指针的函数的用途十分广泛。事实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个“变量”。例如下面一个返回指针函数的例子: char data[10]; char* test(void);
main() { char *ptr; ptr=test(); }
char* test(void) { char *p; p=data; return p; } 注意:该程序在picc18中调试
2.2,“函数指针”是指向函数的指针变量,因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上一致的。函数指针有两个用途:调用函数和做函数的参数。函数指针的说明方法为: 数据类型标志符 (*指针变量名)(参数);注:函数括号中的参数可有可无,视情况而定。 下面的程序说明了函数指针调用函数的方法: char max(char x,char y); char min(char x,char y); char (*ptr)(char,char); char a=2,b=3,c; main() { ptr=max; c=(*ptr)(a,b); ptr=min; c=(*ptr)(a,b); } char max(char x,char y) { return x>=y?x:y; } char min(char x,char y) { return x<=y?x:y; } 注意:该程序在picc18中调试在pic的
c程序编写中,函数指针不是很常用,如果大家有兴趣看看UCOS的pic18的移植版本,就可以发现ucos中是用函数指针传递任务程序的入口地址的。
3:结构联合与指针先看下面的一个简单的举例 typedef struct datas { char datah; char datal; struct datas *next; } data; //a是一个结构变量 data a,b; data *ptr1,*ptr2; main() { ptr1=&a; ptr1->datah =1; ptr1->datal =2; ptr2=&b; ptr1->datah =3; ptr1->datal =4; ptr1->next=ptr2; ptr2->next=0; } 数据a,b是一个结构型变量,ptr则是指向结构型变量的指针,在c语言里,通过形如ptr->x的形式来访问结构或者指针的成员。在结构变量中定义了一个指针struct datas *next; 这是在指针链中常用到的,ptr1->next=ptr2; ptr2->next=0;实际上已经建立了一条简单的指针链,当然建立指针链用这种初始化的方法不够简单,但是上面的程序只是为了说明指针和结构而已。在单片机的c语言程序中,联合和结构是经常用在一起的下面在举一个简单的列子: typedef struct { char datah; char datal; } data; typedef union {
data twpbyte; int bytes;
} piccdata;
piccdata adres[10]; piccdata *ptr1,*ptr2; char lenth1,lenth2; main() { ptr2=adres; ptr1=adres; ptr1++; lenth1=ptr1-ptr2; lenth2=(char)ptr1-(char)ptr2;
} 在以上程序中,lenth2为2,而lenth1为1,道理和第一个列子一样,只是应该注意的是在piccdata型变量中占用的空间为2个字节而不是4个字节!!!另:结构与联合是pic应用的一个好东西,这点HOTpower曾经有一很经典的文章,在此列中,只要稍微加以改动,就可以对一个16位变量即可以整体访问,也可分为高八位访问和低八位访问。这在ad转换中是很有用的。
4: const与指针 const是一个C语言的关键字,它限定一个变量不允许被改变。 如果const关键字不涉及到指针,我们很好理解,下面是涉及到指针的情况: int b = 500; const int* a = &b; [1] int const *a = &b; [2] Int* const a = &b; [3] const int* const a = &b; [4] 如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。因此,[1]和[2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下不允许对内容进行更改操作,如不能*a = 3 ;[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;[4]为指针本身和指向的内容均为常量。有了上面的基础,对于在picc18中的c语言应用就可以开始了,在单片机编程中,经常会用到查表程序等,通常把大量的数据放入rom中,下面是一个简单的列子
const int a[8]={1,2,3,-3,3,5,6,7}; const int *ptr; main() { ptr=a; ptr++; } 显然ptr是一个指向常量的指针,ptr指向的数是不可变的,但是ptr本身是可变的,我们可以通过ptr来访问定义在rom中的数组a[8];
欢迎补充和指正,谢谢!