RYMCU

第5章 单片机 C 语言基础(一)

5.1 进制转换基础知识

进制实际是一个非常简单易懂的概念,对于初学者来说也很容易上手。我们接触最多的就是十进制了,它的特点为逢十进一,包含 0,1,2,3,4,5,6,7,8,9 共十个元素。

在生活中我们用到的基本都是十进制了,所以大家对它已经非常熟悉并能应用自如,但是在计算机(包括单片机)世界里,所有都是以二进制为基础的。二进制的特点为逢二进一,包含 0,1 共两个元素。计算机中的数据都是以二进制存储的,这就是我们所说的 0,1 世界。

通常我们讲的 32 位或 64 位操作系统这里的位指的就是二进制位数。因为我们实际多用十进制,那么我们在和计算机系统沟通过程中,十进制与二进制之间的转换就变得很重要了。进制之间的转换如下表所示。

表 5-1 常用进制转换表

十进制 二进制 十六进制
0 0b0 0x00
1 0b1 0x01
2 0b10 0x02
3 0b11 0x03
4 0b100 0x04
5 0b101 0x05
6 0b110 0x06
7 0b111 0x07
8 0b1000 0x08
9 0b1001 0x09
10 0b1010 0x0A
11 0b1011 0x0B
12 0b1100 0x0C
13 0b1101 0x0D
14 0b1110 0x0E
15 0b1111 0x0F
16 0b10000 0x10
17 0b10001 0x11

二进制转换十进制公式如下:

其中,n 表示二进制的位数。

下面我们举个例子来更加直观的说明这个公式:

例如: 0b 1101,这是一个 4 位的二进制数, 0b 表示二进制,计算如下:

大家可以利用这个公式计算的结果和表 4-1 进行一一对照。

十六进制也是我们常用的进制,它的特点为逢十六进一,包括 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F 共十六个元素。实际上十六进制是二进制的一种特殊形式,十六进制的 1 位等价于二进制的 4 位,在 C 语言编程中我们常用十六进制来表示二进制数。在实际应用中我们常常在数字之前加一个前缀来表示他的进制:"
0b" 表示二进制, "0x" 表示十六进制。下面我们举例说明:

0b1001,0010 = 0x92

上面一个八位的二进制数转换为一个两位的十六进制数。二进制的前 4 位等于十六进制的第 1 位:

0b1001 = 0x9

二进制数的后 4 位等于十六进制的第 2 位:

0b0010 = 0x2

在计算机中,我们通常所说的二进制的 1 位也叫 1bit,8 位表示 1 个字节,也叫 1Byte。根据二进制与十六机制的关系,一个 2 位的十六进制数则可表示 1 个字节。在运用的过程中牢记 0~15
的十进制与二进制、十六进制之间的转换关系,对程序的编写有很大的好处。

5.2 字节表示法

我们通常用下图所示的方式来表示 1 个字节,也就是 1Byte。8 个连续的方框,每一个方框表示 1 位,即 1bit,总共 8bit 组成 1 个字节。从左往右,第一个方框表示字节的最高位,也称为 bit7,或者叫
MSB。最后一个方框表示字节的最低位,也称为 bit0,或者叫 LSB。方框内的值要么是 0,要么是 1。

图 5-1 字节表示法

注:MSB:Most Significant Bit, LSB:Least Significant Bit。

下面我们举两个例子,并熟悉一下进制之间的转换。1 个字节能表示的最大值,自然是每一个方框中的值都为 1,转换成十进制等于 255,十六进制表示为 0xFF。最小值能表示的自然是每个方框中的值都为 0,如下图所示。

图 5-2 进制转换

上面这两个算是比较特殊的值,很容易记住,其他的值靠记忆似乎不太好办,我们可以利用电脑的计算器工具快速计算。例子如下:

图 5-3 进制转换实例

打开电脑计算器,选择程序员模式,输入数字可以在各个进制之间自由转换。

5.3 简便转换法

记住 8 个数 128, 64, 32, 16,8,4, 2,1,分别对应 1Byte 中的 8 个 位,二进制如果是 1,那么表示那个位上的值,如果为 0 那值就为 0,最后相加即是 10 进制。

例如 0b1001,0110 转换成 十进制

1 0 0 1 0 1 1 0
128 0 0 16 0 4 2 0

最后得出 128+16+4+2 = 150

二进制十六进制记住 4 个数 8,4,2,1,将 8 位拆成两个 4 位,相加然后转换成 十六进制贴在一起即可:

