当前位置:网站首页 > 技术博客 > 正文

单片机c语言编程入门教程



1、结合8051介绍单片机C语言的优越性:
·无须懂得单片机的具体硬件,也能够编出符合硬件实际的专业水平的程序;
   ·不懂得单片机的指令集,也能够编写完美的单片机程序;
   ·不同函数的数据实行覆盖,有效利用片上有限的RAM空间;
   ·提供auto、static、const等存储类型和专门针对8051单片机的data、idata、pdata、xdata、code等存储类型,自动为变量合理地分配地址;
   ·C语言提供复杂的数据类型(数组、结构、联合、枚举、指针等),极大地增强了程序处理能力和灵活性;
   ·提供small、compact、large等编译模式,以适应片上存储器的大小;
   ·中断服务程序的现场保护和恢复,中断向量表的填写,是直接与单片机相关的,都由C编译器代办;
   ·程序具有坚固性:数据被破坏是导致程序运行异常的重要因素。C语言对数据进行了许多专业性的处理,避免了运行中间非异步的破坏
   ·提供常用的标准函数库,以供用户直接使用;
·有严格的句法检查,错误很少,可容易地在高级语言的水平上迅速地被排掉;
   ·可方便地接受多种实用程序的服务:如片上资源的初始化有专门的实用程序自动生成;再如,有实时多任务操作系统可调度多道任务,简化用户编程,提高运行的安全性等等。
   ·头文件中定义宏、说明复杂数据类型和函数原型,有利于程序的移植和支持单片机的系列化产品的开发;
2、HEX文件
建立了第一个单片机C语言项目,但为了让编译好的程序能通过编程器写入51芯 片中,要先用编译器生成HEX文件
3、C 编译器所支持的注释语句:
一种是以“//”符号开始的语句,符号之后 的语句都被视为注释,直到有回车换行。另一种是在“/”和“/”符号之内的为注释。注 释不会被 C 编译器所编译。
4、main函数:
一个 C 应用程序中应有一个 main 主函数,main 函数能调用别的功能函数,但其它功能函数不允许调用 main 函数。不论 main 函数放在程序中的那个位置, 总是先被执行。
5、最小系统
其中加了一个电阻和一个 LED,用以显示它的状态,晶体震荡器能根据自己的情况使用, 一般实验板上是用 11.0592MHz 或 12MHz,使用前者的好外是能产生标准的串行口波特率,后 者则一个机器周期为 1 微秒,便于做精确定时。

1、常量数据类型说明:
(1)整型常量能表示为十进制如 123,0,-89 等。十六进制则以 0x 开头如 0x34,-0x3B 等。长整型就在数字后面加字母 L,如 104L,034L,0xF340 等。
(2)浮点型常量可分为 十进 制和指数表示形式。指数表 示形式为[±]数字[.数字]e[±]数字,[]中的内容为可选项,其中内容根据具体情 况可有可无,但其余部分必须有,如125e3,7e9,-3.0e-3。
(3)字符型常量是单引号内的字符,如‘a’,‘d’等,不能显示的控制字符,能 在该字符前面加一个反斜杠“”组成专用转义字符。常用转义字符表请看表:
在这里插入图片描述
(4)字符串型常量由双引号内的字符组成,如“test”,“OK”等。当引号内的没有字 符时,为空字符串。在使用特殊字符时同样要使用转义字符如双引号。在 C 中字符 串常量是做为字符类型数组来处理的,在存储字符串时系统会在字符串尾部加上o 转义字符以作为该字符串的结束符。字符串常量“A”和字符常量‘A’是不一样的, 前者在存储时多占用一个字节的字间。
(5)位标量,它的值是一个二进制。
2、应用
常量可用在不必改变值的场合,如固定的数据表,字库等。常量的定义方式有几种,下面来加以说明。
#difine False 0x0;//用预定义语句能定义常量
#difine True 0x1;//这里定义 False 为 0,True 为 1
//在程序中用到 False 编译时自动用 0 替换,同理 True 替换为 1
unsigned int code a=100;//这一句用 code 把 a 定义在程序存储器中并赋值
const unsigned int c=100;//用 const 定义 c 为无符号 int 常量并赋值 以上两句它们的值都保存在程序存储器中,而程序存储器在运行中是不允许被修改的,所以如果在这两句后面用了类似 a=110,a++这样的赋值语句,编译时将会出错。

