这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » PIC单片机C编程技巧

共1条 1/1 1 跳转至

PIC单片机C编程技巧

助工
2014-12-01 22:57:58     打赏
1、PICC和MPLAB集成
PICC和MPLAB集成:
PICC有自己的文本编辑器,不过是DOS风格的,看来PICC的工程师要专业冷到酷底了...
大家大可不必用它,如果你没什么癖好的话,你不会不用UltraEdit 吧?
1:建立你的工作目录:
建议在C盘根目录下建立一个以A开头的文件夹做为工作目录.因为你会发现它总是在你查找文件时候第
一个跳入你眼中.
2:MPLAB调用PICC.(以MPLAB5.7版本为例子)
启动MPLAB.在Project-->Install Language Tool:
Language Suite----->hi-tech picc
Tool Name ---->PICC Compiler
Executable ---->c:hi-pic¬inpicc.exe (假如你的PICC是默认安装的)
选Command-line
最后OK.
上面这步只需要设定一次,除非你重新安装了MPLAB.
3:创建你的项目文件:(假如你实现用EDIT编辑好了一个叫AA.C的C代码文件)
Project-->New Project-->File Name--->myc (假如我们把项目文件取名字叫MYC.PJT)
右边窗口当然要选择中你的工作目录.然后OK.
4:设定你的PICC工作参数:
Project-->Edit Project
上面4个栏目就用默认的,空的也就让它空着,无所谓的.
需要修改的是:
Development Mode---->选择你的PIC型号.当然要选择Mplab SIM Simulator
让你可以用软件仿真.
Language Tool Suite--->HI-TECH PICC
上面的步骤,你可能会遇见多个提示条,不要管它,一路确定.
下面是PICC编译器的选择项:
双击Project Files 窗口里面的MYC.HEX,出现一个选择拦目.命令很多,大家可以看PICC文本编
辑器里面的HELP,里面有详细说明.
下面就推荐几个常用也是建议用的:
Generate debug info 以及下面的2项.
Produce assembler list file
就在它们后面打勾即可,其它的不要管,除非你有特殊要求.
5:添加你的C代码文件:
当进行了前面几步后,按Add Node 找到AA.C文件就OK了.
6:编译C代码:
最简单的一步:直接按下F10.
编译完后,会出现各种调试信息.C代码对应的汇编代码就是工作目录里面的AA.IST,用EDIT
打开可以看见详细的对比.
7:其它,要是一切都没问题,那么你就可以调试和烧片了,和以往操作无异.
2、如何从汇编转向PICC
首先要求你要有C 语言的基础。PICC 不支持C++,这对于习惯了C++的朋友还得翻翻C 语言的书。C
代码的头文件一定要有#i nclude<pic.h>,它是很多头文件的集合,C 编译器在pic.h 中根据你的芯片自动栽
入相应的其它头文件。这点比汇编好用。载入的头文件中其实是声明芯片的寄存器和一些函数。顺便摘抄
一个片段:
static volatile unsigned char TMR0 @ 0x01;
static volatile unsigned char PCL @ 0x02;
static volatile unsigned char STATUS @ 0x03;
可以看出和汇编的头文件中定义寄存器是差不多的。如下:
TMR0 EQU 0X01;
PCL EQU 0X02;
STATUS EQU 0X03;
都是把无聊的地址定义为大家公认的名字。
一:怎么附值?
如对TMR0 附值,汇编中:
MOVLW 200;
MOVWF TMR0;
当然得保证当前页面在0,不然会出错。
C 语言:
TMR0=200;//无论在任何页面都不会出错。
可以看出来C 是很直接了当的。并且最大好处是操作一个寄存器时候,不用考虑页面的问题。一切由
C 自动完成。
二:怎么位操作?
汇编中的位操作是很容易的。在C 中更简单。C 的头文件中已经对所有可能需要位操作的寄存器的每
一位都有定义名称:
如:PORTA 的每一个I/O 口定义为:RA0、RA1、RA2。。。RA7。OPTION 的每一位定义为:PS0、
PS1、PS2 、PSA 、T0SE、T0CS、INTEDG 、RBPU。可以对其直接进行运算和附值。
如:
RA0=0;
RA2=1;
在汇编中是:
BCF PORTA,0;
BSF PORTA,2;
可以看出2 者是大同小异的,只是C 中不需要考虑页面的问题。
三:内存分配问题:
在汇编中定义一个内存是一件很小心的问题,要考虑太多的问题,稍微不注意就会出错。比如16 位的
运算等。用C 就不需要考虑太多。下面给个例子:
16 位的除法(C 代码):
INT X=5000;
INT Y=1000;
INT Z=X/Y;
而在汇编中则需要花太多精力。
给一个小的C 代码,用RA0 控制一个LED 闪烁:
#i nclude<pic.h>
void main()
{
int x;
CMCON=0B111; //掉A 口比较器,要是有比较器功能的话。
ADCON1=0B110; //掉A/D 功能,要是有A/D 功能的话。
TRISA=0; //RA 口全为输出。
loop:RA0=!RA0;
for(x=60000;--x;){;} //延时
goto loop;
}
说说RA0=!RA0 的意思:PIC 对PORT 寄存器操作都是先读取----修改----写入。上句的含义是程序先
读RA0,然后取反,最后把运算后的值重新写入RA0,这就实现了闪烁的功能。
3、浅谈PICC 的位操作
由于PIC 处理器对位操作是最高效的,所以把一些BOOL 变量放在一个内存的位中,既可以达到运算
速度快,又可以达到最大限度节省空间的目的。在C 中的位操作有多种选择。
*********************************************
如:char x;x=x|0B00001000; /*对X 的4 位置1。*/
char x;x=x&0B11011111; /*对X 的5 位清0。*/
把上面的变成公式则是:
#define bitset(var,bitno)(var |=1<<bitno)
#define bitclr(var,bitno)(var &=~(1<<bitno))
则上面的操作就是:char x;bitset(x,4)
char x;bitclr(x,5)
*************************************************
但上述的方法有缺点,就是对每一位的含义不直观,最好是能在代码中能直观看出每一位代表的意思,
这样就能提高编程效率,避免出错。如果我们想用X 的0-2 位分别表示温度、电压、电流的BOOL 值可以
如下:
unsigned char x @ 0x20; /*象汇编那样把X 变量定义到一个固定内存中。*/
bit temperature@ (unsigned)&x*8+0; /*温度*/
bit voltage@ (unsigned)&x*8+1; /*电压*/
bit current@ (unsigned)&x*8+2; /*电流 */
这样定义后X 的位就有一个形象化的名字,不再是枯燥的1、2、3、4 等数字了。可以对X 全局修改,
也可以对每一位进行操作:
char=255;
temperature=0;
if(voltage)......
*****************************************************************
还有一个方法是用C 的struct 结构来定义:
如:
struct cypok{
temperature:1; /*温度*/
voltage:1; /*电压*/
current:1; /*电流*/
none:4;
}x @ 0x20;
这样就可以用
x.temperature=0;
if(x.current)....
等操作了。
**********************************************************
上面的方法在一些简单的设计中很有效,但对于复杂的设计中就比较吃力。如象在多路工业控制上。
前端需要分别收集多路的多路信号,然后再设定控制多路的多路输出。如:有2 路控制,每一路的前端信号有温度、电压、电流。后端控制有电机、喇叭、继电器、LED。如果用汇编来实现的话,是很头疼的事情,用C 来实现是很轻松的事情,这里也涉及到一点C 的内存管理(其实C 的最大优点就是内存管理)。
采用如下结构:
union cypok{
struct out{
motor:1; /*电机*/
relay:1; /*继电器*/
speaker:1; /*喇叭*/
led1:1; /*指示灯*/
led2:1; /*指示灯*/
}out;
struct in{
none:5;
temperature:1; /*温度*/
voltage:1; /*电压*/
current:1; /*电流*/
}in;
char x;
};
union cypok an1;
union cypok an2;
上面的结构有什么好处呢?
细分了信号的路an1 和an2;
细分了每一路的信号的类型(是前端信号in 还是后端信号out):
an1.in ;
an1.out;
an2.in;
an2.out;
然后又细分了每一路信号的具体含义,如:
an1.in.temperature;
an1.out.motor;
an2.in.voltage;
an2.out.led2;等
这样的结构很直观的在2 个内存中就表示了2 路信号。并且可以极其方便的扩充。
如添加更多路的信号,只需要添加:
union cypok an3;
union cypok an4;
从上面就可以看出用C 的巨大好处
4、PICC 之延时函数和循环体优化。
很多朋友说C 中不能精确控制延时时间,不能象汇编那样直观。其实不然,对延时函数深入了解一下
就能设计出一个理想的框价出来。一般的我们都用for(x=100;--x;){;}此句等同与x=100;while(--x){;};
或for(x=0;x<100;x++){;}。
来写一个延时函数。
在这里要特别注意:X=100,并不表示只运行100 个指令时间就跳出循环。
可以看看编译后的汇编:
x=100;while(--x){;}
汇编后:
movlw 100
bcf 3,5
bcf 3,6
movwf _delay
l2 decfsz _delay
goto l2
return
从代码可以看出总的指令是是303 个,其公式是8+3*(X-1)。注意其中循环周期是X-1 是99 个。这
里总结的是x 为char 类型的循环体,当x 为int 时候,其中受X 值的影响较大。建议设计一个char 类型的
循环体,然后再用一个循环体来调用它,可以实现精确的长时间的延时。下面给出一个能精确控制延时的
函数,此函数的汇编代码是最简洁、最能精确控制指令时间的:
void delay(char x,char y){
char z;
do{
z=y;
do{;}while(--z);
}while(--x);
}
其指令时间为:7+(3*(Y-1)+7)*(X-1)如果再加上函数调用的call 指令、页面设定、传递参数
花掉的7 个指令。则是:14+(3*(Y-1)+7)*(X-1)。如果要求不是特别严格的延时,可以用这个函数:
void delay(){
unsigned int d=1000;
while(--d){;}
}
此函数在4M 晶体下产生10003us 的延时,也就是10MS。如果把D 改成2000,则是20003us,以此类
推。有朋友不明白,为什么不用while(x--)后减量,来控制设定X 值是多少就循环多少周期呢?现在看看编
译它的汇编代码:
bcf 3,5
bcf 3,6
movlw 10
movwf _delay
l2
decf _delay
incfsz _delay,w
goto l2
return
可以看出循环体中多了一条指令,不简洁。所以在PICC 中最好用前减量来控制循环体。
再谈谈这样的语句:
for(x=100;--x;){;}和for(x=0;x<100;x++){;}
从字面上看2 者意思一样,但可以通过汇编查看代码。后者代码雍长,而前者就很好的汇编出了简洁的代
码。所以在PICC 中最好用前者的形式来写循环体,好的C 编译器会自动把增量循环化为减量循环。因为
这是由处理器硬件特性决定的。PICC 并不是一个很智能的C 编译器,所以还是人脑才是第一的,掌握一些
经验对写出高效,简洁的代码是有好处的。
5、深入探讨PICC之位操作
一:用位操作来做一些标志位,也就是BOOL变量.可以简单如下定义:
bit a,b,c;
PICC会自动安排一个内存,并在此内存中自动安排一位来对应a,b,c.由于我们只是用它们来简单的
表示一些0,1信息,所以我们不需要详细的知道它们的地址\位究竟是多少,只管拿来就用好了.
二:要是需要用一个地址固定的变量来位操作,可以参照PIC.H里面定义寄存器.
如:用25H内存来定义8个位变量.
static volatile unsigned char myvar @ 0x25;
static volatile bit b7 @ (unsigned)&myvar*8+7;
static volatile bit b6 @ (unsigned)&myvar*8+6;
static volatile bit b5 @ (unsigned)&myvar*8+5;
static volatile bit b4 @ (unsigned)&myvar*8+4;
static volatile bit b3 @ (unsigned)&myvar*8+3;
static volatile bit b2 @ (unsigned)&myvar*8+2;
static volatile bit b1 @ (unsigned)&myvar*8+1;
static volatile bit b0 @ (unsigned)&myvar*8+0;
这样即可以对MYVAR操作,也可以对B0--B7直接位操作.
但不好的是,此招在低档片子,如C5X系列上可能会出问题.
还有就是表达起来复杂,你不觉得输入代码受累么?呵呵
三:这也是一些常用手法:
#define testbit(var, bit) ((var) & (1 <<(bit))) 
//测试某一位,可以做BOOL运算
#define setbit(var, bit) ((var) |= (1 << (bit))) //把某一位置1
#define clrbit(var, bit) ((var) &= ~(1 << (bit))) //把某一位清0
付上一段代码,可以用MPLAB调试观察
#i nclude<pic.h>
#define testbit(var, bit) ((var) & (1 <<(bit)))
#define setbit(var, bit) ((var) |= (1 << (bit)))
#define clrbit(var, bit) ((var) &= ~(1 << (bit)))
char a,b;
void main(){
char myvar;
myvar=0B10101010;
a=testbit(myvar,0);
setbit(myvar,0);
a=testbit(myvar,0);
clrbit(myvar,5);
b=testbit(myvar,5);
if(!testbit(myvar,3))
a=255;
else
a=100;
while(1){;}
}
四:用标准C的共用体来表示:
#i nclude<pic.h>
union var{
unsigned char byte;
struct {
unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
} bits;
};
char a,b;
void main(){
static union var myvar;
myvar.byte=0B10101010;
a=myvar.bits.b0;
b=myvar.bits.b1;
if(myvar.bits.b7)
a=255;
else
a=100;
while(1){;}
}
五:用指针转换来表示:
#i nclude<pic.h>
typedef struct {
unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
} bits; //先定义一个变量的位
#define mybit0 (((bits *)&myvar)->b0) //取myvar 
的地址(&myvar)强制转换成bits 类型的指针
#define mybit1 (((bits *)&myvar)->b1)
#define mybit2 (((bits *)&myvar)->b2)
#define mybit3 (((bits *)&myvar)->b3)
#define mybit4 (((bits *)&myvar)->b4)
#define mybit5 (((bits *)&myvar)->b5)
#define mybit6 (((bits *)&myvar)->b6)
#define mybit7 (((bits *)&myvar)->b7)
char myvar;
char a,b;
void main(){
myvar=0B10101010;
a=mybit0;
b=mybit1;
if(mybit7)
a=255;
else
a=100;
while(1){;}
}


[NextPage]
六:五的方法还是烦琐,可以用粘贴符号的形式来简化它.
#i nclude<pic.h>
typedef struct {
unsigned b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
} bits;
#define _paste(a,b) a##b
#define bitof(var,num) (((bits *)&(var))->_paste(b,num))
char myvar;
char a,b;
void main(){
a=bitof(myvar,0);
b=bitof(myvar,1);
if(bitof(myvar,7))
a=255;
else
a=100;
while(1){;}
}

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]