【电子工作室培训(一)】 |
一、Keil For C51开发环境的搭建
1、下载Keil安装包及注册机压缩包
- 编译器Keil5 for c51链接如下:
链接:https://pan.baidu.com/s/1osO4K9HQlCS52o6RcJttDA
提取码:JSU6 - 注册机链接:https://pan.baidu.com/s/1Qaa_BYtrXsQsx6kaTmUnSw
提取码:JSU6 - STC-ISP下载工具连接:https://pan.baidu.com/s/1wNquPWFLCOjDJjiARw_PZw
提取码:JSU6
工作室同学请到QQ群下载。
2、安装
解压Keiluvision5C51版,找到安装程序C51-V957.exe,右键以管理员身份运行,按提示操作(该√的√上,该NEXT就NEXT)当出现如下界面,选择一个安装路径,建议在自己D盘根目录下,新建一个有辨识性的文件夹来作为软件安装路径(如这里在D盘新建keil5_c51)。
这里随便填
点NEXT,等待安装完成。可以在桌面上看到keil的图标,右键以管理员身份打开。
3,破解
解压注册机压缩包:2020版keilCRACK.zip,打开程序:keygen.exe(此处友情提醒调小电脑的音量~),打开keil,按如下步骤打开License Management。
复制CID
转到注册机中,按如图操作
将注册机生成的License ,复制至keil的License Management中,如图:
完成之后,关闭keil重新打开,就可以开始你的编程之旅了。
4、STC-ISP下载工具的使用 - 下载压缩包:stc-isp-15xx-v6.87P.zip并解压。
- 安装USB驱动程序:打开解压后文件夹下的USB to UART Driver文件夹,打开CH340_CH341,找到CH340_CH341.exe文件右键以管理员身份运行,点击安装
- 在解压后的文件夹里找到stc-isp-15xx-v6.87P.exe,双击就可以运行,不需要安装,可以右键选择发送到-发送到桌面快捷方式,这样就可以在桌面运行它啦。(也可以直接拖到桌面上)。
- 至此,开发环境搭建成功。
二、常用C语法
参考菜鸟教程:https://www.runoob.com/cprogramming/c-tutorial.html
if-else
一般形式:
- 一个 if 后可跟零个或一个 else,else 必须在所有 else if 之后。
- 一个 if 后可跟零个或多个 else if,else if 必须在 else 之前。
- 一旦某个 else if 匹配成功,其他的 else if 或 else 将不会被测试。
if(boolean_expression 1)
{
/* 当布尔表达式 1 为真时执行 */
}
else if( boolean_expression 2)
{
/* 当布尔表达式 2 为真时执行 */
}
else if( boolean_expression 3)
{
/* 当布尔表达式 3 为真时执行 */
}
else
{
/* 当上面条件都不为真时执行 */
}
实例:当输入20,将输出:a 的值是 20
#include <stdio.h>
int main ()
{
/* 局部变量定义 */
int a = 100;
/* 检查布尔条件 */
if( a == 10 )
{
/* 如果 if 条件为真,则输出下面的语句 */
printf("a 的值是 10\n" );
}
else if( a == 20 )
{
/* 如果 else if 条件为真,则输出下面的语句 */
printf("a 的值是 20\n" );
}
else if( a == 30 )
{
/* 如果 else if 条件为真,则输出下面的语句 */
printf("a 的值是 30\n" );
}
else
{
/* 如果上面条件都不为真,则输出下面的语句 */
printf("没有匹配的值\n" );
}
printf("a 的准确值是 %d\n", a );
return 0;
}
switch
-
先计算switch表达式的值,再逐个和 case 后的常量表达式比较,若不等则继续往下比较,若一直不等,则执行 default 后的语句;若等于某一个常量表达式,则从这个表达式后的语句开始执行,并执行后面所有 case 后的语句。
-
与 if 语句的不同:if 语句中若判断为真则只执行这个判断后的语句,执行完就跳出 if 语句,不会执行其他 if 语句;而 switch 语句不会在执行判断为真后的语句之后跳出循环,而是继续执行后面所有 case 语句。在每一 case 语句之后增加 break 语句,使每一次执行之后均可跳出 switch 语句,从而避免输出不应有的结果。
switch语句一般形式:
switch(表达式)
{
case 常量表达式1:语句1;
case 常量表达式2:语句2;
...
default:语句n+1;
}
例:输入1-12,输出对应的月份。
#include <stdio.h>
int main()
{
int a;
printf("input integer number: ");
scanf("%d",&a);
switch(a)
{
case 1:printf("Monday\n");
break;
case 2:printf("Tuesday\n");
break;
case 3:printf("Wednesday\n");
break;
case 4:printf("Thursday\n");
break;
case 5:printf("Friday\n");
break;
case 6:printf("Saturday\n");
break;
case 7:printf("Sunday\n");
break;
default:printf("error\n");
}
}
for
一般形式:
for ( init; condition; increment )
{
statement(s);
}
下面是 for 循环的控制流:
- init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。
- 接下来,会判断 condition。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会- 跳转到紧接着 for 循环的下一条语句。
- 在执行完 for 循环主体后,控制流会跳回上面的 increment 语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。
- 条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for 循环终止。
实例:
#include <stdio.h>
int main ()
{
/* for 循环执行 */
for( int a = 10; a < 20; a = a + 1 )
{
printf("a 的值: %d\n", a);
}
return 0;
}
输出如下:
a 的值: 10
a 的值: 11
a 的值: 12
a 的值: 13
a 的值: 14
a 的值: 15
a 的值: 16
a 的值: 17
a 的值: 18
a 的值: 19
while
一般形式:
while(condition)
{
statement(s);
}
- 在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。
- condition 可以是任意的表达式,当为任意非零值时都为 true。当条件为 true 时执行循环。 当条件为 false 时,退出循环,程序流将继续执行紧接着循环的下一条语句。
实例:
#include <stdio.h>
int main ()
{
/* 局部变量定义 */
int a = 10;
/* while 循环执行 */
while( a < 20 )
{
printf("a 的值: %d\n", a);
a++;
}
return 0;
}
运行结果:
a 的值: 10
a 的值: 11
a 的值: 12
a 的值: 13
a 的值: 14
a 的值: 15
a 的值: 16
a 的值: 17
a 的值: 18
a 的值: 19
do-while
一般形式:
do
{
statement(s);
}while( condition );
- 不像 for 和 while 循环,它们是在循环头部测试循环条件。在 C 语言中,do…while 循环是在循环的尾部检查它的条件。
- do…while 循环与 while 循环类似,但是 do…while 循环会确保至少执行一次循环。
宏定义
- C语言中,可以用 #define 定义一个标识符来表示一个常量。其特点是:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了。
- 所谓的常量可以是各种数据类型,如:int、float、char等。
- 可以理解为替换,编译器在编译前自动将标识符替换成代表的常量。
- 后面不要加分号!
- 宏定义最大的好处是“方便程序的修改”。使用宏定义可以用宏代替一个在程序中经常使用的常量。注意,是“经常”使用的。这样,当需要改变这个常量的值时,就不需要对整个程序一个一个进行修改,只需修改宏定义中的常量即可。且当常量比较长时,使用宏就可以用较短的有意义的标识符来代替它,这样编程的时候就会更方便,不容易出错。因此,宏定义的优点就是方便和易于维护。
一般形式:
#define 标识符 常量 //注意, 最后没有分号
实例:
#define Days 30
编译器会将所有出现Days的地方将其换为30。
函数
数组
指针
结构体
联合
枚举
以上内容由于时间关系,暂时不聊,请同学们自行了解。
2、数据类型
- 我们在做单片机、嵌入式程序设计的时候,往往芯片内存资源十分有限,所以根据不同的数据选择合适的数据类型可以节约内存空间,提高程序的执行效率。
- 需要注意数据类型所表示的数值范围,避免溢出,使程序运行错误。
*整型类型:
*浮点型
*void型
- void 类型指定没有可用的值。它通常用于以下三种情况下:
*51单片机中特殊的数据类型:
应对51单片机硬件的一些特点,Keil 扩展了bit、sbit、sfr、sfr16等四种特殊基本数据类型,它们都是标准C中所没有的。其中:
- bit:声明一个普通的位变量,值为0或1。例如:“bit flag;”
- sbit:声明特殊功能寄存器中的某一位。例如,使用"sbit TI = P0^0;",就声明了TI为P0端口的第0位。(端口我们将马上介绍)
- sfr:声明一个8位寄存器为特殊功能寄存器。例如,将51单片机内存地址0x98处的存储单元声明为8位特殊功能寄存器SCON,可以使用"sfr SCON = 0x98;"语句。
- sfr16:声明一个16位的寄存器为特殊功能寄存器。为了将51单片机内存地址0xCC处开始的连续两个存储单元声明为一个统一的16位特殊功能寄存器T2,可以使用"sfr16 T2 = 0xCC;"语句。
注意:
- 在Keil uVision中,用sbit、sfr、sfr16声明特殊功能寄存器变量或特殊功能寄存器位变量时,其声明语句都只能放在函数外,而不能放在函数内,否则出现语法错误。
- 用bit声明普通位变量时,声明语句既可放在函数外,也可放在函数内。
- bit、sbit、sfr、sfr16都不支持指针和数组扩展,因此,不能定义bit、sbit、sfr、sfr16型指针和数组。
- 对于初学者来说,其实没有必要深究sbit、sfr、sfr16的用法,它们通常用在51单片机的系统自带头文件中,一般情况下无需用户关心。
三、数字逻辑及进制转换
1、数字逻辑
1、与(and)
- 全为ture结果才为ture。
- &——按位与。
如:10&7=1010b&0111b=0010b=2; - &通常用来对某些位清0或保留某些位。
如:有一个数为0110 1101b,希望保留低四位而把高4位清0。
0110 1101b & 0000 1111b = 0000 1101b; - &&——逻辑与。
如: (1>=0)&&(1>=2)=1&0=0;
2、或(or)
- 只要一个为ture结果就为ture。
- ‘|’——按位或
如:6|2=0110b | 0010b = 0110b; - ‘||’逻辑或
如: (1>=0)|| (1>=2)=1|0=1;
3、非(not)
- ture to false——false to ture;
- ‘~’——按位取反
如:~(0000 1001)= 1111 0110 - ‘~’也可进行单目运算
如:~1=0;
4、异或
- 不同为Ture,相同为false。
- “^”——按位异或。双目运算符,其功能是参与运算的两数各对应的二进位相异或;当两对应的二进位相异时,结果为1。
如:0000 1010 ^ 0000 0101 = 0000 1111;
5、移位
- “<<”,左移,双目运算符,功能:把“<<”左边运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数。
- 通俗来说,就是整串数左移,高位的不要,低位补0
如:1111 1111 << 3 = 1111 1000;
- “>>”右移,这里不赘述。
2、进制转换
这里不展开讲,请点击参考此文
1、十进制转二进制
2、八进制转二进制
3、十六进制转二进制
4、二进制转十进制
5、二进制转八进制
6、二进制转十六进制
因为单片机编程常需要二进制与十六进制转换,故讨论之。
- “8421”法,每4位二进制对应应一位十六进制,其权对应分别为8、4、2、1。注意取4为二进制应从低位开始取,高位不足4位则高位补0即可。
如:1111 1001b = (8+4+2+1)(8+0+0+1)h=F9h=0XF9;
11 1011b = (0+0+2+1)(8+0+2+1) = 3BH = 0X3B;
这里的h表示十六进制(hexadecimal),我们写C程序一般在数字前写0X为表示十六进制(注意:是零0,而不是英文o) - 这里的英文字符十六进制的a,b,c啊大小写都可以。如:0XFA = 0xfa;
3、单片机电平逻辑
- 什么是电平:电平”就是指电路中两点或几点在相同阻抗下电量的相对比值。
- 可以近似理解为电平为电压。但电平只有高低之分,对应的应该是一个电压范围。如:TTL电平中,+5V等价于逻辑"1",0V等价于逻辑"0"。我们单片机系统使用的就是TTL电平,但高电平取决于电源(VCC)的电压。
- 我们使用的单片机中高电平代表“1”,低电平代表“0”。
四、什么是单片机?
- 单片微型计算机( Single-Chip -Microcomputer),现在也称微控制器(Micro Controller Unit)简称MCU。
- 它其实就是一种集成电路芯片,是通过超大规模集成电路技术,将CPU、RAM、ROM、输入输出和中断系统、定时器/计数器等功能,塞进一块硅片上,变成一个超小型的计算机。
- 通俗的说,单片机就是一个芯片而已,只是我们可以通过对其编写程序,去控制它进行相应的工作。
- 8051单片机:51单片机是对所有兼容Intel 8051指令系统的单片机的统称。 并不是指的某一特定型号的单片机,而是使用51内核单片机的统称。 8051是一种内核,其他公司基于此内核的基础上加入其他的外设开发出多种型号的"51单片机"。如:我们此次使用的是宏晶公司(STC)的STC15W4K58S4单片机。
STC官网:http://www.stcmcudata.com/
五、认识原理图
- 原理仅表示电路连接关系,与具体的PCB的布局、布线并没有直接的关系
- 我们需要从原理图中得到单片机与外围电路的连接,从而知道该如何写程序控制某一外设。
六、新建一个keil工程
1、添加STC型号和头文件到keil中
- 打开STC-ISP软件,按图操作
- 打开keil5,选择Project->New …如图:
- 选择工程保存路径,建议每一个工程建一个文件夹好方便管理,如我这里新建了一个51单片机的文件夹,用来保存51单片机的工程,建了一个LED的文件夹来保存我们这次的工程文件,之所以叫LED是因为我们此次培训内容为点亮一个LED,这样具有辨识性,方便你在某个夜深人静的时候想看点亮LED的代码能一下子就找到~~,然后点击保存。
- 选择单片机型号,我们可以在1那里的选择栏里面找到STC MCU Dtabase(默认不是这个),然后再2那里选择我们本次使用的芯片STC15W4K32S4。然后点击OK。其实,如果你的程序实现的功能是STC各种型号普遍都有的,你也可以随便选一个,比如我们此次点亮一个LED,仅仅就是控制单片机的一个IO(输入输出引脚)口,当然任意型号的都能满足要求,但建议养成选择对应型号单片机的习惯!
- 如果你找不到STC的单片机,也就是前面的没有成功添加STC型号和头文件,这也没关系,可以在Search栏里面输入AT89C51来找到AT89C51然后选择也可以的。
- 之后会提示你是否复制51单片机的启动文件到工程中,这里我们选择是。之后请结合你自己的工程需要来选择。
- 然后我们可以看到如下界面,就是我们工程的界面了,点击左边的“+”,可以展开。
- 新建一个C语言文件:点击状态栏上的File->New
可以看到新建的文件text1,然而这并不是我们想要的,我们点击1那里的save(保存),然后将文件名命名为main.c,这里的文件名可以起其他名字,但最好也具有辨识性。文件也可以保存在其他地方,但需要在KEIL里面配置好路径(这个可以先不了解)。但.c一定不能改!!因为我们使用C语言进行开发。如果你写的是汇编,当我没说~~,然后点击保存。
- 添加文件到工程中:
我们上面新建了一个.c文件,但目前这个文件并不在我们的工程目录里面,如下图:并没有看到一个main.c文件。
在你想要把文件添加到的分组上右键,我们这里就Source Group1一个分组,等以后可能会有不同的分组,分组的目的也是为了方便管理代码。注意不要点到Target1那里,然后选择添加已存在的文件
然后,会跳出工程所在的文件夹,找到要添加的文件,我们这里是main.c,然后添加。就可以在Source Group1下面看到main.c了。
七、梦开始的地方-点亮一个LED
- 单片机板子LED部分原理图。大家可以打开群里的“”“课程设计小系统板”PDF文件,这个就是我们这次使用的板子的与原理图,我们可以看到里面有一个黄色的方形,那就单片机的原理图表示,单片机上标了很多数字和引出来很多的线,那其实表示的是单片机的引脚,我们这次使用的单片机有32个引脚,不同封装的单片机引脚可能不同。我们编写单片机程序,本质是就是控制其引脚输出指定的电平(高或者低),来完成对应的功能。
- 从原理图可能看到单片机的每个引脚上都有对应的功能描述,比如:32号引脚上标了P0.3/AD3/TXD4。“/”分开的表示这引脚具有多种功能,我们可以通过对单片机进行不同的配置来选择不同的功能。我们这里仅对P0.3进行解释:P0代表了单片机的P0端口(一个端口一般对应8位,也就8个引脚),P0.3则表示是P0端口的3号引脚。AD3/TXD4属于单片机引脚的特殊功能,我们这里不需要关注。
- 从原理图,我们需要知道某个LED连在单片机哪一个引脚上
比如:图上标号为1(原理图上的元件都有一个对应的元件标号与其对应,因为原理图上LED的标号与元件重叠了,可能看的不是很清,所以我用红字标出了标号)的LED连在了P1(P1实际上是一个排针的接口,大家这里把它忽略掉就好了,只关注LED与单片机的连接关系)的4号上,而4连在了单片机的P0.0脚上。 - 如何使点亮LED?
LED(发光二极管):属于半导体器件,具有单向导电性。原理图中LED的符号是一个类似箭头的符号,“箭头”指向的方向代表了;LED导通的方向。只有在LED的正向有一个电压降,LED才会导通。 所以我们要做是如何使LED正向具有这个电压降。从图中我们可以知道LED的阳极全部连在了电源上(一般原理图用VCC代表电源),故阳极是高电平这点我们是无法改变的,我们能改变是单片机的引脚输出的电压。当使单片机引脚输出高电平(1)时,因为LED亮端都是高电平,没有电压降,故此时LED处于熄灭状态。只有当单片机引脚输出一个低电平(0),LED有一个正向的电压降,LED导通,有电流流过LED,LED此时会点亮。 - 为什么LED与带VCC之间还串联了一个电阻呢?
LED阳极与VCC之间那个方块表示的是一个电阻(原理图里一般用R标识),串电阻是因为LED导通的电流是有上限的,电流过大会烧毁LED,故串联一个电阻来限制流过LED的电流。如这里串了2000欧姆的电阻,板子上VCC是5V,这样,流过LED的电流最大为:5/2000=25mA。(LED电流范围大概是:10-30mA)。
接下来正式开始编程。
- 打开刚才的main.c文件,然后再代码编辑框里面写编写代码。我这里给出一个点亮P0.3连接的LED(边上那个),大家可在此基础上试试点亮其他的LED,也可以尝试去点亮(熄灭)多个LED。
#include "reg51.h"//51单片机底层头文件
sbit LED0 = P0^3;//p要大写
void main()
{
LED0 = 1;//灭掉led
while(1)
{
LED0 = 0;//给p0.3一个低电平,点亮
}
}
-
对代码的说明:
1、#include "reg51.h"
是一个51单片机的底层头文件,我们可以打开这个文件看看里面写了什么。我们将鼠标选在这一行,和右键会出现如下界面,我们可以通过图中的选项打开这个头文件。
可以从头文件里面看到,里面对P0等端口和单片机的一些特殊功能寄存器进行了定义。大家可以看main.c里面有用到P0.3,但并没有对P0^3进行任何定义,我们学习C语言应该知道,凡是需要用到的变量或者常量必须在使用前定义,我们在main.c里面没有定义,是因为P0 ^3在reg51.h这个头文件里面已经定义了,我们在程序中引用了这个头文件就不需要再对P0 ^3进行定义。就像大家写printf(“hello world!”);必须在程序里面引用#include "stdio.h"一样。因为printf函数是在stdio.h这个头文件定义的。
2、 sbit 前面提到过,是声明一个位,P0^3表示的是P0端口的第3个端口,对应原理图里面的P0.3。我们这里定义一个位变量LED0,让其代表P0.3这个引脚。注意:所有的sbit定义必须在main函数外部。
3、 在main函数里面我们先写了一句LED0 = 1
,来灭掉P0.3对应的LED,然后写了一个死循环,这与我们平时在电脑写C语言不一样,我们想要单片机一直不停的按照一定顺序工作,不要去做我们不希望它去做的事(也就是程序不要跑飞了)。LED0 = 0;
一句时P0.3引脚一直输出低电平,那么与此引脚连接的LED就会一直处于点亮的状态。 -
我们写好程序之后需要生成.hex,然后通过STC-ISP把hex文件下载到单片机中去,生成hex文件需要对keil进行设置。如下图,点击红圈中的options for target。
然后点Output选项,把Create…勾上,然后ok,这样每次我们编译程序之后,keil都会重新生成一次hex文件。
-
对程序进行编译。其中:标号为“1”的表示对当前的文件进行翻译,“2”表示对修改过的文件进行编译,“3”表示全编译,对全部文件进行一次编译,这在当我们的程序文件比较多时编译的时间会比较长 。一般我们编译时点“2”也就是每次都对修改的文件再编译一下就可以了。
如果编译时提示有错误,先解决第一个错误, 因为很多时候后面的错误是前面的错误引起的。 -
程序编译没错误后,可以看到keil生成了hex文件。
八、流水灯的几种打开方式
- 什么是“流水灯”?
- 如何在单片机中实现延时。
- 麻雀虽小——
#include "reg51.h"
unsigned char LEDS[8]={};
void main()
{
unsigned char i;
i = 0;
while(1)
{
for(i=0;i<8;i++)
{
if(i<4)
P0 = LEDS[i];
else
P2 = LEDS[i];
}
i = 0;
}
}
#include "reg51.h"
void main()
{
unsigned char i = 0;
while(1)
{
for(i=0;i<8;i++)
{
P0 = ~(0X01<<i);
Delayms(500);
}
i = 0;
}
}
错误之处,请大家指正、批评!