1、变量格式
[存储种类] 数据类型 [存储器类型] 变量名表
  在定义格式中除了数据类型和变量名表是必要的,其它都是可选项。
2、存储种类
存储种类有四种:自动(auto),外部(extern),静态(static)和寄存器(register),缺省类型为自动(auto)。
(1)static(静态局部)变量
在程序整个运行期间都不会释放内存。如果定义局部变量的时候不赋值,则编译的时候自动赋值为0。而对于自动变量而言,定义的时候不赋值,则是一个不确定的值。其他函数不能引用。
(2)用extern声明外部变量
一个程序能由多个源程序文件组成。如果一个程序中需要引用另外一个文件中已经定义的外部变量,就需要使用extern来声明。
例: 一个文件中: int abc;
另外一个文件中: extern abc;
3、数据类型
(1)数据类型
在这里插入图片描述
字节中最高位字节表示数据的符号,“0”表示正数,“1”表示负数, 负数用补码表示。
(2)特殊
bit 位标量是 c51 编译器的一种扩充数据类型,利用它可定义一个位标量,但不能定义 位指针,也不能定义位数组。它的值是一个二进制位,不是 0 就是 1,类似一些高级语 言中的 Boolean 类型中的 True 和 False。
sfr 也是一种扩充数据类型,点用一个内存单元,值域为 0~255。利用它能访问 51 单片机内部的所有特殊功能寄存器。
sfr16 占用两个内存单元,值域为 0~65535。sfr16 和 sfr 一样用于操作特殊功能寄存 器,所不一样的是它用于操作占两个字节的寄存器,如定时器 T0 和 T1。sfr16 T2 = 0xCC; //这里定义8052定时器2,地址为T2L=CCH,T2H=CDH。用sfr16定义16位特殊功能寄存器时,等号后面是它的低位地址,高位地址一定要位于物理低位地址之上。注意的是不能用于定时器0和1的定义。
sbit 同样是 单片机c语言 中的一种扩充数据类型,利用它能访问芯片内部的 RAM 中的可寻址位或特殊功能寄存器中的可寻址位。如先前定义了sfr P1=0x90;//因 P1 端口的寄存器是可位寻址的,所以能定义sbit P1_1=P1^1;//P1_1 为 P1 中的 P1.1 引脚。同样我们能用 P1.1 的地址去写,如 sbit P1_1=0x91;这样在以后的程序语句中就能用 P1_1 来对 P1.1 引脚进行读写操作了。
(3)重新定义数据类型的的语句typedef
typedef 的语法:typedef 已有的数据类型 新的数据类型名
样写:typedef int integer; integer a,b;
typedef 不能直接用来定义变量,它只是对已有的数据类型作一个名字上的置换,并不是产生一个新的数据类型。
4、存储器类型
指定该变量在单片机c语言硬件系统中所使用的存储区域,并在编译时准确的定位。
在这里插入图片描述
注意的是在AT89c51芯片中RAM只有低128位,位于80H到FFH的高128位则在52芯片中才有用,并和特殊寄存器地址重叠。如果省略存储器类型,系统则会按编译模式SMALL,COMPACT或LARGE所规定的默认存储器类型去指定变量的存储区域。无论什么存储模式都能声明变量在任何的8051存储区范围,然而把最常用的命令如循环计数器和队列索引放在内部数据区能显著的提高系统性能。还有要指出的就是变量的存储种类与存储器类型是完全无关的。
(1)特殊寄存器(SFR)的地址
AT89C51特殊功能寄存器列表(适用于同一架构的芯片)

