- 嵌入式入门教学汇总:
- 文章中所使用到的所有代码模块:【免费】基于STC89C52RC的代码模块资源-CSDN文库
目录
一、前期准备
1、硬件设备
- 后续所有实验都是使用STC89C52RC。
2、软件设备
二、预备知识
1、什么是单片机?
- 在一片集成电路芯片上集成微处理器、存储器、IO接口电路,从而构成了单芯片微型计算机,及单片机。
- STC89C52单片机:
- STC:公司
- 89:所属系列
- C:工作电压(5.5V~3.8V)
- 52:程序空间及RAM空间大小(8KB程序空间及512B的RAM空间)
2、电平特性
- 定义单片机为TTL电平:高+5v、低0v
- RS232电平(计算机的串口):高-12v、低+12v
- 故计算机与单片机之间通讯时需要加电平转换芯片max232
3、二进制数的逻辑关系
- 与:&
- 或:+
- 非:!
- 异或:⊕(相同为0,不同为1)
4、80C51系列介绍
- 以8051为基核开发出的CMOS工艺单片机产品统称为80C51系列。
- STC(公司名)89(系列)C(CMOS)52(2为内部存储空间大小2*4=8k)RC
- 40(晶振频率)C(商业级-温度)-PDIP(封装格式)
- 0721(07年21周)CV4336
5、80C51引脚封装
- 总线型:四组,每组8位。
- 非总线型
6、C-51数据类型扩充定义
- sfr:特殊功能寄存器声明。
- sfr16:sfr16位数据声明。
- sbit:特殊功能位声明。
- bit:位变量声明。
7、C-51包含的头文件
- reg51.h:定义特殊功能寄存器和位寄存器。
- math.h:定义常用数学运算。
8、C-51的运算符
- >>:位右移
- <<:位左移
- &:按位与
- |:按位或
- ^:按位异或
- ~:按位取反
9、单片机主要掌握一下几点
- 最小系统能够运行起来的必要条件:电源、晶振、复位电路(程序从头执行)。
- 对单片机任意IO口的随意操作:输出控制电平高低、输出检测电平高低。
- 定时器:重点掌握最常用的方式2。
- 中断:外部中断、定时器中断、串口中断。
- 串口通信:单片机之间、单片机与计算机间。
10、电路图符号表示
- 电阻:R
- 电容:C
- 电感:L
- 集成块/芯片:U
- 地:GND
- 电源:VCC、AVDD、DVDD
三、LED模块
1、点亮一个LED
1.1、LED的位置
1.2、新建一个工程
- 新建一个工程。
- 选择存放位置,新建一个文件夹用于存放该工程。
- 进入文件夹,输入文件名,即工程名。
- 选择单片机的型号。选择Atmel下的AT89C52。(本单片机使用的是STC89C52)
- 新建一个c语言程序文件。
1.3、LED原理图
- 下图红框区域相连接。
- LED右端接的是电源,为高电平,故左端为低电平即可导通。(单片机默认输出高电平)
1.4、单片机如何控制LED
- P2寄存器的8位决定了LED的高低电平。1为高电平,0为低电平。
- 当为低电平时,低于外部电压,有电流通过,点亮led。
- 【注】51单片机所有IO口上电后默认全是高电平。
- 例如:控制第一个灯亮,p2寄存器应为:1111 1110。
1.5、进制转换
- c语言中,不能识别二进制,应该转换为十六进制。
- 例如:
- p2=1111 1110; // 错误
- p2=0xFE; // 正确,前面0x表示十六进制。
1.6、编写代码
#include <REGX52.H> // 头文件,定义特殊功能寄存器和位寄存器
void main(){
P2=0xFE; // 1111 1110
while(1); // 让程序停止在这,不让main重复执行
}
1.7、设置生成.hex下载文件
- 设置完后,编译程序,生成.hex文件。
1.8、将.hex文件下载到单片机中
- 将单片机连接电脑串口。
- 打开STC-ISP程序,选择单片机型号(查看芯片上的字符,89C52和89C52RC不同),选择hex文件,点击下载。
- 重启单片机,下载成功,成功点亮第一个led。。
2、LED闪烁
- 新建工程和c语言程序文件,与前相同。
- 尝试点亮一个led再熄灭它。
-
#include<REGX52.H> void main(){ while(1){ P2=0xFE; // 亮 P2=0xFF; // 灭 } }
- 结果led常亮,并未闪烁。原因是led闪烁的频率过快,人的肉眼无法捕捉。
- 使用STC-ISP生成延时函数。设置要与使用的单片机一致。
- 添加延时函数。
-
#include<REGX52.H> #include<INTRINS.H> // 引入nop函数 void Delay500ms() // 500ms延时函数 { unsigned char i, j, k; _nop_(); i = 4; j = 205; k = 187; do { do { while (--k); } while (--j); } while (--i); } void main(){ while(1){ P2=0xFE; // 亮 Delay500ms(); // 延时500ms P2=0xFF; // 灭 Delay500ms(); } }
- 编译后下载程序到单片机,LED灯以500ms的时间间隔闪烁。
3、LED流水灯
- 新建工程和c语言程序文件,设置生成hex文件。
- 编辑代码:
-
#include<regx52.h> #include<intrins.h> // 引入nop函数 void Delay500ms() //500ms延时函数 { unsigned char i, j, k; _nop_(); i = 4; j = 205; k = 187; do { do { while (--k); } while (--j); } while (--i); } void main(){ while(1){ P2=0xFE; // 1111 1110 Delay500ms(); // 延时500ms P2=0xFD; // 1111 1101 Delay500ms(); P2=0xFB; // 1111 1011 Delay500ms(); P2=0xF7; // 1111 0111 Delay500ms(); P2=0xEF; // 1110 1111 Delay500ms(); P2=0xDF; // 1101 1111 Delay500ms(); P2=0xBF; // 1011 1111 Delay500ms(); P2=0x7F; // 0111 1111 Delay500ms(); } }
- 编译后下载程序到单片机,LED灯以500ms的时间间隔流动。
- 原延时函数不方便,不能随时更改延时时间,重新编辑延时函数。先使用STC-ISP生成一个延时1ms的函数。
- C51数据类型
- 为延时函数添加一个参数,控制延时时间。
-
#include<regx52.h> void Delay(unsigned int xms) //带参延时函数 { unsigned char i, j; while(xms--){ i = 2; j = 239; do { while (--j); } while (--i); } } void main(){ while(1){ P2=0xFE; // 1111 1110 Delay(500); // 延时500ms P2=0xFD; // 1111 1101 Delay(500); P2=0xFB; // 1111 1011 Delay(500); P2=0xF7; // 1111 0111 Delay(500); P2=0xEF; // 1110 1111 Delay(500); P2=0xDF; // 1101 1111 Delay(500); P2=0xBF; // 1011 1111 Delay(500); P2=0x7F; // 0111 1111 Delay(500); } }
- 带参数的延时函数更加灵活。
四、独立按键
1、独立按键控制LED亮灭
1.1、独立按键
- 相当于是一种电子开关,按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开。
1.2、独立按键原理图
- 下图红框区域相连接。
1.3、编写代码
- 由原理图知,K1由P3.1控制。
-
#include<regx52.h> void main(){ //P2=0xFE; while(1){ if(P3_1==0){ // K1按下 P2_0=0; // 按位操作,在regx52.h头文件中有声明 }else{ // K1松开 P2_0=1; } } }
- K1按下,LED亮;K1松开,LED灭。
2、独立按键控制LED状态
2.1、按键的抖动
- 对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。
- 抖动会使对按键的判断产生一些误操作,可以通过代码解决。
2.2、解决抖动并控制LED状态
-
#include<regx52.h> void Delay(unsigned int xms) //带参延时函数 { unsigned char i, j; while(xms--){ i = 2; j = 239; do { while (--j); } while (--i); } } void main(){ while(1){ if(P3_1==0){ // K1按下 // 防抖 Delay(20); while(P3_1==0); Delay(20); P2_0=~P2_0; // 取反 } } }
- 按下K1,没反应,松开K1,灯亮;按下K1,无反应,松开K1,灯灭。
3、独立按键控制LED显示二进制
-
#include<regx52.h> void Delay(unsigned int xms) { unsigned char i, j; while(xms--){ i = 2; j = 239; do { while (--j); } while (--i); } } void main(){ unsigned char Led_Num=0; // 8位,相当于寄存器的8位 while(1){ if(P3_1==0){ // K1按下 // 防抖 Delay(20); while(P3_1==0); Delay(20); Led_Num++; P2=~Led_Num; // 取反 } } }
- 每按一次按键,8个LED显示对应的二进制。
- 如:,表示0000 0111。
4、独立按键控制LED移位
-
#include<regx52.h> void Delay(unsigned int xms) // 延时函数 { unsigned char i, j; while(xms--){ i = 2; j = 239; do { while (--j); } while (--i); } } void main(){ unsigned char Led_Num=0; P2=~0x01; // 初始化 while(1){ // K1按键,左移 if(P3_1==0){ Delay(20); while(P3_1==0); Delay(20); if(Led_Num==0) Led_Num=7; else Led_Num--; P2=~(0x01<<Led_Num); } // K2按键,右移 if(P3_0==0){ Delay(20); while(P3_0==0); Delay(20); Led_Num++; if(Led_Num>=8) // 越界判断 Led_Num=0; P2=~(0x01<<Led_Num); } } }
- 按下K1,LED左移一个;按下K2,LED右移一个。
五、数码管
1、静态数码管显示
1.1、数码管介绍
- 数码管是一种简单、廉价的显示器,是由多个发光极管封装在一起组成“8”字型的器件。
1.1.1、一位数码管
- 段的名称、引脚序号
- 共阴极连接、共阳极连接
- 例如,让共阴极数码管显示6。
- ,应该让A、C、D、E、F、G点亮。
- 首先需要将3,8引脚接地,即位选(让某个数码管亮)。
- ,控制7、4、2、1、9、10输出高电平,即段选(让数码管输出6)。
1.1.2、四位一体数码管
- 引脚序号
- 连接方式
- 例如,让共阴极数码管第3位显示1。
- ,需要如下图所示。
- 【注】同一时刻,只能有一个位被选中显示数字,或者4个位都显示相同的数字。(静态)
1.2、数码管原理图
- 上图左边74HC573为双向数据缓冲器,来提高驱动能力。
- 138译码器用来控制位选(3位输入,8位输出),原理如下表。
1.3、编写代码
- 让第三位数码管显示6。
-
#include<regx52.h> void main(){ // 位选 P2_4=1; P2_3=0; P2_2=1; // 段选 P0=0x7D; // 0111 1101 while(1); }
1.4、引入函数
-
#include<regx52.h> //段选 unsigned char NixieTable[]={ 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x00 }; // 数码管显示函数 void Nixie(unsigned char Location, int Number){ switch(Location){ case 1: P2_4=1; P2_3=1; P2_2=1; break; case 2: P2_4=1; P2_3=1; P2_2=0; break; case 3: P2_4=1; P2_3=0; P2_2=1; break; case 4: P2_4=1; P2_3=0; P2_2=0; break; case 5: P2_4=0; P2_3=1; P2_2=1; break; case 6: P2_4=0; P2_3=1; P2_2=0; break; case 7: P2_4=0; P2_3=0; P2_2=1; break; case 8: P2_4=0; P2_3=0; P2_2=0; break; } P0=NixieTable[Number]; } void main(){ Nixie(3,6); while(1); }
2、动态数码管显示
2.1、数码管消影
- 现象:数码管数字显示的位置错乱。
- 动态数码管显示过程:
- 位选 -> 段选 -> 位选 -> 段选。。。
- 原因:速度太快,数据串位。
- 解决方法:
- 位选 -> 段选 -> 清零 -> 位选 -> 段选。。。
2.2、编写代码
-
#include<regx52.h> // 延时函数 void Delay(unsigned int xms) { unsigned char i, j; while(xms--){ i = 2; j = 239; do { while (--j); } while (--i); } } //段选 unsigned char NixieTable[]={ 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x00 }; // 数码管显示函数 void Nixie(unsigned char Location, int Number){ switch(Location){ case 1: P2_4=1; P2_3=1; P2_2=1; break; case 2: P2_4=1; P2_3=1; P2_2=0; break; case 3: P2_4=1; P2_3=0; P2_2=1; break; case 4: P2_4=1; P2_3=0; P2_2=0; break; case 5: P2_4=0; P2_3=1; P2_2=1; break; case 6: P2_4=0; P2_3=1; P2_2=0; break; case 7: P2_4=0; P2_3=0; P2_2=1; break; case 8: P2_4=0; P2_3=0; P2_2=0; break; } P0=NixieTable[Number]; // 消影 Delay(1); P0=0x00; } void main(){ while(1){ Nixie(1,1); //Delay(20); Nixie(2,2); //Delay(20); Nixie(3,3); //Delay(20); } }
2.3、数码管驱动方式
- 单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间(如上代码)。
- 专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可。(TM1640芯片)
六、模块化编程及LCD1602调试工具
1、模块化编程
- 把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "xxx.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等。
1.1、案例
- 新建工程文件,在其目录下新建三个文件夹。Functions用来放模块化的函数,Objects用来放.hex文件,Listings用来存在.lst文件。
- 设置生成.hex文件,并把它存放到Objects文件夹中。
- 设置listing存放到Listings文件夹中。
- 假设已经有下面延时函数的模块。
- Delay.h
-
#ifndef __DELAY_H__ #define __DELAY_H__ void Delay(unsigned int xms); #endif
- 开头两句和最后一句代码是为了防止重复定义。(在头文件中都有)
- #ifndef __XX_H__:如果没有定义__XX_H__,执行下列语句。
- #define __XX_H__:定义__XX_H__。
- #endif:与#ifndef匹配,组成“括号”。
- Delay.c
-
void Delay(unsigned int xms) { unsigned char i, j; while(xms--){ i = 2; j = 239; do { while (--j); } while (--i); } }
- 数码管模块
- Nixie.c
-
#include <REGX52.H> #include "Delay.h" //段选 unsigned char NixieTable[]={ 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x00 }; /** * @brief 动态数码管显示 * @param Location 显示位置 范围:1~6 * @param Number 显示数值 范围:0~16 * @retval 无 */ void Nixie_Dynamic(unsigned char Location, int Number){ switch(Location){ case 1: P2_4=1; P2_3=1; P2_2=1; break; case 2: P2_4=1; P2_3=1; P2_2=0; break; case 3: P2_4=1; P2_3=0; P2_2=1; break; case 4: P2_4=1; P2_3=0; P2_2=0; break; case 5: P2_4=0; P2_3=1; P2_2=1; break; case 6: P2_4=0; P2_3=1; P2_2=0; break; case 7: P2_4=0; P2_3=0; P2_2=1; break; case 8: P2_4=0; P2_3=0; P2_2=0; break; } P0=NixieTable[Number]; // 消影 Delay(1); P0=0x00; } /** * @brief 静态数码管显示 * @param Location 显示位置 范围:1~6 * @param Number 显示数值 范围:0~16 * @retval 无 */ void Nixie_Static(unsigned char Location, int Number){ switch(Location){ case 1: P2_4=1; P2_3=1; P2_2=1; break; case 2: P2_4=1; P2_3=1; P2_2=0; break; case 3: P2_4=1; P2_3=0; P2_2=1; break; case 4: P2_4=1; P2_3=0; P2_2=0; break; case 5: P2_4=0; P2_3=1; P2_2=1; break; case 6: P2_4=0; P2_3=1; P2_2=0; break; case 7: P2_4=0; P2_3=0; P2_2=1; break; case 8: P2_4=0; P2_3=0; P2_2=0; break; } P0=NixieTable[Number]; }
- Nixie.h
-
#ifndef __NIXIE_H__ #define __NIXIE_H__ void Nixie_Dynamic(unsigned char Location, int Number); void Nixie_Static(unsigned char Location, int Number); #endif
- 将延时函数和数码管模块复制到Functions文件夹中。
- 先添加main函数,再添加Delay.c和Nixie.c到工程中。
- 设置引入路径(用于寻找.h文件)。
- 编写main函数。
-
#include <REGX52.H> #include "Delay.h" #include "Nixie.h" void main(){ while(1){ Nixie_Dynamic(1,1); Nixie_Dynamic(2,2); Nixie_Dynamic(3,3); } }
- 延时函数和数码管函数被成功调用。
1.2、注意事项
- .c文件:函数、变量的定义。
- .h文件:可被外部调用的函数、变量的声明。
- 任何自定义的变量、函数在调用前必须有定义或声明(同一个.c)。
- 使用到的自定义函数的.c文件必须添加到工程参与编译。
- 使用到的.h文件必须要放在编译器可寻找到的地方(工程文件夹根目录、安装目录、自定义)。
2、LCD1602调试工具
- 下图为插入位置和调节亮度(使用螺丝刀左右调节)的位置。
- 使用LCD1602液晶屏作为调试窗口,提供类似printf函数的功能,可实时观察单片机内部数据的变换情况,便于调试和演示。
- 这里有LCD1602模块化的代码,只需要知道怎么使用即可。下载链接:【免费】基于STC89C52RC的代码模块资源-CSDN文库
函数 | 作用 |
LCD_Init(); | 初始化 |
LCD_ShowChar(1,1,'A'); | 显示一个字符 |
LCD_ShowString(1,3,"Hello"); | 显示字符串 |
LCD_ShowNum(1,9,123,3); | 显示十进制数字 |
LCD_ShowSignedNum(1,13,-66,2); | 显示有符号十进制数宇 |
LCD_ShowHexNum(2,1,0xA8,2); | 显示十六进制数字 |
LCD_ShowBinNum(2,4,0xAA,8); | 显示二进制数宇 |
2.1、调用方法
- 将LCD1602的模块化代码复制到工程目录中。
- 在Keil中引入LCD1602驱动代码。
- 如果添加的文件打不开,将文件格式改为C。
- 【注】Flah->configure Flash Tools->C51->Include Paths,添加.h文件的路径(一定要添加,不然不显示!)。
- 编写main函数。
-
#include <REGX52.H> #include "LCD1602.h" void main(){ LCD_Init(); // 初始化 LCD_ShowChar(1,1,'A'); // 第一行,第一列,显示一个字符 LCD_ShowString(1,3,"Hello"); // 第一行,第三列,显示一个字符串 LCD_ShowNum(1,9,123,3); // 第一行,第九列,显示数字,三位 LCD_ShowSignedNum(1,13,-66,2); // 第一行,第十三列,显示数字,两位 LCD_ShowHexNum(2,1,0xA8,2); // 第二行,第一列,显示十六进制数,两位 LCD_ShowBinNum(2,4,0xAA,8); // 第二行,第四列,显示二进制数,八位 while(1); }
- 下载hex文件到单片机,LCD1602显示如下。
2.2、调试方法
- 假设,验证程序中的变量是否每秒加一。
- 添加上面延时函数模块到工程目录中。
- 将Delay.c添加到工程中。
- 设置引入路径。
- 编写main函数。
-
#include <REGX52.H> #include "LCD1602.h" #include "Delay.h" int Result=0; void main(){ LCD_Init(); // 初始化 while(1){ Result++; Delay(1000); LCD_ShowNum(1,1,Result,3); } }
- 编译下载后,LCD1602显示每秒加一。