Nebula Pi·

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

ronger

ronger

3274 0

5.1 进制转换基础知识

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

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

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

表 5-1 常用进制转换表

十进制二进制十六进制
00b00x00
10b10x01
20b100x02
30b110x03
40b1000x04
50b1010x05
60b1100x06
70b1110x07
80b10000x08
90b10010x09
100b10100x0A
110b10110x0B
120b11000x0C
130b11010x0D
140b11100x0E
150b11110x0F
160b100000x10
170b100010x11

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

其中,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 转换成 十进制

10010110
12800160420

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

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

1001
8001

9

0110
0420

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("Hello\nWorld");  // \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.bit10~1
2.signed char81-128~127
3.unsigned char810~255
4.enum162-32768~32767
5.signed short162-32768~32767
6.unsigned short1620~65535
7.signed int162-32768~32767
8.unsigned int1620~65535
9.signed long324-2147483648~2147483648
10.unsigned long3240~4294967295
11.float3240.175494E-38~0.402823E+38
12.sbit10~1
13.sfr810~255
14.sfr161620~65535

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

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

所属系列

从当前文章继续阅读它所在合集中的前后内容。

关于我和 Hugh 学嵌入式开发这件事 —— 51 篇 第 6 / 22 篇
查看合集

> 本作品集内教程基于 [Hugh](https://rymcu.com/user/hugh) 的创作基础上进行修订发布 关于我~和昊楠君~(昊楠君已经阵亡了,现在和 Hugh )学嵌入式开发这件事。

相关文章

优先推荐同专题、同标签和同作者内容,补足热门文章。

评论 0

登录 后参与评论

评论

成为第一个评论的人