在这里插入图片描述
带*号的特殊功能寄存器都是可以位寻址的寄存器
(2)数据存储模式
①Small模式:所有缺省变量参数均装入内部RAM,优点是访问速度快,缺点是空间有限,只适用于小程序。
②Compact模式:所有缺省变量均位于外部RAM区的一页(256Bytes),具体哪一页可由P2口指定,在STARTUP.A51文件中说明,也可用pdata指定,优点是空间较Small为宽裕速度较Small慢,较large要快,是一种中间状态。
③large模式:所有缺省变量可放在多达64KB的外部RAM区,优点是空间大,可存变量多,缺点是速度较慢。
5、Keil c51指针变量
单片机c语言支持一般指针(Generic Pointer)和存储器指针(Memory_Specific Pointer)。
(1)一般指针
一般指针的声明和使用均与标准C相同,不过同时还能说明指针的存储类型,例如:char * xdata ptr;ptr为一个指向char数据的指针,而ptr本身放于外部RAM区。一般指针本身用3个字节存放,分别为存储器类型,高位偏移,低位偏移量。
(2)存储器指针
基于存储器的指针说明时即指定了存贮类型,例如:char data * str;str指向data区中char型数据;这种指针存放时,只需一个字节或2个字节就够了,因为只需存放偏移量。
(3)指针转换
当基于存储器的指针作为一个实参传递给需要一般指针的函数时,指针自动转化。如果不说明外部函数原形,基于存储器的指针自动转化为一般指针,导致错误,因而请用“#include”说明所有函数原形。
6、单片机c语言中变量的空间分配几个方法

(1)data区空间小,所以只有频繁用到或对运算速度要求很高的变量才放到data区内,比如for循环中的计数值。
data区内最好放局部变量。局部变量空间在退出该函数是就释放,当然静态局部变量除外,其内存使用方式与全局变量相同;
(2)确保你的程序中没有未调用的函数。在Keil
C里遇到未调用函数,编译器就将其认为可能是中断函数。函数里用的局部变量的空间是不释放,也就是同全局变量一样处理。
(3)程序中遇到的逻辑标志变量能定义到bdata中,能大大降低内存占用空间。
(4)其他不频繁用到和对运算速度要求不高的变量都放到xdata区。如果想节省data空间就必须用large模式,将未定义内存位置的变量全放到xdata区。当然最好对所有变量都要指定内存类型。
(5)当使用到指针时,要指定指针指向的内存类型。未定义指向内存类型的通用指针占用3个字节;而指定指向data区的指针只占1个字节;指定指向xdata区的指针占2个字节。如指针p是指向data区,则应定义为:char
data *p;。还可指定指针本身的存放内存类型,如:char data *xdata p;

运算符按其表达式中与运算符的关系可分为单目运算符,双目运算符和三目运算符。单目就是指需要有一个运算对象,双目就要求有两个运 算对象,三目则要三个运算对象。表达式则是由运算及运算对象所组成的具有特定含义的式子。表达式后面加“;”号就构成了一个表达式语句。
1、运算符和表达式
①赋值运算符:变量=表达式;
②算术运算符:+,/,-,,%;除法运算符和一般的算术运算规则有所不一样,如是两浮点数相除,其结果为浮点数,如10.0/20.0 所得值为 0.5,而两个整数相除时,所得值就是整数,如7/3,值为2。
③++ 增量运算符,-- 减量运算符。这两个运算符是 C 语言中特有的一种运算符。在 VB,PASCAL 等都是没有的。I++(或I–)是先使用I的值,再执行 I+1(或 I-1);++I(或–I)是先执行I+1(或 I-1),再使用I的值。
④关系运算符:>,<,>=,<=,==,!=。“==”在 VB 或 PASCAL 等中是用“=”,“!=”则是用“not”。
⑤逻辑运算符:逻辑与:条件式1&&条件式2;逻辑或:条件式1||条件式2;逻辑非: !条件式2。注意的是用逻辑运算符的运算结果只有0和1两种,也就是逻辑的真与假,换句话说也就是逻辑量。
⑥位运算符:~,<<,>>,&,^,|。位运算符的作用是按位对变量进行运算,但是并不改变参与运算的变量的值。如果要求按位改变变量的值,则要利用相应的赋值运算。位运算符是不能用来对浮点型数据进行操作的。
⑦复合赋值运算符:在赋值运算符“=”的前面加上其他运算符。+=,-=,
=,/=,>>=,&=,|=,^=,%=,!=,<<=。a+=56等价于a=a+56;y/=x+9 等价于y=y/(x+9)。
⑧逗号运算符
⑨条件运算符:逻辑表达式? 表达式1 : 表达式2。当逻辑表达式的值为真时(非0值)时,整个表达式的值为表达式1的值;当逻辑表达式的值为假(值为0)时,整个表达式的值为表达式2的值。
⑩指针和地址运算符:*: 取内容;&:取地址;取内容和地址的一般形式分别为:变量=*指针变量;指针变量=&目标变量。 ⑪sizeof 运算符:语法:sizeof(数据类型)。例句:printf(“char 是多少个字节?½字节 ”,sizeof(char)),结果是:char 是多少个字节?1字节。
⑫强制类型转换运算符:语句:(类型) 表达式。
注:程序进行编译时由编译器自动去处理完成的转换称为隐式转换。其规则如下:
变量赋值时发生的隐式转换,“=”号右边的表达式的数据类型转换成左边变量的数据类型。
所有 char 型的操作数转换成 int 型。
两个具有不一样数据类型的操作数用运算符连接时,隐式转换会按以下次序进行:如有一操作数是float类型,则另一个操作数也会转换成float类型;如果一个操作数为long类型,另一个也转换成long;如果一个操作数是 unsigned 类型,则另一个操作会被转换成 unsigned 类型。

