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 单片机存储结构
如上图所示,存储器就是一个个字节叠加起来,类似于盖楼房。每一层表示一个字节,每一层给一个地址,相当于楼层号码一样。如上图所示,第一、二、三层的地址分别是 0x00
, 0x01
,0x02
,一般情况下地址都是连续增加的。
因此,我们只要知道地址,就能找到指定的字节了,单片机正是通过地址来实现对存储器的读写的。例如,我们 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 语言的一些规则:
-
标识符只能是由英文字母(A-Z, a-z),数字(0-9),下划线(_)组成的字符串,并且第一个字符不能是数字。除此之外的特殊字符如 @,$等不能出现在标识符中,例子如下:
// 定义了一个标识符,即名称为 LED_0 的整型变量 int LED_0 ;
-
C 语言区分大小写,下面表示两个不同的变量:
//LED_0、led_0 为两个不同的变量 int LED_0; int led_0;
-
关键字不能作为标识符使用。
关键字是 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。