蓝桥杯单片机入门培训笔记 (一)
第一章:工程搭建 - 给程序一个“家”
核心思想:写程序就像盖房子,需要先规划好地方、准备好图纸和工具。
1. 建立项目文件夹
-
比喻:在电脑上新建一个文件夹,就像为你的“代码房子”买了一块地皮。以后这个项目的所有文件(图纸、材料)都放在这里,方便管理,不会乱。
-
操作:在合适的位置(如桌面或D盘),新建一个文件夹,命名为
LED_Test。
2. 使用Keil创建工程
-
比喻:Keil是我们的“建筑软件”,用来编写和编译代码。创建工程就是在这个软件里为新房子立项。
-
步骤:
-
打开Keil uVision。
-
Project → New μVision Project…。
-
导航到你刚建立的
LED_Test文件夹。 -
输入工程名(建议和文件夹同名,如
LED_Test),点击保存。 -
在弹出的芯片选择窗口中,找到并选择 AT89C52,点击OK。
-
关于“是否添加启动文件”的弹窗,选择“否”即可(对于基础学习够用了)。
-
3. 工程设置与文件创建
-
步骤:
-
点击工具栏的 “魔法棒” (Options for Target) 按钮。
-
切换到 “Output” 选项卡。
-
勾选 “Create HEX File”。(关键!) 这个
HEX文件就是能下载到单片机里运行的“可执行文件”。 -
点击 “品字形” (Manage Project Items) 按钮。
-
在
Project Targets下将Target 1改名为你的工程名(如LED_Test)。 -
在
Groups下,将原有的Source Group 1重命名为User(寓意用户编写的代码组)。点击OK。 -
在左侧
Project窗口,右键点击User组,选择 “Add New Item to Group ‘User’…”。 -
选择
C File (.c),命名为main.c,点击Add。
-
4. 编写初始代码框架
-
习惯:良好的代码结构是优秀程序员的标志。我们采用分区编写法。
-
操作:在
main.c文件中输入以下内容。/* ... */或//是注释,用于解释代码,不会被编译。
/*=============================================
* 头文件声明区域 (工具箱引入区)
* 说明:这里用来包含程序需要的“工具箱”,比如单片机寄存器的定义。
=============================================*/
#include <REGX52.H> // 引入了AT89C52单片机的“操作说明书”
/*=============================================
* 主函数区域 (程序心脏区)
* 说明:单片机通电后,就从这里开始执行代码。
=============================================*/
void main() // 主函数,程序入口
{
while(1) // 一个无限循环,让单片机永不停止地工作
{
// 你的功能代码将写在这里
}
}
第二章:点亮LED - 与硬件第一次对话
核心原理:单片机通过控制其引脚(Pin)输出高电平(1,通常是5V)或低电平(0,通常是0V)来控制外设。根据开发板原理图,LED通常一端接电源(VCC),另一端通过电阻连接到单片机引脚。当引脚输出低电平(0)时,形成电压差,LED点亮;输出高电平(1)时,没有电压差,LED熄灭。
- 比喻:单片机的引脚就像一排开关。输出“0”是按下开关(灯亮),输出“1”是抬起开关(灯灭)。
方法一:整体赋值法(操控整个端口)
-
思路:P1端口有8个引脚(P1.0 ~ P1.7),对应8个LED(通常是D1 ~ D8)。我们可以一次性给整个P1端口赋值一个8位二进制数来控制所有LED。
-
例如:想让 D2 和 D6 亮,其余灭。
-
从右向左看(P1.0 → P1.7):
P1.7 P1.6 P1.5 P1.4 P1.3 P1.2 P1.1 P1.0 -
灯亮为0,灯灭为1:
1 0 1 1 1 0 1 1 -
得到二进制:
1011 1011 -
转换为十六进制:
0xBB(在计算器程序员模式下转换)
-
-
代码实现:将以下代码放入
while(1)循环中。
/*
* 实验:点亮指定LED (整体赋值法)
* 效果:D2和D6亮,其余灭
*/
P1 = 0xBB; // 一次性给P1口所有引脚赋值
方法二:位赋值法(操控单个引脚)
-
思路:直接操作某个具体的引脚,语法为
P1_引脚号。更加直观灵活。 -
代码实现:将以下代码放入
while(1)循环中。
/*
* 实验:点亮指定LED (位赋值法)
* 效果:第一个灯(D1,对应P1.0)和最后一个灯(D8,对应P1.7)亮
*/
P1_0 = 0; // 点亮连接到P1.0引脚的LED (D1)
P1_7 = 0; // 点亮连接到P1.7引脚的LED (D8)
// 注意:其它引脚状态未知,可能为亮也可能为灭。通常先全部关闭再控制特定灯是更好的习惯。
第三章:LED闪烁 - 让程序学会“等待”
原理:让灯亮一会,然后灭一会,循环往复,就形成了闪烁。
关键问题:单片机执行一条指令的速度极快(微秒甚至纳秒级别),如果直接 亮 -> 灭 -> 亮 -> 灭,人眼根本无法分辨,看到的将是灯持续亮着但稍微变暗。所以我们需要一个延时函数,让程序在执行 亮 和 灭 之间“休息”一会儿。
- 比喻:延时函数就像一个“定时器”或“让CPU打盹的指令”,告诉单片机:“现在什么也别做,数够一定的时间再继续”。
1. 获取延时函数代码
我们使用STC官方工具 STC-ISP 来生成精准的延时代码,这是一个非常实用的“外挂”。
-
打开
STC-ISP软件。 -
找到 “软件延时计算器” 选项卡。
-
系统频率 设置为
12MHz(蓝桥杯板载晶振通常是这个频率)。 -
定时长度 设置为
100毫秒。 -
选择
STC-Y1型号。 -
点击 “生成C代码”。
-
复制生成的代码。
2. 将延时函数加入工程
将复制的代码粘贴到你的 main.c 文件中,放在头文件声明之后,主函数之前。
/*=============================================
* 头文件声明区域
=============================================*/
#include <REGX52.H>
/*=============================================
* 延时函数区域 (系统休眠区)
=============================================*/
void Delay100ms(void) //@12.000MHz
{
unsigned char data i, j; // 声明两个变量用于循环计数
i = 195;
j = 138;
do
{
while (--j); // 内层循环,消耗时间
} while (--i); // 外层循环
}
/*=============================================
* 主函数区域
=============================================*/
void main()
{
while(1)
{
P1_0 = 0; // 步骤1:点亮D1 (给低电平)
Delay100ms(); // 步骤2:等待100毫秒 (注意!调用时不需要写 `void`)
P1_0 = 1; // 步骤3:熄灭D1 (给高电平)
Delay100ms(); // 步骤4:再等待100毫秒
// 然后循环回到步骤1,实现闪烁
}
}
3. 进阶:可调参数的延时函数
每次都生成不同时长的延时函数很麻烦。我们可以改造它,让它通过参数来控制延时长短。
方法:先利用 STC-ISP 生成一个 1毫秒 的基础延时函数,然后通过循环调用它 xms 次来实现任意毫秒的延时。
/*=============================================
* 可调参数延时函数
* 函数名:Delay1ms
* 参数:xms - 希望延时的毫秒数
* 示例:Delay1ms(500); // 延时500毫秒
=============================================*/
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--) // 循环xms次,每次循环大约耗时1毫秒
{
// 以下是STC-ISP生成的1ms延时核心代码
i = 12;
j = 169;
do
{
while (--j);
} while (--i);
}
}
使用示例:实现一个频率更快的闪烁。
void main()
{
while(1)
{
P1_0 = 0;
Delay1ms(50); // 只延时50ms
P1_0 = 1;
Delay1ms(50); // 只延时50ms
}
}
温馨提示:如果生成的代码中有
_nop_();语句,而你的代码没有包含#include <INTRINS.H>,编译器会报错。简单处理方法是直接删除这行_nop_();语句,通常不影响基础延时功能。
第四章:流水灯 - 让灯光“流动”起来
思路:让亮灯的状态像水流一样在8个LED之间依次移动。例如:1000 0000 → 0100 0000 → 0010 0000 → …
实现妙招:使用C51标准库里的循环移位函数,它能让我们的代码极其简洁。
1. 引入“工具库”
循环移位函数 _crol_ (循环左移) 和 _cror_ (循环右移) 定义在 intrins.h 头文件中,所以需要先引入它。
2. 代码实现(循环左移流水灯)
/*=============================================
* 头文件声明区域
=============================================*/
#include <REGX52.H>
#include <INTRINS.H> // 引入循环移位函数库
/*=============================================
* 延时函数区域 (使用可调参数版)
=============================================*/
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 12;
j = 169;
do
{
while (--j);
} while (--i);
}
}
/*=============================================
* 变量声明区域
=============================================*/
unsigned char ucLed = 0xFE; // 初始值 1111 1110, 表示只有D1(P1.0)亮
/*=============================================
* 主函数区域
=============================================*/
void main()
{
while(1)
{
P1 = ucLed; // 将当前灯的状态输出到P1口
Delay1ms(200); // 保持当前状态200ms,让人眼能看到
ucLed = _crol_(ucLed, 1); // 关键!将ucLed的8位二进制数循环左移1位
// 例如:1111 1110 -> 1111 1101 (D1灭,D2亮)
}
}
效果:你会看到亮灯从D1流向D2,再流向D3……到D8后,又从D1开始,循环往复。
尝试修改:将 _crol_ 改为 _cror_,观察灯光流动方向的变化。
恭喜你! 至此,你已经掌握了单片机最基础的输入输出控制、函数使用和库函数调用,成功迈入了单片机编程的大门。接下来的学习就是对更多硬件模块(如数码管、键盘、蜂鸣器、定时器)的探索,但核心思想都是相通的:查看原理图 → 控制对应引脚 → 考虑时序延时。