方波信号发生器实例
/**************************************************
; CPU类型: PIC12F629 ; 内部RC振荡器: 4MHz
; C编译器: HT-PICC
; 集成开发环境: MPLAB v8.01
; 方波信号发生器实例
**************************************************/
#include
#define GPIO2_STATUS GPIO2 #define GPIO4_STATUS GPIO4 #define GPIO5_STATUS GPIO5 #define GPIO2_ON GPIO2=1 #define GPIO2_OFF GPIO2=0 #define GPIO4_ON GPIO4=1 #define GPIO4_OFF GPIO4=0 #define GPIO5_ON GPIO5=1 #define GPIO5_OFF GPIO5=0 unsigned char Hz50_Count=0;//产生50Hz频率的软计数器变量 unsigned char Hz1_Count=0;//产生1Hz频率的软计数器变量
void System_Init(void) {
CMCON = 0x00;//将GP0、GP1初始化为数字I/O引脚
ANSEL = 0x07;//将GP0、GP1、GP2、GP4初始化为数字I/O引脚
TRISIO2=0;//置GP2为输出
TRISIO4=0;//置GP4为输出
TRISIO5=0;//置GP5为输出
GPIO2 = 0;//GP2输出为低电平
GPIO4 = 0;//GP4输出为低电平
GPIO5 = 0;//GP5输出为低电平
T0CS = 0;//选择内部Fosc/4时钟源,作为定时
PSA = 0;//将预分频器分配给Timer0模块
PS2 = 0;//初始化预分频比为1:2
PS1 = 0;
PS0 = 0;
TMR0=256-250;//初始化定时500uS的初值
}
void main(void)
{
System_Init();//调用系统初始化函数
while(1)
{
if(1==T0IF)//判断T0IF溢出标志是否为1
{
T0IF=0;//清T0IF溢出标志
TMR0=256-250;//重新装入定时500uS初值
if(1==GPIO2_STATUS)GPIO2_OFF;//如果GP2引脚电平为高电平,则输出低电平
else GPIO2_ON;//否则输出高电平;此处是产生1KHz方波信号从GP2引脚输出
Hz50_Count++;//产生50Hz方波信号变量加1
if(20==Hz50_Count)//判断是否满足产生50Hz计数条件
{
Hz50_Count=0;//清计数值变量
if(1==GPIO4_STATUS)GPIO4_OFF;//如果GP4引脚电平为高电平,则输出低电平
else GPIO4_ON;//否则输出高电平;此处是产生50Hz方波信号从GP4引脚输出
Hz1_Count++;//产生1Hz方波信号变量加1
if(50==Hz1_Count)//判断是否满足产生1Hz计数条件
{
Hz1_Count=0;//清计数值变量
if(1==GPIO5_STATUS)GPIO5_OFF;//如果GP5引脚电平为高电平,则输出低电平
else GPIO5_ON;//否则输出高电平;此处是产生1Hz方波信号从GP5引脚输出
}
}
}
}
}
PIC单片机控制的电动自行车驱动系统 #i nclude
#i nclude
//电动车双闭环程序,采用双闭环方式控制电机,以得到最好的zh转速性能,并且可以 //限制电机的最大电流。本应用程序用到两个CCP部件,其中CCP1用于PWM输出,以控 //制电机电压;CCP2用于触发AD,定时器TMR2、TMR1,INT中断,RB口电平变化中断, //看门狗以及6个通用I/O口
#define AND 0xe0 //状态采集5,6,7位
#define CURA 0X0a //电流环比例和积分系数之和
#define CURB 0X09 //电流环比例系数
#define THL 0X6400 //电流环最大输出
#define FULLDUTY 0X0FF //占空比为1时的高电平时间
#define SPEA 0X1d //转速环比例和积分系数之和
#define SPEB 0X1c //转速环比例系数
#define GCURHILO 0X0330 //转速环最大输出
#define GCURH 0X33 //最大给定电流
#define GSPEH 0X67 //最大转速给定
#define TSON 0X38 //手柄开启电压1.1 V,TSON*2为刹车后手柄开启电压,即
//2.2 V #define VOLON 0X4c //低电压保护重开电压3.0 V即33 V
#define VOLOFF 0X49 //低电压保护关断电压2.86 V即31.5 V volatile unsigned char DELAYH,DELAYL,oldstate,speed,
speedcount,tsh,count_ts,count_vol,gcur,currenth,
voltage; //寄存器定义
static bit sp1,spe,ts,volflag,spepid,lowpower,
off,shutdown,curpid; //标志位定义
static volatile unsigned char new[10]={0xaf,0xbe,0xff,0x7e,0xcf,
0xff,0xd7,0x77,0xff,0xff}; //状态寄存器
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
//------------PIC16F877初始化子程序------------
void INIT877()
{
PORTC=0X0FF; //关断所有MOSFET
TRISC=0X02; //设置C口输出
PIE1=0X00; //中断寄存器初始化,关断所有中断
TRISA=0XCF; //设置RA4,RA5 输出
TRISB=0XEF; //RB 口高三位输入,采集电机三相的霍尔信号
PORTC=new[(PORTB&AND)>>5];//采集第一次霍尔信号,并输出相应的信号,导通
//两个MOS管
T2CON=0X01; //TMR2 4分频
CCPR1L=0X0FF; //初始时PWM输出全高
CCP1CON=0X0FF; //CCP1设置为PWM方式
CCP2CON=0X0B; //CCP2设置为特殊方式,以触发AD
ADCON0=0X81; //AD时钟为32分频,且AD使能,选择AN0通道采集手
//柄电压
TMR2=0X00; //TMR2寄存器初始化
TMR1H=0X00; //TMR1寄存器初始化
TMR1L=0X00;
T1CON=0X00; //TMR1为
CCPR2H=0X08;
CCPR2L=0X00; //电流采样周期设置为TAD=512 μs
PR2=0XC7; //PWM频率设置为5 kHz
ADCON1=0X02; //AD结果左移
OPTION=0XFB; //INT上升沿触发
TMR2ON=1; //PWM开始工作
INTCON=0XD8; //中断设置GIE=1,PEIE=1,RBIE=1
ADIE=1; //AD中断使能
speedcount=0x00; //转速计数寄存器
speed=0x7f; //转速保持寄存器
spe=1; //低速标志位
sp1=1; //低速标志位
oldstate=0x0ff; //初始状态设置,区别于其他状态
count_ts=0x08; //电流采样8次,采集1次手柄
count_vol=0x00; //采样256次手柄,采集1次电池电压
ts=1; //可以采集手柄值的标志位
ADGO=1; //AD采样使能
TMR1ON=1; //CCP2部件开始工作
}
//------------延时子程序--------------- #pragma interrupt_level 1 void DELAY1(x)
char x;
{
DELAYH=x; //延时参数设置
#asm
DELAY2 MOVLW 0X06
MOVWF _DELAYL
DELAY1 DECFSZ _DELAYL
GOTO DELAY1
DECFSZ _DELAYH
GOTO DELAY2
#endasm
}
//-----------状态采集子程序----------------------
void sample()
{
char state1,state2,state3,x;
do {
x=1;
state1=(PORTB&AND); //霍尔信号采集
DELAY1(x);
state2=(PORTB&AND);
}while(state1-state2); //当三次采样结果不相同时继续采集状态
if(state1-oldstate!=0) //看本次采样结果是否与上次相同,不同
//则执行
{oldstate=state1; //将本次状态设置为旧状态
state1=(oldstate>>5);
PORTC=new[state1]; //C口输出相应的信号触发两个MOS管
if(sp1==1){spe=1;sp1=0;}
else { //如果转速很低,则spe置1
spe=0;sp1=0;
speedcount<<=1;
state3=(TMR1H>>2); //否则,spe=0,计转速
speed=speedcount+state3; //speed寄存器为每256 μs加1
}
speedcount=0;
}
}
//-----------------AD采样子程序---------------------- void AD()
{
char x;
ADIF=0; //清AD中断标志位
if(ts==1){ //如果为手柄采样,则采样手柄值
CHS0=1; //选择电流采样通道
count_vol=count_vol+1; //电池采样计数寄存器
spepid=1; //置转速闭环运算标志
ts=0;tsh=ADRESH; //存手柄值
if(count_vol==0) { //如果电池采样时间到,则选择AN2通道,采集电池电压
CHS0=0;CHS1=1;volflag=1;x=1;DELAY1(x);ADGO=1;
}
}
else if(volflag==1) { //电池采样完毕,进行相应的处理
CHS1=0;CHS0=1;volflag=0;voltage=ADRESH;lowpower=1;
}
else
{ //否则,中断为采样电流中断
speedcount=speedcount+1; //speedcount寄存器加1,作为测量转速用
if(speedcount>0x3d)
sp1=1; //如果转速低于1 000 000 μs/(512 μs*3eh*3)
// 则认为为低速状态
currenth=ADRESH;
curpid=1;
count_ts=count_ts-1;
if(count_ts==0) { //如果手柄时间到,则转入手柄采样通道
CHS0=0;count_ts=0x08;ts=1;x=1;DELAY1(x);ADGO=1;
}
}
}
//-------------刹车处理子程序------------------ void BREAKON()
{
char x;
off=0; //off清零,如果是干扰则不复位
shutdown=0;
if(RB0==1) { //如果刹车信号为真,则停止输出电压
ADIE=0; //关AD中断
INTE=0; //关刹车中断
CCPR1L=FULLDUTY; //输出电压0
TMR1ON=0; //关CCP2,不再触发AD
for(;ADGO==1;) continue; //如正在采样,则等待采样结束
ADIF=0; //ADIF位清零
CHS0=0; //选择通道0采样手柄
CHS1=0;
x=1;
DELAY1(x);
do {
ADGO=1;
for(;ADIF==0;)continue;
ADIF=0;
CCPR1L=FULLDUTY;
asm("CLRWDT");
tsh=(ADRESH>>1);
}while(tsh>TSON||RB0==1); //当手柄值大于2.2 V或刹车仍旧继续时,执行以
//上语句
off=1; //置复位标志
}
}
//---------欠保护子程序------------------- void POWER()
{
char x;
lowpower=0;
voltage>>=1; //电压值换为7位,以利于单字节运算
if(voltage ADIE=0;
INTE=0;
TMR1ON=0;
CCPR1L=FULLDUTY;
for(;ADGO==1;)continue;
ADIF=0;
CHS0=0;CHS1=1;
x=1;
DELAY1(x);
do{ADGO=1;
for(;ADIF==0;)continue;
ADIF=0;
voltage=(ADRESH>>1);
CCPR1L=FULLDUTY;
asm("CLRWDT");
}while(voltage off=1; //置复位标志
}
}
//------------电流环运算子程序-----------------
void CURPI()
{
static int curep=0x00,curek=0x00,curuk=0x00;
union data{int pwm;
char a[2];}b; //定义电流环运算寄存器
curpid=0; //清电流运算标志
curep=curek*CURB; //计算上一次偏差与比例系数的积
if(currenth<2)currenth=2; //如果采样电流为零,则认为有一个小电
流以利于
//使转速下降
currenth>>=1;
curek=gcur-currenth; //计算本次偏差
curuk=curuk+curek*CURA-curep; //按闭环PI运算方式得到本次输出结果,
下
//面对结果进行处理
if(curuk<0x00) { //如果输出小于零,则认为输出为零
curuk=0;CCPR1L=FULLDUTY;CCP1X=0;CCP1Y=0;
}
else if(curuk-THL>=0)
{ //如果输出大于限幅值,则输出最大电压
curuk=THL;CCPR1L=0;CCP1X=0;CCP1Y=0;
}
else { //否则,按比例输出相应的高电平时间到CCPR1寄存器
b.pwm=THL-curuk;
b.pwm<<=1;
CCPR1L=b.a[1]; //CCPR1L=(b.pwm>>8)&0x0ff;将PWM寄存器的高半字节
if(b.pwm&0x80!=0) CCP1X=1;
else CCP1X=0;
if(b.pwm&0x40!=0) CCP1Y=1;
else CCP1Y=0;
}
}
//---------------转速环运算子程序----------------------- void SPEPI()
{
static int speep=0x00,speek=0x00,speuk=0x00;
int tsh1,speed1; //转速寄存器定义
spepid=0; //清转速运算标志
if(spe==1)
speed1=0x00; //若转速太低,则认为转速为零
else speed1=0x7f-speed; //否则计算实际转速
if(speed1<0) speed1=0;
speep=speek*SPEB;
tsh1=tsh-0x38; //得到计算用的手柄值
speek=tsh1-speed1;
if(tsh1<0) {speuk=0;gcur=0;} //当手柄值低于1.1 V时,则认为手柄给定为零
else { //否则,计算相应的转速环输出
if(tsh1>=GSPEH) //限制最大转速
tsh1=GSPEH;
speuk=speuk+speek*SPEA-speep; //计算得转速环输出
if(speuk<=0X00) {speuk=0x00;gcur=0x00;}//转速环输出处理
else if(speuk>GCURHILO) { //转速环输出限制,即限制最大电流约12
A
speuk=GCURHILO;gcur=GCURH;}
else { //调速状态时的输出
gcur=(speuk>>4)&0x0ff;
}
}
}
//-----------主程序------------------------- main()
{
for(;;){
INIT877(); //单片机复位后,先对其进行初始化
off=0; //清复位标志
for(;off==0;) { //复位标志为零,则执行下面程序,否则复位
if(curpid==1) CURPI(); //电流PI运算
else if(spepid==1) SPEPI(); //转速PI运算
else if(lowpower==1) POWER();
else if(shutdown==1) BREAKON();
asm("CLRWDT");
}
}
}
//---------中断服务子程序--------------------- #pragma interrupt_level 1
void interrupt INTS(void)
{
if(RBIF==1) {RBIF=0;sample();}
else if(ADIF==1) AD();
else if(INTF==1)
{shutdown=1;INTF=0;} //刹车中断来,置刹车标志
}
使用PIC单片机的TMR1作为实时时钟
为Timer1 添加外部LP 振荡器,可以为用户提供RTC 功能。这是通过一个提供精确时基的廉价时钟晶振以及几行计算时间的应用程序代码实现的。当器件工作于休眠模式下并使用电池或超大容量电容作为电源时,可以省去额外的RTC
器件和备用电池。
应用代码程序RTCisr,给出了使用中断服务程序以1 秒的间隔递增计数器的简单方法。 将TMR1 寄存器对的值不断加1 直至溢出,触发中断并调用中断服务程序,该程序会使秒计数器加1,而分钟和小时计数器则会在前面的计数器溢出时加1。由于这对寄存器为16 位宽,因此使用32.768 kHz 时钟,将其计数到溢出需要2 秒。要使溢出按所需的1 秒间隔进行,必须预先装载这对寄存器。最简单的方法是使用BSF 指令将TMR1H 的最高有效位置1。请注意决不要预先加载或改变TMR1L 寄存器,这样做可能会引起多个周期的累积错误。要使此方法精确,Timer1 必须工作在异步模式且必须允许Timer1 溢出中断(PIE1<0> = 1),如程序RTCinit所示。同时Timer1 振荡器也必须使能并始终保持运行。 RTCinit
MOVLW 80h ; Preload TMR1 register pair
MOVWF TMR1H ; for 1 second overflow
CLRF TMR1L
MOVLW b’00001111’ ; Configure for external clock,
MOVWF T1CON ; Asynchronous operation, external oscillator
CLRF secs ; Initialize timekeeping registers
CLRF mins ;
MOVLW d'12'
MOVWF hours
BSF PIE1, TMR1IE ; Enable Timer1 interrupt
RETURN
RTCisr
BSF TMR1H, 7 ; Preload for 1 sec overflow
BCF PIR1, TMR1IF ; Clear interrupt flag
INCF secs, F ; Increment seconds
MOVLW d'59' ; 60 seconds elapsed?
CPFSGT secs
RETURN ; No, done
CLRF secs ; Clear seconds
INCF mins, F ; Increment minutes
MOVLW d'59' ; 60 minutes elapsed?
CPFSGT mins
RETURN ; No, done
CLRF mins ; clear minutes
INCF hours, F ; Increment hours
MOVLW d'23' ; 24 hours elapsed?
CPFSGT hours
RETURN ; No, done
MOVLW d'23' ; 1 hours elapsed?
MOVWF hours
RETURN ; Done
PICC ME16 C简要说明
书
关于书的成语关于读书的排比句社区图书漂流公约怎么写关于读书的小报汉书pdf
第一部分PICC C 的使用说明. 这里我们只讲述了PICC C与标准C的不同,它不是一本C语言的教
程, 并且我们假定你有C语言的基础.
第二部分关于如何在MPLAB下使用PICC C语言, 请参阅Microchip相应的手册.
第一部分
为了对PIC单片机有更好的支持,PICC在标准C的基础上作了一些扩充:
, 定义I/O函数,以便在你的硬件系统中使用中定义的函数。
, 用C语言编写中断服务程序
, 用C语言编写I/O操作程序
, C语言与汇编语言间的接口
与标准C的不同 1-1
PICC只在一处与标准C不同:函数的重入。 因为PIC单片机的寄存器及堆栈有限,所以PICC不支持可重入函数。
支持的PIC芯片 1-2
PICC 支持很多PIC单片机,支持PIC单片机的类型在LIB目录下的picinfo.ini 文件中有定义。
PICC 包含一些标准库 1-3
PICC 编译器可以输出一些
格式
pdf格式笔记格式下载页码格式下载公文格式下载简报格式下载
的目标文件,缺省设置为输出Bytecraft的'COD' 格式和 Intel的'HEX'1-4
格式。你可以用表1-1中的命令来指定输出格式。
表,-,
格式名称 描述 PICC 命令 文件类型
Motorola HEX S1/S9 type hex file -MOT .HEX
HEX Intel style hex records(缺省) Intel -INTEL .HEX
Binary Simple binary image -BIN .BIN
UBROF Universal Binary Image Relocatable Format -UBROF .UBR
Tektronix HEX Tektronix style hex records -TEK .HEX American Hex format with symbols for American -AAHEX .HEX Automation HEX Automation emulators
Bytecraft code format(缺省) n/a(缺省) Bytecraft .COD .COD Library HI-TECH library file n/a .LIB
符号文件 1-5
PICC -G 命令用于生成符号文件,有了符号文件,你就可以进行源程序调试.
命令格式为: PICC -16F877 -G test.c
在使用仿真器时必须使用-G命令。
配置字 1-6
PIC单片机的配置字可以用__CONFIG命令来定义:
,i nclude
__CONFIG(x)
其中x是配置字,头文件中定义了相应的配置说明符,如:
__CONFIG(WDTDIS & XT & UNPROTECT);
这将关闭看门狗,设置XT振方式,程序不加密。注意:不同的配置符间用'&'相联,未定义的部分保留未编程状态。详细的情况请参考头文件及PIC数据手册。
ID 位置 1-7
有些PIC单片机在程序空间外还有ID空间,可用下面的方法来定义:
,i nclude
__IDLOC(x) 其中x是ID标示,如:
__IDLOC(15F0);
将ID的四个单元定义为:1, 5, 15, 0. ID的具体位置由所指定的PIC芯片自动设定。
EEPROM 数据 1-8
有些PIC单片机支持用外部编程器对内部的EEPROM进行编程。
__EEPROM_DATA()可以将用于初始化的数据放入HEX文件中,如:
__EEPROM_DATA(0, 1, 2, 3, 4, 5, 6, 7)
可将0-7八个数放入HEX文件中,在用外部的编程器进行编程时将 这八个数写入PIC单片机中。 __EEPROM_DATA不是用于运行时写入EEPROM数据的,在运行时请用 EEPROM_READ(), EEPROM_WRITE()。
位指令 1-9
只要有可能,PICC总是采用位指令。如:
int foo;
foo |= 0x40;
的编译结果为:bsf _foo, 6
为了方便可以定义如下宏:
#define bitset(var, bitno) ((var) |= (1 << (bitno)))
#defien bitclr(var, bitno) ((var) &= (1 << (bitno)))
上一条语句可写为:bitset(foo, 6);
支持的数据类型 1-10
PICC 支持1,2,4字节的基本类型。 所有的多字节类型都采用低有效位在前的格式, 表1-2列出了所有
数据类型及它们所占空间大小。
表,-,
类型 大小(位) 数字类型 值
逻辑类型 0 或 1 bit 1
有符号字符 signed char 8 -128..+127
无符号字符 unsigned char 8 0..255
有符号整数 signed short 16 -32768..+32767
无符号整数 unsigned short 16 0..65535
有符号整数 signed int 16 -32768..+32767
无符号整数 unsigned int 16 0..65535
有符号整数 signed long 32 -2147483648..+2147483647
无符号整数 unsigned long 32 0..4294967295
浮点 float 24
浮点 由-D24, -D32决定 double 24 or 32
常量及进制表示 1-10-1
PICC 支持标准C的进制表示方法。
l或L后缀表明常量为long类型, u或U后缀表示常量为unsinged类型。 浮点数为double类型,可
以用f或F指定浮点数为float类型。
字符型由单引号括起,如'a'.
字符串由双引号括起,如"Hello world".
位数据类型 1-10-2
PICC 支持一位的变量,用bit来定义。如: static bit init_flag;
变量必须是全局的或静态的,它不能是自动变量或一个函数的参数,但可以作为 一个函数的返回类型。 位变量很象unsigned char,但它只有0或1两个值,位变量占用空间少,且运算 速度快. 所有的位变量在startup是被清0,请在程序开始处初始化它们。
如果将一个整型数赋给位变量,只是将最低位赋给位变量,如果你是想要将一个整型 变量是否为0赋值给一个位变量,请用: bitvar = other_var != 0;
如要你使用了PICC的-STRICT命令,bit将被视为非法命令。
使用可位寻址的寄存器 1-10-2-1
位变量的定义可以与绝对地址的定义结合起来使用。如: 为了访问STATUS中Power Down位,先定义STATUS的绝对地址为3,然后再 定义一位变量绝对地址为27
static unsigned char STATUS @ 0x03;
static bit PD @ (unsigned)&STATUS*8+3;
注意: 头文件中已定义所有的特殊功能寄存器及相应的位寄存器.
PICC浮点数使用IEEE754 1-10-3
32位格式和IEEE754(截断)24位格式.
float类型使用24位格式,double使用24位或32位格式, 由PICC命令控制, -D24使用24位格式, -D32使用32位格式.
绝对地址变量 1-11
一个全局的或静态的变量可以定位绝对地址, 使用如下格式:
unsigned char Portvar @ 0x06;
这里定义了一个名为'Portvar'的变量, 地址为06h,注意,编译器并不保留任何 单元,仅仅是将一个变量分配在06h单元. 它等价于汇编语言: _Portvar EQU 06h 编译器及连接器都不作任何检查, 完全由程序员保证分配不会发生冲突.
结构与联合 1-12
PICC 支持struct及union, 它们可以作为函数的参数及返回值, 也可以作为指针 指向的目标.
结构限定 1-12-1
PICC 支持在结构上使用限定符, 如果在一个结构上使用限定符, 那么, 这个结构的 所有成员都被限定. 如: bank1 struct { int number; int *ptr; }
在这个结构里,number, ptr 都被放在bank1寄存器内(
结构中定义位成员 1-12-2
PICC 支持在结构中定义位成员(
位成员按最低有效位在前的方式存储,位成员总是按8位字节方式存放, 当当前 字节放满后再放下一个字节,位成员不会跨字节存放. 如:
struct {
unsigned hi: 1;
unsigned dummy: 6;
unsigned lo: 1;
} foo @ 0x10;
结构foo占用10h单元, hi为10h单元的第0位, lo为10h单元的第7位, dummy为10h 单元的2-6位,(第6位为最高有效位)
不使用的位可用未命名的位成员来定义, 如果我们不使用dummy, 就可定义为: struct {
unsigned hi: 1;
unsigned : 6;
unsigned lo: 1;
} foo @ 0x10;
在ROM及RAM存放字符串. 1-13
一个未说明的字符串总是存放在ROM中, 并且只能通过常量指针为访问. #define HELLO "Hello word"
SendBuff(HELLO);
一个非常量的数组被一个字符串初始化, 如:
char fred[] = 'Hello world";
将在RAM中保留一个数组, 在startup时, 用存放在ROM中的"Hello world"来初始化. 如果要将一个常数字符串作为函数参数或将它赋给一个指针, 必须定义一个常数指针. 如: void SendBuff(const char * ptr)
const, volatile 类型限定符. 1-14
PICC 支持标准C的const, volatile 类型限定符
const类型限定符通知编译器一个目标含有的常量并且不会改变. 一个常量被放在ROM中, 显然一个常量是不能被赋值的. 如:
const int version = 3;
volatile 类型限定符通知编译器, 一个目标不能保证在连续的访问中不被改变. 这将禁止编译器对该目标的优化. 所有的I/O口及在中断中使用的变量必须 有volatile 类型限定符. 如: volatile unsigned char P_A @ 0x05;
对volatile目标的访问与对non-volatile的访问是不同的, 如对volatile目标置1是 先将该目标清0后
加1, 而对non-volatile目标置1是先将1放在W中后再将W 赋值到目标中.
特别的类型限定符. 1-14
PICC 支持一些特别的类型限定符: persistent, bank1, bank2及bank3,这些限定符不可用于自动变量.
1-14-1 persistent
按C的标准, 所有的C变量在startup时被清为0. 但是在有些情况下, 我们希望在 复位后仍保持一些变
量的值. persistent类型限定符使被其限定的变量在 startup时不被清0, 而保留原有的值.
bank1, bank2及bank3类型限定符. 1-14-2
bank1, bank2及bank3类型限定符用于指定变量所在的寄存器页. 如: static bank3 unsigned char fred;
bank3 unsigned char *ptrfred;
缺省页是 bank0
C语言中的中断处理 1-15
在PICC C语言中可以用"interrupt"限定符来编写中断服务程序.
一个中断服务函数必须用 interrupt void 来定义, 不能有参数, 并且不能被C语言直接调用. 如: long tick_count;
void interrupt tc_int(void)
{ ++tick_count; }
在中断中保存环境
PIC单片机硬件只保存PC, PICC 编译器自动地保存其它可能用到的变量. 但是编译器无法确定inline中1-15-1
的汇编语言段使用变量的情况, 你必须自已保护它们.
开启中断 1-15-2
在PIC.H中定义了所有的中断位, di()关闭所有中断, ei()打开所有中断. 如: ADIE = 1; // A/D interrupt will be used
PEIE = 1; // all peripheral interrupts are enabled
ei(); // enable all interrupts
di(); // disable all interrupts
在C中使用汇编语言 #asm, #endasm, asm() 1-16
可以在C语言中直接使用汇编语言. #asm, #endasm用来加入一段汇编语言, 而asm()用来加入一条汇
编语言. 如:
,i nclude
unsinged char var;
void main(void)
{ var = 1;
#asm
rlf _var, 1
rlf _var, 1
#endasm
asm("rlf _var, 1");
}
函数调用变换 1-17
由于PIC5X只有两级堆栈, PICC 编译器使用转移指令来调用函数, 这样被套调用 层次增加, 但调用速度
下降, 请在需要快速调用的函数前加fastcall来指定编译器 直接使用调用指令调用函数. 对于14位的PIC单片机, 将永远使用调用指令调用.
MPLAB使用的调试控制项 1-18
-FACKLOCAL 命令用于在MPLAB下观察函数内的局部变量 -MPLAB_ICD 命令用于使用ICD调试C语言.
[ TOP ]
第二部分
安装PICC 2-1
将CD-ROM装入光驱, 自动运行程序将自动启动, 如果你已禁止自动运行功能, 可以 直接运行:
cd_drive:\compiler\install.exe
安装程序将指导你完成PICC的安装.
MPLAB下使用PICC C语言, 请参阅Microchip相应的手册. 2-2
用PICC编译器语言开发PIC系列单片机的代码 用PICC编译器语言开发PIC系列单片机的代码
Develop Codes of PIC series Microcontrollers Using PICC Complier
关键词:PIC PICC编译器 汇编语言 Hi-Tech
Key Words: PIC PICC Complier Assembly Language Hi-Tech
摘 要:介绍了PIC系列单片机C语言的发展,并以HI-TECH Software公司的HI-TECH PICC为例介绍了PICC编译器的特点和用其开发PIC系列单片机时的应注意的一些问题。
Abstract: The article introduces the developing process of C language used in PIC series
microcontrollers, explains the features of HI-TECH PICC, one kind of PICC complier
manufactured by HI-TECH Software. It also gives some notes on developing PIC utilizing
HI-TECH PICC.
一. 前言
目前在市场上应用最广泛的应该属于8位单片机,Microchip Technoloogy公司推出的8位PIC系列单片机目前在国内市场上深受用户欢迎,已经逐渐成为单片机应用的新潮流,但遗憾的是,目前国内介绍它的C语言开发工具的书籍和文章却比较少,而且用的人也不多,广大的程序员在用其开发的过程中都在慢慢摸索,可能会走一些弯路,笔者最近在用PIC的C语言时就遇到了好些问题,在这里笔者想就最近一段时间用PIC的C语言的一些经验和广大的底层软件程序员做一下交流和介绍,希望本文对用PICC开发PIC系列单片机的人有所帮助。
目前在国内用的比较多的是Hi-Tech的HI-TECH PICC编译器,而且目前市场上一些国内的PIC单片机仿真器也开始支持HI-TECH PICC编译格式,因此本文主要以Hi-Tech的PICC为基础介绍一下PIC的C语言的基本特点。
二. HI-TECH PICC的C语言开发工具的语言特点
PICC的C语言按ANSI C来定义,并进行了C语言的扩展,PICC和ANSI C有一个根本的区别就是PICC不支持函数的递归调用,这是因为PIC单片机内的堆栈大小是由硬件决定的,资源有限,所以不支持递归调用。它的数据也遵从标准C的数据结构,PICC的数据结构是以数据类型的形式出现的。PICC编译器支持的数据类型有位类型(bit)、无符号字符(unsigned char)、有符号字符(signed char)、无符号整形(unsigned int)、有符号整形(signed int)、无符号长整型(unsigned long)、有符号长整型(signed long)、浮点(float)和指针类型等,需要注意的是,PICC支持的多字节数据都采用低字节在前,高字节在后的原则,即一个多字节数,比如int型,在内存单元中存储顺序为低位字节存储在地址低的存储单元中,高位字节存储在地址高的存储单元中,程序员在用union定义变量时一定要注意这一特点。
PIC的C语言变量分为局部变量和全局变量,所有变量在使用前必须先定义后使用。全局变量是在任何函数之外说明的、可被任意模块使用的、在整个程序执行期间都保持有效的变量;局部变量在函数内部说明,局部变量有两种:自动变量和静态变量,缺省类型为自动变量,除非明确将其声明为静态变量,而且,所有的自动变量都被分配在寄存器页0,所以bank限定词不能用于自动变量,但可以用于静态的局部变量,当程序退出时,自动变量占用的空间释放,自动变量也就失去意义;静态变量是一种局部变量,因此只在声明他的函数内部有效,但它占用固定的存储单元,而这个存储单元不会被别的函数使用,因此其它函数可以通过指针访问或修改静态变量的值;静态变量在程序开始只初始化一次,因此若需只在某函数内部使用一变量,而又希望其值在2次函数调用期间保持不变,为实现程序模块化,则可将其
声明为静态变量。例如以下声明中,有些为合法,有些为非法:
void max(void)
{
unsigned char var1; //合法声明
unsigned char bank1 var2; //非法声明
static unsigned char bank1 var3; //合法声明
unsigned char var4 = 0x02; //合法声明,每次调用都初始化
static unsigned char bank1 var5 = 0x02; //合法声明,但只初始化一次
…………
}
PICC编译器对局部变量及传递参数使用RAM覆盖技术,编译时,连接器会自动把一些不可能被同时调用的函数的自动变量区重叠在一起,以达到内存的高效利用,因此其内部RAM的利用效率非常高。
1、 位变量的使用
需要说明的是虽然PICC允许利用bit定义位变量,比如:
static bit init_flag;
但位变量不能定义为自变量,也能不能作为函数参数,但可以作为函数的返回值。而且位变量也不能被静态初始化。比如若只想在某一个函数中使用位变量flag,用如下两种方法都是错误的:
void max(void)
{
bit flag; //非法,位变量不能定义为局部自变量
…………
}
void max(void)
{
static bit flag = 1; //非法,位变量不能被静态初始化
…………
}
PICC 支持在结构中定义位成员,位成员按最低有效位在前的方式存储,位成员总是以8bit为单位进行分配,当当前字节分配满后再分配下一个字节,但位成员不会跨字节存放.,例如定义:
struct {
unsigned lo: 1;
unsigned mid: 1;
unsigned hi: 7;
} flag @ 0x60;
结构flag占用两个单元0x60和0x61, lo分配到0x60单元的第0位, mid分配到0x60单元的第1位, 由于位成员不会跨字节存放,故hi分配到0x61单元的0-6位(第6位为最高有效位),而0x60单元的2-7位则跳过。
当然,上述的结构定义也可不使用绝对地址定义:
struct {
unsigned lo: 1;
unsigned mid: 1;
unsigned hi: 7;
} flag;
另外,不使用的位可用未命名的位成员来定义, 如果我们不使用mid, 就可定义为: struct {
unsigned lo: 1;
unsigned : 1;
unsigned hi: 7;
}flag;
如果将一个整型数赋给位变量,只是将最低位赋给位变量,如果你是想要将一个整型 变量是否为0赋值给一个位变量,必须用:
bitvar = (char_var != 0);
而不能象有些编译器那样可以用:bitvar = char_var;
也不能用类型强制转换:bitvar = (bit)char_var;
2、 绝对地址变量的定义
在某些特殊情况下,程序员可能不需要PICC动态分配某些特殊的全局或静态的局部变量,而使用@address结构定义全局或静态的局部变量,例如:
static unsigned char var1 @ 0x60;
定义了一个称为var1的变量,使其定位于单元0x60,但是需要程序员注意的是,编译器并不保留任何存储空间,而只是将此变量分配到此地址,在编译时,编译器也不能给出任何的警告或错误提示,因此程序员在用绝对地址定义变量时,应该自己查看编译器产生的映象文件或符号表,必须保证绝对变量被分配了唯一的地址,尤其是当此绝对地址正好落在了PICC分配的自动变量区时,查错是很困难的。当然简单的方法就是程序员自己使用高端的一块寄存器区定义绝对变量,因为,PICC的变量先从低端分配的。
3、 使用可位寻址绝对地址变量
在某些特殊情况下,为编程方便,程序员可能需要定义如下变量:
static unsigned char flag_var; //非法
static bit flag0 @ (unsigned)& flag_var *8+0; static bit flag1 @ (unsigned)& flag_var *8+1; static bit flag2 @ (unsigned)& flag_var *8+2; static bit flag3 @ (unsigned)& flag_var *8+3; static bit flag4 @ (unsigned)& flag_var *8+4; 程序员希望这个变量(假设是故障标志)既可以按字节访问,也可按位访问,这在有些编译器中是可以的,但是在PICC中这样定义是不合法的,而只有将flag_var按如下绝对地址变量定义才可以定义可位寻址位:
static unsigned char flag_var @0x7f; static bit flag0 @ (unsigned)& flag_var *8+0; …………………………
但这样定义以后flag_var就成为绝对地址变量,就要程序员自己来保证此地址不会和PICC自己分配的
变量冲突,这又带来一定的麻烦,经笔者测试,要解决此问题,还有一种方法,那就是使用联合和结构位成员的方法,例如上例可如下定义:
union{
struct {
unsigned flag0:1;
unsigned flag1:1;
unsigned flag2:1;
unsigned flag3:1;
unsigned flag4:1;
}bit_type;
unsigned char byte_type;
}flag_var;
这样定义以后,程序员既可以按照字节访问此故障标志,也可按位访问某一类型故障。例如: flag_var.bit_type.flag0 = 1; //按位访问
if(flag_var.byte_type) //按字节访问
{
……………
}
这两种访问都是合法的,而且程序员也不用在担心绝对地址是否有冲突的问题。
4、const类型限定符
const类型限定符用来通知编译器一个目标具有常数值,不能被改变,被const定义的常量被放在ROM中,例如:
unsigned char const var1[] ={"Microchip"}; unsigned char const var2[] ={0x00,0x01,0x02,0x03}; 这两种定义都是合法的,但若通过指针访问这些数组变量,必须将指针定义为常数字符指针才能访问。例如某函数声明为:
void func1(const unsigned char *ptr); 则调用常数数组的方法为:
void func2(void)
{
…………
func1((const unsigned char *)var1); …………
}
这一点程序员一定要认真对待,以免编译运行错误很难查找故障。
5、可变型变量volatile类型限定符
可变型变量volatile类型限定符用来通知编译器某一变量不能保证在连续访问的条件下,其值不被改变,例如所有的与I/O口有关的变量在编译器自带的头文件中都是被声明为volatile类型,如下所示: static volatile unsigned char PORTA @ 0x05; 需要程序员注意的是有可能在中断时被改变的变量应该被定义为volatile类型,尤其是编译时选择全局优化级别较高时,定义为volatile可以禁止编译器对此变量进行优化,这能够防止编译器进行程序优化
时,将认为明显多余的可变型变量删除,我们可以查看一下编译后生成的编译列表文件就会发现,编译器对volatile目标的访问与对non-volatile的访问是不同的, 如对non-volatile目标置1是先将该变量清0后加1, 而对volatile目标置1是先将1放在W中后再将W赋值给可变型变量。 6、persistent类型限定符
按C的标准, PICC在编译时,所有的C变量在启动时都会调用clear_ram模块将其清为0,但在某些情况下,程序员希望在处理器复位后仍保持一些变量的值,比如在做抗干扰处理时,希望若是由于看门狗溢出造成的复位则保留变量的值,若还按照缺省定义,则C程序无法实现,此时则可使用persistent类型限定符使被其限定的变量在启动时不被清0,而保留原有的值,程序员可在程序中根据看门狗的情况自己判断是否清零,例如可用如下方法:
persistent unsigned char var1;//定义为变量var1为persistent类型
void initialization(void) //初始化函数
{
……………
if(TO) //发生看门狗中断时
{
…………
var1 = 0; //发生看门狗中断时不清变量var1
…………
,
…………
,
此处须注意的是自动变量不能使用persistent限定词。
7、bank1, bank2及bank3类型限定符
PICC在缺省情况下,将变量分配到RAM区的寄存器页0,所有的动态变量和函数参数也被缺省分配到寄存器页0,但是当寄存器页0分配满以后,PICC并不能自动将变量分配到寄存器页1,因此程序员在定义变量时,要根据情况将互相关联较多的一类变量应用bank1, bank2及bank3类型限定符指定在同一寄存器页,这样编译器在优化程序时,就不会频繁的修改寄存器页选择位。但须注意的是,动态变量不能使用bank限定符。例如以下定义都属于合法的声明:
static bank1 unsigned char var1; //var1变量被定位于bank1
bank3 unsigned char *ptr1;//声明指向定位于bank3的无符号字符的指针
static bank3 unsigned char * bank1 ptr1; //声明指向定位于bank3的无符号字符的指针,但此指针定位于bank1
三. 函数调用时参数的传递
PICC函数参数的传递是根据被传参数的长度用W、被调函数的自动变量区域或被调函数的参数区域传递,传递代码比较高效,传递给函数的参数可以通过一个由问号“,”、下划线“_”及函数名加一个偏移量构成的标号获取,下面为一调用求和子程序的源代码:
unsigned char add_function(unsigned char augend,unsigned char addend);
void main(void)
{
unsigned char temp1,temp2,temp3;
temp3 = add_function(temp1,temp2); }
unsigned char add_function(unsigned char augend,unsigned char addend)
{
return(augend + addend);
}
编译后生成的为汇编程序为:
_main
; _temp2 assigned to ?a_main+0 ; _temp3 assigned to ?a_main+1 ; _temp1 assigned to ?a_main+2
bcf status,5
bcf status,6
movf (((?a_main+0))),w
movwf (((?_add_function)))
movf (((?a_main+2))),w
fcall (_add_function)
movwf (((?a_main+1)))
_add_function
; _augend assigned to ?a_add_function+0 ; _augend stored from w
bcf status,5
bcf status,6
movwf (((?a_add_function+0))) movf (((?a_add_function+0))),w addwf (((?_add_function+0))),w return
四. PICC语言和汇编语言的混合编程
一般情况下,主程序都是用C语言编写,C语言与汇编语言最大的区别就在于汇编程序执行效率较高些,
因为C语言首先要用C编译器生成汇编代码,在不少情况下,C编译器生成的汇编代码不如用手工生成
的汇编代码效率高。在PICC中,可以用两种办法在C程序中调用汇编程序,一是使用#asm,#endasm
及asm()在C语言中直接嵌入汇编代码,#asm和#endasm指令分别用于标示嵌入汇编程序块的开头
和结尾;asm()用于将单条汇编指令嵌入到编译器生成的代码中,如下所示:
void func1(void)
{
asm("NOP");
#asm
nop
rlf _var,f
#endasm
asm("rlf _var,f ");
,
需要注意的是嵌入汇编不是完整意义上的汇编,是一种伪汇编指令,使用时必须注意它们与编译器生成代码之间的互相影响。
另一种方法是将汇编作为一个独立的模块,用汇编编译器(ASPIC)生成目标文件,然后用连接器和C语言生成的其他模块的目标文件连接在一起,如果变量要公用时,则在另一个模块中说明为外部类型,并允许使用形式参数和返回值。
例如,如果在C模块中使用汇编模块中的函数,那么在C中可如下声明:
extern char rotate_left(char);
本声明说明了要调用的这个外部函数有一个char型形式参数,并返回一个char型的值。而rotate_left()函数的真正的函数体在外部的可以被ASPIC编译的汇编模块(文件名后缀.as)中,具体代码可以如下编写:
processor16C84
PSECT text0,class=CODE,local,delta=2
GLOBAL _rotate_left
SIGNAT _rotate_left,4201
_rotate_left
movwf ?a_rotate_left
rlf ?a_rotate_left,w
return
FNSIZE _rotate_left,1,0
GLOBAL ?a_rotate_left
END
需要注意的是,在C模块中声明的函数名称,在汇编模块中是以下划线开头的,GLOBAL定义了一个全局变量,也等同于C模块中的extern,SIGNAT强制连接器在在连接各个目标文件模块时进行类型匹配检查,FNSIZE定义局部变量和形式参数的内存分配,关于这些伪指令的详细说明见参考文献3。 这种方法比较麻烦一些,但是如果程序员对某一模块的执行效率要求较高时,可以采取这种办法,但是,为了保证汇编程序能正常运行,必须严格遵守函数参数传递和返回规则,当然,为避免这些规则带来的麻烦,一般情况下,程序员可以先用C语言大致编写一个类似功能的函数,预先定义好各种变量,采用PICC-S选项对程序进行编译,然后手工优化编译器产生的汇编代码后将其作为独立的模块就可以了。
五. 注意事项
使用PICC时,为了更有效的利用资源,应注意以下几点:
(1) 尽量使用无符号数和字节变量。
(2) 在寄存器资源允许的情况下,对某些对执行效率要求较高的平级无相互调用函数中用到的内部变量,可将其定义为全局临时变量,编程时覆盖使用,这样可减少很多编译代码。而对于中断函数内部用到的变量,可用全局变量
(3) 寄对于有一定汇编经验的人在开始使用PICC时,应多注意观看经编译后产生的汇编源代码,并应经常观看经正确编译连接后产生的映象文件(.MAP文件),在该文件中详细列出了分配给变量和代码的地址和生成代码的大小等信息。使用者可了解代码是否优化,变量分配是否合理,堆栈是否溢出等,从而写出高效简洁的C源代码。
(4) 对于有一定汇编经验的人在开始使用PICC时,应多注意观看经编译后产生的汇编源代码,并应经常观看经正确编译连接后产生的映象文件(.MAP文件),在该文件中详细列出了分配给变量和代码的地址和生成代码的大小等信息。使用者可了解代码是否优化,变量分配是否合理,堆栈是否溢出等,从而写出高效简洁的C源代码。
(5) PICC在好多情况下,不支持类型强制转换,即在类型不匹配时须查验编译后的汇编代码,看是否正确,尤其是对指针操作的时候一定要注意。
(6) 对某位变量自操作时,比如求反,不可以直接简写,例如: !flag; 编译后不能正确产生代码,而须写成:flag = !flag;
(7) 尽量选择全局优化编译选项,为保证寄存器页(包括程序存储期页面和RAM寄存器页)的正确转换,PICC的编译代码中有大量的变换寄存器页的代码,选择全局优化PICC会优化去大有关RP0、RP1、PCLAPH所增加的变换代码,从而加快程序执行速度,并节省大量的程序空间。
(8) 若有某一代码很短的函数被多个函数经常调用,最好将其定义为宏,因为若函数代码很短时,由于被调函数和调用函数不在同一代码页所产生的附加代码可能都会超过函数代码本身的长度。
六. 结论
经过作者在开发项目过程中的实践,PICC编译器产生的代码在有些时候虽然比较繁琐,但结构和逻辑性很强,开发效率。
用PICC编译器开发PIC系列单片机的代码
摘要:介绍PIC系列单片机C语言的发展;以HI-TECH Software公司的HI-TECH PICC为例,介绍PICC编译器的特点和用其开发PIC系列单片机时应注意的一些问题。
关键词:PIC PICC编译器 C语言/汇编语言 Hi-Tech
目前,在市场上应用最广泛的应该属于8位单片机,Microchip Technoloogy公司推出的8位PIC系列单片机,目前在国内市场上深受用户欢迎,已经逐渐成为单片机应用的新潮流;但遗憾的是,目前国内介绍它的C语言开发工具的书籍和文章却比较少,而且用的人也不多,广大的程序员在用其开发的过程中都在慢慢摸索,可能会走一些弯路。笔者最近在用PIC的C语言时就遇到了好些问题,在这里想和最近一段时间用PIC的C语言的一些经验和广大的底层软件程序员做一下交流和介绍希望本文对用PICC开发PIC系列单片机的人有所帮助。
目前,在国内用得比较多的是Hi-Tech的Hi-Tech PICC编译器,而且目前市场上一些国内的PIC单片机仿真器也开始支持Hi-Tech PICC编译格式;因此,本文主要以Hi-Tech的PICC为基础,介绍一下PIC的C语言的基本特点。
1 Hi-Tech PICC的C语言开发工具的语言特点
PICC的C语言按ANSI C来定义,并进行了C语言的扩展。PICC和ANSI C有一个根本的区别就是,PICC不支持函数的递归调用。这是因为PIC单片机的堆栈大小是由硬件决定的,资源有限,所以不支持递归调用。它的数据也遵从标准C的数据结构,PICC的数据结构是以数据类型的形式出现的。PICC编译器支持的数据类型有位类型(bit)、无符号字符(unsigned char)、有符号字符(signed char)、无符号整型(unsigned int)、有符号整形(signed int)、无符号长整型(unsigned long)、有符号长整型(signed long)、浮点(float)和指针类型等。需要注意的是,PICC支持的多字节数据都采用低字节在前,高字节在后的原则。即一个多字节数,比如int型,在内存单元中存储顺序为低位字节存储在地址低的存储单元。高位字节存储在地址高的存储单元中,程序员在用union定义变量时一定要注意这一特点。
PIC的C语言变量分为局部变量和全局变量,所有变量在使用前必须先定义后使用。全局变量是在任何函数之外说明的、可被任意模块使用的、在整个程序执行期间都保持有效的变量。局部变量在函数内部说明。局部变量有两种:自动变量和静态变量。缺省类型为自动变量,除非明确将其声明为静态变量。而且,所有的自动变量都被分配在寄存器页0,所以bank限定词不能用于自动变量,便可以用于静
态的局部变量。当程序退出时,自动变量占用的空间释放,自动变量也就失去意义。静态变量是一种局部变量,只在声明它的函数内部有效;但它占用固定的存储单元,而这个存储单元不会被别的函数使用,因此其它函数可以通过指针访问或修改静态变量的值。静态变量在程序开始只初始化一次,因此若只在某函数内部使用一变量,而又希望其值在2次函数调用期间保持不变,为实现程序模块化,则可将其声明为静态变量。例如以下声明中,有些为合法,有些为非法:
void max(void)
unsigned char var1; //合法声明
unsigned char bank1 var2; //非法声明
static unsigned char bank1 ver3; //合法声明
unsigned char var4=0x02; //合法声明,每次调用都初始化
static unsigned char bank1 var5=0x02; //合法声明,但只初始化一次
…………
}
PICC编译器对局部变量及传递参数使用RAM覆盖技术。编译时,连接器会自动把一些不可能被同时调用的函数的自动变量区重叠在一起,以达到内存的高效利用,因此其内部RAM的利用效率非常高。 2 函数调用时参数的传递
PICC函数参数的传递是根据被传参数的长度,用W、被调函数的自动变量区域或被调函数的参数区域传递,传递代码比较高效。传递给函数的参数可以通过一个由问号“,”、下划线“_”及函数名加一个偏移量构成的标号获取。下面为一调用求和子程序的源泉代码:
Unsigned char add_function(unsigned char augend,unsigned char addend);
Void main(void)
{
unsigned char temp1,temp2,temp3; tem3=add_function(temp1,temp2); }
unsigned char add_function(unsigned char augend,unsigned char addend)
{
return(augend + addend);
}
编译后生成的汇编程序为:
_main
; _temp2 assigned to?a_main+0 ;_temp3 assigned to ?a_main+1 ; _temp1 assigned to ?a_main+2 bcf status,5
bcf status,6
movf (((?a_main+0))),w
movwf(((?_add_function))) movf (((?a_main+2))),w
fcall (_add_function)
movwf(((?a_main+1)))
_add_function
; _augend assigned to ?a_add_function+0
; _augend stored from w
bcf status,5
bcf status,6
movwf(((?a_add_function+0)))
movf (((?a_add_function+0))),w
addwf (((?_add_function+0))),w
return
3 PICC语言和汇编语言的混合编程
一般情况下,主程序都是用C语言编写的。C语言与汇编语言最大的区别在于,汇编程序执行效率较高,因为,C语言首先要用C编译器生成汇编代码,在不少情况下,C编译器生成的汇编代码不如用手工生成的汇编代码效率高。在PICC中,可以用两种方法在C程序中调用汇编程序。一种方法是使用#asm,#endasm及asm()在C语言中直接嵌入汇编代码,#asm和#endasm指令分别用于标示嵌入汇编程序块的开头和结属;asm()用于将单条汇编指令嵌入到编译器生成的代码中,如下所示:
void func1(void){
asm("NOP");
#asm
nop
rlf_var,f
#endasm
asm("rlf_var,f");
}
需要注意的是,嵌入汇编不是完整意义上的汇编,是一种伪汇编指令,使用时必须注意它们与编译器生成代码之间的互相影响。
另一种方法是将汇编作为一个独立的模块,用汇编编译器(ASPIC)生成目标文件,然后用链接器和C语言生成的其它模块的目标文件链接在一起。如果变量要公用时,则在另一个模块中说明为外部类型,并允许使用形式参数和返回值。
例如,如果在C模块中使用汇编模块中的函数,那么在C中可知下声明:
extern char rotate_left(char);
本声明说明了要调用的这个外部函数有一个char型形式参数,并返回一个char型的值。而rotate_left()函数的真正函数体在外部可以被ASPIC编译的汇编模块(文件名后缀.as)中,具体代码可以如下编写:
processor16C84
PSECT text0,class=CODE,local,delta=2
GLOBAL _rotate_left
SIGNAT _rotate_4201
_rotate_left
movwf?a_rotate_left
rlf?a_rotate_left,w
return
FNSIZE _rotate_left,1,0
GLOBAL?a_rotate_left
END
需要注意的是,以C模块中声明的函数名称,在汇编模块中是以下划线开头的。GLOBAL定义了一个全局变量,也等同于C模块中的extern,SIGNAL强制链接器在链接各个目标文件模块时进行类型匹配检查,FNSIZE定义局部变量和形式参数的内存分配。
这种方法比较麻烦,如果对某一模块的执行效率要求较高时,可以采取这种办法;但是,为了保证汇编程序能正常运行,必须严格遵守函数参数传递和返回规则。当然,为避免这些规则带来的麻烦,一般情况下,可以先用C语言大致编写一个类似功能的函数,预先定义好各种变量,采用PICC-S选项对程序进行编译,然后手工优化编译器产生的汇编代码后将其作为独立的模块就可以了。 4 注意事项
使用PICC时,为了更有效地利用资源,应注意以下几点:
?尽量使用无符号数和字节变量。
?在寄存器资源允许的情况下,对某些执行效率要求较高的平级元相互调用函数中用到的内部变量,可将其定义为全局临时变量,编程时覆盖使用,这样可减少很多编译代码。而对于中断函数内部用到的变量,可用全局变量。
?对于有一定汇编经验的人在开始使用PICC时,应多注意观看编译后产生的汇编源代码,并经常观看经正确编译链接后产生的映像文件(.MAP文件)。在该文件中,详细列出了分配给变量和代码的地址和生成代码的大小等信息。使用者可了解代码是否优化,变量分配是否合理,堆栈是否溢出等,从而写出高效简洁的C源代码。
?在很多情况下,PICC不支持类型强制转换。即在类型不匹配时须查验编译后的汇编代码,看是否正确,尤其是对指针操作的时候一定要注意。
?对某位变量自操作时,比如求反,不可以直接简写,例如:~flag;编译后不能正确产生代码,而须写成:“flag=!flag;”
?尽量选择全局优化编译选项。为保证寄存器页(包括程序存储期页面和RAM寄存器页)的正确转换,PICC的编译代码中有大量的变换寄存器页的代码,选择全局优化PICC会优化去大量有关RP0、RP1、PCLAPH所增加的变换代码,从而加快程序执行速度,并节省大量的程序空间。
?若有某一代码很短的函数被多个函数经常调用,最好将其定义为宏。因为若函数代码很短时,由于被调函数和调用函数不在同一代码页所产生的附加代码可能都会超过函数代码本身的长度。 5 结论
PICC编译器产生的代码在有些时候虽然比较繁琐,但结构和逻辑性很强,开发效率大大提高,调试与维护都很方便。不论是从程序的开发速度、软件质量还是从程序的可维护性和可移植性上讲,PICC的优点绝非汇编语言所能比拟的。
在PICC中使用常数指针
在PIC中使用常数指针
常数指针使用非常灵活,可以给编程带来很多便利。
我测试过,PICC也支持常数指针,并且也会自动分页,实在是一大喜事。
定义一个指向8位RAM数据的常数指针(起始为0x00):
#define DBYTE ((unsigned char volatile *) 0)
定义一个指向16位RAM数据的常数指针(起始为0x00):
#define CWORD ((unsigned int volatile *) 0)
((unsigned char volatile *) 0)中的0表示指向RAM区域的起始地址,可以灵活修改它。
DBYTE[x]中的x表示偏移量。
下面是一段代码1:
char a1,a2,a3,a4;
#define DBYTE ((unsigned char volatile *) 0)
void main(void){
long cc=0x89abcdef;
a1=DBYTE[0x24];
a2=DBYTE[0x25];
a3=DBYTE[0x26];
a4=DBYTE[0x27];
while(1);
}
2:
char a1,a2,a3,a4;
#define DBYTE ((unsigned char volatile *) 0)
void pp(char y){
a1=DBYTE[y++];
a2=DBYTE[y++];
a3=DBYTE[y++];
a4=DBYTE[y];
}
void main(void){
long cc=0x89abcdef;
char x;
x=&cc;
pp(x);
while(1);
}
3:
char a1,a2,a3,a4;
#define DBYTE ((unsigned char volatile *) 0)
void pp(char y){
a1=DBYTE[y++];
a2=DBYTE[y++];
a3=DBYTE[y++];
a4=DBYTE[y];
}
void main(void){
bank1 static long cc=0x89abcdef;
char x;
x=&cc;
pp(x);
while(1);
}
PIC8位在PICC中的数据类型 PIC8位MCU的内存都是8位字节结构的,所以PICC中对于数据的划分都是以8位为基础的。
在汇编中,我们对一个地址为0X20的内存附值:
movlw 255;
movwf 0x20;
但一个内存是8位结构,能表示最大的数是255。要是超过了会怎么样呢, movlw 256;
movwf 0x20;
通过DEBUG后,可以看出0X20中的值不是256,而是0了。 这里可以看出PIC处理器计算过程是当计算结果超过内存能容纳的最大数(也就是所谓溢出),则
自动抛弃最高位。
如下:
255 - 256
11111111-100000000
256的2进制有9位,于是PIC处理器就自动抛弃最高位1,剩下的8个0放入内存,于是就成
了0了。这过程是由硬件自动完成的。
在PICC中象这样的概验比比都是。
如:char x;
for(x=255;x--;){;}
是完成255个循环。
要是for(x=256;x--;){;}
则是0个循环。
因为PICC中定义CHAR类型数据是8位。
再如:int x;
for(x=65537;x--){;}
则是只循环一次。因为PICC中INT数据是16位,占2个存储单位。
如上所述,在规划一个程序之前要考虑程序中可能涉及到的最大计算量来定义数据类型,不然将会发生溢出,导致数据混乱。
但也不可为了简便,全用长结构的类型来申明数据类型,不然会导致计算时间慢,空间占用多。
如X可能大于255,但不会大于65535,那么申明:unsigned int x;
如果X可能会是负数就一定要定义int x;
同上要是X可能大于65535,那么就得定义long x;
浮点数是一个很特殊的类型,不需要了解它的构造,只需要了解它是一个24位或32位的存储结构,是一个能表示小数的数据类型,浮点表示的数字范围极大,所以在用浮点类型时候,不需要考虑是否溢出的问题。
一个PIC读写内部EEPROM的程序(c)
,i nclude int i,j=1,k=1;
void initial(void) //初始化
{
TRISB=0x00; //RB为输出
EEDATA=0x00; //数据赋初值
EEADR=0x00; //地址赋初值
}
void write_data(void)
{ for(i==1;i<=0xFF;++i)
{ PIR2bits.EEIF=0;
EECON1bits.EEPGD=0; //设置访问目标为EEPROM
EECON1bits.WREN=1; //允许进行写操作
INTCONbits.GIE=0; //禁止中断
EECON2=0x55;
EECON2=0xAA; //校验
EECON1bits.WR=1; //启动一次写操作
EECON1bits.WREN=0; //关闭写操作
EEADR=++EEADR; //地址递增
EEDATA=++EEDATA; //数据递增
}
}
void read_display(void)
{ EEDATA=0x00; //数据赋初值
EEADR=0x00; //地址赋初值
for (j=1;j<=0xFF;j++)
{ EECON1bits.EEPGD=0; //设置访问目标为EEPROM
EECON1bits.RD=1; //启动一次读操作
PORTB=EEDATA; //送显
Delay1KTCYx(252); //延时
EEADR=++EEADR; //地址递增
EEDATA=++EEDATA; //数据递增
}
}
void main(void)
{ initial();
while(1)
{ write_data();
read_display();
}
}
PIC单片机双机异步通信程序 pic单片机双机异步通信(dhd0)
1 单片机PIC1编程(发送部分)
#include
/*该程序实现单片机双机异步通信功能,该程序是发送部分*/ unsigned char tran[8]; /*定义一个数组存储发送数据*/ unsigned char k,data; /*定义通用寄存器*/ const char table[20]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0XD8,0x80,
0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0x7f,0xbf,0x89,0xff};
/*不带小数点的显示段码表*/
/*spi显示初始化子程序*/
void SPIINIT()
{
PIR1=0;
SSPCON=0x30;
SSPSTAT=0xC0;
/*设置SPI的控制方式,允许SSP方式,并且时钟下降沿发送,与"74HC595,当其
*SCLK从低到高跳变时,串行输入寄存器"的特点相对应*/ TRISC=0xD7; /*SDO引脚为输出,SCK引脚为输出*/ TRISA5=0; /*RA5引脚设置为输出,以输出显示锁存信号*/ }
/*给数组赋初值子程序 */
void fuzhi()
{
for(k=0;k<8;k ) {
tran[k]=k 3;
}
}
/*SCI部件初始化子程序*/
void sciint()
{
SPBRG=0X19; /*将传输的波特率设为约9 600位/秒*/
TXSTA=0X04; /*选择异步高速方式传输8位数据*/
RCSTA=0X80; /*允许同步串行口工作*/ TRISC6=1;
TRISC7=1; /*将RC6、RC7设置为输入方式,对外部呈高阻状态*/
}
/*SPI传输数据子程序*/
void SPILED(data) {
SSPBUF=data; /*启动发送*/
do {
;
}while(SSPIF==0);
SSPIF=0;
}
/*显示子程序,显示8位数*/
void display()
{
RA5=0; /*准备锁存*/
for(k=0;k<8;k ) {
data=tran[k];
data=table[data]; /*查得显示的段码*/
/*发送显示段码*/ SPILED(data);
}
RA5=1; /*最后给一个锁存信号,代表显示任务完成*/
}
/*主程序*/
main()
{
SPIINIT();
fuzhi(); /*给数组赋初值*/
sciint(); /*SCI部件初始化*/
di(); /*中断禁止*/
TXEN=1; /*发送允许*/
CREN=1; /*接收数据允许*/
for(k=0;k<8;k ){
TXREG=tran[k]; /*发出一个字符*/
while(1){
if(TXIF==1) break;
} /*等待写入完成*/
while(1){
if(RCIF==1) break;/*若收到响应字节,则终止等待*/ }
RCREG=RCREG; /*读响应字节,清RCIF*/ }
display(); /*显示发送的数据*/
while(1){
;
}
}
2 单片机PIC2编程(接收部分)
#include
/*该程序实现单片机双机异步通信功能,该程序是接收部分,并把接收的数据显示在8*个LED上*/
unsigned char rece[8];/*定义一个数组存储接收数据*/ unsigned char k,data;/*定义通用寄存器*/ const char table[20]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,
0XD8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0x7f,0xbf,0x89,0xff};
/*不带小数点的显示段码表*/
/*spi显示初始化子程序*/
void SPIINIT()
{
;详细语句见发送程序
}
/*SCI部件初始化子程序*/
void sciint()
{
SPBRG=0X19; /*波特率设置与PIC1相同,为约9 600位/秒*/
TXSTA=0X04; /*异步高速传输*/ RCSTA=0X80; /*串行口工作使能*/ TRISC6=1;
TRISC7=1; /*将RC6、RC7设置为输入方式,对外部呈高阻状态*/
}
/*SPI传送数据子程序*/
void SPILED(data) {
;详细语句与见发送程序
}
/*显示子程序,显示4位数*/ void display() {
RA5=0; /*准备锁存*/
for(k=0;k<8;k ){
data=rece[k];
data=table[data]; /*查得显示的段码*/ SPILED(data); /*发送显示段码*/ }
RA5=1; /*最后给一个锁存信号,代表显示任务完成*/
}
/*主程序*/
main()
{
SPIINIT(); /*spi显示初始化*/ sciint(); /*SCI部件初始化*/ di(); /*中断禁止*/
CREN=1; /*接收允许*/
TXEN=1; /*发送允许*/
for(k=0;k<8;k ){
while(1){
if(RCIF==1) break;
} /*等待接收数据*/
rece[k]=RCREG; /*读取接收数据,同时清掉RCIF*/
TXREG=rece[k]; /*发送接收到的数据*/ while(1){
if(TXIF==1) break;
} /*等待写入完成*/
}
display(); /*显示接收的数据*/ while(1){
;
}
}
8路顺序控制器源程序
我自己在学习C时,想拿一个能程序来试试,发现网上的例子不是C18的,要不就是一段的小例子,能够直接用的很少,
对大虾来说,这是很简单的事,可是对初学来说,这不是那么容易,特别是C也是刚入门的人,起码我是折腾了很久,这
是我个是调试通过的程序,给大家做个参考
//****************************************************************/
/*8路顺序控制器源程序,源文件名order.c*/
//先顺序点亮led0--led7,然后再反序点亮led7--led0 //****************************************************************/
//能够用的点灯程序
#include
#include
#define PORTBIT(add,bit) ((unsigned)(&add)*8+(bit))
static bit PORT_0 @ PORTBIT(PORTB,0); /*定义PORTB 0位*/ static bit PORT_1 @ PORTBIT(PORTB,1); /*定义PORTB 1位*/ static bit PORT_2 @ PORTBIT(PORTB,2); /*定义PORTB 2位*/ static bit PORT_3 @ PORTBIT(PORTB,3); /*定义PORTB 3位*/ static bit PORT_4 @ PORTBIT(PORTB,4); /*定义PORTB 4位*/ static bit PORT_5 @ PORTBIT(PORTB,5); /*定义PORTB 5位*/ static bit PORT_6 @ PORTBIT(PORTB,6); /*定义PORTB 6位*/ static bit PORT_7 @ PORTBIT(PORTB,7); /*定义PORTB 7位*/ void delay(); //延迟函数的声明
main()
{ TRISB=0x00; //设置portb口全为输出
INTCON=0x00; //关闭所有的中断
PORTB=0x00; //RB口先送低电品平
loop1:
//点亮led的第0位,顺序显示
//PORTB=0x01;
PORT_0=1;
delay();
//点亮led的第1位
//PORTB=0x03;
PORT_1=1;
delay();
//点亮led的第2位
//PORTB=0x07;
PORT_2=1;
delay();
//点亮led的第3位
//PORTB=0x0f;
PORT_3=1;
delay();
//点亮led的第4位
//PORTB=0x1f;
PORT_4=1;
delay();
//点亮led的第5位
//PORTB=0x3f;
PORT_5=1;
delay();
//点亮led的第6位
//PORTB=0x7f;
PORT_6=1;
delay();
//点亮led的第7位
//PORTB=0xff;
PORT_7=1;
delay();
//全灭
PORTB=0x00;
delay();
//点亮led的第7位,反序显示
//PORTB=0x80;
PORT_7=1;
delay();
//点亮led的第6位
//PORTB=0xc0;
PORT_6=1;
delay();
//点亮led的第5位
//PORTB=0xe0;
PORT_5=1;
delay();
//点亮led的第4位
//PORTB=0xf0;
PORT_4=1;
delay();
//点亮led的第3位
//PORTB=0xf8;
PORT_3=1;
delay();
//点亮led的第2位
//PORTB=0xfc;
PORT_2=1;
delay();
//点亮led的第1位
//PORTB=0xfe6;
PORT_1=1;
delay();
//点亮led的第0位
//PORTB=0xff;
PORT_0=1;
delay();
//全灭
PORTB=0x00;
delay();
goto loop1; }
void delay() {
int i;
for(i=0;i<=10000;i++)
continue;
}
初学者拿来就可以用的程序四(中断+数码管动态扫描+3*4矩阵按键)
这是我这几天写的程序,是初学者的话,可以做一下参考,PICC网上的例子不是很多~
注解不一定对,我可能没更改过来
//K程序主要是测试配置位的使用要调用87X.H杳看
//定时中断
//原来4的程序是可能为没有按键放开的程序
//程序5修正按键松开的程序
//biao是指示是否有按键按下如果没有的话就指示为显示4个8 #include
#include
#define PORTDIT(add,bit) ((unsigned)(&add)*8+(bit))
#define PORTBIT(add,bit) ((unsigned)(&add)*8+(bit))
//__IDLOC(1233) ;
//__CONFIG (XT&PWRTEN) ;
static bit PORT_0 @ PORTDIT(PORTD,0); /*定义PORTD 0位*/ static bit PORT_1 @ PORTDIT(PORTD,1); /*定义PORTD 1位*/ static bit PORT_2 @ PORTDIT(PORTD,2); /*定义PORTD 2位*/ static bit PORT_3 @ PORTDIT(PORTD,3); /*定义PORTD 3位*/ static bit PORT_4 @ PORTDIT(PORTD,4); /*定义PORTD 4位*/ static bit PORT_5 @ PORTDIT(PORTD,5); /*定义PORTD 5位*/ static bit PORT_6 @ PORTDIT(PORTD,6); /*定义PORTD 6位*/ static bit PORT_7 @ PORTDIT(PORTD,7); /*定义PORTD 7位*/ //-------------------------------------------------
static bit PORTD_0 @ PORTBIT(PORTB,0); /*定义PORTB 0位*/ static bit PORTD_1 @ PORTBIT(PORTB,1); /*定义PORTB 1位*/ static bit PORTD_2 @ PORTBIT(PORTB,2); /*定义PORTB 2位*/ static bit PORTD_3 @ PORTBIT(PORTB,3); /*定义PORTB 3位*/ static bit PORTD_4 @ PORTBIT(PORTB,4); /*定义PORTB 4位*/ static bit PORTD_5 @ PORTBIT(PORTB,5); /*定义PORTB 5位*/ static bit PORTD_6 @ PORTBIT(PORTB,6); /*定义PORTB 6位*/ static bit PORTD_7 @ PORTBIT(PORTB,7); /*定义PORTB 7位*/ //const char table[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x7C};
unsigned char s; /*每0.125S累加1*/ unsigned char ss,fan; /*每1秒累加1*/ int i=0,tt;
unsigned char ledadr,biao; static unsigned char k,led1,led2,led2,led3,led4;
static unsigned char adrshow,count,show1,show2,show3,show4;//显示位控制 const char table[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7
C,0x39}; //A~F
/*0,1,2,3,4,5,6,7,8,9,A,b,C,c,d,E,*/ void keyscan();
void delay10ms(unsigned char time); void Dispaly(unsigned char k); unsigned char key,temp;
void tmint(void)
{ T0CS=0;
PSA=0;
PS2=0;
PS1=1;
PS0=1;
T0IF=0;
T0IE=1;
}
void interrupt clkint(void) //定时器中断函数
{ if(T0IF=1) //增加是定时中断时进入
{
TMR0=0x5b;
T0IF=0;
ledadr=ledadr+1;
if(biao==0)
{ if (ledadr>count)
{ledadr=0;}
}
if ((ledadr==1)&(show1==1||biao==1))
{if(biao==1)
{PORTC=0XFF;
PORTA=0;
RA0=1;}
else {PORTC=led1;
PORTA=0;
RA0=1;}
}
if ((ledadr==2)&(show2==1||biao==1))
{ PORTA=0;
if(biao==1)
{PORTC=0XFF;
PORTA=0;
RA1=1;}
else {
PORTA=0;
PORTC=led2;
RA1=1;}
}
if ((ledadr==3)&(show3==1||biao==1))
{PORTA=0;
if (biao==1)
{PORTC=0xff;
PORTA=0;
RA2=1;}
else {
PORTA=0;
PORTC=led3;
RA2=1;}
}
if ((ledadr==4)&(show4==1||biao==1))
{PORTA=0;
if (biao==1)
{PORTC=0XFF;
PORTA=0;
RA3=1;
ledadr=0;}
else{
PORTC=led4;
PORTA=0;
RA3=1;
ledadr=0;}
}
if(s++>8){ s=0;
if(ss++>05)/*每分钟清0*/
ss=0;
}
/* RC3=(bit)(s>4);//可改变占空比1秒闪一次
RC1=(bit)(!ss);//1分闪一次
RC2=(bit)(s==0 || s==2 || s== 4 || s== 6);//0.25秒闪一次*/
}
}
main(void)
{
TRISD=0x0F;//低四位是输入,高四位为输出
TRISC=0;
// PORTD=0X55;
TRISB=0x00;
TRISC=0x00;
PORTB=0X00;
PORTC=0x00;
RB0=1;RB2=0;//接两个LED,开始为全灭
PCFG3=0;
PCFG2=1;
PCFG1=1;
PCFG0=1;
TRISA=0;
PORTA=0;
//RTC=0xff;
T0CS=0;
RA4=0;
ledadr=0;
tmint(); //调用定时器初始化程序
GIE=1;
ledadr=0;
count=0;
// show1=1;biao=0;
// ledadr=2;
//if ((ledadr==2)&(show1==1||biao==1))
// {asm("nop");}
//show=0;//显示清零
s=0 ;
ss=0;
adrshow=1;
TMR0=0x06; //装入定时器初始值
biao=1;led1=led2=led3=led4=0xff;
show1=show2=show3=show4=0;
while(1)
{
// ledadr=0;
keyscan();
if(key!=0)
{ if (count<=4)
{ count=count+1;
if (count==1)
{ led1=table[key];
key=0;
show1=1;show2=0;show3=0;show4=0;biao=0;}
if(count==2)
{ led2=table[key];
key=0;
show2=1;show1=1;show3=0;show4=0;biao=0; }
if(count==3)
{ led3=table[key];
key=0;
show1=1;show2=1;show3=1;show4=0;biao=0;}
if(count==4)
{led4=table[key];
key=0;
show1=1;show2=1;show3=1;show4=1;biao=0;}
}
else count=0;
}
}
}
/* RC3=(bit)(s>4);//可改变占空比1秒闪一次
RC1=(bit)(!ss);//1分闪一次
RC2=(bit)(s==0 || s==2 || s== 4 || s== 6);//0.25秒闪一次 */
void keyscan() //按键扫描子程序
{
PORTD=0xFF;
RD7=0; //低四位的第四位为0(输出)
temp=PORTD;
temp&=0x0F;//屏蔽掉高四位
if(temp !=0x0F) //如果有按键按下的话
{
delay10ms(1);
temp=PORTD;
temp&=0x0F;
if(temp !=0x0F) ;//如果确认有按键按下
{
temp=PORTD;
temp&=0x0F;//屏蔽高位位
asm("nop");
switch(temp)
{
case 0x0E: //1110000b b4有按下
key=4;break;
case 0x0D:
key=8;break;// 10110000b d
case 0x0B:
key=12;break; //11010000b
}
Dispaly(key);
asm("nop");
}
}
PORTD=0xFF;
RD6=0;//低四位的第三位
temp=PORTD;
temp&=0x0F;
if(temp !=0x0F) {
delay10ms(1);
temp=PORTD;
temp&=0x0F;
if(temp !=0x0F)
{
temp=PORTD;
temp&=0x0F;
switch(temp)
{
case 0x0E: //1110000b b4有按下
key=3;break;
case 0x0D:// 10110000b d
key=7;break;
case 0x0B: //11010000b
key=11;break;
}
Dispaly(key);
}
}
PORTD=0xFF;
RD5=0;//第二位为0
temp=PORTD;
temp&=0x0F;
if(temp !=0x0F) {
delay10ms(1);
temp=PORTD;
temp&=0x0F;
if(temp !=0x0F)
{
temp=PORTD;
temp&=0x0F;
switch(temp)
{
case 0x0E:
key=2;break;
case 0x0D:
key=6;break;
case 0x0B:
key=10;break;
}
Dispaly(key);
}
}
PORTD=0xFF;
RD4=0;//第1位为0
temp=PORTD;
temp&=0x0F;
if(temp !=0x0F) {
delay10ms(1);
temp=PORTD;
temp&=0x0F;
if(temp !=0x0F)
{
temp=PORTD;
temp&=0x0F;
switch(temp)
{
case 0x0E:
key=1;break;
case 0x0D:
key=5;break;
case 0x0B:
key=9;break;
}
Dispaly(key);
}
}
}
//延时程序
void delay10ms(unsigned char time)
{
unsigned char a,b,c; for(a=0;a