1、不一样的程序设计语言都会有不一样的表达式语句,如VB就是在表达式后面加入回车就构成了VB 的表达式语句,而在51单片机的C语言中则是加入分号“;”构成表达式语句。
2、一个特殊的表达式语句,称为空语句
通常用while,for 构成的循环语句后面加一个分号,形成一个不执行其它操作的空循环体。
示例:#include <AT89x51.h>
void main(void)
{ unsigned int a;
do
{
P1 = 0xFF; //关闭 P1 上的 LED
while(P3_7); //空语句,等待 P3_7 按下为低电平,低电平时执行下面的语句 P1 = 0; //点亮 LED
for(;a<60000;a++); //这也是空语句的使用方法,注意 a 的初值为当前值
} //这样第一次按下时会有一延时点亮一段时间,以后按多久就亮多久
while(1); //点亮一段时间后关闭再次判断 P3_7,如此循环
}
3、复合语句:将若干条语句组合在一起形成一种功能块,这种由若干条语句组合 而成的语句就叫复合语句。它内部的各条语句还是需要以分号“;” 结束。复合语句是允许嵌套的。对于一个函数而言,函数体就是一个复合语句,也许大家会因 此知道复合语句中不单能用可执行语句组成,还能用变量定义语句组成。
4、条件语句:
C 语言供给了 3 种形式的条件语句:
①if(条件表达式) 语句。当条件表达式的结果为真时,就执行语句,不然就跳过。 如if(ab) a++; 当a等于b时,a就加1
②if(条件表达式) 语句1 else 语句2。当条件表达式成立时,就执行语句1,不然就执行语句2。如if(a
b) a++;else a–;当a等于b时,a加1,不然a-1。
③if(条件表达式1) 语句1;
else if(条件表达式2) 语句2;
else if(条件表达式3) 语句3;
else if(条件表达式m) 语句n;
else 语句m;
5、开关语句:
语法:switch(表达式)

 

6、循环语句:在C语言中构成循环控制的语句有while,do-while,for和goto语句。
(1)goto语句
一个无条件的转向语句。语法:goto 语句标号; 其中的语句标号为一个带冒号的标识符。示例如下:
void main(void)
{
unsigned char a;
start: a++;
if (a==10) goto end;
goto start;
end:;
}

常见的 goto 语句使用方法是用它来跳出多重循环,不过它只能从内层循环 跳到外层循环,不能从外层循环跳到内层循环。(2)while语句
语法:while(条件表达式) 语句;
当条件表达式为真时,它才执行后面的语句,执行完后再次回到 while 执行条件判断,为真时重复执行语句,为假时退出循环体。当条件一开始就为假时, 那么 while 后面的循环体(语句或复合语句)将一次都不执行就退出循环。在调试程序时要注意 while 的判断条件不能为假而造成的死循环,调试时适当的在 while 处加入断点,也许 会使你的调试工作更加顺利。
(3)do while语句
语法:do 语句 while(条件表达式);
先执行循环体,再根据条件判断是否要退出循环。
(4)for语句
for([初值设定表达式];[循环条件表达式];[条件更新表达式]) 语句;语句中括号中的表达式是可选的。
7、continue 语句(也叫中断语句)
一个无条件跳转语句。作用是结束本次循环,跳过循环体中没有执行的语句,跳转到下一次循环周期。和前面说到的break语句有所不一样,continue执行后不是跳出循环,而是跳到循环的开始并执行下一次的循环。
8、return语句
语法:return (表达式);返回时先计算表达式,再返回表达式的值。不带表达式则返回的值不确定。

