STC-ISP是一款单片机下载编程烧录软件,是针对STC系列单片机而设计的,可下载STC89系列、12C2052系列和12C5410等系列的STC单片机,使用简便。
思路:将电脑磁盘上已存在的文件通过串口的方式下发到单片机中。
USB转串口驱动安装:CH340。
将单片机通过type-C数据线和电脑连接(注意是数据线,不是充电线。数据线一般都有圆形、方形、箭头三线标识)
连接完毕后,打开此电脑,右键 / 属性 / 设备管理器 / 端口,检查是否识别成功,如果成功则会显示端口号
下载STC-ISP软件
根据自己的单片机,在软件页面选择单片机型号:我的是STC89C52RC / STC89C52RC/LE52RC
确认串口号是否和第4步显示的串口号一致
打开程序文件,在上一节的示例项目中,我们已经建立了一个hex文件:moban.hex
点击下载编程,但是会显示正在检测目标单片机…(这说明还没有开始下载)
给单片机重新上电,对于我的单片机来说,是有一个开关,捎下去即可。重新上电后,发现从原先的两盏灯在亮变成了三盏灯亮起来(我们的代码中给了两盏灯低电平),说明操作成功。
单片机(Single-Chip Microcomputer)是一种集成电路芯片。
把具有数据处理能力的中央处理器CPU、随机存储器RAM(也叫做数据存储器)、只读存储器ROM(也叫做程序存储器)
多种I/O口和中断系统、定时器/计数器等功能
可能还包括显示驱动电路、脉宽调制电路(PWM)、模拟多路转换器、A/D转换器等电路
集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。
什么是发开发板?:基于单片机设计一些外接电路,模块以满足学习,日常调研等工作需求
在开发板生产前,硬件工程师会将电路的原理图画好,我们在进行单片机学习时,重要的两个参考对象就是:
- 开发板原理图
- 芯片手册
注:摘自周佳社的微机原理,课程讲解的芯片是8086CPU,我们学习8051单片机时可以借鉴一下微机原理的知识,两者差不多,没必要单独去学8086。如果想知道两者区别,可以点击这个网页链接。
微处理器的外部结构表现为“数量有限的输入输出引脚”。这些输入输出引脚构成了“微处理器集总线”。输入输出引脚按其功能分就是三种,即数据引脚(数据总线DB)、地址引脚(地址总线AB)、控制引脚(控制总线CB)。
I/O端口寄存器是I/O接口中,用来实现I/O设备与CPU之间的数据交换的端口(通道),CPU给这个寄存器分配了一个地址,就是I/O端口地址。
CPU与智能设备的数据交换原理:通过三个端口寄存器始终保持握手状态。
状态输入端口:CPU需要知道打印机的状态,判断它连接好了吗,墨水是否足够,装上纸了吗,是否还在打印等,这些状态信息,CPU是不知道的,但是I/O接口可以获取到,因此需要在接口电路中设计了一个能传送状态信息的通道,这个通道就是一个端口,称为状态输入端口。
I/O端口地址:CPU需要读这个状态输入端口,因此需要给它分配一个地址,最后,这个状态信息沿数据总线传送给CPU。地址总线上的地址信号没有选中端口的地址时,即地址信号无效时,端口的输出端呈现 (高阻),三态表示通道未导通;当地址总线上的地址信号选中端口地址时,端口的输出端呈现二态,二态表示通道导通(若状态输入端口的输出端没有三态功能,需要自己设计,若没有这样的设计,那么会出现同一位数据总线在同一个时刻会出现两个以上的状态,即0和1,这种情况称为总线的竞争,轻则计算机瘫痪,重则冒烟(倒灌电流))。I/O端口地址就是状态输入端口的三态控制端。
读取状态与输出信息:若是0(设备没有准备好),则接着取状态,直到所有状态都准备好了;然后这时,CPU才可以通过数据总线给打印机输出数据。此时需要再设计一个通道,也就是一个I/O端口,称为数据输出端口,CPU的数据先输出给这一端口,由这个端口再给设备,同样,这个端口也必须有端口地址。
必要的命令输出端口:由于CPU很快而打印机很慢,所以在数据输出端口的输出端必须具有锁存功能(锁存器,就是D触发器),数据保存在那,让CPU干别的活去,但是在CPU没有输出数据给打印机之前,与设备相连的数据总线上是有数据残留的,设备不知道目前数据线上的数据是否有效,需要CPU告诉它,因此在I/O接口中还要设计一个端口,称为命令输出端口,CPU将数据总线上的某一根给命令输出端口,当命令输出端口的输出端将0传递给打印机时,打印机才知道它与数据输出端口之间的数据总线上的数据有效。
单片机编程是什么:人类要求单片机干具体的活,有点像提前写个小纸条贴冰箱上,告诉你女朋友煮鸡蛋不加水
单片机执行程序怎么理解: 女朋友看到纸条后,照做!
单片机能做什么事情:就是硬件的那些事,比如 I/O口供电,串口数据传输等。
肉眼可见的引脚是什么:就是单片机芯片引脚通过电路线引出去的插针。方便人类对单片机I/O口操作(通过杜邦线接传感器),串口插东西。
单片机CPU怎么找到I/O口:通过寻址,说白了有一些地址数据代表了某些I/O口,我们只需要在包含头文件之后拿来用就行了(具体怎么实现的不要着急了解,汇编的内容)。
- 尽量用P1/P2/P3/P4,不要用P0
- RC系列(CPU内部添加了RAM)的单片机新增了P4口:如果你想用P4口,那么就把手册中的代码拷贝到"reg52.h"中
sfr“指令”: 用来直接描述硬件地址,可以先理解成“一组IO口”中起始地址中的数据
代码形式例如:
sfr P0 = 0x80;
如果想让P0口的7个针脚都输出低电平,那么代码就写成:
P0= 0;
用的比较少。
sbit“指令”:对应可位寻址空间的一个位(bit)。可以先理解成“一个IO口/针脚”的地址中数据
代码形式例如:
sbit led2 = P3^6;
也就是我们找到了P3口的第6位(第6个位,即引脚P3.6)。
I/O口及其针脚的示意图:我们将来用一个字节来表示一个I/O口。这个I/O口中还有8个引脚,每个引脚占1bit。
原理:将来我们只需要设置I/O口的每个针脚为0或1就行了,所以“一个IO口/针脚”的地址中数据就是一个二进制数。
sfr指令和sbit指令不是标准C,所以不认识sfr和sbit,C51单片机在底层上去解析了这两个指令。在头文件"reg52.h"中这两个指令都被使用了。
输入:IO口的 输入 能 把外面东西拿回来。比如将传感器的out引脚用杜邦线链接到单片机的某个插针上。
sbit IFfire = P1^0; //让单片机的P1.0针脚接着火灾传感器
int main()
{
int io_data;
io_data = IFfire; // 给变量赋值,就是输入,或者理解为,这个针脚有数据啦,拿个变量来保存
if(io_data == 0){
}
return 0;
}
输出:IO口的 输出 能 把东西给外面。比如单片机的某个插针用杜邦线连接到继电器的in引脚上,能够控制继电器的通断。
sbit IOPORT = P1^0; //让单片机的P1.0针脚接着某些执行器
int main()
{
IOPORT = 1;// 被给予一个数值,引起IO口电压变化(一般1输出高电平,0输出低电平),来影响外接的电路
return 0;
}
查看开发板原理图,找到LED:可以发现我的开发板一上电后,D4因为一头接地所以就会亮,而D5和D6因为一头悬空所以暂时不会亮
用ctrl + F 的方式在原理图其他位置找到LED1以及LED2标号:记住:原理图中标号一样代表是同一根“电线”。
芯片手册很大的话,肉眼很难去找,因此要用ctrl + F的方式
思路:
全局变量: sbit指令找到D5这个LED,引脚为P3^7
1. while死循环,重复执行D5的亮灭
1.1 让D5亮
1.2 延时,延时期间保持灯亮
1.3 让D5灭
1.4 延时,延时期间保持灯灭
延时多少呢?在没有学到定时器之前,我们用STC-ISP中的一个工具——软件延时计算器。
- 我们单片机的晶振是11.0592MHz,不用改。
- 输入定时的长度,单位比如说设置成ms,长度比如说设置成500。那么我们知道亮灭这个过程经过了1秒钟。
- 8051指令集我们修改成Y1,因为Y1适用于STC89C系列(右侧方框有显示)
- 点击生成C代码,复制代码。
复制来的代码里面的nop()指令其实是包含在头文件intrins.h当中的。nop()是一个空操作,对应于汇编语言中的NOP语句。执行该函数,将占用1个机器周期的时间,常用于局部短暂延时。
LED闪烁代码:
#include "reg52.h"
#include "INTRINS.H"
sbit ledD5 = P3^7; //对D5这个LED操作
/* API1: 从STC-ISP的软件延时计算器中拷贝下来的函数,用于延时 */
void Delay500ms();
int main(void)
{
while(1){
ledD5 = 0;
Delay500ms();
ledD5 = 1;
Delay500ms();
}
return 0;
}
void Delay500ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
通过查阅开发板原理图:
对于我的51单片机开发板来说:
- 用引脚P2^1识别按键SW1是否被按下,0说明按键被按下,1说明按键没被按下。
- 用引脚P2^0识别按键SW2是否被按下,0说明按键被按下,1说明按键没被按下。
示例代码(无软件消抖):用SW1控制D5,用SW2控制D6,功能是按下按键LED灯才会亮,松开按键LED灭。
全局变量:
sbit指令找到与SW1连接的引脚,为P2^1
sbit指令找到与SW2连接的引脚,为P2^0
sbit指令找到D5这个LED,引脚为P3^7
sbit指令找到D6这个LED,引脚为P3^6
1. while死循环,按下按键LED灯才会亮,松开按键LED灭
1.1 判断SW1是否为0
1.1.1 如果是,则说明按下了
1.1.1.1 令D5亮: ledD5 = 0;
1.1.2 否则,则说明没按下
1.1.2.1 令D5灭: ledD5 = 1;
1.2 判断SW2是否为0
1.2.1 如果是,则说明按下了
1.2.1.1 令D6亮: ledD6 = 0;
1.2.2 否则,则说明没按下
1.2.2.1 令D6灭: ledD6 = 1;
#include "reg52.h"
sbit SW1 = P2^1;
sbit SW2 = P2^0;
sbit ledD5 = P3^7;
sbit ledD6 = P3^6;
int main(void)
{
/* 查询法按键控制灯,不妨用SW1控制D5,用SW2控制D6 */
while(1){
if(SW1 == 0){
ledD5 = 0;
}else{
ledD5 = 1;
}
if(SW2 == 0){
ledD6 = 0;
}else{
ledD6 = 1;
}
}
return 0;
}
存在的问题:有时候在工业环境中,因为抖动而非人为的按键,造成了灯亮和灯灭。比如说时间久了以后,LED的针脚出现了松动。
解决方法:软件消抖。原理:人按下按键是有个操作时间的,比方说0.1s,0.2s等等,但是因为抖动按下按键的操作时间会比人为操作要短,比方说0.01s ~ 0.05s。所以当我们不能判断按键的状态改变是因为抖动还是人为的时候,我们就等50ms再判断。因此,给查询法按键控制灯程序加一个延迟就可以消除抖动。
修改后的代码(加入软件消抖):用SW1控制D5,用SW2控制D6,功能是按下按键LED灯才会亮,松开按键LED灭。
#include "reg52.h"
sbit SW1 = P2^1;
sbit SW2 = P2^0;
sbit ledD5 = P3^7;
sbit ledD6 = P3^6;
/* API1: 从STC-ISP的软件延时计算器中拷贝下来的函数,用于软件消抖 */
void Delay50ms();
int main(void)
{
/* 查询法按键控制灯,不妨用SW1控制D5,用SW2控制D6 */
while(1){
Delay50ms();
if(SW1 == 0){
ledD5 = 0;
}else{
ledD5 = 1;
}
if(SW2 == 0){
ledD6 = 0;
}else{
ledD6 = 1;
}
}
return 0;
}
void Delay50ms() //@11.0592MHz
{
unsigned char i, j;
i = 90;
j = 163;
do
{
while (--j);
} while (--i);
}
两种切换LED灯亮灭状态的思路:
- 用两个按键控制一个LED:一个负责查询有没有按下“开机”按键,另一个负责查询有没有按下“关机”按键
- 用一个按键控制一个LED:人用眼睛去看,灯亮着的时候你想关闭就按一下按键,灯灭的时候你想打开就按一下按键。
代码心得:
- 灯状态的切换实质上就是I/O状态的翻转,不仅用在本次测试代码中,在其他I/O状态切换中也十分常见。
- 写状态切换的代码时,我们有两个思路,一个比较蠢,一个比较明智。先说比较蠢的方法:对当前灯的状态进行判断,如果灯亮着,就把让灭掉;如果灯灭着,就把它亮起来。再说比较明智的做法:用逻辑非运算符 ! 对操作数取其反向状态。
示例代码(一个按键控制一个LED,50ms延时):用SW1控制D5,用SW2控制D6,功能是按下按键并松开后,切换LED状态,保持LED长亮或长灭。
全局变量:
sbit指令找到与SW1连接的引脚,为P2^1
sbit指令找到与SW2连接的引脚,为P2^0
sbit指令找到D5这个LED,引脚为P3^7
sbit指令找到D6这个LED,引脚为P3^6
f1. 50ms延时函数
1. 初始化D5状态: ledD5 = 0;
2. 初始化D6状态: ledD6 = 0;
3. while死循环,用按键切换LED状态
3.1 50ms延时
3.2 判断SW1是否为0
3.2.1 如果是,
3.2.1.1 切换D5的状态: ledD5 = !ledD5;
3.2.2 否则,啥也不干
3.3 判断SW2是否为0
3.3.1 如果是,
3.3.1.1 切换D6的状态: ledD6 = !ledD6;
3.3.2 否则,啥也不干
#include "reg52.h"
sbit SW1 = P2^1;
sbit SW2 = P2^0;
sbit ledD5 = P3^7;
sbit ledD6 = P3^6;
void Delay50ms();
int main(void)
{
/* 查询法按键控制灯,不妨用SW1控制D5,用SW2控制D6 */
ledD5 = 0;
ledD6 = 0;
while(1){
Delay125ms();
if(SW1 == 0){
ledD5 = !ledD5;
}
if(SW2 == 0){
ledD6 = !ledD6;
}
}
return 0;
}
void Delay50ms() //@11.0592MHz
{
unsigned char i, j;
i = 90;
j = 163;
do
{
while (--j);
} while (--i);
}
存在的问题:按下按键后灯可能亮一下就灭掉了,无法正确切换状态。
解决方法:
- 把延时的时间稍微加长,至少大于等于正常人按下按键的操作时间——0.1s,保证灯不至于在按下按键的操作时间内有多次状态切换。经过我个人的测试,这个时间在0.1s~0.2s都是可以接受的。设置成0.2s以上的延时的话,你可能要按的时间稍微长一点才会出现反应。当然如果你一直按着按键的话,那么就和我们之前灯的闪烁程序无异了(因为灯的闪烁实质上也是I/O状态翻转问题)。
- 选择用2个按键控制1个LED的方法,这样比较可靠,只需要给上50ms的软件消抖就行了。
修改后的代码(一个按键控制一个LED,125ms延时):用SW1控制D5,用SW2控制D6,功能是按下按键并松开后,切换LED状态,保持LED长亮或长灭。
#include "reg52.h"
#include "INTRINS.H"
sbit SW1 = P2^1;
sbit SW2 = P2^0;
sbit ledD5 = P3^7;
sbit ledD6 = P3^6;
/* API1: 从STC-ISP的软件延时计算器中拷贝下来的函数,用于LED状态切换时的延时 */
void Delay125ms();
int main(void)
{
/* 查询法按键控制灯,不妨用SW1控制D5,用SW2控制D6 */
ledD5 = 0;
ledD6 = 0;
while(1){
Delay125ms();
if(SW1 == 0){
ledD5 = !ledD5;
}
if(SW2 == 0){
ledD6 = !ledD6;
}
}
return 0;
}
void Delay125ms() //@11.0592MHz
{
unsigned char i, j;
i = 225;
j = 28;
do
{
while (--j);
} while (--i);
}
应用场景:当业务逻辑比较复杂时,我们不希望在流程控制语句中直接修改I/O状态,而是先记住某个I/O口的状态,然后在需要修改的地方对状态进行修改。
这种方法在我们学习C语言的时候也经常使用。我通常用flag变量来表示标志位。
单片机中的方法:我们使用标志位时不希望看到0和1,因为代码的可阅读性不好,所以我们一开始给0和1分别用符号常量ON和OFF表示(宏定义)。
用标志位实现代码8.2.1(按键软件消抖):
#include "reg52.h"
#define LED_ON 0
#define LED_OFF 1
sbit SW1 = P2^1;
sbit SW2 = P2^0;
sbit ledD5 = P3^7;
sbit ledD6 = P3^6;
/* API1: 从STC-ISP的软件延时计算器中拷贝下来的函数,用于软件消抖 */
void Delay50ms();
int main(void)
{
char D5flag = LED_OFF;
char D6flag = LED_OFF;
/* 查询法按键控制灯,不妨用SW1控制D5,用SW2控制D6 */
while(1){
/* 软件消抖 */
Delay50ms();
/* 按下按键LED灯才会亮,松开按键LED灭。 */
if(SW1 == 0){
D5flag = LED_ON;
}else{
D5flag = LED_OFF;
}
if(SW2 == 0){
D6flag = LED_ON;
}else{
D6flag = LED_OFF;
}
/* 标志位判断 */
if(D5flag == LED_ON){
ledD5 = 0;
}else{
ledD5 = 1;
}
if(D6flag == LED_ON){
ledD6 = 0;
}else{
ledD6 = 1;
}
}
return 0;
}
void Delay50ms() //@11.0592MHz
{
unsigned char i, j;
i = 90;
j = 163;
do
{
while (--j);
} while (--i);
}
用标志位实现代码8.2.2(I/O状态翻转):
#include "reg52.h"
#define LED_ON 0
#define LED_OFF 1
sbit SW1 = P2^1;
sbit SW2 = P2^0;
sbit ledD5 = P3^7;
sbit ledD6 = P3^6;
/* API1: 从STC-ISP的软件延时计算器中拷贝下来的函数,用于LED状态切换时的延时 */
void Delay125ms();
int main(void)
{
char D5flag = LED_OFF;
char D6flag = LED_OFF;
ledD5 = LED_OFF;
ledD6 = LED_ON;
/* 查询法按键控制灯切换LED状态,用SW1控制D5,用SW2控制D6 */
while(1){
/* 延时,包含软件消抖以及保证灯不至于在按下按键的操作时间内有多次状态切换 */
Delay125ms();
/* 按下按键并松开后,切换LED状态,保持LED长亮或长灭。 */
if(SW1 == 0){
D5flag = ! D5flag;
}
if(SW2 == 0){
D6flag = ! D6flag;
}
/* 标志位判断 */
if(D5flag == LED_ON){
ledD5 = 0;
}else{
ledD5 = 1;
}
if(D6flag == LED_ON){
ledD6 = 0;
}else{
ledD6 = 1;
}
}
return 0;
}
void Delay125ms() //@11.0592MHz
{
unsigned char i, j;
i = 225;
j = 28;
do
{
while (--j);
} while (--i);
}