1 0 0 1
8 0 0 1

9

0 1 1 0
0 4 2 0

6

最后得出:0x96

5.4 数据存储方式

上一章的单片机内部结构讲到过程序存储器 Flash,他数据存储方式按照下面讲的方式存储。

图 5-4 单片机存储结构

如上图所示,存储器就是一个个字节叠加起来,类似于盖楼房。每一层表示一个字节,每一层给一个地址,相当于楼层号码一样。如上图所示,第一、二、三层的地址分别是 0x000x010x02,一般情况下地址都是连续增加的。

因此,我们只要知道地址,就能找到指定的字节了,单片机正是通过地址来实现对存储器的读写的。例如,我们 STC89C52RC 单片机的程序存储器的大小为 8K 字节,也就是 8*1024=8192 个字节,每一个字节有一个地址,而且从 0 开始的话,那么它的地址范围为 0~8191,转换为十六进制的话,地址范围为 0x00~0x1FFF

现实中的硬盘,内存条的硬件内部的结构就如上图所示,只不过每一个方框就是一个晶体管,一个晶体管可以存储 0 或者 1,也就是 1bit。

接着上面的讲,一个晶体管能存储 1bit 的数据,也就是说芯片上集成的晶体管越多,能存储的数据就更大,运行的程序可以更复杂,某种意义上也就是芯片的运算能力越强了。所以,许多芯片或者手机厂商经常宣传说他的芯片有多少亿个晶体管,比同行性能强多少倍,就是这个道理。

另外一个就是芯片制造工艺,有的是 16 纳米,有的是 5 纳米,3 纳米,这里的纳米指的就是能把晶体管做的多小,所以在相同面积的芯片上,3 纳米工艺自然能比 5 纳米工艺多集成晶体管数量了。那 5 纳米工艺制造的手机芯片自然没有 3 纳米工艺制造的有竞争力了。

5.5 第一个 C 语言程序

C 语言是一种通用的、面向过程式的计算机程序设计语言。 1972 年,丹尼斯·里奇在贝尔电话实验室设计开发了 C 语言。

我们的第一个 C 语言程序的功能为:编写一个能够在桌面上显示 "Hello,World!" 的程序,通过这例子掌握 C 语言程序的基本结构。

5.5.1 C 环境设置

想让 C 语言编写的程序能够正常运行,需要安装两种软件,分别为文本编辑器和 C 编译器,编译器的作用就是把我们写的 C 语言代码转成成计算机可识别的代码,相当于翻译员。

Window 自带文本编辑器,无需安装。 C 语言编译器我们使用 GCC + MinGW,软件可以从网络上下载或者在 Nebula-Pi 资料包获得,如下图所示:

图 5-5 编译器工具路径

将工具软件解压至电脑即可,无需安装! 同时将 GCC、MinGW 的 bin 子目录到添加到您的 PATH 环境变量中,右键我的电脑,在"高级系统设置"中找到"系统属性",添加步骤如下图所示。

图 5-6 添加环境变量

至此,工具软件准就绪了,打开 windows 系统命令提示符,输入 " gcc -v" 如下图所示,如果出现了 gcc 版本信息表明我们上面的操作无误。接下来开始撸第一段 C 语言代码了。

图 5-7 GCC 版本检查

5.5.2 编写第一段 C 代码

新建文本 hello.txt,并输入如下代码,存储为 hello.c,务必要将后缀改成.c

#include <stdio.h>

void main() {
    /* 第一段C语言代码 */
    printf("Hello, World! n");
    // 返回
    return;
}

图 5-8 第一行 C 代码

代码很简单,总共只有 8 行,下面逐行进行解释:

a) 程序的第 1 行 #include <stdio.h> 是预处理器指令,告诉 C 编译器在实际编译之前要包含 stdio.h 文件。

b) 第 2 行 void main()为 C 语言的主函数,程序从这里开始执行。在整个代码中有且只能有一个 main() 函数,是程序的唯一入口,和第 3 行,第 8 行的"{ }"是一体的,所有代码放在这对双括号里面。

c) 第 4 行为注释,放置在 /*...*/ 之间的内容会被编译器忽略,这就是注释内容,方便程序员看的。

d) 第 5 行 printf(...) 是 C 中另一个可用的函数,会在屏幕上显示消息 "Hello, World!"。

e) 第 6 行也是注释,放在 // 后面的内容会被编译器忽略,这个是单行注释,下一行的内容不会注释掉。