1、函数的定义
定义的模式如下:
函数类型 函数名称(形式参数表)

函数体

函数类型是说明所定义函数返回值的类型。返回值其实就是一个变量,只要按变量类型来定义函数类型就行了。如函数不需要返回值函数类型能写作“void”表示该函数没 有返回值。注意的是函数体返回值的类型一定要和函数类型一致。形式参数是指调用函数时要传入到函数体内参与运算的变量,它能有一个、几个或没有,当不需要形式参数也就是无参函数,括号内能为空或写入“void”表示,但括号不能少。函数体中能包含有局部变量的定义和程序语句,如函数要返回运算值则要使用return语句进行返回。在函数的{}号中也能什么也不写,这就成了空函数,在一个程序项目中能写一些空函数,在以后的修改和升级中能方便的在这些空函数中进行功能扩充。
2、函数调用
(1)在调用函数前,必须对函数的类型进行说明,就算是标准库函数也不例外。标准库函数的说明会被按功能分别写在不一样的头文件中,使用时只要在文件最前面用#include< .h> 预处理语句引入相应的头文件。
(2)函数语句。如printf(“Hello World! ”); 这是在我们的第一个程序中出现的,它以"Hello World! "为参数调用printf这个库函数。在这里函数调用被看作了一条语句。
(3)函数参数:如 temp=StrToInt(CharB(16));CharB 的返回值作为 StrToInt 函数的实际参数传递。
(4)函数表达式:temp=Count();这样一句,这个时候函数的调用作为一个运算对象出现在表达式中,能称为函数表达式。
(5)调用的是自定义的函数则要用如下形式编写函数类型说明:类型标识符 函数的名称(形式参数表); 这样的说明方式是用在被调函数定义和主调函数是在同一文件中。
(6)写到 文件名.h 的文件中用#include "文件名.h"引入。
如果被调函数的定义和主调函数不是在同 一文件中的,则要用如下的方式进行说明,说明被调函数的定义在同一项目的不一样文件之上
extern 类型标识符 函数的名称(形式参数表);
3、中断服务函数
扩展的关键字是 interrupt,它是函数定义时的一个选项。
形式:函数类型 函数名(形式参数) interrupt n [using n]
AT89C51芯片中断号和中断向量
在这里插入图片描述

例:unsigned int xcount [10]; //定义无符号整形数组,有 10 个数据单元
char inputstring [5]; //定义字符形数组,有 5 个数据单元
float outnum [10],[10];//定义浮点型数组,有 100 个数据单元
unsigned char LEDNUM[2]={12,35}; //一维数组赋初值
int Key[2][3]={{1,2,4},{2,2,1}}; //二维数组赋初值
unsigned char IOStr[]={3,5,2,5,3}; //没有指定数组长度,编译器自动设置
unsigned char code skydata[]={0x02,0x34,0x22,0x32,0x21,0x12}; //数据保存在 code 区

例: unsigned char xdata *pi;//指针会占用二字节,指针自身存放在编译器默认存储区,指向xdata存储区的char类型
unsigned char xdata * data pi; //除指针自身指定在 data 区,其它同上
int * pi; //定义为一般指针,指针自身存放在编译器默认存储区,占三个字节
指针变量最大的值为 0xFFFF,这样就决定了一般指针在内存会占用3个字节,第一字节存放该指针存储器类型编码,后两个则存放该指针的高低位址。而基于存储器的指针因为不用识别存储器类型所以会占一或二个字节,idata,data,pdata存储器指针占一个字节,code,xdata则会占二个字节。

