ccbupt / 单片机 / 单片机C语言学习

分享

   

单片机C语言学习

2009-04-28  ccbupt
单片机C语言学习
来源:电子工程师笔记 

单片机C语言


                预处理
一》宏定义:

1、不带参数:
#define 标识符 常量表达式
    /*#define是宏定义命令,宏名(标识符)好习惯用大写*/
#define NIL 0x80

2、带参数:/*相当于小函数*/
#define 宏名(参数表) 字符串
    /*不仅要时行字任串替换还要进行参数的替换,在宏定义时,宏名与带参数的括弧之间不应该加空格,否则将空格以后的字符串都作为替代字符串的一部分,这可是很容易出错的*/
    如:#define SQ(a,b) a*b
    使用:x=12;y=10;area=SQ(x,y);/*则area=12*10=120*/

二》文件包含:

#include <文件名>或#include "文件名"
    /*在C中用双引用形式更保险,在C51中常用物是尖括弧形式*/

三》条件编译:
    /*一般源程序中的所有程序行都参加编译,但有时希望对其中一部分内容只在满足一定条件下才进行编译,也就是对一部分内容指定编译的条件。*/

#if、#elif、 #else、#endif、#ifdef、#ifndef
    /*选择不同的编译范围,产生不同的代码,提供通用性。*/
    /*如对8051在6MHZ与12MHZ下有*/
#ifdef cpu==8051
#define FREQ 6 /*程序段*/
#else
#define FREQ 12/*程序段*/
#endif
    /*这样下面的原程序不用做任何修改便可以使用于两种时钟频率的单片机系统*/

四》其他:
1、#error:捕捉不可预料的编译条件
#if (myv!=0&&myv!=1)/*假定其值必为0或1*/
#error myv must be 1 or 0/*出错时显示*/
#endif
2、#pragma:用于在程序中向编译器传送各种编译控制命令
#pragma 编译命令序列
    /*例:想按如下命令编译ex.c c51 ex.c debug cod large可用:*/
#pragma DB CD LA
#pragma disable
    /*禁止中断*/


单片机C语言之二_____________________________________________________________________________________
一》数据类型:
        char         int         long
1:unsinged     0~255         0~65535     0~4294967295
2:signed     -128~127     -32768~32767     -2147483648~2147483647

指针:*        3字节

位标量:    sbit
特殊功能寄存器:sfr
16位特殊功能寄存器:sfr16     占2个内存单元,0~65535
可寻址位:sbit利用他可访问51单片机的内部RAM中的可寻址位或特殊功能寄存器中的可寻址位
sfr P0=0x80;
sbit P0_1=P0^1;
    /*将P0口的口地址定义为80H,将P0.1位定义为P1_1*/

二》数据存贮类型
                  表1. C51数据存贮类型
━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━
数据存贮类型 ┃        与存贮空间的对应关系                
━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━
     data     ┃  直接寻址片内数据存贮区,访速度快                 
     bdata    ┃  可位寻址片内数据存贮区,允许位与字节混合访问
     idata    ┃  间接寻址片内数据存贮区,可访问片内全部RAM地址空间
     pdata    ┃  分页寻址片外数据存贮区(256字节)由MOVX @R0访问           
     xdata    ┃  片外数据存贮区(64K),由MOVX @DPTR访问
     code     ┃  代码存贮区(64K),由MOVC @DPTR访问
━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━
变量的存贮类型定义:
char data var
    /*字符变量var被定义为data存贮类型,C51编译器将把该变量定位在51单片机片内数据区存贮区中*/
bit bdata flag
    /*位变量flag被定义为bdata存贮类型,C51编译器将把该变量定位在51单片机片内数据区存贮区(RAM)中的位寻址区:20H--2FH*/


三》typedef:重新定义数据类型

typedef 已有数据类型 新的数据类型
typedef int word;
    /*将word定义为整型*/
word i,j;
    /*将i,j定义为整型*/

四》位运算符:
        ━━━━┳━━━━━┳━━━━━┳━━━━━━┳━━━━━━┳━━━━━━
            ~   ┃    &     ┃     |    ┃    ^          ┃     <<     ┃    >>        
        ━━━━╋━━━━━╋━━━━━╋━━━━━━╋━━━━━━╋━━━━━━ 
        按位取反┃  按位与  ┃   按位或 ┃ 按位异或   ┃  左移      ┃     右移
        ━━━━┻━━━━━┻━━━━━┻━━━━━━┻━━━━━━┻━━━━━━

对移位:如<< ,a<<2,即为将二进制的a左移两位,若a=0x8f,即10001111,a=a<<2,将导致a=0x3c(00111100),右边补零。

五》条件运算符:
逻辑表达式? 表达式1:表达式2

六》指针与地址运算符:
*取内容        &取地址

七》强制类型转换:(类型)=表达式
(char *)0xb000

八》sizeof 取数据类型、变量以及表达式的字节数的运算符;

九》continue:中断语句:结束本次循环。


单片机C语言之三_____________________________________________________________________________________
函数:
一》中断服务函数与寄存器组定义:

函数类型 函数名(形式参数表) [interrupt n][using n]
n为中断号,0~31:
━━━━┳━━━━━┳━━━━━
中断编号┃  中断向量┃ 入口地址
━━━━╋━━━━━╋━━━━━
    0   ┃  外中断0 ┃   0003H
━━━━╋━━━━━╋━━━━━
    1   ┃  定时器0 ┃   000BH
━━━━╋━━━━━╋━━━━━
    2   ┃  外中断1 ┃   0013H
━━━━╋━━━━━╋━━━━━
    3   ┃  定时器1 ┃   001BH
━━━━╋━━━━━╋━━━━━
    4   ┃  串行口  ┃   0023H
━━━━┻━━━━━┻━━━━━
后面的n指的是四个工作寄存器组的一个:0~3
对函数目标代码影响如下:
在函数入口处将当前工作寄存器组保护到堆栈中;指定的工作寄存器内容不会改变,函数返回前将被保护的工作寄存器组从堆栈中恢复!
例(定时1ms):

    #include <reg51.h>
    sbit P1_0=P1^0;   

void timer0(void) interrupt 1 using 1{
    P1_0=!P1_0;
    TH0=-(1000/256);
    TL0=-(1000%256);
}
main(){   
    SP=0x60;
    P1_0=0;
    TMOD=0X01;
    TH0=-(1000/256);
    TL0=-(1000%256);
    EA=1;
    ET0=1;
    TR0=1;
    do{}while(1);
}

/*    注意:
    1、如果中断函数中用到浮点运算,必须保存浮点寄存器的状态。(在math.h中保存浮点寄存器函数为pfsave, 恢复浮点寄存器的状态函数为fprestore)
    2、如果在中断函数中调用了其他函数,则被调函数所使用的工作寄存器组与中断函数的一致!
*/


单片机C语言之四_____________________________________________________________________________________
一、 局部变量与全局变量(外部变量):

1、 全局变量若不在开头定义则加extern
2、 全局变量会使代码长,占用内存多

二、 存储方式:
自动变量(auto):缺省,函数调用存在,退出消失。

内部变量 静态变量(static):static int a=5;始终存在,退出不消失,但不能访问。

寄存器变量(register):速度最快。通常只给编译器一个建议,由编译器根
据实际情况确定。(见下)

变量 全局变量(global):
外部变量
静态变量(static):

寄存器变量例:
    #include<stdio.h>
    int_power(m,e)
    int m;
register int e;
{
    register int temp;
    temp=1;
    for(;e;e--)
    temp*=m;
    return(temp);
}
main()
{
    ……
}

三、 函数的参数和局部变量的存储器模式:

三种存储器模式:small,compact,large.
一个函数的存储器模式确定了函数的参数和局部变量在内存中的地址空间
small:内部ram
compact, large:外部RAM
函数类型 函数名(形式参数表)[存储器模式]

例:
#pragma large /*默认存储器模式为large*/
extern int calc(char I,int b)small; /*指定small模式*/
extern int func(int I,float f) large; /*指定large模式*/
int large_te(int I,int k) /*未指定,按默认的large模式处理*/
{
return(mtest(I,k)+2);
}

利用存储器混合模式编程,充分利用有限的存储空间,还可加快程序的执行速度!


单片机C语言之五_____________________________________________________________________________________
        数组

1>初始化数组:
    unsigned char a[5]={0x11,0x22,0x33,0x44,0x55}

    unsigned char a[ ] ={0x11,0x22,0x33,0x44,0x55,0x66}

3>数组作为函数的参数:不但可以由变量作为函数的参数外,还可以用数组名作为函数的参数。一个数组数组名表示该数组的首地址。用一个数组名作为函数的参数时,在执行函数调用的过程中参数传递方式采用的是地址传递。将实际参数数组首地址传递给被调函数中的形式参数数组,这样一来两个数组就占有同一段内存单元。见下图:

a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]

起始地址1000

b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9]
用数组名作为函数的参数,应该在主调函数和被调函数中分别进行数组定义而不能只在一方定义数组。而且在两个函数中定义的数组类型必须一致,如果类型不一致将导致编译出错。实参数组和型参数组的长度可以一致可以不一致,编译器对形参数组的长度不做检查,直只是将实参数组的首地址传递给行参数组。如果希望行参数组能得到实参数组的全部元素,则应使两个数组的长度一致。定义型参数组时可以不指定长度,只在数组名后面跟一个方括号[]。这时为了在被调函数中处理数组元素的需要,应另外设置一个参数来传递数组元素的个数。
例:用数组作为函数的参数,计算两个不同长度的数组中所有元素的平均值

    #include<stdio.h>

float average(array,n)
    int n;
    float array[ ];
{
    int I;
    float aver,sum=array[0];
    for(I=1;I<n;I++)
        sum=sum+array[I];
    aver=sum/n;
    return(aver);
}
main()
{
    float pot_1[2]={99.9,88.8};
    float pot_2[3]={11,22,33.3};
    average(pot_1,2);
    average(pot_1,3);
}

 