f) 第 7 行 return 为 C 语言关键字,表示 main() 函数运行结束。

g) 第 5,7 行语句后面都有一个分号,表明语句结束,C 语言要求在每条语句之后添加";"。

h) 代码编写完成,下面编译并执行。

5.5.3 编译执行 C 代码

操作步骤如下:

a) 新建文本文件,添加上述代码;

b) 文件保存为 hello.c,一定要确保文件格式为 .c

c) 打开系统命令提示符 CMD 窗口,并进入保存 hello.c 的目录下,切换目录可以使用 "cd /d + 目录地址";

d) 输入 gcc hello.c,按回车,开始编译代码;

e) 如代码编译成功,将会在目录下生产 a.exe 的可执行文件;

f) 命令行输入 a.exe,执行程序;

g) 屏幕将出现 "Hello World!" 字样,如下图所示。

图 5-9 第一行 C 代码执行结果

5.6 C 语言语法

通过上一节掌握 C 语言程序的基本结构,本节开始讲解 C 语言的基本语法。

5.6.1 标识符与关键字

标识符其实就是一个代号,就像人的名字一样。在 C 语言中,常量、变量、函数的名称都是标识符。

标识符可以根据你个人的喜好来决定,前提条件是必须遵守 C 语言的一些规则:

  1. 标识符只能是由英文字母(A-Z, a-z),数字(0-9),下划线(_)组成的字符串,并且第一个字符不能是数字。除此之外的特殊字符如 @,$等不能出现在标识符中,例子如下:

    // 定义了一个标识符,即名称为 LED_0 的整型变量
    int LED_0 ;
    
  2. C 语言区分大小写,下面表示两个不同的变量:

    //LED_0、led_0 为两个不同的变量
    int LED_0;
    int led_0;
    
  3. 关键字不能作为标识符使用。

关键字是 C 语言程序中保留的一些字符串,有特定的用途,不能用来当做标识符。例如语句 "int for ;"是错误的,会在编译的时候报错。因为 for 是关键字,不能用来当做变量名。C 语言关键字如下:

图 5-10 C 语言关键字

上图为 32 个关键字,量不大,不用死记硬背,用多了自然能烂熟于胸。各个标识符功能介绍如下:

表 5-2 C 语言关键字

序号 标识符 功能
1. int 声明整型变量或函数
2. float 声明浮点型变量或函数返回值类型
3. double 声明双精度浮点型变量或函数返回值类型
4. char 声明字符型变量或函数返回值类型
5. short 声明短整型变量或函数
6. long 声明长整型变量或函数返回值类型
7. signed 声明有符号类型变量或函数
8. unsigned 声明无符号类型变量或函数
9. if 条件语句
10. else 条件语句否定分支(与 if 连用)
11. switch 开关语句分支
12. case 开关语句分支
13. default 开关语句中的"其它"分支
14. for 一种循环语句
15. while 循环语句的循环条件
16. do 循环语句的循环体
17. break 跳出当前循环
18. continue 结束当前循环,开始下一轮循环
19. return 子程序返回语句(可以带参数,也可不带参数)
20. void 声明函数无返回值或无参数,声明无类型指针
21. const 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变
22. sizeof 计算数据类型或变量长度(即所占字节数)
23. struct 声明结构体类型
24. typedef 用以给数据类型取别名
25. static 声明静态变量
26. extern 声明变量或函数是在其它文件或本文件的其他位置定义
27. auto 声明自动变量
28. register 声明寄存器变量
29. enum 声明枚举类型
30. goto 无条件跳转语句
31. union 声明共用体类型
32. volatile 说明变量在程序执行中可被隐含地改变

5.6.2 常量与变量

在程序中有些数据是不需要改变的,我们把这些不需要改变的固定值称为常量。例如,我们前面程序用到的 "hello world!"就是一个字符串常量。常量的类型包括:整数常量、浮点数常量、字符常量、字符串常量。下面举例说明:

// 55 为整数常量
int  a = 55 ; 
// 1.23 为浮点数常量,即常说的小数
float  b = 1.23 ; 
// a 为字符常量,需把字符常量需放在单引号里面
char mychar = 'a'; 
// hello world 为字符串常量,需用双引号括起来
printf("hello world ");

有些特殊字符常量是无法通过字母来表示的,例如回车、换行等。C 语言通过转义字符来实现,即反斜杠加字符的方式,例如'\n',表示换行符。如果我们前面的 hello .c 程序中想要把 hello 和 world 分别显示在两行,修改程序如下:

