冲哥-AI32G12K128-全集学习心得(更新中)
<h1>1. <strong>什么是单片机</strong></h1><h2><strong>1.1 单片机介绍</strong></h2>
<blockquote>
<p><em>单片机(Single-Chip Microcomputer)是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。从上世纪80年代,由当时的4位、8位单片机,发展到现在的300M的高速单片机。</em><br />
-- 引用于百度百科</p>
</blockquote>
<hr />
<p>STC系列单片机具备不同型号不同封装,在不同的使用场景下具备不同用途。本次学习的STC32G12K128单片机有不同封装规格如下:<br />
<em>- LQFP32、QFN32、LQFP48、QFN48、LQFP64、QFN64</em><br />
STC32G系列单片机命名规则也有所不同:<br />
<em>- 32X代表:子系列,32G无浮点32F有浮点</em><br />
<em>- xK代表:SRAM空间大小如40K字节</em><br />
<em>- xx代表:64为64K字节128为128K字节</em></p>
<hr />
<p>单片机基本就是一个小的功能较少的计算机,通过编写程序来读取输入引脚上的电压信号,能够在引脚上输出特定的电平信号,能够在特定的引脚上输出电压信号,可进行时间计算、能够进行数学1计算、能够进行逻辑运算、能够顺序动作、能通信等功能。单片机可进行有规律可循控制应用。<br />
单片机的广泛用途:<br />
智能仪器、工业控制、家用电器、网络及通信、医用设备领域、模块化系统、汽车电子</p>
<hr />
<h2><strong>1.2 常见问题</strong></h2>
<p>使用C进行编程<br />
使用寄存器和库函数相关:<br />
使用寄存器:通过使用其他单片机时可更快更好上手<br />
使用库函数:库函数较为麻烦需要不断核对</p>
<p>学习相关:</p>
<ol>
<li>理论实践结合</li>
<li>举一反三</li>
<li>多看官网、多看手册、多在论坛沟通交流<br />
官网链接:[[]]</li>
<li>尝试做项目、竞赛题</li>
</ol>
程序文件和完整笔记会在该贴完结时附件在此回复上,原来带有图片的可能会上传github,使用的markdown文档编辑器是obsidian。 <h1>2. 编译、仿真开发环境的建立</h1>
<p>详细参考STC32G12K128官方文档<br />
主要步骤分为:<br />
<em>1. 安装Keil</em><br />
<em>2. 安装C251编译环境</em><br />
<em>3. 在STC-ISP(官方烧录工具)添加芯片型号及头文件到keil</em><br />
<em>4. 配置Keil中的超64K程序项目</em><br />
<em>5. 选择CPU module、程序大小、hex format选择HEX-386模式</em><br />
<em>6. 编写简单点灯工程进行测试</em><br />
主要下载链接:<br />
STC-ISP:[[]]<br />
其他开发步骤:<br />
程序设计阶段(与原理图设计并发进行):<br />
1.单片机资源配置<br />
2.功能实现<br />
原理图设计阶段:<br />
1.查阅数据手册<br />
2.典型的模拟电路运用<br />
3.典型的数字电路运用<br />
4.ERC检查<br />
PCB设计阶段:<br />
1.确定结构及周边器件位置<br />
2.确定设计规范<br />
3.进行PCB设计<br />
4.DRC检查及其他特性检查<br />
STC32G12K128实验箱9.4版本下载备注:<br />
USB连接电脑按下P3.2按钮松开OFF松开P3.2进入USB下载模式<br />
参考STC32G12K128手册中的参考电路设计</p>
<h1>3. 了解硬件</h1>
<h2><strong>3.1 STC32G12K128实验箱功能说明</strong></h2>
<p>实验箱上有各类独立模块</p>
<pre><code>*蜂鸣器、红外发射、红外接收、电压比较器、数码管、LED灯、万能板*
*调试接口、RS232电路+RS232接口、FLASH扩展、ADC、DAC、PWM*
*NTC测温、18B20、24C02、矩阵键盘、独立按键、LCD接口及USB接口*
</code></pre>
<hr />
<p>独立模块功能及应用介绍--来源:ChatGPT</p>
<pre><code>1. 蜂鸣器(Buzzer)
功能:发出声音警报或提示音。
应用:用于报警、提示用户操作结果或系统状态。
2. 红外发射(IR Transmitter)
功能:通过红外线发送控制信号。
应用:遥控器、无线数据传输、传感器通信等。
3. 红外接收(IR Receiver)
功能:接收红外线信号,并将其解码为电信号。
应用:红外遥控接收器、障碍检测传感器。
4. 电压比较器(Voltage Comparator)
功能:对两个输入电压进行比较,输出高电平或低电平。
应用:电压检测、信号整形、开关电路。
5. 数码管(7-segment Display)
功能:显示数字、字母和一些符号。
应用:用于电子钟、计数器、温度计等设备。
6. LED
功能:用于指示状态、显示信息或作为光源。
应用:指示灯、背光、显示屏等。
7. 万能板(Breadboard)
功能:用于快速搭建电路,便于原型开发和调试。
应用:实验、原型设计、快速测试。
8. 调试接口(Debug Interface)
功能:连接调试设备,用于程序下载和调试。
应用:JTAG、SWD接口,用于微控制器调试。
9. RS232电路 + RS232接口
功能:实现串行通信,标准电平为±12V。
应用:计算机通信、工业设备数据传输。
10. FLASH扩展
功能:增加系统的非易失性存储容量。
应用:用于存储程序、数据日志、配置文件等。
11. ADC(模数转换器)
功能:将模拟信号转换为数字信号。
应用:温度传感、光电传感等模拟信号采集。
12. DAC(数模转换器)
功能:将数字信号转换为模拟信号。
应用:音频输出、控制模拟设备。
13. PWM(脉宽调制)
功能:通过调节脉冲宽度来控制电压或电流输出。
应用:电机控制、LED亮度调节、音频合成。
14. NTC测温
功能:利用负温度系数热敏电阻(NTC)测量温度。
应用:温度检测、过热保护。
15. 18B20(温度传感器)
功能:数字温度传感器,通过单总线协议通信。
应用:温度监测、环境温度测量。
16. 24C02(EEPROM)
功能:I2C接口的EEPROM,用于存储数据。
应用:配置数据保存、参数存储、非易失性数据存储。
17. 矩阵键盘(Matrix Keyboard)
功能:按键矩阵,用于多按键输入检测。
应用:数字输入设备、密码锁、用户输入界面。
18. 独立按键
功能:用于检测单个按键的输入。
应用:设备启动、模式选择、用户控制。
19. LCD接口
功能:连接LCD屏幕,用于显示文本、图像信息。
应用:嵌入式系统中的数据可视化,如仪表盘、显示器。
20. USB接口
功能:用于数据通信和供电,支持多种协议(如USB 2.0、USB 3.0)。
应用:数据传输、设备充电、外设连接(如鼠标、键盘、U盘)。
</code></pre>
<hr />
<p>简要举了一些实际使用的例子、EEPROM保存数据<br />
课后作业:<br />
看完了上述板子上许许多多的传感器,和使用案例。在正式开始学习写代码之前,可以先想一想你可以用这个板子做一个什么东西,不需要考虑现在自己的能力能不能做出来,只需要考虑假如我是一个产品经理,我想做个什么东西出来。<br />
答:设定一个==基于STC32G的NTC测温显示报警系统==<br />
主要功能:<br />
*通过NTC测温将实时温度检测下来保存到EEPROM中*<br />
*通过使用RS232与上位机通讯读取EEPROM保存的测温数据,生成2-3天内的日志。*<br />
*当数据存储快满的时候,EEPROM擦除后重新进行对测温数据的存储。*<br />
*单片机要对EEPROM数据进行转换与上位机通信。*<br />
*外接TFT显示显示测温效果,并且设计一个差不多的面板。*<br />
*蜂鸣器用于当测温数据超过设定值时进行一个报警。*<br />
*当超过设定值则向RS232上位机通信发送报警警告,并且LCD弹出警报页面。*<br />
*当前温度低于报警设定值时恢复正常。*<br />
*外置Flash保存LCD显示需要的字库、图片。*<br />
其他功能:<br />
*红外遥控负责控制单片机来远程关闭I/O口设定的一些功能(扩展)*<br />
*RS232与上位机通讯可改成其他通信方式如IIC、SPI*</p>
<p>本次记录学习第四集内容,并且使用markdown记录:</p>
<p>由于图片存在本地,后续学习完成后我会上传开源markdown工程</p>
<h1>4.点亮第一颗LED灯</h1>
<h2>4.1LED点亮原理</h2>
<p>点亮原理:<br />
高低电平控制LED亮<br />
输出电压VCC是高电平输出电压GND为低电平(一般为0V)分别用1和0来表示(理想值)</p>
<p>GPIO定义:<br />
GPIO(General purpose input output)是通用输入输出端口简称,可以通过软件来读取其输入电平,或者控制他进行高低电平的输出。</p>
<p>程序设定:<br />
通过硬件连接LED与电阻串联到单片机的IO口上,程序输出高电平驱动LED亮。<br />
P0是一组GPIO口、P0.0是一组中的单个GPIO,可以一次性操作一组或者单个或者多个。<br />
![]<br />
在图上硬件为一个PNP三极管及8个电阻和LED灯<br />
需要点亮LED4则P4.0输出低电平,P6.0输出低电平</p>
<hr />
<h2>4.2新建工程</h2>
<p>具体参考project新建教程<br />
1.选用STC32G12K128芯片<br />
2.向工程文件夹添加程序文件<br />
3.开始编写程序<br />
![]<br />
这里Target中的Memory Mode和Code Flom Size可以进行修改,根据STC32G12K128手册中推荐设置来进行选用,一般为XSmall 和Huge模式,晶振频率可自行选择。</p>
<hr />
<h2>4.3点亮LED灯</h2>
<p>根据参考文档编写的程序实际上并没有点亮灯</p>
<pre><code>sfr P0 = 0x80;
sfr P0M1 = 0x93;
sfr P0M0 = 0x94;
void main()
{
P0M0 = 0x00;
P0M1 = 0x00;
while(1)
{
P0++;
}
}
</code></pre>
<p>是因为点灯中硬件电路设计为P4和P6,则这里需要进行更改<br />
在STC32G12K128参考手册中发现P0的寄存器地址为80<br />
对应的代码为</p>
<pre><code>sfr P0 = 0x80;
</code></pre>
<p>那么推断出需要用到P4和P6端口则</p>
<pre><code>sfr P4 = 0xC0;
</code></pre>
<p>代码中用到了P0M1、P0M0对应的P4则是</p>
<pre><code>sfr P4M1 = 0xB3;
sfr P4M0 = 0xB4;
</code></pre>
<p>推理可得出,P6相关的寄存器配置为:</p>
<pre><code>sfr P6 = 0xE8;
sfr P6M1 = 0xCB;
sfr P6M0 = 0xCC;
</code></pre>
<p>![]<br />
![]<br />
如果要实现点亮单个灯,则需要用到新的关键词sbit控制单独的引脚</p>
<pre><code>sbit P00 = P0^0;
</code></pre>
<p>那么控制P4和P6程序如下:</p>
<pre><code>sbit P40 = P4^0; // 三极管控制针脚
sbit P60 = P6^0; // LED控制针脚
</code></pre>
<p>寄存器是什么?关键词又是什么?<br />
![]<br />
C语言中需要用0xXX表示,比如0x80,后面的H代表十六进制</p>
<p>C 语言代码的定义方法:</p>
<pre><code> sfr P0 = 0x80; //定义 SFR
sbit P00 = P0^0; //使用 sbit 在 SFR 中声明 SFR 位
sfr PCON = 0x87; //定义 SFR
sbit PD = PCON^1; //使用 sbit 在 SFR 中声明 SFR 位
sfr ACC = 0xE0; //定义 SFR
sbit ACC7 = ACC^7; //使用 sbit 在 SFR 中声明 SFR 位
PD = 0; //位变量置 1STC32G 系列技术手册
P00 = 1; //位变量清 0
ACC7 = ~ACC7 //位变量取反
</code></pre>
<p>通过对地址定义名称后,则可通过名称往寄存器写入数值。<br />
端口模式配置:<br />
![]<br />
具体的端口模式功能如下所示:<br />
![]<br />
<em>详细看STC32手册291页</em></p>
<hr />
<h2>4.4实现自动下载工程</h2>
<h3>4.4.1自动下载工程讲解</h3>
<p>每次进行下载程序需要反复按下断电+下载程序按钮进入HID模式,反复调试过程中则会变得极为麻烦,以及对于远程不断电升级程序不友好。<br />
通过不断电下载,用户程序复位到系统区进行USB模式ISP下载方法,需要移植USB_CDC驱动<br />
<em>详细STC32G手册5.15章节有说明</em><br />
原理:![]</p>
<p>具体操作步骤如下:<br />
1、下载最新版本的 STC-ISP 下载软件<br />
2、选择正确的单片机型号<br />
3、打开“收到用户命令后复位到 ISP 监控程序区”选项页<br />
4、选择“USB(HID)模式”,并设置 USB 设备的 VID 和 PID,STC 提供的范例中的 VID 为“34BF”,<br />
PID 为“FF01”<br />
5、选择 HEX 模式或者文本模式<br />
6、设置自定义下载命令,需要和代码中的自定义命令相一致<br />
7、选择上这两项,当目标代码重新编译后,STC-ISP 下载软件便会自动发送复位命令,并自动开始<br />
USB 模式的 ISP 下载<br />
![]<br />
除了需要初步烧入自动下载程序之外,其配套烧录软件也需要进行配置即可<br />
![]</p>
<p>代码说明:</p>
<pre><code>#include "COMM/stc.h"
#include "COMM/usb.h"
char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";
</code></pre>
<p>在此部分代码中,由于引用的usb相关lib文件放入到单独的文件夹"COMM"中,在使用#include 引用需要声明其当前工程路径中的库文件路径,如"COMM/stc.h“<br />
但实际执行并且编译代码烧录后,功能并不能实现<br />
具体原因分析:<br />
实际使用案例中无 <code>sys_init()</code>语句,且该语句为设置usb使用的时钟源。</p>
<pre><code>void sys_init()
{
WTST = 0;//设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; //扩展寄存器(XFR)访问使能
CKCON = 0; //提高访问XRAM速度
P0M1 = 0x00; P0M0 = 0x00; //设置为准双向口
P1M1 = 0x00; P1M0 = 0x00; //设置为准双向口
P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口
P3M1 = 0x00; P3M0 = 0x00; //设置为准双向口
P4M1 = 0x00; P4M0 = 0x00; //设置为准双向口
P5M1 = 0x00; P5M0 = 0x00; //设置为准双向口
P6M1 = 0x00; P6M0 = 0x00; //设置为准双向口
P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口
//====== USB 初始化 ======
P3M0 &= ~0x03;
P3M1 |= 0x03;
IRC48MCR = 0x80;
while (!(IRC48MCR & 0x01));
USBCLK = 0x00;
USBCON = 0x90;
//========================
}
</code></pre>
<p>新函数或是复制过来的函数,放在main函数之前才可运行,而放在后面会重新定义<br />
将void sys_init()复制到main()之前并且在main()函数中的usb_init()之前加上sys_init()语句后,重新编译并且在进行手动烧录程序后,则可不断电自动下载程序。<br />
![]</p>
<h3>4.4.2工程中文件讲解</h3>
<p>打开该工程的头文件,stc.h进入后发现:</p>
<pre><code>#ifndef __STC_H__
#define __STC_H__
#include <intrins.h>
#include <stdio.h>
#include <string.h>
#include "stc32g.h"
typedef bit BOOL;
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
typedef unsigned char uchar;
typedef unsigned int uint;
typedef unsigned int ushort;
typedef unsigned long ulong;
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
typedef unsigned long uint32_t;
#endif
</code></pre>
<p>在如上代码中,#include作为引用,引用了一些系统的头文件,打开其中的stc32g.h文件发现其中已有工程文件中的定义语句,如sfr P4 = xxx或sbit P40 = xxx,则原工程文件中可进行屏蔽。正常编写完成后,工程是从头到尾进行编译,如果有未定义的语句放在main()而其语句函数在main()函数之后,是无法找到函数,所以可以在main()函数前加上函数声明。</p>
<pre><code>USBCLK = 0x00;// 使用CDC功能需要使用此两行,HID功能则禁用此两行
USBCON = 0x90;
</code></pre>
<p>总体工程代码如下(附注释)</p>
<pre><code>#include "COMM/stc.h"//调用头文件
#include "COMM/usb.h"
char *USER_DEVICEDESC = NULL; //配置
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";
void sys_init(); //函数申明
void main()
{
WTST = 0;//设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; //扩展寄存器(XFR)访问使能
CKCON = 0; //提高访问XRAM速度
P0M1 = 0x00; P0M0 = 0x00; //设置为准双向口
P1M1 = 0x00; P1M0 = 0x00; //设置为准双向口
P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口
P3M1 = 0x00; P3M0 = 0x00; //设置为准双向口
P4M1 = 0x00; P4M0 = 0x00; //设置为准双向口
P5M1 = 0x00; P5M0 = 0x00; //设置为准双向口
P6M1 = 0x00; P6M0 = 0x00; //设置为准双向口
P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口
sys_init(); //USB功能+IO口初始化
usb_init(); //USB口初始化
EA = 1; //EA为寄存器
while(1) // 死循环
{
P40 = 0; // 三极管引脚输出低电平
P60 = 0; // led4引脚输出低电平
P61 = 0; // led5引脚输出低电平
}
}
void sys_init() // 函数定义
{
//====== USB 初始化 ======
P3M0 &= ~0x03;
P3M1 |= 0x03;
// 设置USB使用时钟源
IRC48MCR = 0x80; // 使能内部48M高速IRC
while (!(IRC48MCR & 0x01)); // 等待时钟稳定
USBCLK = 0x00;// 使用CDC功能需要使用此两行,HID功能则禁用此两行
USBCON = 0x90;
//========================
}
</code></pre>
<p>其中EA为寄存器,对应STC32G芯片开发文档中的12.4.1小节内容。<br />
其语句中设定为1,则CPU开放中断,打开总中断。</p>
你这整理的好呀,甚至比原版的还详细。 <h1>5. C语言运算符及进制数入门</h1>
<h2>5.1 单片机实现printf函数</h2>
<p>在该例程当中,在while()语句中添加如下代码块:</p>
<pre><code>if(DeviceState != DEVSTATE_CONFIGURED)
continue;
if(bUsbOutReady)
{
usb_OUT_done();
printf("Hello World!");
}
</code></pre>
<p>添加完成后,printf()语句括号中则是需要打印的字符串,通过USB-CDC通讯完成后打开烧录<br />
软件CDC/HID串口助手,任意发送一个指令接收到printf中的字符串。<br />
![]</p>
<h2>5.2 进制转换</h2>
<p>HEX模式是十六进制数,文本模式则可看到输出的是什么了。(使用此方法后可编写其他程序来控制单片机读取湿度温度等实现更多功能)如下使用示范:</p>
<p><code>printf("当前温度:%.2f\r\n", 11.2);</code></p>
<p>语句用法为一个百分号%在双引号内,在双引号外必须接上一个数值或变量(C语言指针)<br />
通过使用此方法则可实现任意数值的打印输出。而对应的字符定义如下图所示:<br />
![]<br />
而对应的" .2f "含义为两位小数<br />
![]<br />
\r\n则为换行,不同的系统回车换行不同,在Windows系统中我们输入回车时,输入的字符是\r\n,Linux系统中回车为\n,而mac系统中回车的字符为\r.windows的具体转义字符参考下图:<br />
![]<br />
当然HEX模式与ASCII码表对应,通过16进制转换为10进制(可通过程序员模式计算器换算),汉字则对应汉字内码实现。当然,下图仅为一部分ASCII码表对应图,具体可在网上查阅,推荐一个网站为:[]<br />
![]<br />
如下为数的进制转换,为2进制、10进制、16进制转换。而在C语言中,并没有类似80H、FFH这种写法,在C中用0x80、0xff来表示。十进制表示的数字逢10进1(从0计数),而十六进制逢16进1,分别从0 ~ F轮播,F后进位10,到19后1A ~ 1F。<br />
![]<br />
二进制中,0x00和0xff分别表示为:00000000和11111111,再通过二进制转换为16进制。<br />
![]</p>
<p>而对应的端口可控制一排引脚,如P4端口控制P4.0 ~ P4.7,P6端口控制P6.0 ~ P6.7,可以用P6=0x00或P6=EE表示控制其对应的二进制端口:<br />
![]<br />
C语言常用的运算符如下:</p>
<pre><code>一、算数运算符
加(+)减(—)乘(*)除(/)
模(余)运算符(%):不允许出现浮点型,余数正负取决于被除数正负
自增(++i,–i;i++,i–)
二、位运算符
右移(>>)左移(<<)
按位与(&)
按位或(|)
按位异或(^)
取反(~)
三、赋值运算符
+= 加赋值(a+=3等价a=a+3)
-= 减赋值
*= 乘赋值
/= 除赋值
%= 求余赋值
&= 按位与赋值
|= 按位或赋值
^= 按位异或赋值
<<= 左移位赋值 (>>=右移位赋值)
</code></pre>
<p>通过上述运算符可对数值进行基础的运算。<br />
而一些变量规定了其数据的长度,如bit为一位二进制数,仅1位,如果是unsigned char(一般默认变量前有signed定义,直接使用char代表signed char)则代表8位变量值,取0 ~ 7位,最大值为0x00 ~ 0xFF(有正负值);short代表16位,一般来说无符号数据使用较多,一般为unsigned char和unsigned int,最大取值一般为0 ~ 255和0 ~ 65535。<br />
数据类型及取值范围参考如下图:<br />
![]</p>
<h1>6. LED闪烁及花式点灯</h1>
<h2>6.1 基于Delay实现LED闪烁</h2>
<p>控制LED一亮一灭的过程使得LED不断闪烁,在程序中使用Delay语句实现,若实现0.5秒闪烁则为500微秒,可进行单位换算。<br />
在工程中添加如下语句:</p>
<pre><code>#define MAIN_Fosc 24000000UL
</code></pre>
<p>如上,定义了IRC系统时钟的频率为24MHz,在单片机使用过程中,内部设定IRC频率要和程序一致。定义主时钟频率后,接下来定义延时函数:</p>
<pre><code>void delay_ms(u16 ms){
u16 i;
do{
i=MAIN_Fosc/6000;
while(i--);
}
while(--ms);
}
</code></pre>
<p>在如上代码中,"u16"定义为 <code>typedef unsigned int u16;</code>该定义是stc.h中的定义语句,用于简化定义语句的书写。函数执行时,先定义了局部变量u16 i,变量i会执行对定义的时钟值运算后得到一个确切的值,并且在循环中执行i--(备注:变量运算性质不同,--i和i--的区别在于,前者--i是先进行运算后再输出i的值,后者i--是先输出i的值后再进行运算);该语句经过测试,24MHz的频率下属于1毫秒的延时。</p>
<p>那么在main()程序中,保持0.5s的不断闪烁则将程序写入到while(1)死循环中进行,如下程序所示:</p>
<pre><code>while(1) // 死循环
{
P40 = 0; // 三极管引脚输出低电平
P60 = 1; // led4引脚输出高电平
P61 = 0; // led5引脚输出低电平
delay_ms(500);
P60 = 0; // led4引脚输出低电平
P61 = 1; // led5引脚输出高电平
delay_ms(500);
}
</code></pre>
<hr />
<p>while和do while语句的功能使用不同:<br />
![]<br />
while语句会执行括号内的条件1,当条件1为真时不断执行代码,当条件为假(条件1=0)时会跳出循环体;do while语句是先执行代码B后,进行一个while判断,当while()中条件2为真时执行代码,条件2为假时(条件2=0)跳出循环。<br />
下面为使用案例:</p>
<pre><code>//定义变量a的值
int a = 10;
//do while方式执行程序
do{
printf("a的值: %d\n",a);
a = a+1;
}while(a<20);
//while方式执行程序
while(a<20){
printf("a的值: %d\n",a);
a++;
}
</code></pre>
<p>在上述代码当中,要注意的是int a的值放在main()函数内外的性质不同,放在main()函数外表示全局变量,在任意函数中该a的值已被定义,放置在任意函数中如main()函数中,则表示局部变量,在该函数中a的值已被定义。代码部分do while先执行“打印a的值”后对变量“a"进行处理,当a变量的值已经不满足条件时,则跳出循环。while循环性质一样,在语句中执行对a变量处理,当a变量值无法满足条件语句时,则跳出循环。在一般的程序编写中,使用到的while语句相对do while较多。</p>
<p>在单片机中,使用如上示例来打印一个值,具体的示例代码如下:</p>
<pre><code>int a = 10;
while(1){
if(DeviceState != DEVSTATE_CONFIGURED)
continue;
if(bUsbOutReady){
usb_OUT_done();
do{
printf("a的值为:%d\r\n",a);
a = a+1;
}while(a<20);
}
}
</code></pre>
<p>在如上代码中,while(1)循环内的if-----if语句是5.1节中说明的在单片机中使用printf语句,将此部分代码烧录后,打开调试软件的USB-CDC/HID串口助手并发送1作为数据,返回上列程序中计算的值。while方式执行程序代码如下,和do while几乎相似:</p>
<pre><code>int a = 10;
while(1){
if(DeviceState != DEVSTATE_CONFIGURED)
continue;
if(bUsbOutReady){
usb_OUT_done();
while(a<20){
printf("a的值: %d\n",a);
a++;
}
}
}
</code></pre>
<hr />
<h2>6.2 函数及其他</h2>
<h3>6.2.1 函数的定义</h3>
<p>函数的大致结构如下:</p>
<pre><code>返回值 函数名称(入口参数)
{
函数要执行的功能
}
@返回值:没有返回值则为void
@函数名称:避开关键词,不重复,非特殊字符随便取
@入口参数:类型+名称,多个参数“,”分开,空则写void
</code></pre>
<p>一般来说在单片机中所使用的函数返回值为void,返回值的概念和用法可在网上查找到,此处使用ChatGPT来生成了一段回答:</p>
<pre><code> 在C语言中,“函数的返回值”是指函数执行完毕后,返回给调用者的一个值。返回值的类型由函数的声明决定。函数返回值可以是各种数据类型,例如 int、float、char,甚至可以是指针或自定义的结构体。
函数返回值的关键点:
1. 返回值类型:在函数定义时需要指定,比如 `int` 表示返回一个整数。
2. return语句:用于指定返回值,并终止函数的执行。
3. void类型:如果函数不需要返回值,可以用 void 声明。
示例1、返回整数:
#include <stdio.h>
// 定义一个函数,返回两个整数的和
int add(int a, int b) {
return a + b; // 返回两个整数的和
}
int main() {
int result = add(3, 5); // 调用函数并接收返回值
printf("The result is: %d\n", result);
return 0;
}
>>>>> The result is: 8
示例2、返回浮点数
#include <stdio.h>
// 定义一个函数,返回两个浮点数的乘积
float multiply(float x, float y) {
return x * y; // 返回两个浮点数的乘积
}
int main() {
float result = multiply(2.5, 4.0); // 调用函数并接收返回值
printf("The result is: %.2f\n", result);
return 0;
}
>>>>> The result is: 10.00
示例3、无返回值的函数void
#include <stdio.h>
// 定义一个函数,只打印信息,没有返回值
void greet() {
printf("Hello, World!\n");
}
int main() {
greet(); // 调用函数
return 0;
}
>>>>> Hello, World!
注意事项:
1. 如果函数声明了返回值类型(如 `int`),但没有使用 `return` 返回值,会导致未定义行为。
2. 如果函数声明为 `void` 类型,使用 `return` 时不能带返回值。
3. 函数的返回值可以直接用于表达式中,例如:
int x = add(1, 2) * 3; // 使用返回值进行运算
</code></pre>
<p>上述案例中列举了三类返回值示例。需要注意的是,在实际的程序编写过程中,需要避开已定义的关键词,对于冲突的用户关键词则需要重新命名使用。一般来说,此类关键词在编辑器中会以不同颜色被区分开来如char、void、while等,并且在实际的使用过程中不可重复命名,如已使用了delay_ms则下一个定义的函数名称就不可用了。</p>
<h3>6.2.2 函数的声明</h3>
<p>define定义用法为 <code>#define name value</code> 其中name为定义名称,在代码实例运行过程中,name的属性则由value表示。</p>
<p>下例会使用一个定义了的加法计算函数来直观地展示函数定义声明的作用:</p>
<pre><code>int add(int parm1, int parm2){
return parm1 + parm2;
}
</code></pre>
<p>在如上代码中,入口函数为<em>parm1、parm2</em>,返回值为 <code>parm1+parm2</code>,进行了计算(该示例参考了6.2.1中的示例1)。<br />
在使用过程中,将该部分代码块放置在main()函数后,则需要进行声明,需要在void main()之前进行对add函数的声明:</p>
<pre><code>int add(int parm1, int parm2);
</code></pre>
<p>在如上代码中,可见函数声明的大致结构如下:</p>
<pre><code>返回值 函数名称(入口参数);
</code></pre>
<p>我们在实际的例程中使用一下加法函数查看下其效果如何:</p>
<pre><code>//加法函数声明
int add(int parm1, int parm2);
void main(){
... 此处省略前部分代码 ...
while(1){
if( DeviceState != DEVSTATE_CONFIGURED )
continue;
if( bUsbOutReady ){
usb_OUT_done();
printf("计算结果为:%d\r\n",add(1,2));
}
}
}
//加法函数定义
int add(int parm1, int parm2){
return parm1 + parm2;
}
</code></pre>
<p>通过编译并烧录后,实际调试串口助手返回值为3。在上述程序中,printf语句中调用add函数,入口参数值为1、2,该值分别带入了 <code>int parm1</code>和 <code>int parm2</code>中,并且在add函数中执行了一步运算,再通过printf中的 <code>%d</code>打印输出。</p>
<h3>6.2.3 模块化编程</h3>
<p>在上述内容中,介绍了函数定义以及函数声明如何进行,在函数声明中我们会发现在单个文件中既要定义函数又要声明函数才可调用函数,会使得在单个程序文件中编写代码变得极为繁琐。那么可根据需要实现函数定义的功能,来新建一个.c和.h文件存储该函数定义的功能,如下为示例:<br />
在<strong>mathca.h</strong>文件中,建立基本的定义语句:</p>
<pre><code>#ifndef __MATH_H
#define __MATH_H
#endif
</code></pre>
<p>上述代码中 <code>#ifndef __MATH_H</code>含义为“如果没有定义 <em>MATH_H</em>”配合下列 <code>#define __MATH_H</code>来定义一个<em>MATH_H</em>,且 <code>#endif</code>结束定义。此部分语句的主要作用是防止在单个工程中重复定义语句。<br />
在<strong>mathca.c</strong>中引用<strong>mathca.h</strong>:</p>
<pre><code>#include "mathca.h"
</code></pre>
<p>完成上列步骤后,我们添加6.2.2中函数的部分 <code>int add(int parm1, int parm2);</code>到<strong>mathca.h</strong>中:</p>
<pre><code>#ifndef __MATH_H
#define __MATH_H
int add(int parm1, int parm2);
#endif
</code></pre>
<p><strong>mathca.c</strong>文件中也需要添加add函数:</p>
<pre><code>#include "mathca.h"
int add(int parm1, int parm2){
return parm1 + parm2;
}
</code></pre>
<p>需要注意的是,在<strong>main.c</strong>文件中引用了<strong>mathca.h</strong>后,添加一下引用路径:<br />
![]<br />
在添加了引用文件夹后,可以将<strong>mathca.c</strong>和<strong>mathca.h</strong>放入到引用文件夹中,添加引用文件夹双击工程文件夹,弹出选择文件窗口,选择引用文件夹内的文件即可。<br />
为了验证其可行性,我们添加一则新的应用:</p>
<pre><code>#ifndef __MATH_H
#define __MATH_H
int add(int parm1, int parm2);
int sub(int parm1, int parm2);
int mul(int parm1, int parm2);
int div(int parm1, int parm2);
#endif
</code></pre>
<p>上述为<strong>mathca.h</strong>中代码</p>
<pre><code>#include "mathca.h"
int add(int parm1, int parm2){
return parm1 + parm2;
}
int sub(int parm1, int parm2){
return parm1 - parm2;
}
int mul(int parm1, int parm2){
return parm1 * parm2;
}
int div(int parm1, int parm2){
return parm1 / parm2;
}
</code></pre>
<p>上述为<strong>mathca.c</strong>中代码<br />
在main()函数中执行下列语句:</p>
<pre><code>printf("计算结果为:%d\r\n",add(1,2));
printf("计算结果为:%d\r\n",sub(1,2));
printf("计算结果为:%d\r\n",mul(1,2));
printf("计算结果为:%d\r\n",div(1,2));
</code></pre>
<p>返回结果为:<br />
![]<br />
作业部分:编写SOS求救灯光</p>
<pre><code>while(1){
P40 = 0;
P60 = 1;
delay_ms(200);
P60 = 0;
delay_ms(200);
P60 = 1;
delay_ms(200);
P60 = 0;
delay_ms(200);
P60 = 1;
delay_ms(200);
P60 = 0;
delay_ms(200);
P60 = 1;
delay_ms(500);
P60 = 0;
delay_ms(500);
P60 = 1;
delay_ms(500);
P60 = 0;
delay_ms(500);
P60 = 1;
delay_ms(500);
P60 = 0;
delay_ms(500);
}
</code></pre>
<h1>7 按键点灯程序</h1>
<h2>7.1 按键原理</h2>
<h3>7.1.1 按键类型</h3>
<p>按键在使用的场景不同时,选择不同类型的按键更好符合功能的设计需求。按键种类繁多,如下是一些比较常见的按键类型:<br />
![]<br />
虽然按键长得千奇百怪,但是本质就是两个引脚之间的通断。有的是按下之后两个引脚导通;有的是按键之后两个引脚断开,分为常闭常开两种类型。</p>
<h3>7.1.2 按键使用示例</h3>
<p>介绍完上类按键类型后,如下是一则按键使用场景的原理说明,在原理图中,按键左侧接入上拉电阻,单片机引脚P3.2检测到一个SYS_VCC的电压,按键右侧接地,此处按键没有按下为断开的信号,按下后301R和10K电阻一侧的电位为0,表明P3.2引脚检测到为高电平,按下按键后低电平信号进入单片机引脚。在此处R10为限流保护电阻用于保护单片机引脚。<br />
![]<br />
需要注意的是,按键属于机械开关,按下会产生抖动,一般来说如果要消除抖动带来的影响,可以选用较好的按键,或是在程序上写入一个延迟消除抖动功能。<br />
![]</p>
<hr />
<h2>7.2 按键的代码实现过程</h2>
<p>下列是实现按键功能的代码部分:</p>
<pre><code>if(KEY_NAME == 0){
delay_ms(10);
if(KEY_NAME == 0){
...需要执行的功能...
}
}
</code></pre>
<p>在上述代码中,<em>KEY_NAME</em>是已定义的按键,在定义过程中需要引用到单片机针脚接口,以及部分可使用ADC方式实现的矩阵按键也有不同。<em>delay_ms</em>延迟了10个毫秒,用于软件消除机械按键按下的抖动产生不良影响。如下则是在实际程序中使用演示代码:</p>
<pre><code>#define KEY_1 P32 //定义一个按键 引脚选用P32
void main()
{
//初始化
sys_init();
usb_download();
usb_init();
//寄存器使能
EA = 1;
while(1) // 死循环
{
P40 = 0;
//判断按键有没有按下
if(KEY_1 == 0){
delay_ms(10);
if(KEY_1 == 0){
P60 = 0;
delay_ms(500);
P60 = 1;
delay_ms(500);
}
}
}
}
</code></pre>
<p>在如上代码中,主要讲解==while()==语句中的内容;在代码最开始部分,定义了P32引脚名称为==KEY_1==,在==while==循环中,控制了P40引脚,该引脚控制PNP三极管驱动8位LED灯,随后进入if判断语句,在判断语句中,“ == ”用法是进行判断定义变量的值,当==KEY_1==变量值为0也就是低电平时,进入软件消抖部分,延迟10ms后再次进行判断==KEY_1==是否为低电平,当为高电平则无法实现第二个if语句中的功能,当为低电平时则执行第二个if语句中需要执行的功能,此处功能具体实现为P60控制的led进行闪烁。</p>
<h2>7.3 按键的应用</h2>
<h3>7.3.1 按键按下LED点亮松开熄灭</h3>
<p>那么在实例中,如何实现按下按键,串口通讯显示按下按键,LED亮,松开按键后停止通讯并且熄灭LED呢?if语句用于成立语句,==else==进行判断如果if不成立:</p>
<pre><code>void main()
{
sys_init();
usb_download();
usb_init();
EA = 1;
while(1) // 死循环
{
if( DeviceState != DEVSTATE_CONFIGURED )
continue;
if( bUsbOutReady )
{
usb_OUT_done();
}
P40 = 0;
//判断按键有没有按下
if(KEY_1 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_1 == 0){
printf("按键已按下\r\n");
P60=0;
}
}
//如果按键没有按下
else{
P60=1;
}
}
}
</code></pre>
<p>在如上程序中加入了可进行串口打印语句,在之后部分进行判断按键是否按下,按下实现功能,没有按下实现相反功能。</p>
<h3>7.3.2 按键按下LED熄灭松开点亮</h3>
<p>可以使用多个按键实现不同功能LED亮灭:</p>
<pre><code>#define KEY_1 P32 //定义一个按键 引脚选用P32
#define KEY_2 P33 //定义一个按键 引脚选用P33
void main()
{
while(1) // 死循环
{
/*-------------------串口打印代码-------------------*/
if( DeviceState != DEVSTATE_CONFIGURED )
continue;
if( bUsbOutReady )
{
usb_OUT_done();
}
P40 = 0; // LED驱动
/*-----------------按下点亮松开熄灭------------------*/
//判断按键有没有按下
if(KEY_1 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_1 == 0){
printf("按键已按下\r\n");
P60=0;
}
}
//如果按键没有按下
else{
P60=1;
}
/*-----------------按下熄灭松开点亮------------------*/
//判断按键有没有按下
if(KEY_2 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_2 == 0){
printf("按键已按下\r\n");
P61=1;
}
}
//如果按键没有按下
else{
P61=0;
}
}
}
</code></pre>
<h3>7.3.3 按键按下改变状态</h3>
<p>按键按下状态取反:</p>
<pre><code>//判断按键有没有按下
if(KEY_2 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_2 == 0){
printf("按键已按下进行取反\r\n");
P61 = !P61;
}
}
</code></pre>
<p>在这里实际情况下,LED的交替亮灭功能实现异常,原因是按键判断检测过多取反过快,导致无法正常实现功能,则需要在取反后添加延迟来实现,正常代码如下:</p>
<pre><code>//判断按键有没有按下
if(KEY_2 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_2 == 0){
printf("按键已按下进行取反\r\n");
P61 =! P61;
delay_ms(500);
}
}
</code></pre>
<p>如上代码真正实现功能还是有缺陷部分,如果按着按钮不放,还是会执行取反,如果想要按下后不放但不会取反,则需要使用到==while==循环判断:</p>
<pre><code>//判断按键有没有按下
if(KEY_2 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_2 == 0){
printf("按键已按下进行取反\r\n");
P61 =! P61;
while(KEY_2 == 0){}//如果按键一直按下,一直执行while
}
}
</code></pre>
<p>如上代码在取反语句后加入循环,当取反状态实现后,按键一直按下使得==KEY_2==变量为0,进入循环,循环内无代码语句执行,则会一直保持取反状态,也就实现了按键按下仅取反一次。那么要实现松开按键后执行取反语句,则只需要将循环语句放置在取反语句之前即可:</p>
<pre><code>//判断按键有没有按下
if(KEY_2 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_2 == 0){
while(KEY_2 == 0){}//如果按键一直按下,一直执行while
printf("按键已按下进行取反\r\n");
P61 =! P61;
}
}
</code></pre>
<p>根据如上所有不同功能代码,可在不同的使用场景下执行。</p>
<h3>7.3.4 按键按下一次移位LED</h3>
<p>如果要实现按下一次按键,LED进行一次移位,向左依次点亮LED,按下一次执行一下程序:</p>
<pre><code>P6 = 0xfe;
while(1)
{
/*-----------------按下按键执行一次-------------------*/
//判断按键有没有按下
if(KEY_2 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_2 == 0){
printf("按键已按下进行移位\r\n");
P6 = (P6<<1);
while(KEY_2 == 0){}//如果按键一直按下,一直执行while
}
}
}
</code></pre>
<p>这里如果要对LED进行移位操作,那么是用到了十六进制转换二进制。</p>
<pre><code>1111 1110
1111 1101
1111 1011
</code></pre>
<p>代码中移位符号使用“<<”表示,该移位方式补0,已定义的寄存器仅8位,按下一次按键后,最高位被替换,后7位向高位挪动一位并补上0。那么如何实现补1从而实现LED仅单个亮起向左进位呢?</p>
<pre><code>P6 = ((P6<<1)+1);
</code></pre>
<p>使用如上代码语句进行替换,在括号内优先执行,意义为向前进位,而括号外的+1则是将补位的0值进行修改+1,使得实现单个LED移位。但是当8位全部执行完,LED就不会在亮了,如何解决该问题呢?</p>
<pre><code> //判断按键有没有按下
if(KEY_2 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_2 == 0){
printf("按键已按下进行移位\r\n");
P6 = ((P6<<1)+1);
//当进位完成,重新置位P6
if( P6 == 0xff){
P6 = 0xfe;
}
while(KEY_2 == 0){}//如果按键一直按下,一直执行while
}
}
</code></pre>
<p>当代码不断执行后,移位的8位会全部变成1,在这里LED就无法亮了,那么需要在代码语句执行完成后添加一则if语句进行判断,全部置1时其对应的十六进制为==0xFF==,添加对P6的判断,当进位完成P6值为==0xFF==时,重新对P6进行置位为==0XFE==,代码进入循环重新执行一遍。</p>
<p>为了方便进行编程,可在之前添加变量,可添加一则8位无符号数字。定义为==u8 LED_Data==,将P6的值定义为变量中的值后,在后续代码编写中可对变量名称的值进行操作。</p>
<pre><code>void main(){
u8 LED_Data = 0xfe;
P6 = LED_Data;
while(1){
if( DeviceState != DEVSTATE_CONFIGURED )
continue;
if( bUsbOutReady )
{
usb_OUT_done();
}
P40 = 0; // LED驱动
if(KEY_2 == 0){
delay_ms(10);
//再次判断按键有没有按下
if(KEY_2 == 0){
printf("按键已按下进行移位\r\n");
LED_Data = ((LED_Data<<1)+1);
//当进位完成,重新置位P6
if( LED_Data == 0xff) //本来直接输出P6此部分先计算后输出
LED_Data = 0xfe;
P6 = LED_Data;
while(KEY_2 == 0){}//如果按键一直按下,一直执行while
}
}
}
}
</code></pre>
<p>按键除了可对LED进行移位操作,还可以对别的进行一系列操作。</p>
<hr />
<h2>7.4 数组的使用</h2>
<p>数组是哟分别为如下两步:</p>
<pre><code>1.定义
类型 名称[长度]={数值};
2.使用
赋值:名称[索引]=数值
</code></pre>
<p>![]<br />
在使用十六进制转换直接进行位移操作,代码写起来会很繁琐:</p>
<pre><code>P6 = 0xfe; delay_ms(500);
P6 = 0xfd; delay_ms(500);
P6 = 0xfb; delay_ms(500);
P6 = 0xf7; delay_ms(500);
P6 = 0xef; delay_ms(500);
P6 = 0xdf; delay_ms(500);
P6 = 0xbf; delay_ms(500);
P6 = 0x7f; delay_ms(500);
</code></pre>
<p>那么则需要使用到数组执行的概念:</p>
<pre><code>void main(){
u8 num = 0;
u8 LED_DataTab = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
while(1){
P6 = LED_DataTab;
delay_ms(500);
num++;
}
}
</code></pre>
<p>但在上列代码中,流水灯仅只有8位,变量==num==不断的增加会使得实际执行效果出错,为了避免此种情况出现,在代码中需要添加一则==if语句==进行基本的判断。</p>
<pre><code>P6 = LED_DataTab;
delay_ms(500);
num++;
if(num>7)
num = 0;
</code></pre>
<p>当==num==值大于7时,重新置位==num==变量为0,继续执行代码。<br />
如果在执行中,我不希望第7位LED亮起忽略第七位LED如何实现呢?仅需要在if语句后添加一条语句:</p>
<pre><code>LED_DataTab = 0xfe;
</code></pre>
<p>该种方法直接进入变量数组中,将<em>0xfd</em>值修改为<em>0xfe</em>,当执行需要进位第七LED时,亮起来的还是第8位LED,从而实现跳过第七位LED,此种方法可对==LED_DataTab==数组中的值进行修改,数组的值可进行增加减少皆可修改,只需要数组中的数值和后续代码执行相对应上且数组不越界即可。</p>
<h1>8. 蜂鸣器的使用</h1>
<h2>8.1 认识蜂鸣器</h2>
<p>有源蜂鸣器和无源蜂鸣器的区别:</p>
<ol>
<li>有源蜂鸣器内部带震荡源,所以通电就会鸣叫;无源蜂鸣器内部不带振荡源,仅使用直流信号则无法令其鸣叫。有源蜂鸣器通过高低电平来使其鸣叫,无源蜂鸣器则需要不断进行高低电平的输入才可鸣叫。</li>
<li>二者价格不同,有源蜂鸣器价格贵于无源蜂鸣器,贵在有源蜂鸣器相对于无源蜂鸣器多了振荡源。</li>
</ol>
<p>不同的运用场景不同,一般来说无源蜂鸣器成本更低,还会使用在一些发声项目上,无源蜂鸣器相对于有源蜂鸣器其实更好。</p>
<h2>8.2 控制原理</h2>
<p>在PCB板上,蜂鸣器原理图如下所示,P5.4接入限流电阻,电源部分C35作为滤波电容进行简单滤波,过滤掉高频信号,SS8550作为驱动三极管,<br />
![]<br />
如何驱动蜂鸣器呢?P54针脚作为单片机控制针脚,输入高电平关闭蜂鸣器,输入低电平打开蜂鸣器。结合程序,使用按键按下一次打开蜂鸣器,按下一次关闭蜂鸣器。</p>
<pre><code>#define KEY_1 P32 //定义一个按键 引脚选用P32
#define BEEP P54//定义蜂鸣器 引脚选用P54
void main()
{
sys_init();
usb_download();
usb_init();
EA = 1;
while(1) // 死循环
{
/*-------------------串口打印代码-------------------*/
if( DeviceState != DEVSTATE_CONFIGURED )
continue;
if( bUsbOutReady )
{
usb_OUT_done();
}
if(KEY_1 == 0)
{
delay_ms(10);
if(KEY_1 == 0)
{
while(KEY_1 == 0);
//蜂鸣器鸣叫定义
BEEP =! BEEP;
}
}
}
}
</code></pre>
<h2>8.3 蜂鸣器运用实战</h2>
<p>根据如上代码参考示例,可以设计一个类似电磁炉的功能,如下是具体需求分析:</p>
<pre><code>按键*2,LED*8,蜂鸣器*1
1、按键1按下,蜂鸣10ms,
LED1-8全部点亮200ms在熄灭,表示开机。
2.开机后,按键2按下,蜂鸣10ms,LED1-8轮流点亮,表示切换煲汤、烧水等功能。
3.开机后按键1再次按下,蜂鸣10ms,LED全部熄灭,表示关机
</code></pre>
<p>那么进入代码编写环节:<br />
首先在工程文件夹内新建==USER==用户文件夹,在==USER==文件夹内新建功能文件夹,本次要实现电磁炉控制面板功能,那么命名规范就以英文命名,为**Induction_cooker**,在该文件夹下新建**cooker.c**和**cooker.h**文件,并且文件内参考之前示例代码规范来编写:<br />
以下是**cooker.h**文件</p>
<pre><code>cooker.h
#ifndef __COOKER_H
#define __COOKER_H
#include "COMM/stc.h"//调用头文件
#define KEY_1 P32 //定义一个按键 引脚选用P32
#define BEEP P54//定义蜂鸣器 引脚选用P54
#endif
</code></pre>
<p>以下是<strong>cooker.c</strong>文件</p>
<pre><code>cooker.c
#include "cooker.h"
bit Run_Flag = 0; //开关机变量 bit变量取值范围0~1
//电磁炉功能
void BEEP_function(void){
}
</code></pre>
<p>在<strong>cooker.c</strong>中,使用了==bit==关键词定义变量==Run_Flag==,==bit==变量的取值范围为0~1,可以用来作为开关量使用,该变量为开关机使用变量。程序代码如下,在**cooker.c**中实现功能。</p>
<pre><code>#include "cooker.h"
#include "delay.h"
bit Run_Flag = 0; //开关机变量 bit变量取值范围0~1
u8Run_Mode = 0; //定义电磁炉LED灯
//电磁炉功能
void BEEP_function(void){
if(KEY_1 == 0){
delay_ms(10);
if(KEY_1 == 0){
while(KEY_1 == 0); //按键按下等待松开
if(Run_Flag == 0){ //还没有开机
Run_Flag = 1; //开机变量置1表示开机
BEEP = 0; //打开蜂鸣器
delay_ms(10); //延时10ms
BEEP = 1; //关闭蜂鸣器
P40 = 0; //打开LED驱动
P6 = 0x00; //打开所有LED
delay_ms(200); //延时200ms
P6 = 0xFF; //关闭所有LED
}
}
}
}
</code></pre>
<p>在这里需要注意的是,如果==delay_ms()==语句出现无法执行的问题,多数是因为<strong>cooker.c</strong>中没有引用到<strong>delay</strong>函数,使用<strong>include</strong>来添加<strong>delay</strong>功能或是直接在<strong>cooker.c</strong>文件中写入<strong>delay</strong>函数功能。在如上代码中仅实现了如何开机,关机功能暂未实现,那么开始编写关机程序:</p>
<pre><code>else{
Run_Flag = 0;
BEEP = 0; //打开蜂鸣器
delay_ms(10); //延时10ms
BEEP = 1;
P6 = 0xFF; //关闭所有LED
}
</code></pre>
<p>在if判断开机的语句下添加<strong>else</strong>用于对开机后状态进行判定,开机后再次按下按钮,==Run_Flag==值为1,不满足if语句执行条件,自动跳转到**else**语句执行。在第二个问题当中,我们所写的程序如下所示:</p>
<pre><code> if(KEY_2 == 0){
delay_ms(10);
if(KEY_2 == 0){
while(KEY_2 == 0); //将要执行代码
Run_Mode++; //模式自加
if(Run_Mode>8) //判断模式值大于8
Run_Mode = 1; //回到模式1
P6 = ~(1<<(Run_Mode-1)); //按位取反左移P6值
BEEP = 0; //打开蜂鸣器
delay_ms(10); //延时10ms
BEEP = 1; //关闭蜂鸣器
}
}
</code></pre>
<p>在如上代码中,==P6 = ~(1<<(Run_Mode-1));==语句中==~==作为取反应用符号,与==!==不同的是可对整个数值进行取反,当if语句执行的==Run_Mode==值大于8时重新置1,P6作为LED,实现左移LED亮灭则是通过==Run_Mode==减1后左移1位,原因是LED计数是从0到7为8位,这里C语言编写则是==Run_Mode==大于8才重新计数,而且所表示的为低位十六进制,全部取反后才可使得LED单个亮灭。</p>
<p>那么要添加按键3,按下后表示启动,选择对应功能LED持续闪烁并且表示正在工作,并且在工作时候无法切换功能应该如何实现呢?<br />
首先可以定义一个变量</p>
<pre><code>//在头文件中定义
#define KEY_3 P34 //定义一个按键 引脚选用P32
//在cooker.c中定义
bit Run_Now = 0;
</code></pre>
<p>其次编写按键3按下后应该执行哪些功能:</p>
<pre><code>if(KEY_3 == 0){
delay_ms(10);
if(KEY_3 == 0){
while(KEY_3 == 0); //将要执行代码
if(Run_Mode>0){
BEEP = 0;
delay_ms(10);
BEEP = 1;
Run_Now = !Run_Now;
}
}
}
</code></pre>
<p>如上代码是按键2按下后改变了==Run_Mode==的值,变量==Run_Mode==在每次变换后都会进行蜂鸣器鸣叫,当==Run_Mode==的值确定后再松开按键3后会使得==Run_Now==的值取反,取反后则表示启动,也连带LED闪烁代码实现,代码部分则需要写到if之外,执行完if语句后对==Run_Now==进行判断:</p>
<pre><code>if(Run_Now == 1){
P6 = 0xff;
delay_ms(200);
P6 = ~(1<<(Run_Mode-1));
delay_ms(200);
}
else{
P6 = ~(1<<(Run_Mode-1));
}
</code></pre>
<p>在如上代码中,可以看到if语句对==Run_Now==的值进行判断,开启后则是不断闪烁模式选择后的LED,否则熄灭该LED,但在此程序中有明显的逻辑错误,启动工作后,还是可以进行切换功能,那么应该如何修改呢?<br />
首先需要在开关机按键功能中对==Run_Now==进行清零,以免执行代码后==Run_Now==无法为0导致按键2功能切换代码无法正常执行,需要注意的是,在此之前如果没有对==Run_Mode==变量清零,也会导致开机后按下功能切换会发现保持上次记忆选项。</p>
<pre><code> if(KEY_1 == 0){
delay_ms(10);
if(KEY_1 == 0){
while(KEY_1 == 0); //按键按下等待松开
/*-----------------------此段为开机功能代码------------------------------*/
if(Run_Flag == 0){ //还没有开机
Run_Flag = 1; //开机变量置1表示开机
BEEP = 0; //打开蜂鸣器
delay_ms(10); //延时10ms
BEEP = 1; //关闭蜂鸣器
P40 = 0; //打开LED驱动
P6 = 0x00; //打开所有LED
delay_ms(200); //延时200ms
P6 = 0xFF; //关闭所有LED
}
/*-------------------------此段为关机功能代码---------------------------------*/
else{
Run_Flag = 0;
BEEP = 0; //打开蜂鸣器
delay_ms(10); //延时10ms
BEEP = 1;
P6 = 0xFF; //关闭所有LED
Run_Mode = 0; //模式清零
Run_Now = 0; //运行锁定清零
}
}
}
</code></pre>
<p>编写完成后,需要在按键2中添加一条对==Run_Now==状态判断的语句,如果==Run_Now==的值为0时可执行切换功能模式:</p>
<pre><code>if(KEY_2 == 0){
delay_ms(10);
if(KEY_2 == 0){
while(KEY_2 == 0); //将要执行代码
if(Run_Now == 0){
Run_Mode++; //模式自加
if(Run_Mode>8) //判断模式值大于8
Run_Mode = 1; //回到模式1
P6 = ~(1<<(Run_Mode-1)); //按位取反左移P6值
BEEP = 0; //打开蜂鸣器
delay_ms(10); //延时10ms
BEEP = 1; //关闭蜂鸣器
}
}
}
</code></pre>
<p>在如上代码中,为按键2切换模式的主要功能代码部分,在按键2按下后做一个对==Run_Now==的值判断,当==Run_Now==的值是0不工作的情况下,可进行模式的切换,当值不为零,则跳出if语句。<br />
需要注意的是,代码中的==while(KEY_NAME == 值)==放在if语句的前后表示不同的含义,当放在if语句之前时表示松开后执行后续代码,放在if语句之后表示先执行完功能代码后再判断按键是否松开,松开后执行后续代码功能。</p>
页:
[1]
2