32位8051单片机原理及C语言程序设计视频教程-学习打卡
<p>打卡第一集-认识单片机</p><h4>一、什么是单片机?</h4>
<ul>
<li><strong>全称</strong>:单片微型计算机,也叫<strong>微控制器(MCU)</strong>。</li>
<li><strong>本质</strong>:将一台计算机的核心部件集成到<strong>单个芯片</strong>上,包括:
<ul>
<li><strong>CPU</strong>(中央处理器):负责运算和控制。</li>
<li><strong>内存</strong>(RAM):临时存储数据。</li>
<li><strong>存储</strong>(ROM/Flash):存放程序代码。</li>
<li><strong>I/O接口</strong>(输入/输出):连接外部设备(按键、LED、传感器等)。</li>
</ul>
</li>
<li><strong>比喻理解</strong>:
<ul>
<li><strong>电脑</strong> → 一个<strong>大工厂</strong>:各部门独立,空间大,处理能力强。</li>
<li><strong>单片机</strong> → 一个<strong>微型作坊</strong>:所有东西挤在一个小空间,但足够灵活、成本低、功耗小,专干特定任务。</li>
</ul>
</li>
</ul>
<hr />
<h4>二、单片机可以用来做什么?</h4>
<p>单片机的核心是“感知 → 判断 → 控制”,几乎存在于所有智能/自动化设备中。</p>
<h5>1. 生活消费电子</h5>
<ul>
<li><strong>家电</strong>:洗衣机、空调、电饭煲、智能音箱、电动牙刷。</li>
<li><strong>智能家居</strong>:智能灯、智能门锁、智能窗帘。</li>
<li><strong>数码外设</strong>:鼠标、键盘、打印机、手机传感器。</li>
</ul>
<h5>2. 工业与交通</h5>
<ul>
<li><strong>汽车</strong>:发动机控制、ABS防抱死、车窗升降、安全气囊。</li>
<li><strong>工业自动化</strong>:机械臂、电机控制器、仪器仪表。</li>
<li><strong>公共设施</strong>:红绿灯、公交卡读卡器、电梯控制系统。</li>
</ul>
<h5>3. 创意制作(DIY/机器人)</h5>
<ul>
<li>循迹小车、平衡车、机械臂。</li>
<li>自制电子时钟、温湿度计、智能鱼缸、四轴飞行器。</li>
</ul>
<hr />
<h4>三、核心总结</h4>
<ul>
<li><strong>定义</strong>:单片机是一种<strong>可编程的微型计算机系统</strong>,专门用于<strong>实时控制</strong>。</li>
<li><strong>工作模式</strong>:通过<strong>输入</strong>(传感器、按键)获取信息,运行<strong>程序</strong>做出决策,通过<strong>输出</strong>(LED、电机、屏幕)执行动作。</li>
<li><strong>意义</strong>:学习单片机,就是学习如何用软件赋予硬件“智能”,让它们按你的想法工作。</li>
</ul>
<p>打卡第二集-了解硬件</p>
<h4>一、开发板硬件概览</h4>
<p>单片机开发板是将单片机芯片及其常用外围电路集成在一块电路板上,方便学习和实验。常见的开发板上通常包含以下核心硬件:</p>
<table>
<thead>
<tr>
<th>硬件</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>单片机主控芯片</strong></td>
<td>大脑,执行程序。</td>
</tr>
<tr>
<td><strong>电源电路</strong></td>
<td>提供稳定电压(通常5V或3.3V)。</td>
</tr>
<tr>
<td><strong>晶振</strong></td>
<td>提供时钟节拍,同步工作。</td>
</tr>
<tr>
<td><strong>复位电路</strong></td>
<td>让程序重新开始运行。</td>
</tr>
<tr>
<td><strong>按键</strong></td>
<td>输入信号,用于控制。</td>
</tr>
<tr>
<td><strong>LED灯</strong></td>
<td>输出指示,观察状态。</td>
</tr>
<tr>
<td><strong>数码管</strong></td>
<td>显示数字或简单字符。</td>
</tr>
<tr>
<td><strong>蜂鸣器</strong></td>
<td>发出声音,提示或报警。</td>
</tr>
<tr>
<td><strong>红外接收/发射头</strong></td>
<td>接收/发射红外遥控信号。</td>
</tr>
<tr>
<td><strong>接口(排针)</strong></td>
<td>连接外部传感器或模块。</td>
</tr>
</tbody>
</table>
<hr />
<h4>🔊 二、蜂鸣器</h4>
<ul>
<li><strong>定义</strong>:一种能发出声音的电子元件,本质是电流通过线圈驱动振动膜发声。</li>
<li><strong>分类</strong>:
<ul>
<li><strong>有源蜂鸣器</strong>:内部自带振荡源,通上直流电就直接发声(固定频率)。<strong>控制简单</strong>,只需给高/低电平。</li>
<li><strong>无源蜂鸣器</strong>:内部不带振荡源,需要给<strong>一定频率的方波</strong>才能发声,可以改变音调。<strong>控制稍复杂</strong>,但可播放简单旋律。</li>
</ul>
</li>
<li><strong>作用</strong>:报警提示(如按键音、闹钟)、状态提醒。</li>
</ul>
<hr />
<h4>📡 三、红外接收 & 红外发射</h4>
<ul>
<li><strong>红外发射</strong>:
<ul>
<li><strong>原理</strong>:通过红外发光二极管,将电信号转换为红外光信号发射出去。</li>
<li><strong>用途</strong>:模拟电视、空调等遥控器,发送控制指令。</li>
</ul>
</li>
<li><strong>红外接收</strong>:
<ul>
<li><strong>原理</strong>:红外接收头内部有光电二极管和放大电路,接收红外光并还原为电信号。</li>
<li><strong>用途</strong>:接收遥控器信号,实现无线控制(如遥控小车、智能家居)。</li>
</ul>
</li>
<li><strong>常见协议</strong>:NEC协议、RC5协议等,规定了数据的编码方式。</li>
</ul>
<hr />
<h4>💡 四、LED灯</h4>
<ul>
<li><strong>定义</strong>:发光二极管,一种能将电能转化为光能的半导体器件。</li>
<li><strong>工作原理</strong>:正向导通时发光,反向截止。电流越大亮度越高(但需串联限流电阻,防止烧毁)。</li>
<li><strong>控制方式</strong>:
<ul>
<li><strong>点亮/熄灭</strong>:单片机GPIO输出高/低电平控制。</li>
<li><strong>亮度调节</strong>:通过PWM(脉宽调制)模拟电压变化,实现呼吸灯效果。</li>
</ul>
</li>
<li><strong>作用</strong>:状态指示、照明、信息显示(如流水灯)。</li>
</ul>
<hr />
<h4>🔢 五、数码管</h4>
<ul>
<li><strong>定义</strong>:由多个LED封装在一起组成的“8”字形显示器件。</li>
<li><strong>结构</strong>:通常有8个LED段(a~g 和 dp小数点),点亮不同段组合显示数字0~9和部分字母。</li>
<li><strong>分类</strong>:
<ul>
<li><strong>共阴极</strong>:所有LED的负极连在一起接地,点亮某段需给正极高电平。</li>
<li><strong>共阳极</strong>:所有LED的正极连在一起接电源,点亮某段需给负极低电平。</li>
</ul>
</li>
<li><strong>显示方式</strong>:
<ul>
<li><strong>静态显示</strong>:每个数码管独立控制,占用I/O口多,但编程简单。</li>
<li><strong>动态显示</strong>:利用人眼视觉暂留,快速轮流点亮多个数码管,节省I/O口,但需不断刷新。</li>
</ul>
</li>
<li><strong>作用</strong>:显示数字、简单字符(如温度、时间、计数器值)。</li>
</ul>
<hr />
<h4>🧠 核心总结</h4>
<ul>
<li><strong>输入设备</strong>:按键、红外接收头 → 给单片机发送指令或数据。</li>
<li><strong>输出设备</strong>:LED、数码管、蜂鸣器、红外发射头 → 单片机控制它们表达状态或执行动作。</li>
<li><strong>学习重点</strong>:理解每个硬件的工作原理(是电平控制还是脉冲控制),学会通过编程让它们按照预期工作。</li>
</ul>
打卡第三集-开发环境搭建与程序下载
安装ISP
安装Keil
编译程序
要 做到 USB不停电下载;
要 尝试 AiCube 图形化自动配置生成程序工具;
推荐优先看的:
printf_usb("Hello World !\r\n")及
USB不停电下载, 演示视频链接:
https://www.stcaimcu.com/thread-19077-1-1.html
下载 最新的 AiCube-ISP-V6.96T 或以上版本软件 !
深圳国芯人工智能有限公司-工具软件
下载 最新的 USB库函数,永远用最新的 USB库函数 !
深圳国芯人工智能有限公司-库函数
下载 最新的 用户手册 !
下载 最新的 上机实践指导书 !
下载 最新的 STC8H8K64U 用户手册
https://www.stcaimcu.com/data/download/Datasheet/STC8H.pdf
下载 最新的 STC8H8K64U 实验指导书
有 AiCube 图形化自动配置生成程序工具使用说明
https://www.stcaimcu.com/data/do ... %AF%BC%E4%B9%A6.pdf
推荐优先看的 printf_usb("Hello World !\r\n")及usb不停电下载, 演示视频链接
https://v.stcai.com/sv/e49742d-1978afcb431/e49742d-1978afcb431.mp4
上面是 小李 演示:STC8H8K64U, printf_usb("Hello World !\r\n")及usb不停电下载@AiCube之图形化程序自动生成
https://v.stcai.com/sv/61d1aa5-1978c2a6adb/61d1aa5-1978c2a6adb.mp4
上面是 小赵 演示:STC8H8K64U, printf_usb("Hello World !\r\n")及usb不停电下载@AiCube之图形化程序自动生成
<p>打卡第四集-建立工程点亮第一颗LED</p>
<p>GPIO-是通用输入输出端口的简称,可以通过软件来读取其输入电平,或者控制他输出高低电平。<br />
P0表示一组GPIO口 P0.0表示一组的其中一个GPIO口</p>
<hr />
<p>在单片机C语言(特别是51单片机编程)中,<strong><code>sfr</code></strong> 是一个<strong>关键字</strong>,它的全称是 <strong>Special Function Register</strong>(特殊功能寄存器)。</p>
<p>它的主要作用是让程序员能够在C语言中,直接操作单片机内部的硬件寄存器。</p>
<p>下面从几个方面来解释:</p>
<h3>1. 为什么需要 <code>sfr</code>?</h3>
<p>单片机的内部除了用于计算的内存(RAM)之外,还有很多控制硬件的模块(如定时器、串口、I/O口锁存器等)。CPU通过读写特定的RAM单元来控制这些硬件,这些特定的RAM单元就是<strong>特殊功能寄存器</strong>。</p>
<p>在汇编语言中,我们可以直接操作这些地址。但在C语言中,为了不把寄存器和普通变量混淆,Keil C51 等编译器专门提供了 <code>sfr</code> 这个数据类型,用来定义特殊功能寄存器的<strong>名字</strong>和它在内存中的<strong>地址</strong>的对应关系。</p>
<h3>2. <code>sfr</code> 的语法</h3>
<p>它的定义方式很简单,就是把一个寄存器的名字(你可以随便起,但通常用硬件手册上的名字)绑定到一个地址上。</p>
<p><strong>格式:</strong> <code>sfr 寄存器名 = 地址值;</code></p>
<p><strong>示例:</strong></p>
<p><strong>c</strong></p>
<pre><code>// 定义 P0 端口,其特殊功能寄存器地址是 0x80
sfr P0 = 0x80;
// 定义 P1 端口,地址是 0x90
sfr P1 = 0x90;
// 定义定时器控制寄存器 TCON,地址是 0x88
sfr TCON = 0x88;
</code></pre>
<h3>3. <code>sfr</code> 与普通变量的区别</h3>
<ul>
<li><strong>普通变量:</strong> <code>int a;</code> 编译器会在RAM中随便找一个空闲位置存放 a,具体地址由编译器决定,程序员不需要关心。</li>
<li><strong><code>sfr</code> 变量:</strong> <code>sfr P0 = 0x80;</code> <strong>这是强制指定地址</strong>。告诉编译器,以后代码里出现 <code>P0</code> 这个符号,就去操作地址 <code>0x80</code> 的那个特殊单元。这个地址是芯片设计时固定的,不能改变。</li>
</ul>
<h3>4. 使用 <code>sfr</code> 点亮LED的关联</h3>
<p>结合你上一个关于点亮LED的问题,<code>sfr</code> 是如何工作的呢?</p>
<p>通常,LED接在单片机的 P1.0 引脚上。P1 端口是一个8位的特殊功能寄存器,它对应着8个物理引脚。</p>
<p>在代码中,你需要先告诉编译器 P1 寄存器在哪里:</p>
<p><strong>c</strong></p>
<pre><code>// 步骤1:定义特殊功能寄存器
sfr P1 = 0x90; // 告诉编译器:P1 这个硬件控制单元在地址 0x90
void main()
{
// 步骤2:通过寄存器控制硬件
P1 = 0xFE; // 二进制 1111 1110,即 P1.0 引脚输出低电平
// 因为 LED 通常是负极接 P1.0,正极接电源,所以低电平点亮了 LED
}
</code></pre>
<p><strong>总结:</strong><br />
<code>sfr</code> 是C语言用来<strong>给硬件控制接口起名字</strong>的工具。它把抽象的硬件地址(如 <code>0x90</code>)变成了直观的符号(如 <code>P1</code>),让你在写 <code>P1 = 0x00;</code> 这样的代码时,实际上是在操作单片机对应的物理引脚电平。</p>
<hr />
<p><code>sbit P00 = P0^0;</code> 是单片机C语言(主要是51系列)中用来定义<strong>位变量</strong>的语句。它的作用是给P0端口的第0位(也就是P0.0引脚)起一个别名,方便后续在程序中单独控制这一个引脚。</p>
<h3>逐部分解释:</h3>
<ol>
<li><strong><code>sbit</code></strong>:
<ul>
<li>这是Keil C51编译器提供的<strong>关键字</strong>,用来定义可位寻址的特殊功能寄存器(SFR)的某一位。</li>
<li>类似于 <code>sfr</code> 用来定义整个8位寄存器,<code>sbit</code> 用来定义其中的某一位。</li>
</ul>
</li>
<li><strong><code>P00</code></strong>:
<ul>
<li>这是用户自定义的<strong>位变量名</strong>。你可以把它理解成给P0.0引脚起的昵称,之后在代码中写 <code>P00</code> 就代表这个引脚。</li>
<li>名字可以任意取,比如 <code>LED</code>、<code>FLAG</code> 等,但为了清晰,通常用 <code>P00</code> 表示P0.0。</li>
</ul>
</li>
<li><strong><code>=</code></strong>:
<ul>
<li>赋值符号,将右边的位地址赋给左边的名字。</li>
</ul>
</li>
<li><strong><code>P0^0</code></strong>:
<ul>
<li>这是<strong>位地址的表示方法</strong>。<code>P0</code> 是一个已定义的特殊功能寄存器(通常通过 <code>sfr P0 = 0x80;</code> 定义),<code>^</code> 在这里不是异或运算符,而是Keil编译器特定的语法,用来表示取该寄存器的某一位。</li>
<li><code>P0^0</code> 就是指P0寄存器的第0位(bit0),对应单片机的P0.0引脚。</li>
<li><code>^</code> 后面的数字范围通常是0~7,对应8位寄存器中的各个位。</li>
</ul>
</li>
</ol>
<h3>完整含义:</h3>
<p>这条语句告诉编译器:<strong>我要把P0寄存器的第0位(即引脚P0.0)叫做 <code>P00</code>,以后我在代码里写 <code>P00</code> 就相当于直接操作这个引脚。</strong></p>
<h3>使用示例:</h3>
<p>假设你想让P0.0引脚输出低电平来点亮LED,可以这样写:</p>
<p><strong>c</strong></p>
<pre><code>sbit P00 = P0^0; // 先定义
void main()
{
P00 = 0; // 直接对位赋值,P0.0引脚输出低电平
while(1);
}
</code></pre>
<p>如果没有 <code>sbit</code> 定义,你可能需要用位操作或修改整个P0寄存器,比如 <code>P0 = P0 & 0xFE;</code>,这样既麻烦又容易影响其他位。</p>
<h3>注意:</h3>
<ul>
<li><code>sbit</code> 只能在函数外部定义,通常放在头文件或程序开头。</li>
<li>被操作的寄存器必须支持<strong>位寻址</strong>(51单片机中P0、P1、P2、P3等端口都是支持位寻址的)。</li>
<li><code>^</code> 符号是Keil特有的,不是标准C的语法。</li>
</ul>
<hr />
<p><code>P0M0</code> 在单片机C语言中(特别是对于STC系列的51单片机),是一个<strong>特殊功能寄存器(SFR)</strong>,它的全称可以理解为** P0端口模式配置寄存器0**。</p>
<p>它的主要作用,是和另一个叫做 <code>P0M1</code> 的寄存器<strong>组合</strong>在一起,共同决定P0端口每个引脚的工作模式。</p>
<h3>1. 核心作用:配置P0端口的输入/输出模式</h3>
<p>单片机的IO口有多种工作模式,以适应不同的硬件电路需求。<code>P0M0</code> 和 <code>P0M1</code> 就是用来做这个配置的“开关组合”。</p>
<p>它们以<strong>两位(bit)一组</strong>的方式,控制P0端口的8个引脚(P0.0到P0.7)。具体对应关系如下:</p>
<table>
<thead>
<tr>
<th>P0M1 的某一位</th>
<th>P0M0 的某一位</th>
<th>组合值 (P0M1 : P0M0)</th>
<th>配置的 I/O 口模式</th>
<th>简单描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>0</td>
<td><strong>0 0</strong></td>
<td><strong>准双向口</strong> (标准8051模式)</td>
<td>既能输出也能输入,是上电默认模式。输出1时能力弱,输出0时能力强(可吸收电流,如20mA)。</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td><strong>0 1</strong></td>
<td><strong>推挽输出</strong></td>
<td>输出1和0时驱动能力都很强,可以直接驱动LED等外设,但要注意不能将两个推挽输出的引脚短接。</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td><strong>1 0</strong></td>
<td><strong>高阻输入</strong></td>
<td>仅用于输入,引脚呈现高阻抗状态,常用于ADC(模数转换器)信号采集等。</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td><strong>1 1</strong></td>
<td><strong>开漏输出</strong></td>
<td>输出0时能力强,输出1时需外部接上拉电阻,常用于不同电压等级的电平转换。</td>
</tr>
</tbody>
</table>
<p><strong>简单来说,<code>P0M0</code> 和 <code>P0M1</code> 这两个寄存器里,每两个比特位,就决定了P0口一个引脚是“普通模式”、“强输出模式”、“纯输入模式”还是“开漏模式”。</strong></p>
<h3>2. 如何理解和使用它?</h3>
<p>在代码中,你通常会在初始化函数里看到这样的赋值语句:</p>
<p><strong>c</strong></p>
<pre><code>// 例如:将P0端口的所有引脚都配置为“推挽输出”模式
P0M0 = 0xFF; // 二进制 1111 1111,即每一位都是 1
P0M1 = 0x00; // 二进制 0000 0000,即每一位都是 0
</code></pre>
<p>根据上表,<code>P0M1</code> 的每一位 = 0,<code>P0M0</code> 的每一位 = 1,正好对应<strong>推挽输出</strong>模式。</p>
<h3>总结</h3>
<ul>
<li><strong><code>P0M0</code> 是什么?</strong>:它是一个用于配置P0口工作模式的特殊功能寄存器。</li>
<li><strong>它有什么用?</strong>:它与 <code>P0M1</code> 一起,通过两位组合的方式,精确设置P0口每个引脚是<strong>准双向口、推挽输出、高阻输入还是开漏输出</strong>。这是编写单片机程序、让硬件正确工作的第一步。</li>
</ul>
<p>你可以把它理解成是P0端口的“性格设置器”,在操作引脚输入或输出数据之前,先要通过它告诉单片机,这个引脚要以什么“性格”工作。</p>
<hr />
<h3>区分函数的声明与定义(单片机视角)</h3>
<p>在单片机开发中,如果不遵守“声明放在 <code>.h</code>,定义放在 <code>.c</code>”的规则,通常会遇到以下三种典型问题:</p>
<h4>1. 解决“先使用,后定义”的问题</h4>
<p>C语言编译器是顺序扫描的。如果 <code>main</code> 函数写在最上面,而它调用的子函数写在 <code>main</code> 下面,编译器在扫描到调用处时,会报错 <code>implicit declaration of function</code>(隐式声明错误)。</p>
<p><strong>解决方案</strong>:在 <code>main</code> 之前声明函数。</p>
<p><strong>c</strong></p>
<pre><code>// 错误示例
int main(void) {
delay_ms(10); // 报错:找不到 delay_ms
return 0;
}
void delay_ms(int ms) { ... } // 定义在后面
// 正确示例
void delay_ms(int ms); // 声明
int main(void) {
delay_ms(10); // OK,编译器知道有这个函数
return 0;
}
void delay_ms(int ms) { ... } // 定义
</code></pre>
<h4>2. 实现模块化编程(多文件组织)</h4>
<p>在STM32、51或ESP32等单片机工程中,我们通常按外设拆分代码:</p>
<ul>
<li><strong>LED.c</strong>:定义 <code>LED_Init</code>, <code>LED_Toggle</code> 的具体底层寄存器操作。</li>
<li><strong>LED.h</strong>:声明 <code>LED_Init</code>, <code>LED_Toggle</code>。</li>
<li><strong>main.c</strong>:只需 <code>#include "LED.h"</code>,即可调用这些函数。</li>
</ul>
<p>如果省略声明,直接在 <code>main.c</code> 里写 <code>extern void LED_Init(void);</code>,虽然可行,但项目大了之后维护性极差。通过头文件声明,可以实现<strong>接口与实现的分离</strong>,这是嵌入式代码复用和团队协作的基础。</p>
<h4>3. 防止重复定义</h4>
<p>如果两个 <code>.c</code> 文件都包含了同一个函数的定义,链接器会报错 <code>multiple definition</code>(重复定义)。</p>
<p><strong>正确做法</strong>:在 <code>.c</code> 文件中定义一次,在 <code>.h</code> 文件中声明(通过 <code>extern</code> 隐式或显式),其他文件包含头文件即可。</p>
<p>打卡第五集-C语言运算符和进制数入门</p>
<p><strong>波特率</strong>(Baud Rate)是指<strong>单位时间内传输的符号(码元)数量</strong>,单位是 <strong>波特</strong>(Baud)。</p>
<p>在串行通信中,通常一个符号代表一个二进制位(bit),所以:</p>
<p><strong>波特率 = 比特率</strong>(bit per second, bps)</p>
<p>但在高级调制方式中,一个符号可能代表多个比特,此时波特率 ≠ 比特率。</p>
<p><strong>任何传输线(电缆、PCB走线)本质上都是一个低通滤波器</strong>。这意味着:</p>
<ul>
<li><strong>低频信号</strong>:衰减小,能传得远</li>
<li><strong>高频信号</strong>:衰减大,传不远</li>
</ul>
<p>波特率越高,信号中包含的高频分量就越多,经过传输线时衰减就越严重。</p>
<hr />
<p><img src="data/attachment/forum/202603/21/014942ubl5hhicbr75ix85.png" alt="image.png" title="image.png" /></p>
<p><img src="data/attachment/forum/202603/21/014954p73s5s876d35hs9n.png" alt="image.png" title="image.png" /></p>
<p><img src="data/attachment/forum/202603/21/015051njn133ott5tty525.png" alt="image.png" title="image.png" /></p>
<hr />
<p><img src="data/attachment/forum/202603/21/015129gry579plziuon1f7.png" alt="image.png" title="image.png" /></p>
<p>打卡第六集-LED闪烁和花式点灯</p>
<p><code>#define MAIN_Fosc 24000000UL</code></p>
<p>这行代码定义了一个宏常量,我来详细解释其语法:</p>
<h2>语法分解</h2>
<p><strong>c</strong></p>
<pre><code>#define MAIN_Fosc 24000000UL
</code></pre>
<h3>1. <strong><code>#define</code></strong> - 预处理指令</h3>
<ul>
<li>这是C语言的预处理指令</li>
<li>在编译前由预处理器处理</li>
<li>用于定义宏常量或宏函数</li>
</ul>
<h3>2. <strong><code>MAIN_Fosc</code></strong> - 宏名称</h3>
<ul>
<li>标识符,通常使用大写字母</li>
<li>命名含义:MAIN(主)+ Fosc(振荡器频率)</li>
<li>在代码中所有出现这个名称的地方都会被替换</li>
</ul>
<h3>3. <strong><code>24000000UL</code></strong> - 宏值(替换文本)</h3>
<p>这是一个<strong>整型常量</strong>,其中:</p>
<ul>
<li><strong><code>24000000</code></strong> - 数值部分,表示 24,000,000</li>
<li><strong><code>U</code></strong> - 后缀,表示 <strong>unsigned</strong>(无符号类型)</li>
<li><strong><code>L</code></strong> - 后缀,表示 <strong>long</strong>(长整型)</li>
</ul>
<h2>类型说明</h2>
<table>
<thead>
<tr>
<th>后缀</th>
<th>含义</th>
<th>类型</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>U</code></td>
<td>unsigned</td>
<td>无符号</td>
</tr>
<tr>
<td><code>L</code></td>
<td>long</td>
<td>长整型</td>
</tr>
<tr>
<td><code>UL</code></td>
<td>unsigned long</td>
<td>无符号长整型</td>
</tr>
<tr>
<td><code>ULL</code></td>
<td>unsigned long long</td>
<td>无符号长长整型</td>
</tr>
</tbody>
</table>
<h2>实际应用</h2>
<p>这个宏通常用于<strong>单片机/嵌入式开发</strong>中,定义主时钟频率:</p>
<p><strong>c</strong></p>
<pre><code>#define MAIN_Fosc 24000000UL// 主频 24MHz
// 使用示例
void delay_us(unsigned int us) {
unsigned int count = MAIN_Fosc / 1000000 * us;
while(count--);
}
// 预处理器处理后:
void delay_us(unsigned int us) {
unsigned int count = 24000000UL / 1000000 * us;
while(count--);
}
</code></pre>
<h2>优点</h2>
<ol>
<li><strong>提高可读性</strong> - 名称说明含义</li>
<li><strong>便于维护</strong> - 修改频率只需改一处</li>
<li><strong>类型安全</strong> - <code>UL</code>后缀确保计算时使用正确的数据类型</li>
<li><strong>避免魔法数字</strong> - 24,000,000有明确含义</li>
</ol>
<p>这种写法在STM32、8051等单片机固件开发中非常常见。</p>
<hr />
<h2>while 与 do-while 循环的区别</h2>
<h3>1. <strong>语法结构</strong></h3>
<h4>while 循环</h4>
<p><strong>c</strong></p>
<pre><code>while (条件表达式) {
// 循环体
// 先判断条件,后执行
}
</code></pre>
<h4>do-while 循环</h4>
<p><strong>c</strong></p>
<pre><code>do {
// 循环体
// 先执行一次,后判断条件
} while (条件表达式);// 注意这里有分号
</code></pre>
<h3>2. <strong>核心区别</strong></h3>
<table>
<thead>
<tr>
<th>特性</th>
<th>while</th>
<th>do-while</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>执行顺序</strong></td>
<td>先判断,后执行</td>
<td>先执行,后判断</td>
</tr>
<tr>
<td><strong>最少执行次数</strong></td>
<td>0次</td>
<td>1次</td>
</tr>
<tr>
<td><strong>适用场景</strong></td>
<td>可能一次都不执行</td>
<td>至少执行一次</td>
</tr>
</tbody>
</table>
<h3>3. <strong>代码示例</strong></h3>
<h4>示例1:最少执行次数</h4>
<p><strong>c</strong></p>
<pre><code>// while - 可能执行0次
int i = 10;
while (i < 5) {
printf("while: %d\n", i);// 不会执行
i++;
}
// do-while - 至少执行1次
int j = 10;
do {
printf("do-while: %d\n", j);// 会执行1次
j++;
} while (j < 5);
</code></pre>
<h4>示例2:实际应用场景</h4>
<p><strong>while 场景 - 输入验证(可能一次都不需要)</strong></p>
<p><strong>c</strong></p>
<pre><code>int value;
printf("请输入正数:");
scanf("%d", &value);
while (value <= 0) {
printf("输入错误,请重新输入:");
scanf("%d", &value);
}
</code></pre>
<p><strong>do-while 场景 - 菜单程序(至少显示一次菜单)</strong></p>
<p><strong>c</strong></p>
<pre><code>int choice;
do {
printf("\n=== 菜单 ===\n");
printf("1. 开始游戏\n");
printf("2. 设置\n");
printf("3. 退出\n");
printf("请选择:");
scanf("%d", &choice);
switch(choice) {
case 1: printf("游戏开始\n"); break;
case 2: printf("设置界面\n"); break;
case 3: printf("退出游戏\n"); break;
default: printf("无效选择\n");
}
} while (choice != 3);
</code></pre>
<h3>4. <strong>单片机嵌入式场景</strong></h3>
<p>结合你的 <code>MAIN_Fosc</code> 宏定义:</p>
<p><strong>c</strong></p>
<pre><code>// while 场景 - 等待标志位(可能立即满足)
volatile unsigned char flag = 0;
// 等待中断标志,但如果标志已经为1,直接跳过循环
while (!flag) {
// 等待中断发生
}
// do-while 场景 - 至少执行一次延时
void delay_us(unsigned int us) {
unsigned int count;
do {
count = MAIN_Fosc / 1000000;
while (count--);
} while (--us);// 至少延时1微秒
}
</code></pre>
<h3>5. <strong>选择建议</strong></h3>
<table>
<thead>
<tr>
<th>场景</th>
<th>推荐</th>
<th>原因</th>
</tr>
</thead>
<tbody>
<tr>
<td>需要先判断条件</td>
<td>while</td>
<td>可能不需要执行循环体</td>
</tr>
<tr>
<td>至少需要执行一次</td>
<td>do-while</td>
<td>保证循环体至少执行一次</td>
</tr>
<tr>
<td>菜单选择</td>
<td>do-while</td>
<td>至少显示一次菜单</td>
</tr>
<tr>
<td>文件读取</td>
<td>while</td>
<td>文件可能为空</td>
</tr>
<tr>
<td>用户输入验证</td>
<td>do-while</td>
<td>至少要求输入一次</td>
</tr>
</tbody>
</table>
<hr />
<h2>模块化编程中函数使用的三个步骤</h2>
<h3>一、函数定义</h3>
<p>定义函数的具体实现,包括函数要执行的功能。</p>
<p><strong>语法格式:</strong></p>
<p><strong>c</strong></p>
<pre><code>返回值类型 函数名称(入口参数)
{
// 函数体:要执行的功能代码
}
</code></pre>
<p><strong>要点说明:</strong></p>
<table>
<thead>
<tr>
<th>组成部分</th>
<th>说明</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>返回值</strong></td>
<td>没有返回值时使用 <code>void</code></td>
<td><code>void</code>、<code>int</code>、<code>char</code></td>
</tr>
<tr>
<td><strong>函数名称</strong></td>
<td>避开关键词,不重复,不使用特殊字符</td>
<td><code>delay_ms</code>、<code>getValue</code></td>
</tr>
<tr>
<td><strong>入口参数</strong></td>
<td>格式:<code>类型 名称</code>,多个参数用逗号分隔,无参数填 <code>void</code></td>
<td><code>int x</code>、<code>int a, int b</code>、<code>void</code></td>
</tr>
</tbody>
</table>
<p><strong>示例:</strong></p>
<p><strong>c</strong></p>
<pre><code>// 有返回值,有参数
int add(int a, int b)
{
return a + b;
}
// 无返回值,有参数
void delay_ms(unsigned int ms)
{
// 延时功能代码
}
// 无返回值,无参数
void led_toggle(void)
{
// LED翻转功能代码
}
</code></pre>
<hr />
<h3>二、函数声明</h3>
<p>在使用函数前告知编译器函数的存在,通常放在头文件(.h)或源文件(.c)开头。</p>
<p><strong>语法格式:</strong></p>
<p><strong>c</strong></p>
<pre><code>返回值类型 函数名称(入口参数);
</code></pre>
<p><strong>要点:</strong></p>
<ul>
<li>与函数定义的第一行完全相同,末尾加分号 <code>;</code></li>
<li>声明不包含函数体</li>
<li>通常放在 <code>.h</code> 头文件中</li>
</ul>
<p><strong>示例:</strong></p>
<p><strong>c</strong></p>
<pre><code>// 函数声明
int add(int a, int b);
void delay_ms(unsigned int ms);
void led_toggle(void);
</code></pre>
<hr />
<h3>三、函数调用</h3>
<p>在程序中执行函数。</p>
<p><strong>语法格式:</strong></p>
<p><strong>c</strong></p>
<pre><code>函数名称(入口参数);
</code></pre>
<p><strong>要点说明:</strong></p>
<table>
<thead>
<tr>
<th>调用方式</th>
<th>格式</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>无返回值调用</strong></td>
<td><code>函数名(参数);</code></td>
<td><code>delay_ms(100);</code></td>
</tr>
<tr>
<td><strong>有返回值调用</strong></td>
<td><code>变量 = 函数名(参数);</code></td>
<td><code>result = add(3, 5);</code></td>
</tr>
<tr>
<td><strong>无参数调用</strong></td>
<td><code>函数名();</code> 或 <code>函数名(void);</code></td>
<td><code>led_toggle();</code></td>
</tr>
</tbody>
</table>
<p><strong>示例:</strong></p>
<p><strong>c</strong></p>
<pre><code>void main(void)
{
int sum;
// 无返回值函数调用
delay_ms(100);
led_toggle();
// 有返回值函数调用
sum = add(10, 20);// sum = 30
}
</code></pre>
<hr />
<h3>完整示例(模块化编程)</h3>
<p><strong>头文件 <code>math_utils.h</code></strong></p>
<p><strong>c</strong></p>
<pre><code>#ifndef _MATH_UTILS_H_
#define _MATH_UTILS_H_
// 函数声明
int add(int a, int b);
int multiply(int a, int b);
#endif
</code></pre>
<p><strong>源文件 <code>math_utils.c</code></strong></p>
<p><strong>c</strong></p>
<pre><code>#include "math_utils.h"
// 函数定义
int add(int a, int b)
{
return a + b;
}
int multiply(int a, int b)
{
return a * b;
}
</code></pre>
<p><strong>主文件 <code>main.c</code></strong></p>
<p><strong>c</strong></p>
<pre><code>#include <stdio.h>
#include "math_utils.h"
int main(void)
{
int result;
// 函数调用
result = add(5, 3); // result = 8
result = multiply(4, 6); // result = 24
return 0;
}
</code></pre>
<hr />
<h3>快速记忆口诀</h3>
<blockquote>
<p><strong>定义写实现,声明放头前</strong><br />
<strong>调用传参数,注意返回值</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th>步骤</th>
<th>作用</th>
<th>位置</th>
</tr>
</thead>
<tbody>
<tr>
<td>定义</td>
<td>实现函数功能</td>
<td><code>.c</code> 源文件</td>
</tr>
<tr>
<td>声明</td>
<td>告诉编译器函数存在</td>
<td><code>.h</code> 头文件</td>
</tr>
<tr>
<td>调用</td>
<td>执行函数</td>
<td>需要使用的 <code>.c</code> 文件中</td>
</tr>
</tbody>
</table>
<hr />
<h2>.h文件(头文件)的作用</h2>
<h3>一、什么是.h文件</h3>
<p><strong>头文件(Header File)</strong> 是以 <code>.h</code> 为扩展名的文件,用于存放程序中需要共享的声明信息,相当于一个 <strong>接口说明书</strong>。</p>
<hr />
<h3>二、.h文件的核心作用</h3>
<table>
<thead>
<tr>
<th>作用</th>
<th>说明</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>函数声明</strong></td>
<td>告诉其他文件有哪些函数可用</td>
<td><code>void delay_ms(unsigned int ms);</code></td>
</tr>
<tr>
<td><strong>宏定义</strong></td>
<td>定义常量和宏</td>
<td><code>#define MAIN_Fosc 24000000UL</code></td>
</tr>
<tr>
<td><strong>类型定义</strong></td>
<td>定义结构体、枚举等自定义类型</td>
<td><code>typedef struct { int x; } Point;</code></td>
</tr>
<tr>
<td><strong>变量声明</strong></td>
<td>声明全局变量(extern)</td>
<td><code>extern int system_status;</code></td>
</tr>
<tr>
<td><strong>包含其他头文件</strong></td>
<td>引入依赖</td>
<td><code>#include <stdio.h></code></td>
</tr>
</tbody>
</table>
<hr />
<h3>三、为什么需要.h文件</h3>
<h4>场景:没有头文件时</h4>
<p><strong>c</strong></p>
<pre><code>// main.c
void delay_ms(unsigned int ms);// 必须手动声明,每个文件都要写
int main(void)
{
delay_ms(100);
return 0;
}
</code></pre>
<p><strong>问题:</strong></p>
<ul>
<li>❌ 每个使用 <code>delay_ms</code> 的文件都要重复声明</li>
<li>❌ 函数修改时,所有地方的声明都要改</li>
<li>❌ 代码冗长,难以维护</li>
</ul>
<h4>场景:使用头文件后</h4>
<p><strong>c</strong></p>
<pre><code>// delay.h - 头文件
#ifndef _DELAY_H_
#define _DELAY_H_
void delay_ms(unsigned int ms);// 只声明一次
#endif
// main.c
#include "delay.h"// 一行搞定
int main(void)
{
delay_ms(100);
return 0;
}
// other.c
#include "delay.h"// 其他文件也只需一行
void other_func(void)
{
delay_ms(50);
}
</code></pre>
<p><strong>优点:</strong></p>
<ul>
<li>✅ 声明只写一次,多处复用</li>
<li>✅ 函数接口统一管理</li>
<li>✅ 修改只需改一处</li>
</ul>
<hr />
<h3>四、.h文件的内容结构</h3>
<p><strong>c</strong></p>
<pre><code>// ============================================
// 头文件的标准结构
// ============================================
#ifndef _文件名_H_ // 头文件保护(防止重复包含)
#define _文件名_H_
// 1. 包含其他头文件
#include <stdio.h>
#include "other.h"
// 2. 宏定义
#define MAIN_Fosc 24000000UL
#define LED_ON 1
#define LED_OFF0
// 3. 类型定义
typedef struct {
int x;
int y;
} Point;
typedef enum {
STATE_IDLE,
STATE_RUNNING,
STATE_STOP
} SystemState;
// 4. 全局变量声明(extern)
extern int g_system_tick;
extern SystemState g_state;
// 5. 函数声明
void system_init(void);
void delay_ms(unsigned int ms);
int add(int a, int b);
#endif// 结束头文件保护
</code></pre>
<hr />
<h3>五、.h 与 .c 的分工</h3>
<table>
<thead>
<tr>
<th>文件类型</th>
<th>存放内容</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>.h 头文件</strong></td>
<td>声明(接口)</td>
<td>告诉别人"我能做什么"</td>
</tr>
<tr>
<td><strong>.c 源文件</strong></td>
<td>定义(实现)</td>
<td>告诉编译器"我怎么做"</td>
</tr>
</tbody>
</table>
<p><strong>类比理解:</strong></p>
<ul>
<li><strong>.h文件</strong> = 餐馆的 <strong>菜单</strong>(告诉你能点什么菜)</li>
<li><strong>.c文件</strong> = 后厨的 <strong>做法</strong>(具体怎么做这道菜)</li>
</ul>
<hr />
<h3>六、模块化编程示例</h3>
<h4>项目结构</h4>
<p><strong>text</strong></p>
<pre><code>project/
├── main.c // 主程序
├── led.c // LED功能实现
├── led.h // LED功能声明
├── delay.c // 延时功能实现
└── delay.h // 延时功能声明
</code></pre>
<h4>led.h(头文件)</h4>
<p><strong>c</strong></p>
<pre><code>#ifndef _LED_H_
#define _LED_H_
#include "delay.h" // LED需要使用延时
// 宏定义
#define LED_PIN P1_0
#define LED_ON 0
#define LED_OFF 1
// 函数声明
void led_init(void);
void led_on(void);
void led_off(void);
void led_blink(unsigned int ms);
#endif
</code></pre>
<h4>led.c(源文件)</h4>
<p><strong>c</strong></p>
<pre><code>#include "led.h"
// 函数定义(实现)
void led_init(void)
{
LED_PIN = LED_OFF;// 初始关闭
}
void led_on(void)
{
LED_PIN = LED_ON;
}
void led_off(void)
{
LED_PIN = LED_OFF;
}
void led_blink(unsigned int ms)
{
led_on();
delay_ms(ms); // 调用延时函数
led_off();
delay_ms(ms);
}
</code></pre>
<h4>main.c(主程序)</h4>
<p><strong>c</strong></p>
<pre><code>#include "led.h" // 只需包含头文件
void main(void)
{
led_init(); // 调用LED功能
while(1) {
led_blink(500);// 500ms闪烁
}
}
</code></pre>
<hr />
<h3>七、头文件保护机制</h3>
<p><strong>防止重复包含:</strong></p>
<p><strong>c</strong></p>
<pre><code>// 方法1:使用 #ifndef(最常用)
#ifndef _LED_H_
#define _LED_H_
// 头文件内容...
#endif
// 方法2:使用 #pragma once(编译器扩展)
#pragma once
// 头文件内容...
</code></pre>
<p><strong>为什么需要:</strong></p>
<p><strong>c</strong></p>
<pre><code>// 错误示例:没有头文件保护
// a.h
struct Point { int x; int y; };
// b.h
#include "a.h"
// main.c
#include "a.h"
#include "b.h"// 错误!struct Point 被定义了两次
</code></pre>
<hr />
<h3>八、头文件使用总结</h3>
<table>
<thead>
<tr>
<th>内容</th>
<th>放在.h</th>
<th>放在.c</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>函数声明</td>
<td>✓</td>
<td></td>
<td>公开接口</td>
</tr>
<tr>
<td>函数定义</td>
<td></td>
<td>✓</td>
<td>具体实现</td>
</tr>
<tr>
<td>宏定义</td>
<td>✓</td>
<td></td>
<td>常量共享</td>
</tr>
<tr>
<td>类型定义</td>
<td>✓</td>
<td></td>
<td>结构体、枚举等</td>
</tr>
<tr>
<td>全局变量声明</td>
<td><code>extern int x;</code></td>
<td></td>
<td>声明放在.h</td>
</tr>
<tr>
<td>全局变量定义</td>
<td></td>
<td><code>int x;</code></td>
<td>定义放在.c</td>
</tr>
<tr>
<td>static 函数/变量</td>
<td></td>
<td>✓</td>
<td>仅本文件使用</td>
</tr>
</tbody>
</table>
<hr />
<h3>九、常见问题</h3>
<p><strong>Q:.h文件需要编译吗?</strong><br />
A:不需要,头文件通过 <code>#include</code> 被包含到 <code>.c</code> 文件中,随 <code>.c</code> 文件一起编译。</p>
<p><strong>Q:函数声明可以省略吗?</strong><br />
A:如果函数调用在定义之后,可以省略;但通常建议都在 <code>.h</code> 中声明。</p>
<p><strong>Q:.h文件可以包含.c文件吗?</strong><br />
A:可以但不推荐,会导致代码混乱和重复编译。</p>
<hr />
<h3>快速记忆</h3>
<blockquote>
<p><strong>头文件是接口说明书,</strong><br />
<strong>放声明不放实现,</strong><br />
<strong>加保护防重复,</strong><br />
<strong>模块化编程离不开。</strong></p>
</blockquote>
<p>打卡第七集-按键点灯</p>
<p>单片机按键的原理</p>
<h2>一、按键的物理结构</h2>
<p>单片机常用的按键是<strong>机械触点式开关</strong>:</p>
<ul>
<li>按下 → 触点闭合 → 电路导通 → 输入电平由高变低(或反之)</li>
<li>释放 → 触点断开 → 电路开路 → 输入电平恢复</li>
</ul>
<hr />
<h2>二、按键抖动(Key Jitter / Bounce)</h2>
<h3>1. 为什么会有抖动</h3>
<p>正如你所说,机械触点并非理想开关:</p>
<ul>
<li><strong>闭合时</strong>:触点会弹跳多次才能稳定接触</li>
<li><strong>断开时</strong>:同样会弹跳几次才完全分离</li>
</ul>
<h3>2. 抖动波形示意</h3>
<p>理想的按键电平变化是<strong>一次跳变</strong>,实际却是:</p>
<p><strong>text</strong></p>
<pre><code>按下瞬间 稳定闭合 松开瞬间
↓ ↓ ↓
___/‾‾\___/‾‾\__________/‾‾\___/‾‾\___
抖动区 稳定区 抖动区
</code></pre>
<ul>
<li>抖动时间一般为 <strong>5ms ~ 20ms</strong></li>
<li>抖动期间电平会在高、低之间多次变化</li>
</ul>
<hr />
<h2>三、抖动对单片机的影响</h2>
<p>单片机执行速度很快(微秒级):</p>
<ul>
<li>如果在抖动期间多次读取按键状态</li>
<li>程序会误认为“按下了多次”或“断开了又闭合”</li>
</ul>
<p><strong>后果</strong>:</p>
<ul>
<li>一次按键被识别成多次触发</li>
<li>影响功能(如计数不准、模式乱跳)</li>
</ul>
<hr />
<h2>四、按键处理的核心思想</h2>
<p><strong>目的</strong>:消除抖动影响,确保一次机械动作只被识别为一次有效按键。</p>
<p>通常采用两种方式:</p>
<h3>1. 硬件消抖</h3>
<ul>
<li>在按键两端加 <strong>RC 滤波电路</strong>(电阻+电容)</li>
<li>利用电容充放电平滑抖动波形</li>
<li>或使用 <strong>RS触发器</strong> 等专用消抖电路</li>
<li>优点:不占用CPU资源</li>
<li>缺点:增加硬件成本和PCB空间</li>
</ul>
<h3>2. 软件消抖(最常用)</h3>
<ul>
<li>检测到按键状态变化后,<strong>延时 10ms~20ms</strong></li>
<li>延时结束后再次读取该按键状态</li>
<li>若状态一致,则确认为有效按键</li>
<li>优点:零成本、灵活</li>
<li>缺点:占用CPU时间(可用定时器或状态机优化)</li>
</ul>
<hr />
<h2>五、软件消抖的典型实现</h2>
<h3>简单延时法(适用于简单程序)</h3>
<p><strong>c</strong></p>
<pre><code>if (KEY == 0) { // 检测到按下
delay_ms(20); // 延时消抖
if (KEY == 0) {
// 确认有效按键,执行功能
while (KEY == 0); // 等待松手(可选)
}
}
</code></pre>
<h3>状态机法(高效、无阻塞)</h3>
<p>常用在RTOS或前后台系统中:</p>
<ul>
<li>定义按键状态:<code>IDLE</code>, <code>PRESS_DOWN</code>, <code>DEBOUNCE</code>, <code>RELEASE</code></li>
<li>每 10ms~20ms 定时扫描一次按键</li>
<li>根据当前状态和输入电平转移状态,输出有效按键事件</li>
</ul>
<hr />
<h2>六、总结</h2>
<table>
<thead>
<tr>
<th>项目</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>按键原理</td>
<td>机械触点通断改变单片机IO电平</td>
</tr>
<tr>
<td>核心问题</td>
<td>机械抖动导致电平多次跳变</td>
</tr>
<tr>
<td>抖动时间</td>
<td>5ms~20ms</td>
</tr>
<tr>
<td>常用方案</td>
<td>软件延时消抖 / 定时扫描 + 状态机</td>
</tr>
<tr>
<td>注意事项</td>
<td>消抖时间要大于实际抖动时间,但不宜过长影响响应</td>
</tr>
</tbody>
</table>
<hr />
<h2>数组的使用</h2>
<h3>一、核心两步</h3>
<table>
<thead>
<tr>
<th>步骤</th>
<th>操作</th>
<th>语法格式</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>1. 定义</strong></td>
<td>声明数组并分配内存</td>
<td><code>类型 名称[长度] = {初始值};</code></td>
</tr>
<tr>
<td><strong>2. 使用</strong></td>
<td>通过索引访问元素</td>
<td><code>名称[索引] = 数值;</code> (赋值)<br/><code>变量 = 名称[索引];</code> (取值)</td>
</tr>
</tbody>
</table>
<hr />
<h3>二、定义数组</h3>
<h4>基本格式</h4>
<p><strong>c</strong></p>
<pre><code>类型 数组名[数组长度] = {初始值列表};
</code></pre>
<h4>常见写法示例</h4>
<table>
<thead>
<tr>
<th>写法</th>
<th>示例</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>完全初始化</td>
<td><code>int arr = {1, 2, 3, 4, 5};</code></td>
<td>5个元素全部赋值</td>
</tr>
<tr>
<td>部分初始化</td>
<td><code>int arr = {1, 2};</code></td>
<td>前2个赋值,其余自动为0</td>
</tr>
<tr>
<td>全部初始化为0</td>
<td><code>int arr = {0};</code></td>
<td>所有元素为0</td>
</tr>
<tr>
<td>不指定长度</td>
<td><code>int arr[] = {1, 2, 3};</code></td>
<td>编译器自动计算长度为3</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>注意</strong>:数组长度必须是常量(如 <code>5</code>、<code>MAX_SIZE</code> 宏),不能是变量(C99之前标准)。</p>
</blockquote>
<hr />
<h3>三、使用数组</h3>
<h4>1. 赋值(写入)</h4>
<p><strong>c</strong></p>
<pre><code>arr = 10; // 第一个元素赋值为10
arr = 99; // 第四个元素赋值为99
</code></pre>
<h4>2. 取值(读取)</h4>
<p><strong>c</strong></p>
<pre><code>int x = arr; // 取出第三个元素的值赋给变量x
</code></pre>
<h4>3. 索引规则</h4>
<ul>
<li><strong>索引从 0 开始</strong></li>
<li>长度为 <code>n</code> 的数组,有效索引范围:<strong><code>0 ~ n-1</code></strong></li>
<li>访问 <code>arr</code> 属于<strong>越界</strong>,可能导致程序异常或数据错误</li>
</ul>
<p><strong>text</strong></p>
<pre><code>数组:
索引: 0 1 2 3 4
</code></pre>
<hr />
<h3>四、数组的遍历(常用操作)</h3>
<p>使用 <code>for</code> 循环逐个访问数组元素:</p>
<p><strong>c</strong></p>
<pre><code>int arr = {1, 2, 3, 4, 5};
// 遍历输出
for (int i = 0; i < 5; i++) {
printf("%d ", arr);
}
// 遍历赋值
for (int i = 0; i < 5; i++) {
arr = i * 10;
}
</code></pre>
<hr />
<h3>五、常见错误与注意事项</h3>
<table>
<thead>
<tr>
<th>错误类型</th>
<th>错误示例</th>
<th>正确做法</th>
</tr>
</thead>
<tbody>
<tr>
<td>索引越界</td>
<td><code>arr = 10;</code> (长度为5时)</td>
<td>最大索引为4</td>
</tr>
<tr>
<td>定义时长度用变量</td>
<td><code>int n = 5; int arr;</code></td>
<td>使用宏 <code>#define SIZE 5</code> 或常量</td>
</tr>
<tr>
<td>数组间直接赋值</td>
<td><code>arr2 = arr1;</code></td>
<td>需用循环逐元素复制</td>
</tr>
<tr>
<td>混淆数组名与指针</td>
<td><code>arr = &something;</code></td>
<td>数组名是地址常量,不可修改</td>
</tr>
</tbody>
</table>
<hr />
<h3>六、实际应用示例(结合单片机)</h3>
<p><strong>场景</strong>:存储多个按键的消抖状态</p>
<p><strong>c</strong></p>
<pre><code>#define KEY_NUM 4
// 1. 定义数组:存储每个按键的上次电平
unsigned char key_last_state = {1, 1, 1, 1};
// 2. 使用数组:更新按键状态
for (int i = 0; i < KEY_NUM; i++) {
unsigned char current = read_key(i); // 读取当前按键电平
if (current != key_last_state) {
// 状态变化,启动消抖计时
key_last_state = current;
}
}
</code></pre>
<hr />
<h3>七、笔记速查表</h3>
<p><strong>text</strong></p>
<pre><code>┌─────────────────────────────────────────────┐
│ 数组使用速查表 │
├─────────────────────────────────────────────┤
│ 定义 │ int nums = {10, 20, 30}; │
│ 赋值 │ nums = 5; │
│ 取值 │ int x = nums; │
│ 长度 │ sizeof(nums) / sizeof(nums) │
│ 索引 │ 0 ~ 长度-1 │
│ 遍历 │ for(i=0; i<长度; i++) │
└─────────────────────────────────────────────┘
</code></pre>
<p>打卡第八集-蜂鸣器的应用</p>
<h2>认识蜂鸣器</h2>
<h3>一、蜂鸣器分类(核心区别)</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>有源蜂鸣器</th>
<th>无源蜂鸣器</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>内部结构</strong></td>
<td>自带振荡源(振荡电路)</td>
<td>不带振荡源,仅压电片/电磁线圈</td>
</tr>
<tr>
<td><strong>驱动方式</strong></td>
<td>直流电平(通断)</td>
<td>方波/脉冲信号(PWM)</td>
</tr>
<tr>
<td><strong>通电效果</strong></td>
<td>一通电就叫</td>
<td>直流信号无法鸣叫</td>
</tr>
<tr>
<td><strong>声音频率</strong></td>
<td>固定频率(单一音调)</td>
<td>可调频率(多种音调、音乐)</td>
</tr>
<tr>
<td><strong>控制复杂度</strong></td>
<td>简单</td>
<td>较复杂</td>
</tr>
<tr>
<td><strong>成本</strong></td>
<td>稍高</td>
<td>较低</td>
</tr>
<tr>
<td><strong>典型应用</strong></td>
<td>报警提示、按键提示音</td>
<td>音乐播放、多音调报警</td>
</tr>
</tbody>
</table>
<hr />
<h3>二、工作原理简述</h3>
<h4>1. 有源蜂鸣器</h4>
<p><strong>text</strong></p>
<pre><code>电源 ──┬── 蜂鸣器 ──┬── GND
│ │
└── 开关 ────┘
</code></pre>
<ul>
<li>内部有振荡电路产生特定频率(如 2kHz)</li>
<li>只需提供直流电压(如 3.3V / 5V)</li>
<li><strong>通即响,断即停</strong></li>
</ul>
<h4>2. 无源蜂鸣器</h4>
<p><strong>text</strong></p>
<pre><code>IO口 ──┬── 蜂鸣器 ──┬── GND
│ │
└── 限流电阻 ─┘
</code></pre>
<ul>
<li>内部仅是一个发声单元(压电陶瓷片或电磁线圈)</li>
<li>需要外部提供<strong>方波信号</strong>才能发声</li>
<li><strong>频率决定音调,占空比决定音量</strong></li>
</ul>
<hr />
<h3>三、快速识别方法</h3>
<table>
<thead>
<tr>
<th>判断方法</th>
<th>有源蜂鸣器</th>
<th>无源蜂鸣器</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>外观</strong></td>
<td>底部有贴纸/封胶</td>
<td>底部裸露电路板</td>
</tr>
<tr>
<td><strong>引脚</strong></td>
<td>通常有正负极(长正短负)</td>
<td>多数无正负极区分</td>
</tr>
<tr>
<td><strong>直流测试</strong></td>
<td>接直流电直接响</td>
<td>接直流电不响(或仅咔嗒一声)</td>
</tr>
<tr>
<td><strong>万用表测电阻</strong></td>
<td>阻值较小(几十Ω)</td>
<td>阻值较大(几百Ω以上)</td>
</tr>
</tbody>
</table>
<hr />
<h3>四、单片机驱动电路</h3>
<h4>1. 简单驱动(有源蜂鸣器)</h4>
<p><strong>c</strong></p>
<pre><code>// 假设蜂鸣器接 P1.0,低电平有效
#define BEEP P1_0
// 鸣叫
BEEP = 0; // 导通
delay_ms(100);
BEEP = 1; // 关闭
</code></pre>
<h4>2. 无源蜂鸣器驱动(PWM方式)</h4>
<p><strong>c</strong></p>
<pre><code>// 产生 1kHz 方波(示例:延时翻转法)
for (int i = 0; i < 500; i++) {
BEEP = 1;
delay_us(500); // 半周期 500us
BEEP = 0;
delay_us(500);
}
</code></pre>
<blockquote>
<p><strong>注意</strong>:无源蜂鸣器一般需要用 <strong>三极管/MOS管</strong> 驱动,单片机IO口电流可能不足。</p>
</blockquote>
<hr />
<h3>五、典型驱动电路</h3>
<p><strong>text</strong></p>
<pre><code> VCC (5V)
│
R (限流电阻 100~1kΩ)
│
单片机IO ────┬─── 基极
│
GND
三极管 NPN (如 8050)
│
蜂鸣器
│
GND
</code></pre>
<hr />
<h3>六、实际应用对比</h3>
<table>
<thead>
<tr>
<th>应用场景</th>
<th>推荐类型</th>
<th>原因</th>
</tr>
</thead>
<tbody>
<tr>
<td>按键提示音</td>
<td>有源</td>
<td>简单可靠,一响即可</td>
</tr>
<tr>
<td>报警器(固定音)</td>
<td>有源</td>
<td>驱动简单,成本可控</td>
</tr>
<tr>
<td>电子琴/音乐播放</td>
<td>无源</td>
<td>需要多频率输出</td>
</tr>
<tr>
<td>倒车雷达提示</td>
<td>无源</td>
<td>不同频率表示不同距离</td>
</tr>
<tr>
<td>低功耗设备</td>
<td>无源</td>
<td>无源本身不耗电,有源内部振荡器持续耗电</td>
</tr>
</tbody>
</table>
<hr />
<h3>七、常见问题与注意事项</h3>
<table>
<thead>
<tr>
<th>问题</th>
<th>原因</th>
<th>解决方法</th>
</tr>
</thead>
<tbody>
<tr>
<td>有源蜂鸣器不响</td>
<td>电压不足/正负极反</td>
<td>检查供电,确认极性</td>
</tr>
<tr>
<td>无源蜂鸣器不响</td>
<td>用了直流电平</td>
<td>改用方波信号</td>
</tr>
<tr>
<td>声音太小</td>
<td>驱动电流不足</td>
<td>加三极管驱动</td>
</tr>
<tr>
<td>声音刺耳</td>
<td>频率不合适</td>
<td>调整PWM频率</td>
</tr>
<tr>
<td>单片机复位时乱叫</td>
<td>IO口复位瞬间高电平</td>
<td>加下拉电阻或反相驱动</td>
</tr>
</tbody>
</table>
<hr />
<h3>八、笔记速查表</h3>
<p><strong>text</strong></p>
<pre><code>┌─────────────────────────────────────────────────┐
│ 蜂鸣器速查表 │
├─────────────────────────────────────────────────┤
│ 类型 │ 有源 │ 无源 │
├─────────────────────────────────────────────────┤
│ 驱动 │ 直流电平 │ 方波/PWM │
├─────────────────────────────────────────────────┤
│ 频率 │ 固定 │ 可调 │
├─────────────────────────────────────────────────┤
│ 控制 │ ON/OFF │ 频率+占空比 │
├─────────────────────────────────────────────────┤
│ 识别 │ 直流测试响 │ 直流测试不响 │
├─────────────────────────────────────────────────┤
│ 电路 │ 可直接IO驱动 │ 建议加三极管驱动 │
└─────────────────────────────────────────────────┘
</code></pre>
<hr />
<h3>九、一句话总结</h3>
<hr />
<blockquote>
<p><strong>有源蜂鸣器</strong> = 自带振荡源 → 通电就响(固定音)<br />
<strong>无源蜂鸣器</strong> = 不带振荡源 → 需要方波驱动(可编程音调)</p>
</blockquote>
<hr />
<h2>位运算符完整详解</h2>
<h3>一、运算符速查表</h3>
<table>
<thead>
<tr>
<th>运算符</th>
<th>名称</th>
<th>功能</th>
<th>典型用法</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&=</code></td>
<td>按位与赋值</td>
<td>清零特定位</td>
<td><code>reg &= ~(1<<n)</code></td>
</tr>
<tr>
<td><code>|=</code></td>
<td>按位或赋值</td>
<td>置1特定位</td>
<td><code>reg |= (1<<n)</code></td>
</tr>
<tr>
<td><code>^=</code></td>
<td>按位异或赋值</td>
<td>翻转特定位</td>
<td><code>reg ^= (1<<n)</code></td>
</tr>
<tr>
<td><code><<</code></td>
<td>左移</td>
<td>将位向左移动</td>
<td><code>1 << 3</code> → <code>0b00001000</code></td>
</tr>
<tr>
<td><code>>></code></td>
<td>右移</td>
<td>将位向右移动</td>
<td><code>0x10 >> 2</code> → <code>0x04</code></td>
</tr>
<tr>
<td><code>~</code></td>
<td>按位取反</td>
<td>0变1,1变0</td>
<td><code>~0x03</code> → <code>0xFFFFFFFC</code></td>
</tr>
</tbody>
</table>
<hr />
<h3>二、核心运算符详解</h3>
<h4>1. <code>~</code> 按位取反(重点解释)</h4>
<p><strong>作用</strong>:将二进制数的每一位取反(0→1,1→0)</p>
<h5>示例:<code>~0x03</code> 的计算过程</h5>
<p><strong>c</strong></p>
<pre><code>0x03 = 0b00000011(8位表示)
~0x03 = 0b11111100(8位取反)
= 0xFC
</code></pre>
<h5>在单片机中的实际应用</h5>
<p><strong>c</strong></p>
<pre><code>P3M0 &= ~0x03;
</code></pre>
<p><strong>分解步骤</strong>:</p>
<ol>
<li><code>0x03</code> = <code>0b00000011</code></li>
<li><code>~0x03</code> = <code>0b11111100</code> (生成掩码)</li>
<li><code>P3M0 & 0b11111100</code> → 将 bit0、bit1 清零,其他位保持不变</li>
</ol>
<p><strong>为什么需要 <code>~</code>?</strong></p>
<ul>
<li>直接写 <code>P3M0 &= 0xFC</code> 也可以,但用 <code>~0x03</code> 可读性更好</li>
<li>一眼看出要清零的是 bit0 和 bit1(对应 <code>0x03</code>)</li>
</ul>
<hr />
<h4>2. <code><<</code> 左移运算符</h4>
<p><strong>作用</strong>:将二进制数向左移动 n 位,右边补 0</p>
<h5>运算规则</h5>
<p><strong>c</strong></p>
<pre><code>1 << 0 = 0b00000001// 1
1 << 1 = 0b00000010// 2
1 << 2 = 0b00000100// 4
1 << 3 = 0b00001000// 8
1 << 4 = 0b00010000// 16
</code></pre>
<h5>实际应用</h5>
<p><strong>c</strong></p>
<pre><code>// 配置第3个引脚(bit2)
P3M0 &= ~(1 << 2); // 清零 bit2
P3M1 |= (1 << 2); // 置1 bit2
// 等价于
P3M0 &= ~0x04; // 0x04 = 0b00000100
P3M1 |= 0x04;
</code></pre>
<p><strong>优点</strong>:用 <code>(1 << n)</code> 比直接写十六进制更直观,n 就是引脚号。</p>
<hr />
<h4>3. <code>>></code> 右移运算符</h4>
<p><strong>作用</strong>:将二进制数向右移动 n 位,左边补 0</p>
<h5>运算规则</h5>
<p><strong>c</strong></p>
<pre><code>0x10 = 0b00010000
0x10 >> 1 = 0b00001000 = 0x08
0x10 >> 2 = 0b00000100 = 0x04
0x10 >> 4 = 0b00000001 = 0x01
</code></pre>
<h5>实际应用</h5>
<p><strong>c</strong></p>
<pre><code>// 提取高4位
unsigned char data = 0xAB;// 0b10101011
unsigned char high = data >> 4;// 0x0A (0b00001010)
// 提取低4位
unsigned char low = data & 0x0F; // 0x0B (0b00001011)
</code></pre>
<hr />
<h4>4. <code>^=</code> 异或赋值</h4>
<p><strong>运算规则</strong>:相同为0,不同为1</p>
<table>
<thead>
<tr>
<th>位1</th>
<th>位2</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
</tbody>
</table>
<p><strong>特点</strong>:与1异或 → 翻转;与0异或 → 不变</p>
<h5>实际应用</h5>
<p><strong>c</strong></p>
<pre><code>// 翻转 bit2(取反)
P3M0 ^= (1 << 2); // 如果原来是0变成1,原来是1变成0
// 翻转多个位
P3M0 ^= 0x0F; // 翻转低4位
// 实现LED闪烁
LED ^= 1; // 每次执行翻转状态
</code></pre>
<hr />
<h3>三、完整对比示例</h3>
<p>假设我们要配置 P3.0 和 P3.1 为高阻输入模式:</p>
<h4>方法1:直接数值法</h4>
<p><strong>c</strong></p>
<pre><code>P3M0 &= 0xFC; // 清零 bit0、bit1 (0xFC = 0b11111100)
P3M1 |= 0x03; // 置1 bit0、bit1
</code></pre>
<h4>方法2:位操作法(推荐)</h4>
<p><strong>c</strong></p>
<pre><code>P3M0 &= ~0x03; // ~0x03 = 0xFC,清零 bit0、bit1
P3M1 |= 0x03; // 置1 bit0、bit1
</code></pre>
<h4>方法3:引脚号法(最直观)</h4>
<p><strong>c</strong></p>
<pre><code>#define PIN0 0
#define PIN1 1
P3M0 &= ~((1 << PIN0) | (1 << PIN1));// 清零 bit0、bit1
P3M1 |= ((1 << PIN0) | (1 << PIN1)); // 置1 bit0、bit1
</code></pre>
<hr />
<h3>四、实际操作示例</h3>
<h4>1. 配置单个引脚为推挽输出</h4>
<p><strong>c</strong></p>
<pre><code>// 配置 P3.2 为推挽输出
#define PIN2 2
P3M0 |= (1 << PIN2); // bit2 置1
P3M1 &= ~(1 << PIN2); // bit2 清零
// 结果:P3M1=0, P3M0=1 → 推挽输出
</code></pre>
<h4>2. 翻转LED状态</h4>
<p><strong>c</strong></p>
<pre><code>sbit LED = P1^0;
// 每次调用翻转LED状态
LED ^= 1;// 等价于 LED = !LED
</code></pre>
<h4>3. 提取字节的高低位</h4>
<p><strong>c</strong></p>
<pre><code>unsigned char value = 0x7F;// 01111111
unsigned char high = value >> 4;// 00000111 = 0x07
unsigned char low = value & 0x0F;// 00001111 = 0x0F
</code></pre>
<hr />
<h3>五、常见组合用法</h3>
<table>
<thead>
<tr>
<th>操作</th>
<th>代码</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>置1第n位</td>
<td><code>reg |= (1 << n)</code></td>
<td>将第n位设为1</td>
</tr>
<tr>
<td>清零第n位</td>
<td><code>reg &= ~(1 << n)</code></td>
<td>将第n位设为0</td>
</tr>
<tr>
<td>翻转第n位</td>
<td><code>reg ^= (1 << n)</code></td>
<td>将第n位取反</td>
</tr>
<tr>
<td>提取第n位</td>
<td><code>(reg >> n) & 1</code></td>
<td>获取第n位的值</td>
</tr>
<tr>
<td>同时配置多位</td>
<td><code>reg |= (1<<0)|(1<<3)</code></td>
<td>置1 bit0和bit3</td>
</tr>
</tbody>
</table>
<hr />
<h3>六、笔记速查表</h3>
<pre><code>┌─────────────────────────────────────────────────────────┐
│ 位运算符速查表(单片机常用) │
├─────────────────────────────────────────────────────────┤
│ 运算符 │ 名称 │ 示例 │ 效果 │
├─────────────────────────────────────────────────────────┤
│ ~ │ 取反 │ ~0x03 = 0xFC │ 生成清零掩码 │
│ << │ 左移 │ 1<<3 = 0x08 │ 生成位置掩码 │
│ >> │ 右移 │ 0x10>>2 = 0x04 │ 提取高位/除法│
│ &= │ 与赋值 │ reg &= ~0x03 │ 清零低2位 │
│ |= │ 或赋值 │ reg |= 0x03 │ 置1低2位 │
│ ^= │ 异或赋 │ reg ^= (1<<2) │ 翻转bit2 │
└─────────────────────────────────────────────────────────┘
</code></pre>
<hr />
<h3>七、核心要点总结</h3>
<ol>
<li><strong><code>~</code>(取反)</strong>:配合 <code>&=</code> 使用,生成清零所需的掩码</li>
<li><strong><code><<</code>(左移)</strong>:用 <code>1<<n</code> 表示第n位,提高可读性</li>
<li><strong><code>>></code>(右移)</strong>:提取高位或快速除以2的幂</li>
<li><strong><code>^=</code>(异或)</strong>:实现位翻转,常用于LED闪烁、状态切换</li>
<li><strong>组合使用</strong>:<code>(1<<0)|(1<<3)</code> 同时操作多个位</li>
</ol>
<p>打卡第九集-数码管的静态使用</p>
<p><strong>认识数码管</strong></p>
<h2>1. 基本定义</h2>
<ul>
<li><strong>名称</strong>:LED数码管</li>
<li><strong>本质</strong>:由多个发光二极管(LED)封装在一起组成的显示器件</li>
</ul>
<h2>2. 工作原理</h2>
<ul>
<li>通过控制内部不同LED的亮灭来显示内容</li>
<li>本质上是一种<strong>可控的LED组合</strong></li>
</ul>
<h2>3. 特点与特性</h2>
<ul>
<li><strong>颜色多样</strong>:常见红、绿、蓝、黄、白等</li>
<li><strong>外形丰富</strong>:可做成不同形状、尺寸</li>
<li><strong>样式多变</strong>:除常见的“8”字,还可定制任意字符或图案(面板设计好即可)</li>
</ul>
<blockquote>
<p><strong>核心</strong>:无论外观如何变化,数码管的核心原理都是<strong>点亮内部的LED</strong>来实现显示。</p>
</blockquote>
<hr />
<h2>4. 引脚极性(共阴 / 共阳)</h2>
<p>数码管内部LED的连接方式分为两种,决定了驱动逻辑:</p>
<table>
<thead>
<tr>
<th>类型</th>
<th>内部结构</th>
<th>驱动方式</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>共阴极</strong></td>
<td>所有LED的阴极连在一起接地</td>
<td>阳极接高电平(1)点亮</td>
</tr>
<tr>
<td><strong>共阳极</strong></td>
<td>所有LED的阳极连在一起接电源</td>
<td>阴极接低电平(0)点亮</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>选择提示</strong>:共阳极数码管因单片机低电平驱动能力较强,在实际电路中更常用。使用时需注意单片机的输出电流能力。</p>
</blockquote>
<hr />
<h2>5. 段位编码(段码表)</h2>
<p>一位8段数码管(含小数点dp)各段位置如下:</p>
<p><strong>text</strong></p>
<pre><code> a
---
f| |b
---← g
e| |c
---
d . dp
</code></pre>
<h3>共阴极段码表(高电平点亮)</h3>
<table>
<thead>
<tr>
<th>显示</th>
<th>段位 (dp g f e d c b a)</th>
<th>十六进制</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>0 0 1 1 1 1 1 1</td>
<td>0x3F</td>
</tr>
<tr>
<td>1</td>
<td>0 0 0 0 0 1 1 0</td>
<td>0x06</td>
</tr>
<tr>
<td>2</td>
<td>0 1 0 1 1 0 1 1</td>
<td>0x5B</td>
</tr>
<tr>
<td>3</td>
<td>0 1 0 0 1 1 1 1</td>
<td>0x4F</td>
</tr>
<tr>
<td>4</td>
<td>0 1 1 0 0 1 1 0</td>
<td>0x66</td>
</tr>
<tr>
<td>5</td>
<td>0 1 1 0 1 1 0 1</td>
<td>0x6D</td>
</tr>
<tr>
<td>6</td>
<td>0 1 1 1 1 1 0 1</td>
<td>0x7D</td>
</tr>
<tr>
<td>7</td>
<td>0 0 0 0 0 1 1 1</td>
<td>0x07</td>
</tr>
<tr>
<td>8</td>
<td>0 1 1 1 1 1 1 1</td>
<td>0x7F</td>
</tr>
<tr>
<td>9</td>
<td>0 1 1 0 1 1 1 1</td>
<td>0x6F</td>
</tr>
<tr>
<td>A</td>
<td>0 1 1 1 0 1 1 1</td>
<td>0x77</td>
</tr>
<tr>
<td>b</td>
<td>0 1 1 1 1 1 0 0</td>
<td>0x7C</td>
</tr>
<tr>
<td>C</td>
<td>0 0 1 1 1 0 0 1</td>
<td>0x39</td>
</tr>
<tr>
<td>d</td>
<td>0 1 0 1 1 1 1 0</td>
<td>0x5E</td>
</tr>
<tr>
<td>E</td>
<td>0 1 1 1 1 0 0 1</td>
<td>0x79</td>
</tr>
<tr>
<td>F</td>
<td>0 1 1 1 0 0 0 1</td>
<td>0x71</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>共阳极段码</strong>:将共阴极段码按位取反即可得到共阳极的段码(低电平点亮)。</p>
</blockquote>
<hr />
<h2>6. 驱动方式</h2>
<table>
<thead>
<tr>
<th>方式</th>
<th>原理</th>
<th>特点</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>静态驱动</strong></td>
<td>每个数码管独立占用一组I/O口持续供电</td>
<td>硬件简单、编程易、占用I/O多</td>
<td>位数少(1~2位)</td>
</tr>
<tr>
<td><strong>动态扫描</strong></td>
<td>分时轮流点亮各位数码管,利用人眼视觉暂留</td>
<td>节省I/O、编程稍复杂、亮度略低</td>
<td>多位显示(如4位、8位)</td>
</tr>
</tbody>
</table>
<h3>动态扫描要点</h3>
<ul>
<li>一次只点亮一位,每位的点亮时间约1~2ms</li>
<li>一轮扫描周期应小于20ms(即刷新率>50Hz),保证无闪烁</li>
<li>显示亮度与扫描占空比、限流电阻有关</li>
</ul>
<hr />
<p><strong>VCC与GND</strong></p>
<ul>
<li><strong>VCC</strong>:电源正极(供电端)。</li>
<li><strong>GND</strong>:电源负极(公共地,0V参考点)。</li>
<li><strong>关系</strong>:VCC 与 GND 之间的电压就是电路的工作电压(如 5V、3.3V)。</li>
<li><strong>最重要</strong>:VCC 和 GND <strong>绝对不能直接短接</strong>,否则会烧毁电源或电路。</li>
<li><strong>测量</strong>:用万用表电压档,红表笔接 VCC,黑表笔接 GND,应显示稳定的额定电压。</li>
</ul>
页:
[1]
2