1、结构:一种数据的集合体
(1)结构类型一般定义格式:struct 结构名 {结构元素表};
例:truct FileInfo{unsigned char FileName[4]; unsigned long Date; unsigned int Size;}
(2)定义结构变量格式:struct 结构名 结构变量名1,结构变量名2,……结构变量N;
例:struct FileInfo NewFileInfo, OleFileInfo;
只有结构变量才能参与程序的执行,结构类型只是用于说明结构变量是属于那一种结构。通过上面的定义 NewFileInfo 和 OleFileInfo 都是 FileInfo 结构,都具有一个字符型数组一个长整型和一个整形数据。定义结构类型只是给出了这个结构的组织形式,它不会占用存储空间,也就说结构名是不能进行赋值和运算等操作的。结构变量则是结构中的具体成员, 会占用空间,能对每个成员进行操作。结构是允许嵌套的,也就是说在定义结构类型时,结构的元素能由另一个结构构成。
例: struct clock{unsigned char sec, min, hour;}
struct date{
unsigned int year;
unsigned char month,day;
struct clock Time; //这是结构嵌套
}
struct date NowDate; //定义 data 结构变量名为 NowDate
(3)引用、赋值
格式:结构变量名.结构元素。
要存取上例结构变量中的月份时,就要写成 NowDate.year=2021。
NowDate.Time.min++; //分针加 1,嵌套时只能引用最低一级元素一个结构变量中元素的名字能和程序中其他地方使用的变量同名,因为元素是属于它所在的结构中,使用时要用成员运算符指定。
(4)其他定义方式
struct{结构元素表} 结构变量名1,结构变量名2……结构变量名N;
例:struct{unsigned char FileName[4]; unsigned long Date; unsigned int Size;} NewFileInfo, OleFileInfo;
这一种定义方式定义没有使用结构名,称为无名结构。通常会用于程序中只有几个确定 的结构变量的场合,不能在其它结构中嵌套。
struct 结构名{结构元素表} 结构变量名1,结构变量名2……结构变量名N;
例:struct FileInfo{unsigned char FileName[4]; unsigned long Date; unsigned int Size;} NewFileInfo, OleFileInfo;
使用结构名能便于阅读程序和便于以后要在定义其它结构中使用。
2、枚举:把某些整型常量的集合用一个名字表示,其中的整型常量就是这种枚举类型变量的可取的合法值。
(1)定义格式
enum 枚举名 {枚举值列表} 变量列表;例:enum TFFlag {False, True} TFF;
enum 枚举名 {枚举值列表}; emum 枚举名 变量列表;例:enum Week {Sun,Mon,Tue,Wed,Thu,Fri,Sat};enum Week OldWeek,NewWeek;
(2)在枚举列表中,每一项名称代表一个整数值,在默认的情况下,编译器会自动为每一项赋值,第一项赋值为0,第二项为1……如Week中的Sun为0,Fri为5。C语言也允许对各项值做初始化赋值,要注意的是在对某项值初始化后,它的后续的各项值也随之递增。
例: enum Week {Mon=1, Tue, Wed, Thu, Fri, Sat, Sun};
3、联合
(1)它和其他结构类型一样能包含不一样类型的数据元素。所不一样的是其他类型的数据元素都是从同一个数据地址开始存放,结构变量占用的内 存大小是该结构中数据元素所占内存数的总和,而联合变量所占用内存大小只是该联合中最长的元素所占用的内存大小。
(2)程序先为联合中的 int 赋值 1000,后来又为 char 赋值 10,那么这个时候就不能引用int了,不然程序会出错,起作用的是最后一次赋值的元素,而上一次赋值的元素就失效了。使用中还要注意定义联合变量时不能对它的值初始化、能使用指向联合变量的指针对其操作、联合变量不能作为函数的参数进行传递,数组和结构能出现在联合中。
(3)定义形式:union 结构名{结构元素表};
union 结构名 结构变量名1,结构变量名2,……结构变量N;
union {结构元素表} 结构变量名1,结构变量名2……结构变量名N;
union 结构名{结构元素表} 结构变量名1,结构变量名2……结构变量名N;

版权声明


相关文章:

  • js实现轮播图原理及示例2024-11-06 13:30:01
  • python的jieba库教程2024-11-06 13:30:01
  • 数字图像处理实验设计2024-11-06 13:30:01
  • diskgenius修复磁盘错误2024-11-06 13:30:01
  • css组合选择器有哪些2024-11-06 13:30:01
  • linux安装nginx详细教程2024-11-06 13:30:01
  • devc++安装步骤2024-11-06 13:30:01
  • 左外连接 右外连接2024-11-06 13:30:01
  • 新闻管理系统流程图2024-11-06 13:30:01
  • 有哪些项目是k8s集群2024-11-06 13:30:01