单片机内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能,其任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(例如电机,LED等)的控制。在给单片机输入灵魂时常使用C语言进行编程,本文以keil uVision5的编译过程为例作个人在编程过程中的总结。
一、进制换算
通过了解单片机的使用手册,可以发现每个寄存器有特定的地址编号,如C51中P0引脚的地址编号是0x80,SCON(Serial Control)的地址编号是0x98。在电平触发式发送指令或者数据时,用与运算可以实现十六进制转换成二进制一位位地传送,如定义变量Data,i,则可以用for(i=0;i<8;i++){Data&(0x80>>i);}进行传送,0x80用二进制表示是1000 0000,Data也是十六进制数据,把1进行移位,若Data对应位是1则为1,否则是0。
下面是进制间的数据转换:
- 十进制转二进制是除以2取余,即除以2取余后的商重复步骤,将余数倒序写就是结果;
- 二进制转十进制是从右到左按2的n次幂(n=0,左移一位n加1)乘以二进制对应位上的0或1,将积相加;
- 二进制转十六进制是取四合一法,即从二进制数据向左每四位取成一位,按二进制转十进制的方法算出,从右到左组合即可;
- 十六进制转二进制反之。
e、BCD码(Binary Coded Decimal),用4位二进制数来表示1位十进制数。BCD码转十进制:DEC=BCD/16*10+BCD%16(2位BCD);十进制转BCD码:BCD=DEC/10*16+DEC%10(2位BCD)。与二进制转十六进制相似,但是二进制换算结果超过10是不合法的,如0001 0001表示11,0001 1010不合法。与八进制有关的换算就不赘述了,在单片机编译过程中很少涉及。
二、数据类型
类型 | 符号 | 关键字 | 所占位数 | 数的表示范围 |
整 型 | 有 | (signed) int | 16 | -32768~32767 |
(signed) short | 16 | -32768~32767 | ||
(signed) long | 32 | -2147483648~2147483647 | ||
无 | unsigned int | 16 | 0~65535 | |
unsigned short int | 16 | 0~65535 | ||
unsigned long int | 32 | 0~4294967295 | ||
实 型 | 有 | float | 32 | 3.4e-38~3.4e38 |
double | 64 | 1.7e-308~1.7e308 | ||
字 符 型 | 有 | char | 8 | -128~127 |
无 | unsigned char | 8 | 0~255 |
注意:
’a’表示字符常量,”a”表示字符串常量。每个字符串结尾,编译器会自动的添加一个结束标志位’\0’,即”a”包含两个字符’a’和’\0’。
在函数外定义的变量是全局变量,所有函数都可以调用;加上static(静止的)是静态全局变量,如果不赋初值,它的值是0。在函数内定义的变量是局部变量,只能在函数内部调用;加上static是静态局部变量,如果不赋初值,它的值是0;定义普通局部变量,如果不赋初值,它的值是随机的。
三、数据运算符
类别 | 运算符 | 意义 | 类别 | 运算符 | 意义 |
算术 | + | 加 | 判断 | != | 不等于 |
- | 减 | ||||
* | 乘 |
逻辑 | && | 逻辑与 | |
/ | 除 | || | 逻辑或 | ||
% | 取余 | ! | 逻辑非 | ||
= | 赋值 |
位运算 | << | 按位左移 | |
++ | i++(先用后加) ++i(先加后用) | >> | 按位右移 | ||
-- | i--(先用后减) --i(先减后用) | & | 按位与 | ||
判断 | > | 大于 | | | 按位或 | |
>= | 大于等于 | ^ | 按位异或 | ||
< | 小于 | ~ | 按位取反 | ||
<= | 小于等于 | 变量 | 【】 | 数组下标 | |
== | 等于 | * | 定义指针,指针取值 |
注意:/是整除;逻辑是判断真假的,0即为假,非0即为真;按位是对位数据进行运算。
补充:
四、常用基本语句
语句 | 解释 | 语句 | 解释 |
if(逻辑表达式) { 语句体1; } else { 语句体2; } | 如果逻辑表达式成立 执行语句体1 否则 执行语句体2 (else可以不写) | for(初始化;逻辑表达式;更改条件) { 循环体; } | 先执行初始化 再判断逻辑表达式 若成立则执行循环体 执行后更改条件 再判断逻辑表达式 直到表达式不成立 |
while(逻辑表达式) { 循环体; } | 如果逻辑表达式成立 执行循环体 执行后再次判断 若还成立则继续执行 直到表达式不成立 | switch(变量) { case 常量1:语句体1;break; case 常量1:语句体2;break; (...) default:语句体1;break; } | 将变量与case后的各个变量对比 若有相等,则执行相应的语句体 若没有一个相等,则执行default后的语句体 (default可以不写) |
注意:所有需要运行的程序都只能放在main()函数中执行,Project只能有一个main()函数。
五、数组
数组是若干个相同类型的变量在内存中有序存储的集合,可以通过数组名+索引号简单快捷的操作大量数据。如
int i[3]; //定义一组变量(3个)
i[0] //引用数组的第0个变量
i[1] //引用数组的第1个变量
i[2] //引用数组的第2个变量
int i[]={1,2,3}; //定义一组变量并初始化,此定义方法可以省去统计定义的变量个数。
引用i[3]时,数组越界,读出的数值不确定,应避免这种操作。
六、模块化函数
模块化函数是把不同模块的函数放在不同的源文件里,在头文件里声明外部可调用函数,其它源文件可以通过头文件使用其中的代码,使用模块化函数可极大的提高代码的可阅读性、可维护性、可移植性等。以C语言编程为例,main()函数所在的.c文件除外的其他.c文件中可以定义和编写不同功能的函数,建立相应的.h文件,在.h文件中声明自定义的函数,其他.c文件可以通过#include “~~~.h”进行函数调用。
注意:
a、任何自定义的变量、函数在调用前必须有定义或声明,即函数名+()+;
b、使用到的自定义函数的.c文件必须添加到Project参与编译
c、使用的.h文件必须放在编译器可寻找到的地方,如工程文件夹根目录、安装目录、自定义
预编译:(以C语言编译为例)
预编译 | 意义 |
#include <~~~.H> #include “~~~.h” | 头文件的内容替换预编译的语句,从而展开头文件 |
#define PI 3.1415926 | 定义PI,PI替换2.1415926 |
#define A | 定义A |
#ifndef __~~_h_ | 如果没有定义__~~_h_ |
#endif | 与#ifndef,#if匹配,组成“括号” |
为防止头文件重复定义,造成代码错乱,可以使用以下的方法:
#ifndef _DS1302__h_ //如果没有宏定义_DS1302__h_,则执行下面的语句
#define _DS1302__h_ //宏定义_DS1302__h_
外部可调用函数声明 //函数声明
如(void DS1302_Init(void);)
#endif //与#ifndef,#if匹配,组成“括号”