单片机C语言之六_____________________________________________________________________________________
        软件法去干扰:
    工程上我们在采集数据时一般要求精度达到5%%,大于这个值将认为无效。我在实际应用中采用8535对32路数据进行采集(8535带10位AD,带看门狗),发现数据跳动有时达7%%,这是由于各种干扰造成的。主要来自于随机干扰,下面就各种干扰的方法给出简单的去除方法:

    1、白噪声:最重要的统计特性为平均值为0,可采取每路数据采集几次求平均的方法;

    2、随机干扰:该点明显高于或低于附近正常采样值,故采取中值滤波法,即对被测信号连续采样M次,进行大小排序,取大小居中的1/3个采样值进行算术平均;

    3、电源干扰:特点是有固定周期,故可采用定时采样求平均的方法。
    由于各种排序与求平均算法用C易于实现,故C常常用于采集系统中软件去干扰。至于排序算法可参考上一篇文章,有一个经典的程序。
在实际中我们采用每路猜9个值,排序,取中间3个,求平均。然后。。,每路数据几乎不动!


单片机C语言之七_____________________________________________________________________________________
指针:可对内存地址直接操作
    基于存贮器的指以贮器类为参量,它在编译时才被确定。因此为指针选择存贮器的方法可以省掉,以这些指针的长度可为1个字节(idata *,data *,pdata *)或2个这节(code *,xdata *)。
    char xdata *address;

    ADC0809具有8个模拟量输入通道,采用中断方式,在中断函数中读取8个通道的A/D转换值,分别存储在外部RAM的1000H~1007H单元。ADC0809端口地址为00F0H。

    程序定义了两个指针变量* ADC和* ADCdata,分别指向ADC0809端口地址(00F0H)和外部RAM单元地址(1000H~1007H)
    由*ADC=I送入通道数,启动ADC0809进行A/D转换,转换结束时产生INT1中断。在中断服务函数int1()中通过temp=*ADC和*ADCdata=temp;读取A/D转换结果并存到外部RAM中。

    #include<reg51.h>
    unsigned int xdata *ADC; /*定义ADC0809端口指针*/
    unsigned int xdata *ADCdata; /*定义ADC0809数据缓冲器指针*/
    unsigned char I;
void main( )
{
    ADC=0x00f0; /*定义端口地址和数据缓冲器地址*/
    ADCdata=0x1000;
    I=8; /* ADC0809有8个模拟输入通道*/
    EA=1; EX1=1;IT1=1; /*开中断*/
    *ADC=I; /*启动ADC0809*/
    WHILE(I); /*等待8个通道A/D转换完*/
}
void int1() interrupt 2
{
    unsigned char tmp;
    temp=*ADC; /*读取A/D转换结果*/
    *ADCdata=temp; /*结果值存到数据缓冲区*/
    ADCdata++; /*数据缓冲区地址加1*/
    i—;
    *ADC=I; /*启动下一个模拟输入通道A/D转换*/
}

除了用指针变量来实现对内存地址的直接操作外,c51编译器还提供一组宏,该宏定义文件为:“absacc.h”,利用它可十分方便地实现对任何内存空间的直接操作,改写上面的程序:
    #include<reg51.h>
    #include<absacc.h> /*包含绝对地址操作预定义头文件*/
    #define ADC 0x00f0; /*定义ADC0809端口地址*/
    #define ADCdata 0X1000 /*定义数据缓冲器地址*/
    unsigned char I;
void main( )
{
    I=8; / *ADC0809有8个模拟输入通道*/
    EA=1;ex1=1;it1=1; / *开中断*/
    XBYTE[ADC]=I; /*启动0809 */
    While(i); /*等待8个通道转换完毕*/
}

void int1() interrupt2 {
    unsigned char tmp;
    tmp=XBYTE[ADC]; /*读取A/D转换结果*/
    i--;
    XBYTE[ADCdata+I]=tmp; /**结果值存储到数据缓冲器*/
    XBYTE[ADC]=I; /*启动下一个模拟输入通道A/D转换*/
}

两指针相减-----计算字符串的长度
    #include<stdio.h>
main() {
    char *s=”abcdef”;
    int strlen(char *s);
    printf(“\n length of ‘%%s’=%%d\n”,s,strlen(s));
}

int strlen(char *s) {
    char *p=s;
    while(*p!=’\0’)p++;
    return(p-s);
}

结果为:length of ‘abcdef’=6

注:不允许指针之间进行加,乘,除,移位,或屏蔽运算,也不允许用float类型数据与指针做加,减运算!

抽象型指针:
ANSI新标准增加了一种“void * ”的指针类型,这是一种抽象型指针,即可以定义一个指针变量,但不指定该指针是指向哪种类型的数据的。在赋值时需进行强制类型转换:
Char *p1;
Void *p2;
P1=(char*)p2;
抽象型指针可以用来在每个存储区内访问任意绝对地址或者用来产生绝对调用。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多
    喜欢该文的人也喜欢 更多

    ×
    ×

    ¥.00

    微信或支付宝扫码支付:

    开通即同意《个图VIP服务协议》

    全部>>