#include <stdio.h>

void main() {
    /* 第一段C语言代码 */
    printf("HellonWorld");  // n表示换行
    // 返回
    return;
}

运行结果如下:

图 5-11 运行结果

常用转义字符列表如下:

表 5-3 C 转义序列

序号 转义序列 含义
1. \n 换行符
2. \r 回车符
3. \b 退格符
4. \ \ 字符
5. ' ' 字符
6. " " 字符
7. \a 警报铃声
8. \f 换页符
9. \t 水平制表符
10. \v 垂直制表符

单片机 C 编程中最常用的转义字符为换行符(\n)回车符(\r)

在 C 语言中最常用的常量定义法为使用关键字#define,我们通过一个计算圆的周长的例子来说明,编辑代码,存储为 perimeter.c,如下所示:

/*
程序功能:计算圆的周长
*/
#include <stdio.h>

// 定义两个常量 PI,R
#define PI 3.14
#define R 10

void main() {
    // 定义周长变量
    float L;
    // 周长计算公式:L = 2πr
    L = 2 * PI * R;
    // 显示结果
    printf("圆的周长为: %f", L);
    return;
}

图 5-12 使用 #define

运行结果如下:

图 5-13 define 使用试验

上述代码中第 6、7 行,我们用 #define 定义了两个常量 PI 和 R。定义完后,标识符 PI 就等于 3.14,标识符 R 就等于 10,在程序中可以直接使用标识符进行运算,使用标识符能够直观的表示它具体代表什么意思,极大的减轻了程序员的负担。

程序中不能改变数据的称为常量,相对的,能改变的称为变量。变量用来存储程序运行过程产生的中间数据。这些数据实际是存储在内存当中的,变量的定义就是给这些内存取一个名字,编写程序的时候通过这个变量名就可以操作这些内存。变量定义的基本形式如下:

变量类型名 变量名;

举类说明如下:

int a;
char b;
float c;

定义了 int, char, float 三种类型的变量,变量名分别为 a,b,c。

变量定义的另一种形式:

变量类型名 变量名 1,变量名 2,变量名 3;

类型相同的变量可放在同一行,变量名之间用逗号隔开,如下:

int a,b,c;
char d,e,f;

变量定义好之后,可以进行变量的初始化,格式如下:

变量名 = 值;

举例说明:

// 变量定义
int a;
char b;
float c;

// 变量赋值
a = 1;
b = 'x';
c = 1.23;

上述语句中,第 2-4 行是变量的定义,第 6-8 行为变量赋值:将 1 赋值给 int 类型的变量 a,执行完这条语句之后 a 就等于 1 了。字符 x 赋值给 char 类型变量 b,3.14 赋值给了 float 类型的变量 c。还可以在变量定义的时候直接赋值,即变量的初始化,格式如下:

变量类型名 变量名 = 值;

变量类型名 变量名 1 = 值 1,变量名 2 = 值 2,变量名 3 = 值 3......;

举例说明:

// 变量定义,并初始化
int a =1;
char b = 'x';
float c = 1.23, d=4.56, e=7.89;

值得注意的是,只有变量的数据类型相同时,才可以在同一行语句里进行初始化。

5.6.3 数据类型

前面我们定义了变量,那么变量在内存实际占用了多大空间呢?数据类型指定变量占用空间的大小。常用数据类型以及所占内存大小如下表所示:

表 5-4 数据类型

序号 数据类型 位数 占内存字节数 十进制取值范围
1. bit 1 0~1
2. signed char 8 1 -128~127
3. unsigned char 8 1 0~255
4. enum 16 2 -32768~32767
5. signed short 16 2 -32768~32767
6. unsigned short 16 2 0~65535
7. signed int 16 2 -32768~32767
8. unsigned int 16 2 0~65535
9. signed long 32 4 -2147483648~2147483648
10. unsigned long 32 4 0~4294967295
11. float 32 4 0.175494E-38~0.402823E+38
12. sbit 1 0~1
13. sfr 8 1 0~255
14. sfr16 16 2 0~65535

在单片机 C 语言编程中,上述代码中的 char, int 相当于表格中的 unsigned char, unsigned int 。表格中第 12-14 行为单片机特有的数据类型。 sbit 存储空间为 1bit,取值范围为 0 或者 1。

sfr 为特殊功能寄存器类型, 8bit,即一个字节,可表示的范围为 0-255。

后发布评论